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}