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}