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.util.Arrays;
028import java.util.Collections;
029import java.util.Date;
030import java.util.List;
031import java.util.Locale;
032import java.util.Set;
033
034import org.apache.commons.logging.Log;
035import org.apache.commons.logging.LogFactory;
036
037import org.springframework.core.MethodParameter;
038import org.springframework.util.Assert;
039import org.springframework.util.ClassUtils;
040import org.springframework.util.ConcurrentReferenceHashMap;
041import org.springframework.util.ReflectionUtils;
042import org.springframework.util.StringUtils;
043
044/**
045 * Static convenience methods for JavaBeans: for instantiating beans,
046 * checking bean property types, copying bean properties, etc.
047 *
048 * <p>Mainly for internal use within the framework, but to some degree also
049 * useful for application classes. Consider
050 * <a href="https://commons.apache.org/proper/commons-beanutils/">Apache Commons BeanUtils</a>,
051 * <a href="https://hotelsdotcom.github.io/bull/">BULL - Bean Utils Light Library</a>,
052 * or similar third-party frameworks for more comprehensive bean utilities.
053 *
054 * @author Rod Johnson
055 * @author Juergen Hoeller
056 * @author Rob Harrop
057 * @author Sam Brannen
058 */
059public abstract class BeanUtils {
060
061        private static final Log logger = LogFactory.getLog(BeanUtils.class);
062
063        private static final Set<Class<?>> unknownEditorTypes =
064                        Collections.newSetFromMap(new ConcurrentReferenceHashMap<Class<?>, Boolean>(64));
065
066
067        /**
068         * Convenience method to instantiate a class using its no-arg constructor.
069         * @param clazz class to instantiate
070         * @return the new instance
071         * @throws BeanInstantiationException if the bean cannot be instantiated
072         * @see Class#newInstance()
073         */
074        public static <T> T instantiate(Class<T> clazz) throws BeanInstantiationException {
075                Assert.notNull(clazz, "Class must not be null");
076                if (clazz.isInterface()) {
077                        throw new BeanInstantiationException(clazz, "Specified class is an interface");
078                }
079                try {
080                        return clazz.newInstance();
081                }
082                catch (InstantiationException ex) {
083                        throw new BeanInstantiationException(clazz, "Is it an abstract class?", ex);
084                }
085                catch (IllegalAccessException ex) {
086                        throw new BeanInstantiationException(clazz, "Is the constructor accessible?", ex);
087                }
088        }
089
090        /**
091         * Instantiate a class using its no-arg constructor.
092         * <p>Note that this method tries to set the constructor accessible
093         * if given a non-accessible (that is, non-public) constructor.
094         * @param clazz class to instantiate
095         * @return the new instance
096         * @throws BeanInstantiationException if the bean cannot be instantiated
097         * @see Constructor#newInstance
098         */
099        public static <T> T instantiateClass(Class<T> clazz) throws BeanInstantiationException {
100                Assert.notNull(clazz, "Class must not be null");
101                if (clazz.isInterface()) {
102                        throw new BeanInstantiationException(clazz, "Specified class is an interface");
103                }
104                try {
105                        return instantiateClass(clazz.getDeclaredConstructor());
106                }
107                catch (NoSuchMethodException ex) {
108                        throw new BeanInstantiationException(clazz, "No default constructor found", ex);
109                }
110        }
111
112        /**
113         * Instantiate a class using its no-arg constructor and return the new instance
114         * as the specified assignable type.
115         * <p>Useful in cases where the type of the class to instantiate (clazz) is not
116         * available, but the type desired (assignableTo) is known.
117         * <p>Note that this method tries to set the constructor accessible if given a
118         * non-accessible (that is, non-public) constructor.
119         * @param clazz class to instantiate
120         * @param assignableTo type that clazz must be assignableTo
121         * @return the new instance
122         * @throws BeanInstantiationException if the bean cannot be instantiated
123         * @see Constructor#newInstance
124         */
125        @SuppressWarnings("unchecked")
126        public static <T> T instantiateClass(Class<?> clazz, Class<T> assignableTo) throws BeanInstantiationException {
127                Assert.isAssignable(assignableTo, clazz);
128                return (T) instantiateClass(clazz);
129        }
130
131        /**
132         * Convenience method to instantiate a class using the given constructor.
133         * <p>Note that this method tries to set the constructor accessible if given a
134         * non-accessible (that is, non-public) constructor.
135         * @param ctor the constructor to instantiate
136         * @param args the constructor arguments to apply
137         * @return the new instance
138         * @throws BeanInstantiationException if the bean cannot be instantiated
139         * @see Constructor#newInstance
140         */
141        public static <T> T instantiateClass(Constructor<T> ctor, Object... args) throws BeanInstantiationException {
142                Assert.notNull(ctor, "Constructor must not be null");
143                try {
144                        ReflectionUtils.makeAccessible(ctor);
145                        return ctor.newInstance(args);
146                }
147                catch (InstantiationException ex) {
148                        throw new BeanInstantiationException(ctor, "Is it an abstract class?", ex);
149                }
150                catch (IllegalAccessException ex) {
151                        throw new BeanInstantiationException(ctor, "Is the constructor accessible?", ex);
152                }
153                catch (IllegalArgumentException ex) {
154                        throw new BeanInstantiationException(ctor, "Illegal arguments for constructor", ex);
155                }
156                catch (InvocationTargetException ex) {
157                        throw new BeanInstantiationException(ctor, "Constructor threw exception", ex.getTargetException());
158                }
159        }
160
161        /**
162         * Find a method with the given method name and the given parameter types,
163         * declared on the given class or one of its superclasses. Prefers public methods,
164         * but will return a protected, package access, or private method too.
165         * <p>Checks {@code Class.getMethod} first, falling back to
166         * {@code findDeclaredMethod}. This allows to find public methods
167         * without issues even in environments with restricted Java security settings.
168         * @param clazz the class to check
169         * @param methodName the name of the method to find
170         * @param paramTypes the parameter types of the method to find
171         * @return the Method object, or {@code null} if not found
172         * @see Class#getMethod
173         * @see #findDeclaredMethod
174         */
175        public static Method findMethod(Class<?> clazz, String methodName, Class<?>... paramTypes) {
176                try {
177                        return clazz.getMethod(methodName, paramTypes);
178                }
179                catch (NoSuchMethodException ex) {
180                        return findDeclaredMethod(clazz, methodName, paramTypes);
181                }
182        }
183
184        /**
185         * Find a method with the given method name and the given parameter types,
186         * declared on the given class or one of its superclasses. Will return a public,
187         * protected, package access, or private method.
188         * <p>Checks {@code Class.getDeclaredMethod}, cascading upwards to all superclasses.
189         * @param clazz the class to check
190         * @param methodName the name of the method to find
191         * @param paramTypes the parameter types of the method to find
192         * @return the Method object, or {@code null} if not found
193         * @see Class#getDeclaredMethod
194         */
195        public static Method findDeclaredMethod(Class<?> clazz, String methodName, Class<?>... paramTypes) {
196                try {
197                        return clazz.getDeclaredMethod(methodName, paramTypes);
198                }
199                catch (NoSuchMethodException ex) {
200                        if (clazz.getSuperclass() != null) {
201                                return findDeclaredMethod(clazz.getSuperclass(), methodName, paramTypes);
202                        }
203                        return null;
204                }
205        }
206
207        /**
208         * Find a method with the given method name and minimal parameters (best case: none),
209         * declared on the given class or one of its superclasses. Prefers public methods,
210         * but will return a protected, package access, or private method too.
211         * <p>Checks {@code Class.getMethods} first, falling back to
212         * {@code findDeclaredMethodWithMinimalParameters}. This allows for finding public
213         * methods without issues even in environments with restricted Java security settings.
214         * @param clazz the class to check
215         * @param methodName the name of the method to find
216         * @return the Method object, or {@code null} if not found
217         * @throws IllegalArgumentException if methods of the given name were found but
218         * could not be resolved to a unique method with minimal parameters
219         * @see Class#getMethods
220         * @see #findDeclaredMethodWithMinimalParameters
221         */
222        public static Method findMethodWithMinimalParameters(Class<?> clazz, String methodName)
223                        throws IllegalArgumentException {
224
225                Method targetMethod = findMethodWithMinimalParameters(clazz.getMethods(), methodName);
226                if (targetMethod == null) {
227                        targetMethod = findDeclaredMethodWithMinimalParameters(clazz, methodName);
228                }
229                return targetMethod;
230        }
231
232        /**
233         * Find a method with the given method name and minimal parameters (best case: none),
234         * declared on the given class or one of its superclasses. Will return a public,
235         * protected, package access, or private method.
236         * <p>Checks {@code Class.getDeclaredMethods}, cascading upwards to all superclasses.
237         * @param clazz the class to check
238         * @param methodName the name of the method to find
239         * @return the Method object, or {@code null} if not found
240         * @throws IllegalArgumentException if methods of the given name were found but
241         * could not be resolved to a unique method with minimal parameters
242         * @see Class#getDeclaredMethods
243         */
244        public static Method findDeclaredMethodWithMinimalParameters(Class<?> clazz, String methodName)
245                        throws IllegalArgumentException {
246
247                Method targetMethod = findMethodWithMinimalParameters(clazz.getDeclaredMethods(), methodName);
248                if (targetMethod == null && clazz.getSuperclass() != null) {
249                        targetMethod = findDeclaredMethodWithMinimalParameters(clazz.getSuperclass(), methodName);
250                }
251                return targetMethod;
252        }
253
254        /**
255         * Find a method with the given method name and minimal parameters (best case: none)
256         * in the given list of methods.
257         * @param methods the methods to check
258         * @param methodName the name of the method to find
259         * @return the Method object, or {@code null} if not found
260         * @throws IllegalArgumentException if methods of the given name were found but
261         * could not be resolved to a unique method with minimal parameters
262         */
263        public static Method findMethodWithMinimalParameters(Method[] methods, String methodName)
264                        throws IllegalArgumentException {
265
266                Method targetMethod = null;
267                int numMethodsFoundWithCurrentMinimumArgs = 0;
268                for (Method method : methods) {
269                        if (method.getName().equals(methodName)) {
270                                int numParams = method.getParameterTypes().length;
271                                if (targetMethod == null || numParams < targetMethod.getParameterTypes().length) {
272                                        targetMethod = method;
273                                        numMethodsFoundWithCurrentMinimumArgs = 1;
274                                }
275                                else if (!method.isBridge() && targetMethod.getParameterTypes().length == numParams) {
276                                        if (targetMethod.isBridge()) {
277                                                // Prefer regular method over bridge...
278                                                targetMethod = method;
279                                        }
280                                        else {
281                                                // Additional candidate with same length
282                                                numMethodsFoundWithCurrentMinimumArgs++;
283                                        }
284                                }
285                        }
286                }
287                if (numMethodsFoundWithCurrentMinimumArgs > 1) {
288                        throw new IllegalArgumentException("Cannot resolve method '" + methodName +
289                                        "' to a unique method. Attempted to resolve to overloaded method with " +
290                                        "the least number of parameters but there were " +
291                                        numMethodsFoundWithCurrentMinimumArgs + " candidates.");
292                }
293                return targetMethod;
294        }
295
296        /**
297         * Parse a method signature in the form {@code methodName[([arg_list])]},
298         * where {@code arg_list} is an optional, comma-separated list of fully-qualified
299         * type names, and attempts to resolve that signature against the supplied {@code Class}.
300         * <p>When not supplying an argument list ({@code methodName}) the method whose name
301         * matches and has the least number of parameters will be returned. When supplying an
302         * argument type list, only the method whose name and argument types match will be returned.
303         * <p>Note then that {@code methodName} and {@code methodName()} are <strong>not</strong>
304         * resolved in the same way. The signature {@code methodName} means the method called
305         * {@code methodName} with the least number of arguments, whereas {@code methodName()}
306         * means the method called {@code methodName} with exactly 0 arguments.
307         * <p>If no method can be found, then {@code null} is returned.
308         * @param signature the method signature as String representation
309         * @param clazz the class to resolve the method signature against
310         * @return the resolved Method
311         * @see #findMethod
312         * @see #findMethodWithMinimalParameters
313         */
314        public static Method resolveSignature(String signature, Class<?> clazz) {
315                Assert.hasText(signature, "'signature' must not be empty");
316                Assert.notNull(clazz, "Class must not be null");
317                int startParen = signature.indexOf('(');
318                int endParen = signature.indexOf(')');
319                if (startParen > -1 && endParen == -1) {
320                        throw new IllegalArgumentException("Invalid method signature '" + signature +
321                                        "': expected closing ')' for args list");
322                }
323                else if (startParen == -1 && endParen > -1) {
324                        throw new IllegalArgumentException("Invalid method signature '" + signature +
325                                        "': expected opening '(' for args list");
326                }
327                else if (startParen == -1 && endParen == -1) {
328                        return findMethodWithMinimalParameters(clazz, signature);
329                }
330                else {
331                        String methodName = signature.substring(0, startParen);
332                        String[] parameterTypeNames =
333                                        StringUtils.commaDelimitedListToStringArray(signature.substring(startParen + 1, endParen));
334                        Class<?>[] parameterTypes = new Class<?>[parameterTypeNames.length];
335                        for (int i = 0; i < parameterTypeNames.length; i++) {
336                                String parameterTypeName = parameterTypeNames[i].trim();
337                                try {
338                                        parameterTypes[i] = ClassUtils.forName(parameterTypeName, clazz.getClassLoader());
339                                }
340                                catch (Throwable ex) {
341                                        throw new IllegalArgumentException("Invalid method signature: unable to resolve type [" +
342                                                        parameterTypeName + "] for argument " + i + ". Root cause: " + ex);
343                                }
344                        }
345                        return findMethod(clazz, methodName, parameterTypes);
346                }
347        }
348
349
350        /**
351         * Retrieve the JavaBeans {@code PropertyDescriptor}s of a given class.
352         * @param clazz the Class to retrieve the PropertyDescriptors for
353         * @return an array of {@code PropertyDescriptors} for the given class
354         * @throws BeansException if PropertyDescriptor look fails
355         */
356        public static PropertyDescriptor[] getPropertyDescriptors(Class<?> clazz) throws BeansException {
357                CachedIntrospectionResults cr = CachedIntrospectionResults.forClass(clazz);
358                return cr.getPropertyDescriptors();
359        }
360
361        /**
362         * Retrieve the JavaBeans {@code PropertyDescriptors} for the given property.
363         * @param clazz the Class to retrieve the PropertyDescriptor for
364         * @param propertyName the name of the property
365         * @return the corresponding PropertyDescriptor, or {@code null} if none
366         * @throws BeansException if PropertyDescriptor lookup fails
367         */
368        public static PropertyDescriptor getPropertyDescriptor(Class<?> clazz, String propertyName)
369                        throws BeansException {
370
371                CachedIntrospectionResults cr = CachedIntrospectionResults.forClass(clazz);
372                return cr.getPropertyDescriptor(propertyName);
373        }
374
375        /**
376         * Find a JavaBeans {@code PropertyDescriptor} for the given method,
377         * with the method either being the read method or the write method for
378         * that bean property.
379         * @param method the method to find a corresponding PropertyDescriptor for,
380         * introspecting its declaring class
381         * @return the corresponding PropertyDescriptor, or {@code null} if none
382         * @throws BeansException if PropertyDescriptor lookup fails
383         */
384        public static PropertyDescriptor findPropertyForMethod(Method method) throws BeansException {
385                return findPropertyForMethod(method, method.getDeclaringClass());
386        }
387
388        /**
389         * Find a JavaBeans {@code PropertyDescriptor} for the given method,
390         * with the method either being the read method or the write method for
391         * that bean property.
392         * @param method the method to find a corresponding PropertyDescriptor for
393         * @param clazz the (most specific) class to introspect for descriptors
394         * @return the corresponding PropertyDescriptor, or {@code null} if none
395         * @throws BeansException if PropertyDescriptor lookup fails
396         * @since 3.2.13
397         */
398        public static PropertyDescriptor findPropertyForMethod(Method method, Class<?> clazz) throws BeansException {
399                Assert.notNull(method, "Method must not be null");
400                PropertyDescriptor[] pds = getPropertyDescriptors(clazz);
401                for (PropertyDescriptor pd : pds) {
402                        if (method.equals(pd.getReadMethod()) || method.equals(pd.getWriteMethod())) {
403                                return pd;
404                        }
405                }
406                return null;
407        }
408
409        /**
410         * Find a JavaBeans PropertyEditor following the 'Editor' suffix convention
411         * (e.g. "mypackage.MyDomainClass" -> "mypackage.MyDomainClassEditor").
412         * <p>Compatible to the standard JavaBeans convention as implemented by
413         * {@link java.beans.PropertyEditorManager} but isolated from the latter's
414         * registered default editors for primitive types.
415         * @param targetType the type to find an editor for
416         * @return the corresponding editor, or {@code null} if none found
417         */
418        public static PropertyEditor findEditorByConvention(Class<?> targetType) {
419                if (targetType == null || targetType.isArray() || unknownEditorTypes.contains(targetType)) {
420                        return null;
421                }
422                ClassLoader cl = targetType.getClassLoader();
423                if (cl == null) {
424                        try {
425                                cl = ClassLoader.getSystemClassLoader();
426                                if (cl == null) {
427                                        return null;
428                                }
429                        }
430                        catch (Throwable ex) {
431                                // e.g. AccessControlException on Google App Engine
432                                if (logger.isDebugEnabled()) {
433                                        logger.debug("Could not access system ClassLoader: " + ex);
434                                }
435                                return null;
436                        }
437                }
438                String targetTypeName = targetType.getName();
439                String editorName = targetTypeName + "Editor";
440                try {
441                        Class<?> editorClass = cl.loadClass(editorName);
442                        if (!PropertyEditor.class.isAssignableFrom(editorClass)) {
443                                if (logger.isWarnEnabled()) {
444                                        logger.warn("Editor class [" + editorName +
445                                                        "] does not implement [java.beans.PropertyEditor] interface");
446                                }
447                                unknownEditorTypes.add(targetType);
448                                return null;
449                        }
450                        return (PropertyEditor) instantiateClass(editorClass);
451                }
452                catch (ClassNotFoundException ex) {
453                        if (logger.isDebugEnabled()) {
454                                logger.debug("No property editor [" + editorName + "] found for type " +
455                                                targetTypeName + " according to 'Editor' suffix convention");
456                        }
457                        unknownEditorTypes.add(targetType);
458                        return null;
459                }
460        }
461
462        /**
463         * Determine the bean property type for the given property from the
464         * given classes/interfaces, if possible.
465         * @param propertyName the name of the bean property
466         * @param beanClasses the classes to check against
467         * @return the property type, or {@code Object.class} as fallback
468         */
469        public static Class<?> findPropertyType(String propertyName, Class<?>... beanClasses) {
470                if (beanClasses != null) {
471                        for (Class<?> beanClass : beanClasses) {
472                                PropertyDescriptor pd = getPropertyDescriptor(beanClass, propertyName);
473                                if (pd != null) {
474                                        return pd.getPropertyType();
475                                }
476                        }
477                }
478                return Object.class;
479        }
480
481        /**
482         * Obtain a new MethodParameter object for the write method of the
483         * specified property.
484         * @param pd the PropertyDescriptor for the property
485         * @return a corresponding MethodParameter object
486         */
487        public static MethodParameter getWriteMethodParameter(PropertyDescriptor pd) {
488                if (pd instanceof GenericTypeAwarePropertyDescriptor) {
489                        return new MethodParameter(((GenericTypeAwarePropertyDescriptor) pd).getWriteMethodParameter());
490                }
491                else {
492                        return new MethodParameter(pd.getWriteMethod(), 0);
493                }
494        }
495
496        /**
497         * Check if the given type represents a "simple" property:
498         * a primitive, a String or other CharSequence, a Number, a Date,
499         * a URI, a URL, a Locale, a Class, or a corresponding array.
500         * <p>Used to determine properties to check for a "simple" dependency-check.
501         * @param clazz the type to check
502         * @return whether the given type represents a "simple" property
503         * @see org.springframework.beans.factory.support.RootBeanDefinition#DEPENDENCY_CHECK_SIMPLE
504         * @see org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#checkDependencies
505         */
506        public static boolean isSimpleProperty(Class<?> clazz) {
507                Assert.notNull(clazz, "Class must not be null");
508                return isSimpleValueType(clazz) || (clazz.isArray() && isSimpleValueType(clazz.getComponentType()));
509        }
510
511        /**
512         * Check if the given type represents a "simple" value type:
513         * a primitive, an enum, a String or other CharSequence, a Number, a Date,
514         * a URI, a URL, a Locale or a Class.
515         * @param clazz the type to check
516         * @return whether the given type represents a "simple" value type
517         */
518        public static boolean isSimpleValueType(Class<?> clazz) {
519                return (ClassUtils.isPrimitiveOrWrapper(clazz) ||
520                                Enum.class.isAssignableFrom(clazz) ||
521                                CharSequence.class.isAssignableFrom(clazz) ||
522                                Number.class.isAssignableFrom(clazz) ||
523                                Date.class.isAssignableFrom(clazz) ||
524                                URI.class == clazz || URL.class == clazz ||
525                                Locale.class == clazz || Class.class == clazz);
526        }
527
528
529        /**
530         * Copy the property values of the given source bean into the target bean.
531         * <p>Note: The source and target classes do not have to match or even be derived
532         * from each other, as long as the properties match. Any bean properties that the
533         * source bean exposes but the target bean does not will silently be ignored.
534         * <p>This is just a convenience method. For more complex transfer needs,
535         * consider using a full BeanWrapper.
536         * @param source the source bean
537         * @param target the target bean
538         * @throws BeansException if the copying failed
539         * @see BeanWrapper
540         */
541        public static void copyProperties(Object source, Object target) throws BeansException {
542                copyProperties(source, target, null, (String[]) null);
543        }
544
545        /**
546         * Copy the property values of the given source bean into the given target bean,
547         * only setting properties defined in the given "editable" class (or interface).
548         * <p>Note: The source and target classes do not have to match or even be derived
549         * from each other, as long as the properties match. Any bean properties that the
550         * source bean exposes but the target bean does not will silently be ignored.
551         * <p>This is just a convenience method. For more complex transfer needs,
552         * consider using a full BeanWrapper.
553         * @param source the source bean
554         * @param target the target bean
555         * @param editable the class (or interface) to restrict property setting to
556         * @throws BeansException if the copying failed
557         * @see BeanWrapper
558         */
559        public static void copyProperties(Object source, Object target, Class<?> editable) throws BeansException {
560                copyProperties(source, target, editable, (String[]) null);
561        }
562
563        /**
564         * Copy the property values of the given source bean into the given target bean,
565         * ignoring the given "ignoreProperties".
566         * <p>Note: The source and target classes do not have to match or even be derived
567         * from each other, as long as the properties match. Any bean properties that the
568         * source bean exposes but the target bean does not will silently be ignored.
569         * <p>This is just a convenience method. For more complex transfer needs,
570         * consider using a full BeanWrapper.
571         * @param source the source bean
572         * @param target the target bean
573         * @param ignoreProperties array of property names to ignore
574         * @throws BeansException if the copying failed
575         * @see BeanWrapper
576         */
577        public static void copyProperties(Object source, Object target, String... ignoreProperties) throws BeansException {
578                copyProperties(source, target, null, ignoreProperties);
579        }
580
581        /**
582         * Copy the property values of the given source bean into the given target bean.
583         * <p>Note: The source and target classes do not have to match or even be derived
584         * from each other, as long as the properties match. Any bean properties that the
585         * source bean exposes but the target bean does not will silently be ignored.
586         * @param source the source bean
587         * @param target the target bean
588         * @param editable the class (or interface) to restrict property setting to
589         * @param ignoreProperties array of property names to ignore
590         * @throws BeansException if the copying failed
591         * @see BeanWrapper
592         */
593        private static void copyProperties(Object source, Object target, Class<?> editable, String... ignoreProperties)
594                        throws BeansException {
595
596                Assert.notNull(source, "Source must not be null");
597                Assert.notNull(target, "Target must not be null");
598
599                Class<?> actualEditable = target.getClass();
600                if (editable != null) {
601                        if (!editable.isInstance(target)) {
602                                throw new IllegalArgumentException("Target class [" + target.getClass().getName() +
603                                                "] not assignable to Editable class [" + editable.getName() + "]");
604                        }
605                        actualEditable = editable;
606                }
607                PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable);
608                List<String> ignoreList = (ignoreProperties != null ? Arrays.asList(ignoreProperties) : null);
609
610                for (PropertyDescriptor targetPd : targetPds) {
611                        Method writeMethod = targetPd.getWriteMethod();
612                        if (writeMethod != null && (ignoreList == null || !ignoreList.contains(targetPd.getName()))) {
613                                PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName());
614                                if (sourcePd != null) {
615                                        Method readMethod = sourcePd.getReadMethod();
616                                        if (readMethod != null &&
617                                                        ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType())) {
618                                                try {
619                                                        if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
620                                                                readMethod.setAccessible(true);
621                                                        }
622                                                        Object value = readMethod.invoke(source);
623                                                        if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
624                                                                writeMethod.setAccessible(true);
625                                                        }
626                                                        writeMethod.invoke(target, value);
627                                                }
628                                                catch (Throwable ex) {
629                                                        throw new FatalBeanException(
630                                                                        "Could not copy property '" + targetPd.getName() + "' from source to target", ex);
631                                                }
632                                        }
633                                }
634                        }
635                }
636        }
637
638}