001/*
002 * Copyright 2002-2020 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.beans;
018
019import java.beans.PropertyDescriptor;
020import java.beans.PropertyEditor;
021import java.lang.reflect.Constructor;
022import java.lang.reflect.InvocationTargetException;
023import java.lang.reflect.Method;
024import java.lang.reflect.Modifier;
025import java.net.URI;
026import java.net.URL;
027import java.time.temporal.Temporal;
028import java.util.Arrays;
029import java.util.Collections;
030import java.util.Date;
031import java.util.HashMap;
032import java.util.List;
033import java.util.Locale;
034import java.util.Map;
035import java.util.Set;
036
037import kotlin.jvm.JvmClassMappingKt;
038import kotlin.reflect.KFunction;
039import kotlin.reflect.KParameter;
040import kotlin.reflect.full.KClasses;
041import kotlin.reflect.jvm.KCallablesJvm;
042import kotlin.reflect.jvm.ReflectJvmMapping;
043import org.apache.commons.logging.Log;
044import org.apache.commons.logging.LogFactory;
045
046import org.springframework.core.KotlinDetector;
047import org.springframework.core.MethodParameter;
048import org.springframework.lang.Nullable;
049import org.springframework.util.Assert;
050import org.springframework.util.ClassUtils;
051import org.springframework.util.ConcurrentReferenceHashMap;
052import org.springframework.util.ReflectionUtils;
053import org.springframework.util.StringUtils;
054
055/**
056 * Static convenience methods for JavaBeans: for instantiating beans,
057 * checking bean property types, copying bean properties, etc.
058 *
059 * <p>Mainly for internal use within the framework, but to some degree also
060 * useful for application classes. Consider
061 * <a href="https://commons.apache.org/proper/commons-beanutils/">Apache Commons BeanUtils</a>,
062 * <a href="https://hotelsdotcom.github.io/bull/">BULL - Bean Utils Light Library</a>,
063 * or similar third-party frameworks for more comprehensive bean utilities.
064 *
065 * @author Rod Johnson
066 * @author Juergen Hoeller
067 * @author Rob Harrop
068 * @author Sam Brannen
069 * @author Sebastien Deleuze
070 */
071public abstract class BeanUtils {
072
073        private static final Log logger = LogFactory.getLog(BeanUtils.class);
074
075        private static final Set<Class<?>> unknownEditorTypes =
076                        Collections.newSetFromMap(new ConcurrentReferenceHashMap<>(64));
077
078        private static final Map<Class<?>, Object> DEFAULT_TYPE_VALUES;
079
080        static {
081                Map<Class<?>, Object> values = new HashMap<>();
082                values.put(boolean.class, false);
083                values.put(byte.class, (byte) 0);
084                values.put(short.class, (short) 0);
085                values.put(int.class, 0);
086                values.put(long.class, (long) 0);
087                DEFAULT_TYPE_VALUES = Collections.unmodifiableMap(values);
088        }
089
090
091        /**
092         * Convenience method to instantiate a class using its no-arg constructor.
093         * @param clazz class to instantiate
094         * @return the new instance
095         * @throws BeanInstantiationException if the bean cannot be instantiated
096         * @deprecated as of Spring 5.0, following the deprecation of
097         * {@link Class#newInstance()} in JDK 9
098         * @see Class#newInstance()
099         */
100        @Deprecated
101        public static <T> T instantiate(Class<T> clazz) throws BeanInstantiationException {
102                Assert.notNull(clazz, "Class must not be null");
103                if (clazz.isInterface()) {
104                        throw new BeanInstantiationException(clazz, "Specified class is an interface");
105                }
106                try {
107                        return clazz.newInstance();
108                }
109                catch (InstantiationException ex) {
110                        throw new BeanInstantiationException(clazz, "Is it an abstract class?", ex);
111                }
112                catch (IllegalAccessException ex) {
113                        throw new BeanInstantiationException(clazz, "Is the constructor accessible?", ex);
114                }
115        }
116
117        /**
118         * Instantiate a class using its 'primary' constructor (for Kotlin classes,
119         * potentially having default arguments declared) or its default constructor
120         * (for regular Java classes, expecting a standard no-arg setup).
121         * <p>Note that this method tries to set the constructor accessible
122         * if given a non-accessible (that is, non-public) constructor.
123         * @param clazz the class to instantiate
124         * @return the new instance
125         * @throws BeanInstantiationException if the bean cannot be instantiated.
126         * The cause may notably indicate a {@link NoSuchMethodException} if no
127         * primary/default constructor was found, a {@link NoClassDefFoundError}
128         * or other {@link LinkageError} in case of an unresolvable class definition
129         * (e.g. due to a missing dependency at runtime), or an exception thrown
130         * from the constructor invocation itself.
131         * @see Constructor#newInstance
132         */
133        public static <T> T instantiateClass(Class<T> clazz) throws BeanInstantiationException {
134                Assert.notNull(clazz, "Class must not be null");
135                if (clazz.isInterface()) {
136                        throw new BeanInstantiationException(clazz, "Specified class is an interface");
137                }
138                try {
139                        return instantiateClass(clazz.getDeclaredConstructor());
140                }
141                catch (NoSuchMethodException ex) {
142                        Constructor<T> ctor = findPrimaryConstructor(clazz);
143                        if (ctor != null) {
144                                return instantiateClass(ctor);
145                        }
146                        throw new BeanInstantiationException(clazz, "No default constructor found", ex);
147                }
148                catch (LinkageError err) {
149                        throw new BeanInstantiationException(clazz, "Unresolvable class definition", err);
150                }
151        }
152
153        /**
154         * Instantiate a class using its no-arg constructor and return the new instance
155         * as the specified assignable type.
156         * <p>Useful in cases where the type of the class to instantiate (clazz) is not
157         * available, but the type desired (assignableTo) is known.
158         * <p>Note that this method tries to set the constructor accessible if given a
159         * non-accessible (that is, non-public) constructor.
160         * @param clazz class to instantiate
161         * @param assignableTo type that clazz must be assignableTo
162         * @return the new instance
163         * @throws BeanInstantiationException if the bean cannot be instantiated
164         * @see Constructor#newInstance
165         */
166        @SuppressWarnings("unchecked")
167        public static <T> T instantiateClass(Class<?> clazz, Class<T> assignableTo) throws BeanInstantiationException {
168                Assert.isAssignable(assignableTo, clazz);
169                return (T) instantiateClass(clazz);
170        }
171
172        /**
173         * Convenience method to instantiate a class using the given constructor.
174         * <p>Note that this method tries to set the constructor accessible if given a
175         * non-accessible (that is, non-public) constructor, and supports Kotlin classes
176         * with optional parameters and default values.
177         * @param ctor the constructor to instantiate
178         * @param args the constructor arguments to apply (use {@code null} for an unspecified
179         * parameter, Kotlin optional parameters and Java primitive types are supported)
180         * @return the new instance
181         * @throws BeanInstantiationException if the bean cannot be instantiated
182         * @see Constructor#newInstance
183         */
184        public static <T> T instantiateClass(Constructor<T> ctor, Object... args) throws BeanInstantiationException {
185                Assert.notNull(ctor, "Constructor must not be null");
186                try {
187                        ReflectionUtils.makeAccessible(ctor);
188                        if (KotlinDetector.isKotlinReflectPresent() && KotlinDetector.isKotlinType(ctor.getDeclaringClass())) {
189                                return KotlinDelegate.instantiateClass(ctor, args);
190                        }
191                        else {
192                                Class<?>[] parameterTypes = ctor.getParameterTypes();
193                                Assert.isTrue(args.length <= parameterTypes.length, "Can't specify more arguments than constructor parameters");
194                                Object[] argsWithDefaultValues = new Object[args.length];
195                                for (int i = 0 ; i < args.length; i++) {
196                                        if (args[i] == null) {
197                                                Class<?> parameterType = parameterTypes[i];
198                                                argsWithDefaultValues[i] = (parameterType.isPrimitive() ? DEFAULT_TYPE_VALUES.get(parameterType) : null);
199                                        }
200                                        else {
201                                                argsWithDefaultValues[i] = args[i];
202                                        }
203                                }
204                                return ctor.newInstance(argsWithDefaultValues);
205                        }
206                }
207                catch (InstantiationException ex) {
208                        throw new BeanInstantiationException(ctor, "Is it an abstract class?", ex);
209                }
210                catch (IllegalAccessException ex) {
211                        throw new BeanInstantiationException(ctor, "Is the constructor accessible?", ex);
212                }
213                catch (IllegalArgumentException ex) {
214                        throw new BeanInstantiationException(ctor, "Illegal arguments for constructor", ex);
215                }
216                catch (InvocationTargetException ex) {
217                        throw new BeanInstantiationException(ctor, "Constructor threw exception", ex.getTargetException());
218                }
219        }
220
221        /**
222         * Return the primary constructor of the provided class. For Kotlin classes, this
223         * returns the Java constructor corresponding to the Kotlin primary constructor
224         * (as defined in the Kotlin specification). Otherwise, in particular for non-Kotlin
225         * classes, this simply returns {@code null}.
226         * @param clazz the class to check
227         * @since 5.0
228         * @see <a href="https://kotlinlang.org/docs/reference/classes.html#constructors">Kotlin docs</a>
229         */
230        @Nullable
231        public static <T> Constructor<T> findPrimaryConstructor(Class<T> clazz) {
232                Assert.notNull(clazz, "Class must not be null");
233                if (KotlinDetector.isKotlinReflectPresent() && KotlinDetector.isKotlinType(clazz)) {
234                        Constructor<T> kotlinPrimaryConstructor = KotlinDelegate.findPrimaryConstructor(clazz);
235                        if (kotlinPrimaryConstructor != null) {
236                                return kotlinPrimaryConstructor;
237                        }
238                }
239                return null;
240        }
241
242        /**
243         * Find a method with the given method name and the given parameter types,
244         * declared on the given class or one of its superclasses. Prefers public methods,
245         * but will return a protected, package access, or private method too.
246         * <p>Checks {@code Class.getMethod} first, falling back to
247         * {@code findDeclaredMethod}. This allows to find public methods
248         * without issues even in environments with restricted Java security settings.
249         * @param clazz the class to check
250         * @param methodName the name of the method to find
251         * @param paramTypes the parameter types of the method to find
252         * @return the Method object, or {@code null} if not found
253         * @see Class#getMethod
254         * @see #findDeclaredMethod
255         */
256        @Nullable
257        public static Method findMethod(Class<?> clazz, String methodName, Class<?>... paramTypes) {
258                try {
259                        return clazz.getMethod(methodName, paramTypes);
260                }
261                catch (NoSuchMethodException ex) {
262                        return findDeclaredMethod(clazz, methodName, paramTypes);
263                }
264        }
265
266        /**
267         * Find a method with the given method name and the given parameter types,
268         * declared on the given class or one of its superclasses. Will return a public,
269         * protected, package access, or private method.
270         * <p>Checks {@code Class.getDeclaredMethod}, cascading upwards to all superclasses.
271         * @param clazz the class to check
272         * @param methodName the name of the method to find
273         * @param paramTypes the parameter types of the method to find
274         * @return the Method object, or {@code null} if not found
275         * @see Class#getDeclaredMethod
276         */
277        @Nullable
278        public static Method findDeclaredMethod(Class<?> clazz, String methodName, Class<?>... paramTypes) {
279                try {
280                        return clazz.getDeclaredMethod(methodName, paramTypes);
281                }
282                catch (NoSuchMethodException ex) {
283                        if (clazz.getSuperclass() != null) {
284                                return findDeclaredMethod(clazz.getSuperclass(), methodName, paramTypes);
285                        }
286                        return null;
287                }
288        }
289
290        /**
291         * Find a method with the given method name and minimal parameters (best case: none),
292         * declared on the given class or one of its superclasses. Prefers public methods,
293         * but will return a protected, package access, or private method too.
294         * <p>Checks {@code Class.getMethods} first, falling back to
295         * {@code findDeclaredMethodWithMinimalParameters}. This allows for finding public
296         * methods without issues even in environments with restricted Java security settings.
297         * @param clazz the class to check
298         * @param methodName the name of the method to find
299         * @return the Method object, or {@code null} if not found
300         * @throws IllegalArgumentException if methods of the given name were found but
301         * could not be resolved to a unique method with minimal parameters
302         * @see Class#getMethods
303         * @see #findDeclaredMethodWithMinimalParameters
304         */
305        @Nullable
306        public static Method findMethodWithMinimalParameters(Class<?> clazz, String methodName)
307                        throws IllegalArgumentException {
308
309                Method targetMethod = findMethodWithMinimalParameters(clazz.getMethods(), methodName);
310                if (targetMethod == null) {
311                        targetMethod = findDeclaredMethodWithMinimalParameters(clazz, methodName);
312                }
313                return targetMethod;
314        }
315
316        /**
317         * Find a method with the given method name and minimal parameters (best case: none),
318         * declared on the given class or one of its superclasses. Will return a public,
319         * protected, package access, or private method.
320         * <p>Checks {@code Class.getDeclaredMethods}, cascading upwards to all superclasses.
321         * @param clazz the class to check
322         * @param methodName the name of the method to find
323         * @return the Method object, or {@code null} if not found
324         * @throws IllegalArgumentException if methods of the given name were found but
325         * could not be resolved to a unique method with minimal parameters
326         * @see Class#getDeclaredMethods
327         */
328        @Nullable
329        public static Method findDeclaredMethodWithMinimalParameters(Class<?> clazz, String methodName)
330                        throws IllegalArgumentException {
331
332                Method targetMethod = findMethodWithMinimalParameters(clazz.getDeclaredMethods(), methodName);
333                if (targetMethod == null && clazz.getSuperclass() != null) {
334                        targetMethod = findDeclaredMethodWithMinimalParameters(clazz.getSuperclass(), methodName);
335                }
336                return targetMethod;
337        }
338
339        /**
340         * Find a method with the given method name and minimal parameters (best case: none)
341         * in the given list of methods.
342         * @param methods the methods to check
343         * @param methodName the name of the method to find
344         * @return the Method object, or {@code null} if not found
345         * @throws IllegalArgumentException if methods of the given name were found but
346         * could not be resolved to a unique method with minimal parameters
347         */
348        @Nullable
349        public static Method findMethodWithMinimalParameters(Method[] methods, String methodName)
350                        throws IllegalArgumentException {
351
352                Method targetMethod = null;
353                int numMethodsFoundWithCurrentMinimumArgs = 0;
354                for (Method method : methods) {
355                        if (method.getName().equals(methodName)) {
356                                int numParams = method.getParameterCount();
357                                if (targetMethod == null || numParams < targetMethod.getParameterCount()) {
358                                        targetMethod = method;
359                                        numMethodsFoundWithCurrentMinimumArgs = 1;
360                                }
361                                else if (!method.isBridge() && targetMethod.getParameterCount() == numParams) {
362                                        if (targetMethod.isBridge()) {
363                                                // Prefer regular method over bridge...
364                                                targetMethod = method;
365                                        }
366                                        else {
367                                                // Additional candidate with same length
368                                                numMethodsFoundWithCurrentMinimumArgs++;
369                                        }
370                                }
371                        }
372                }
373                if (numMethodsFoundWithCurrentMinimumArgs > 1) {
374                        throw new IllegalArgumentException("Cannot resolve method '" + methodName +
375                                        "' to a unique method. Attempted to resolve to overloaded method with " +
376                                        "the least number of parameters but there were " +
377                                        numMethodsFoundWithCurrentMinimumArgs + " candidates.");
378                }
379                return targetMethod;
380        }
381
382        /**
383         * Parse a method signature in the form {@code methodName[([arg_list])]},
384         * where {@code arg_list} is an optional, comma-separated list of fully-qualified
385         * type names, and attempts to resolve that signature against the supplied {@code Class}.
386         * <p>When not supplying an argument list ({@code methodName}) the method whose name
387         * matches and has the least number of parameters will be returned. When supplying an
388         * argument type list, only the method whose name and argument types match will be returned.
389         * <p>Note then that {@code methodName} and {@code methodName()} are <strong>not</strong>
390         * resolved in the same way. The signature {@code methodName} means the method called
391         * {@code methodName} with the least number of arguments, whereas {@code methodName()}
392         * means the method called {@code methodName} with exactly 0 arguments.
393         * <p>If no method can be found, then {@code null} is returned.
394         * @param signature the method signature as String representation
395         * @param clazz the class to resolve the method signature against
396         * @return the resolved Method
397         * @see #findMethod
398         * @see #findMethodWithMinimalParameters
399         */
400        @Nullable
401        public static Method resolveSignature(String signature, Class<?> clazz) {
402                Assert.hasText(signature, "'signature' must not be empty");
403                Assert.notNull(clazz, "Class must not be null");
404                int startParen = signature.indexOf('(');
405                int endParen = signature.indexOf(')');
406                if (startParen > -1 && endParen == -1) {
407                        throw new IllegalArgumentException("Invalid method signature '" + signature +
408                                        "': expected closing ')' for args list");
409                }
410                else if (startParen == -1 && endParen > -1) {
411                        throw new IllegalArgumentException("Invalid method signature '" + signature +
412                                        "': expected opening '(' for args list");
413                }
414                else if (startParen == -1) {
415                        return findMethodWithMinimalParameters(clazz, signature);
416                }
417                else {
418                        String methodName = signature.substring(0, startParen);
419                        String[] parameterTypeNames =
420                                        StringUtils.commaDelimitedListToStringArray(signature.substring(startParen + 1, endParen));
421                        Class<?>[] parameterTypes = new Class<?>[parameterTypeNames.length];
422                        for (int i = 0; i < parameterTypeNames.length; i++) {
423                                String parameterTypeName = parameterTypeNames[i].trim();
424                                try {
425                                        parameterTypes[i] = ClassUtils.forName(parameterTypeName, clazz.getClassLoader());
426                                }
427                                catch (Throwable ex) {
428                                        throw new IllegalArgumentException("Invalid method signature: unable to resolve type [" +
429                                                        parameterTypeName + "] for argument " + i + ". Root cause: " + ex);
430                                }
431                        }
432                        return findMethod(clazz, methodName, parameterTypes);
433                }
434        }
435
436
437        /**
438         * Retrieve the JavaBeans {@code PropertyDescriptor}s of a given class.
439         * @param clazz the Class to retrieve the PropertyDescriptors for
440         * @return an array of {@code PropertyDescriptors} for the given class
441         * @throws BeansException if PropertyDescriptor look fails
442         */
443        public static PropertyDescriptor[] getPropertyDescriptors(Class<?> clazz) throws BeansException {
444                return CachedIntrospectionResults.forClass(clazz).getPropertyDescriptors();
445        }
446
447        /**
448         * Retrieve the JavaBeans {@code PropertyDescriptors} for the given property.
449         * @param clazz the Class to retrieve the PropertyDescriptor for
450         * @param propertyName the name of the property
451         * @return the corresponding PropertyDescriptor, or {@code null} if none
452         * @throws BeansException if PropertyDescriptor lookup fails
453         */
454        @Nullable
455        public static PropertyDescriptor getPropertyDescriptor(Class<?> clazz, String propertyName) throws BeansException {
456                return CachedIntrospectionResults.forClass(clazz).getPropertyDescriptor(propertyName);
457        }
458
459        /**
460         * Find a JavaBeans {@code PropertyDescriptor} for the given method,
461         * with the method either being the read method or the write method for
462         * that bean property.
463         * @param method the method to find a corresponding PropertyDescriptor for,
464         * introspecting its declaring class
465         * @return the corresponding PropertyDescriptor, or {@code null} if none
466         * @throws BeansException if PropertyDescriptor lookup fails
467         */
468        @Nullable
469        public static PropertyDescriptor findPropertyForMethod(Method method) throws BeansException {
470                return findPropertyForMethod(method, method.getDeclaringClass());
471        }
472
473        /**
474         * Find a JavaBeans {@code PropertyDescriptor} for the given method,
475         * with the method either being the read method or the write method for
476         * that bean property.
477         * @param method the method to find a corresponding PropertyDescriptor for
478         * @param clazz the (most specific) class to introspect for descriptors
479         * @return the corresponding PropertyDescriptor, or {@code null} if none
480         * @throws BeansException if PropertyDescriptor lookup fails
481         * @since 3.2.13
482         */
483        @Nullable
484        public static PropertyDescriptor findPropertyForMethod(Method method, Class<?> clazz) throws BeansException {
485                Assert.notNull(method, "Method must not be null");
486                PropertyDescriptor[] pds = getPropertyDescriptors(clazz);
487                for (PropertyDescriptor pd : pds) {
488                        if (method.equals(pd.getReadMethod()) || method.equals(pd.getWriteMethod())) {
489                                return pd;
490                        }
491                }
492                return null;
493        }
494
495        /**
496         * Find a JavaBeans PropertyEditor following the 'Editor' suffix convention
497         * (e.g. "mypackage.MyDomainClass" -> "mypackage.MyDomainClassEditor").
498         * <p>Compatible to the standard JavaBeans convention as implemented by
499         * {@link java.beans.PropertyEditorManager} but isolated from the latter's
500         * registered default editors for primitive types.
501         * @param targetType the type to find an editor for
502         * @return the corresponding editor, or {@code null} if none found
503         */
504        @Nullable
505        public static PropertyEditor findEditorByConvention(@Nullable Class<?> targetType) {
506                if (targetType == null || targetType.isArray() || unknownEditorTypes.contains(targetType)) {
507                        return null;
508                }
509                ClassLoader cl = targetType.getClassLoader();
510                if (cl == null) {
511                        try {
512                                cl = ClassLoader.getSystemClassLoader();
513                                if (cl == null) {
514                                        return null;
515                                }
516                        }
517                        catch (Throwable ex) {
518                                // e.g. AccessControlException on Google App Engine
519                                if (logger.isDebugEnabled()) {
520                                        logger.debug("Could not access system ClassLoader: " + ex);
521                                }
522                                return null;
523                        }
524                }
525                String targetTypeName = targetType.getName();
526                String editorName = targetTypeName + "Editor";
527                try {
528                        Class<?> editorClass = cl.loadClass(editorName);
529                        if (!PropertyEditor.class.isAssignableFrom(editorClass)) {
530                                if (logger.isInfoEnabled()) {
531                                        logger.info("Editor class [" + editorName +
532                                                        "] does not implement [java.beans.PropertyEditor] interface");
533                                }
534                                unknownEditorTypes.add(targetType);
535                                return null;
536                        }
537                        return (PropertyEditor) instantiateClass(editorClass);
538                }
539                catch (ClassNotFoundException ex) {
540                        if (logger.isTraceEnabled()) {
541                                logger.trace("No property editor [" + editorName + "] found for type " +
542                                                targetTypeName + " according to 'Editor' suffix convention");
543                        }
544                        unknownEditorTypes.add(targetType);
545                        return null;
546                }
547        }
548
549        /**
550         * Determine the bean property type for the given property from the
551         * given classes/interfaces, if possible.
552         * @param propertyName the name of the bean property
553         * @param beanClasses the classes to check against
554         * @return the property type, or {@code Object.class} as fallback
555         */
556        public static Class<?> findPropertyType(String propertyName, @Nullable Class<?>... beanClasses) {
557                if (beanClasses != null) {
558                        for (Class<?> beanClass : beanClasses) {
559                                PropertyDescriptor pd = getPropertyDescriptor(beanClass, propertyName);
560                                if (pd != null) {
561                                        return pd.getPropertyType();
562                                }
563                        }
564                }
565                return Object.class;
566        }
567
568        /**
569         * Obtain a new MethodParameter object for the write method of the
570         * specified property.
571         * @param pd the PropertyDescriptor for the property
572         * @return a corresponding MethodParameter object
573         */
574        public static MethodParameter getWriteMethodParameter(PropertyDescriptor pd) {
575                if (pd instanceof GenericTypeAwarePropertyDescriptor) {
576                        return new MethodParameter(((GenericTypeAwarePropertyDescriptor) pd).getWriteMethodParameter());
577                }
578                else {
579                        Method writeMethod = pd.getWriteMethod();
580                        Assert.state(writeMethod != null, "No write method available");
581                        return new MethodParameter(writeMethod, 0);
582                }
583        }
584
585        /**
586         * Check if the given type represents a "simple" property: a simple value
587         * type or an array of simple value types.
588         * <p>See {@link #isSimpleValueType(Class)} for the definition of <em>simple
589         * value type</em>.
590         * <p>Used to determine properties to check for a "simple" dependency-check.
591         * @param type the type to check
592         * @return whether the given type represents a "simple" property
593         * @see org.springframework.beans.factory.support.RootBeanDefinition#DEPENDENCY_CHECK_SIMPLE
594         * @see org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#checkDependencies
595         * @see #isSimpleValueType(Class)
596         */
597        public static boolean isSimpleProperty(Class<?> type) {
598                Assert.notNull(type, "'type' must not be null");
599                return isSimpleValueType(type) || (type.isArray() && isSimpleValueType(type.getComponentType()));
600        }
601
602        /**
603         * Check if the given type represents a "simple" value type: a primitive or
604         * primitive wrapper, an enum, a String or other CharSequence, a Number, a
605         * Date, a Temporal, a URI, a URL, a Locale, or a Class.
606         * <p>{@code Void} and {@code void} are not considered simple value types.
607         * @param type the type to check
608         * @return whether the given type represents a "simple" value type
609         * @see #isSimpleProperty(Class)
610         */
611        public static boolean isSimpleValueType(Class<?> type) {
612                return (Void.class != type && void.class != type &&
613                                (ClassUtils.isPrimitiveOrWrapper(type) ||
614                                Enum.class.isAssignableFrom(type) ||
615                                CharSequence.class.isAssignableFrom(type) ||
616                                Number.class.isAssignableFrom(type) ||
617                                Date.class.isAssignableFrom(type) ||
618                                Temporal.class.isAssignableFrom(type) ||
619                                URI.class == type ||
620                                URL.class == type ||
621                                Locale.class == type ||
622                                Class.class == type));
623        }
624
625
626        /**
627         * Copy the property values of the given source bean into the target bean.
628         * <p>Note: The source and target classes do not have to match or even be derived
629         * from each other, as long as the properties match. Any bean properties that the
630         * source bean exposes but the target bean does not will silently be ignored.
631         * <p>This is just a convenience method. For more complex transfer needs,
632         * consider using a full BeanWrapper.
633         * @param source the source bean
634         * @param target the target bean
635         * @throws BeansException if the copying failed
636         * @see BeanWrapper
637         */
638        public static void copyProperties(Object source, Object target) throws BeansException {
639                copyProperties(source, target, null, (String[]) null);
640        }
641
642        /**
643         * Copy the property values of the given source bean into the given target bean,
644         * only setting properties defined in the given "editable" class (or interface).
645         * <p>Note: The source and target classes do not have to match or even be derived
646         * from each other, as long as the properties match. Any bean properties that the
647         * source bean exposes but the target bean does not will silently be ignored.
648         * <p>This is just a convenience method. For more complex transfer needs,
649         * consider using a full BeanWrapper.
650         * @param source the source bean
651         * @param target the target bean
652         * @param editable the class (or interface) to restrict property setting to
653         * @throws BeansException if the copying failed
654         * @see BeanWrapper
655         */
656        public static void copyProperties(Object source, Object target, Class<?> editable) throws BeansException {
657                copyProperties(source, target, editable, (String[]) null);
658        }
659
660        /**
661         * Copy the property values of the given source bean into the given target bean,
662         * ignoring the given "ignoreProperties".
663         * <p>Note: The source and target classes do not have to match or even be derived
664         * from each other, as long as the properties match. Any bean properties that the
665         * source bean exposes but the target bean does not will silently be ignored.
666         * <p>This is just a convenience method. For more complex transfer needs,
667         * consider using a full BeanWrapper.
668         * @param source the source bean
669         * @param target the target bean
670         * @param ignoreProperties array of property names to ignore
671         * @throws BeansException if the copying failed
672         * @see BeanWrapper
673         */
674        public static void copyProperties(Object source, Object target, String... ignoreProperties) throws BeansException {
675                copyProperties(source, target, null, ignoreProperties);
676        }
677
678        /**
679         * Copy the property values of the given source bean into the given target bean.
680         * <p>Note: The source and target classes do not have to match or even be derived
681         * from each other, as long as the properties match. Any bean properties that the
682         * source bean exposes but the target bean does not will silently be ignored.
683         * @param source the source bean
684         * @param target the target bean
685         * @param editable the class (or interface) to restrict property setting to
686         * @param ignoreProperties array of property names to ignore
687         * @throws BeansException if the copying failed
688         * @see BeanWrapper
689         */
690        private static void copyProperties(Object source, Object target, @Nullable Class<?> editable,
691                        @Nullable String... ignoreProperties) throws BeansException {
692
693                Assert.notNull(source, "Source must not be null");
694                Assert.notNull(target, "Target must not be null");
695
696                Class<?> actualEditable = target.getClass();
697                if (editable != null) {
698                        if (!editable.isInstance(target)) {
699                                throw new IllegalArgumentException("Target class [" + target.getClass().getName() +
700                                                "] not assignable to Editable class [" + editable.getName() + "]");
701                        }
702                        actualEditable = editable;
703                }
704                PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable);
705                List<String> ignoreList = (ignoreProperties != null ? Arrays.asList(ignoreProperties) : null);
706
707                for (PropertyDescriptor targetPd : targetPds) {
708                        Method writeMethod = targetPd.getWriteMethod();
709                        if (writeMethod != null && (ignoreList == null || !ignoreList.contains(targetPd.getName()))) {
710                                PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName());
711                                if (sourcePd != null) {
712                                        Method readMethod = sourcePd.getReadMethod();
713                                        if (readMethod != null &&
714                                                        ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType())) {
715                                                try {
716                                                        if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
717                                                                readMethod.setAccessible(true);
718                                                        }
719                                                        Object value = readMethod.invoke(source);
720                                                        if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
721                                                                writeMethod.setAccessible(true);
722                                                        }
723                                                        writeMethod.invoke(target, value);
724                                                }
725                                                catch (Throwable ex) {
726                                                        throw new FatalBeanException(
727                                                                        "Could not copy property '" + targetPd.getName() + "' from source to target", ex);
728                                                }
729                                        }
730                                }
731                        }
732                }
733        }
734
735
736        /**
737         * Inner class to avoid a hard dependency on Kotlin at runtime.
738         */
739        private static class KotlinDelegate {
740
741                /**
742                 * Retrieve the Java constructor corresponding to the Kotlin primary constructor, if any.
743                 * @param clazz the {@link Class} of the Kotlin class
744                 * @see <a href="https://kotlinlang.org/docs/reference/classes.html#constructors">
745                 * https://kotlinlang.org/docs/reference/classes.html#constructors</a>
746                 */
747                @Nullable
748                public static <T> Constructor<T> findPrimaryConstructor(Class<T> clazz) {
749                        try {
750                                KFunction<T> primaryCtor = KClasses.getPrimaryConstructor(JvmClassMappingKt.getKotlinClass(clazz));
751                                if (primaryCtor == null) {
752                                        return null;
753                                }
754                                Constructor<T> constructor = ReflectJvmMapping.getJavaConstructor(primaryCtor);
755                                if (constructor == null) {
756                                        throw new IllegalStateException(
757                                                        "Failed to find Java constructor for Kotlin primary constructor: " + clazz.getName());
758                                }
759                                return constructor;
760                        }
761                        catch (UnsupportedOperationException ex) {
762                                return null;
763                        }
764                }
765
766                /**
767                 * Instantiate a Kotlin class using the provided constructor.
768                 * @param ctor the constructor of the Kotlin class to instantiate
769                 * @param args the constructor arguments to apply
770                 * (use {@code null} for unspecified parameter if needed)
771                 */
772                public static <T> T instantiateClass(Constructor<T> ctor, Object... args)
773                                throws IllegalAccessException, InvocationTargetException, InstantiationException {
774
775                        KFunction<T> kotlinConstructor = ReflectJvmMapping.getKotlinFunction(ctor);
776                        if (kotlinConstructor == null) {
777                                return ctor.newInstance(args);
778                        }
779
780                        if ((!Modifier.isPublic(ctor.getModifiers()) || !Modifier.isPublic(ctor.getDeclaringClass().getModifiers()))) {
781                                KCallablesJvm.setAccessible(kotlinConstructor, true);
782                        }
783
784                        List<KParameter> parameters = kotlinConstructor.getParameters();
785                        Map<KParameter, Object> argParameters = new HashMap<>(parameters.size());
786                        Assert.isTrue(args.length <= parameters.size(),
787                                        "Number of provided arguments should be less of equals than number of constructor parameters");
788                        for (int i = 0 ; i < args.length ; i++) {
789                                if (!(parameters.get(i).isOptional() && args[i] == null)) {
790                                        argParameters.put(parameters.get(i), args[i]);
791                                }
792                        }
793                        return kotlinConstructor.callBy(argParameters);
794                }
795
796        }
797
798}