001/* 002 * Copyright 2002-2018 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.util; 018 019import java.lang.reflect.Constructor; 020import java.lang.reflect.Field; 021import java.lang.reflect.InvocationTargetException; 022import java.lang.reflect.Method; 023import java.lang.reflect.Modifier; 024import java.lang.reflect.UndeclaredThrowableException; 025import java.sql.SQLException; 026import java.util.ArrayList; 027import java.util.Arrays; 028import java.util.List; 029import java.util.Map; 030 031/** 032 * Simple utility class for working with the reflection API and handling 033 * reflection exceptions. 034 * 035 * <p>Only intended for internal use. 036 * 037 * @author Juergen Hoeller 038 * @author Rob Harrop 039 * @author Rod Johnson 040 * @author Costin Leau 041 * @author Sam Brannen 042 * @author Chris Beams 043 * @since 1.2.2 044 */ 045public abstract class ReflectionUtils { 046 047 /** 048 * Naming prefix for CGLIB-renamed methods. 049 * @see #isCglibRenamedMethod 050 */ 051 private static final String CGLIB_RENAMED_METHOD_PREFIX = "CGLIB$"; 052 053 private static final Method[] NO_METHODS = {}; 054 055 private static final Field[] NO_FIELDS = {}; 056 057 058 /** 059 * Cache for {@link Class#getDeclaredMethods()} plus equivalent default methods 060 * from Java 8 based interfaces, allowing for fast iteration. 061 */ 062 private static final Map<Class<?>, Method[]> declaredMethodsCache = 063 new ConcurrentReferenceHashMap<Class<?>, Method[]>(256); 064 065 /** 066 * Cache for {@link Class#getDeclaredFields()}, allowing for fast iteration. 067 */ 068 private static final Map<Class<?>, Field[]> declaredFieldsCache = 069 new ConcurrentReferenceHashMap<Class<?>, Field[]>(256); 070 071 072 /** 073 * Attempt to find a {@link Field field} on the supplied {@link Class} with the 074 * supplied {@code name}. Searches all superclasses up to {@link Object}. 075 * @param clazz the class to introspect 076 * @param name the name of the field 077 * @return the corresponding Field object, or {@code null} if not found 078 */ 079 public static Field findField(Class<?> clazz, String name) { 080 return findField(clazz, name, null); 081 } 082 083 /** 084 * Attempt to find a {@link Field field} on the supplied {@link Class} with the 085 * supplied {@code name} and/or {@link Class type}. Searches all superclasses 086 * up to {@link Object}. 087 * @param clazz the class to introspect 088 * @param name the name of the field (may be {@code null} if type is specified) 089 * @param type the type of the field (may be {@code null} if name is specified) 090 * @return the corresponding Field object, or {@code null} if not found 091 */ 092 public static Field findField(Class<?> clazz, String name, Class<?> type) { 093 Assert.notNull(clazz, "Class must not be null"); 094 Assert.isTrue(name != null || type != null, "Either name or type of the field must be specified"); 095 Class<?> searchType = clazz; 096 while (Object.class != searchType && searchType != null) { 097 Field[] fields = getDeclaredFields(searchType); 098 for (Field field : fields) { 099 if ((name == null || name.equals(field.getName())) && 100 (type == null || type.equals(field.getType()))) { 101 return field; 102 } 103 } 104 searchType = searchType.getSuperclass(); 105 } 106 return null; 107 } 108 109 /** 110 * Set the field represented by the supplied {@link Field field object} on the 111 * specified {@link Object target object} to the specified {@code value}. 112 * In accordance with {@link Field#set(Object, Object)} semantics, the new value 113 * is automatically unwrapped if the underlying field has a primitive type. 114 * <p>Thrown exceptions are handled via a call to {@link #handleReflectionException(Exception)}. 115 * @param field the field to set 116 * @param target the target object on which to set the field 117 * @param value the value to set (may be {@code null}) 118 */ 119 public static void setField(Field field, Object target, Object value) { 120 try { 121 field.set(target, value); 122 } 123 catch (IllegalAccessException ex) { 124 handleReflectionException(ex); 125 throw new IllegalStateException( 126 "Unexpected reflection exception - " + ex.getClass().getName() + ": " + ex.getMessage()); 127 } 128 } 129 130 /** 131 * Get the field represented by the supplied {@link Field field object} on the 132 * specified {@link Object target object}. In accordance with {@link Field#get(Object)} 133 * semantics, the returned value is automatically wrapped if the underlying field 134 * has a primitive type. 135 * <p>Thrown exceptions are handled via a call to {@link #handleReflectionException(Exception)}. 136 * @param field the field to get 137 * @param target the target object from which to get the field 138 * @return the field's current value 139 */ 140 public static Object getField(Field field, Object target) { 141 try { 142 return field.get(target); 143 } 144 catch (IllegalAccessException ex) { 145 handleReflectionException(ex); 146 throw new IllegalStateException( 147 "Unexpected reflection exception - " + ex.getClass().getName() + ": " + ex.getMessage()); 148 } 149 } 150 151 /** 152 * Attempt to find a {@link Method} on the supplied class with the supplied name 153 * and no parameters. Searches all superclasses up to {@code Object}. 154 * <p>Returns {@code null} if no {@link Method} can be found. 155 * @param clazz the class to introspect 156 * @param name the name of the method 157 * @return the Method object, or {@code null} if none found 158 */ 159 public static Method findMethod(Class<?> clazz, String name) { 160 return findMethod(clazz, name, new Class<?>[0]); 161 } 162 163 /** 164 * Attempt to find a {@link Method} on the supplied class with the supplied name 165 * and parameter types. Searches all superclasses up to {@code Object}. 166 * <p>Returns {@code null} if no {@link Method} can be found. 167 * @param clazz the class to introspect 168 * @param name the name of the method 169 * @param paramTypes the parameter types of the method 170 * (may be {@code null} to indicate any signature) 171 * @return the Method object, or {@code null} if none found 172 */ 173 public static Method findMethod(Class<?> clazz, String name, Class<?>... paramTypes) { 174 Assert.notNull(clazz, "Class must not be null"); 175 Assert.notNull(name, "Method name must not be null"); 176 Class<?> searchType = clazz; 177 while (searchType != null) { 178 Method[] methods = (searchType.isInterface() ? searchType.getMethods() : getDeclaredMethods(searchType)); 179 for (Method method : methods) { 180 if (name.equals(method.getName()) && 181 (paramTypes == null || Arrays.equals(paramTypes, method.getParameterTypes()))) { 182 return method; 183 } 184 } 185 searchType = searchType.getSuperclass(); 186 } 187 return null; 188 } 189 190 /** 191 * Invoke the specified {@link Method} against the supplied target object with no arguments. 192 * The target object can be {@code null} when invoking a static {@link Method}. 193 * <p>Thrown exceptions are handled via a call to {@link #handleReflectionException}. 194 * @param method the method to invoke 195 * @param target the target object to invoke the method on 196 * @return the invocation result, if any 197 * @see #invokeMethod(java.lang.reflect.Method, Object, Object[]) 198 */ 199 public static Object invokeMethod(Method method, Object target) { 200 return invokeMethod(method, target, new Object[0]); 201 } 202 203 /** 204 * Invoke the specified {@link Method} against the supplied target object with the 205 * supplied arguments. The target object can be {@code null} when invoking a 206 * static {@link Method}. 207 * <p>Thrown exceptions are handled via a call to {@link #handleReflectionException}. 208 * @param method the method to invoke 209 * @param target the target object to invoke the method on 210 * @param args the invocation arguments (may be {@code null}) 211 * @return the invocation result, if any 212 */ 213 public static Object invokeMethod(Method method, Object target, Object... args) { 214 try { 215 return method.invoke(target, args); 216 } 217 catch (Exception ex) { 218 handleReflectionException(ex); 219 } 220 throw new IllegalStateException("Should never get here"); 221 } 222 223 /** 224 * Invoke the specified JDBC API {@link Method} against the supplied target 225 * object with no arguments. 226 * @param method the method to invoke 227 * @param target the target object to invoke the method on 228 * @return the invocation result, if any 229 * @throws SQLException the JDBC API SQLException to rethrow (if any) 230 * @see #invokeJdbcMethod(java.lang.reflect.Method, Object, Object[]) 231 */ 232 public static Object invokeJdbcMethod(Method method, Object target) throws SQLException { 233 return invokeJdbcMethod(method, target, new Object[0]); 234 } 235 236 /** 237 * Invoke the specified JDBC API {@link Method} against the supplied target 238 * object with the supplied arguments. 239 * @param method the method to invoke 240 * @param target the target object to invoke the method on 241 * @param args the invocation arguments (may be {@code null}) 242 * @return the invocation result, if any 243 * @throws SQLException the JDBC API SQLException to rethrow (if any) 244 * @see #invokeMethod(java.lang.reflect.Method, Object, Object[]) 245 */ 246 public static Object invokeJdbcMethod(Method method, Object target, Object... args) throws SQLException { 247 try { 248 return method.invoke(target, args); 249 } 250 catch (IllegalAccessException ex) { 251 handleReflectionException(ex); 252 } 253 catch (InvocationTargetException ex) { 254 if (ex.getTargetException() instanceof SQLException) { 255 throw (SQLException) ex.getTargetException(); 256 } 257 handleInvocationTargetException(ex); 258 } 259 throw new IllegalStateException("Should never get here"); 260 } 261 262 /** 263 * Handle the given reflection exception. Should only be called if no 264 * checked exception is expected to be thrown by the target method. 265 * <p>Throws the underlying RuntimeException or Error in case of an 266 * InvocationTargetException with such a root cause. Throws an 267 * IllegalStateException with an appropriate message or 268 * UndeclaredThrowableException otherwise. 269 * @param ex the reflection exception to handle 270 */ 271 public static void handleReflectionException(Exception ex) { 272 if (ex instanceof NoSuchMethodException) { 273 throw new IllegalStateException("Method not found: " + ex.getMessage()); 274 } 275 if (ex instanceof IllegalAccessException) { 276 throw new IllegalStateException("Could not access method: " + ex.getMessage()); 277 } 278 if (ex instanceof InvocationTargetException) { 279 handleInvocationTargetException((InvocationTargetException) ex); 280 } 281 if (ex instanceof RuntimeException) { 282 throw (RuntimeException) ex; 283 } 284 throw new UndeclaredThrowableException(ex); 285 } 286 287 /** 288 * Handle the given invocation target exception. Should only be called if no 289 * checked exception is expected to be thrown by the target method. 290 * <p>Throws the underlying RuntimeException or Error in case of such a root 291 * cause. Throws an UndeclaredThrowableException otherwise. 292 * @param ex the invocation target exception to handle 293 */ 294 public static void handleInvocationTargetException(InvocationTargetException ex) { 295 rethrowRuntimeException(ex.getTargetException()); 296 } 297 298 /** 299 * Rethrow the given {@link Throwable exception}, which is presumably the 300 * <em>target exception</em> of an {@link InvocationTargetException}. 301 * Should only be called if no checked exception is expected to be thrown 302 * by the target method. 303 * <p>Rethrows the underlying exception cast to a {@link RuntimeException} or 304 * {@link Error} if appropriate; otherwise, throws an 305 * {@link UndeclaredThrowableException}. 306 * @param ex the exception to rethrow 307 * @throws RuntimeException the rethrown exception 308 */ 309 public static void rethrowRuntimeException(Throwable ex) { 310 if (ex instanceof RuntimeException) { 311 throw (RuntimeException) ex; 312 } 313 if (ex instanceof Error) { 314 throw (Error) ex; 315 } 316 throw new UndeclaredThrowableException(ex); 317 } 318 319 /** 320 * Rethrow the given {@link Throwable exception}, which is presumably the 321 * <em>target exception</em> of an {@link InvocationTargetException}. 322 * Should only be called if no checked exception is expected to be thrown 323 * by the target method. 324 * <p>Rethrows the underlying exception cast to an {@link Exception} or 325 * {@link Error} if appropriate; otherwise, throws an 326 * {@link UndeclaredThrowableException}. 327 * @param ex the exception to rethrow 328 * @throws Exception the rethrown exception (in case of a checked exception) 329 */ 330 public static void rethrowException(Throwable ex) throws Exception { 331 if (ex instanceof Exception) { 332 throw (Exception) ex; 333 } 334 if (ex instanceof Error) { 335 throw (Error) ex; 336 } 337 throw new UndeclaredThrowableException(ex); 338 } 339 340 /** 341 * Determine whether the given method explicitly declares the given 342 * exception or one of its superclasses, which means that an exception 343 * of that type can be propagated as-is within a reflective invocation. 344 * @param method the declaring method 345 * @param exceptionType the exception to throw 346 * @return {@code true} if the exception can be thrown as-is; 347 * {@code false} if it needs to be wrapped 348 */ 349 public static boolean declaresException(Method method, Class<?> exceptionType) { 350 Assert.notNull(method, "Method must not be null"); 351 Class<?>[] declaredExceptions = method.getExceptionTypes(); 352 for (Class<?> declaredException : declaredExceptions) { 353 if (declaredException.isAssignableFrom(exceptionType)) { 354 return true; 355 } 356 } 357 return false; 358 } 359 360 /** 361 * Determine whether the given field is a "public static final" constant. 362 * @param field the field to check 363 */ 364 public static boolean isPublicStaticFinal(Field field) { 365 int modifiers = field.getModifiers(); 366 return (Modifier.isPublic(modifiers) && Modifier.isStatic(modifiers) && Modifier.isFinal(modifiers)); 367 } 368 369 /** 370 * Determine whether the given method is an "equals" method. 371 * @see java.lang.Object#equals(Object) 372 */ 373 public static boolean isEqualsMethod(Method method) { 374 if (method == null || !method.getName().equals("equals")) { 375 return false; 376 } 377 Class<?>[] paramTypes = method.getParameterTypes(); 378 return (paramTypes.length == 1 && paramTypes[0] == Object.class); 379 } 380 381 /** 382 * Determine whether the given method is a "hashCode" method. 383 * @see java.lang.Object#hashCode() 384 */ 385 public static boolean isHashCodeMethod(Method method) { 386 return (method != null && method.getName().equals("hashCode") && method.getParameterTypes().length == 0); 387 } 388 389 /** 390 * Determine whether the given method is a "toString" method. 391 * @see java.lang.Object#toString() 392 */ 393 public static boolean isToStringMethod(Method method) { 394 return (method != null && method.getName().equals("toString") && method.getParameterTypes().length == 0); 395 } 396 397 /** 398 * Determine whether the given method is originally declared by {@link java.lang.Object}. 399 */ 400 public static boolean isObjectMethod(Method method) { 401 if (method == null) { 402 return false; 403 } 404 try { 405 Object.class.getDeclaredMethod(method.getName(), method.getParameterTypes()); 406 return true; 407 } 408 catch (Exception ex) { 409 return false; 410 } 411 } 412 413 /** 414 * Determine whether the given method is a CGLIB 'renamed' method, 415 * following the pattern "CGLIB$methodName$0". 416 * @param renamedMethod the method to check 417 * @see org.springframework.cglib.proxy.Enhancer#rename 418 */ 419 public static boolean isCglibRenamedMethod(Method renamedMethod) { 420 String name = renamedMethod.getName(); 421 if (name.startsWith(CGLIB_RENAMED_METHOD_PREFIX)) { 422 int i = name.length() - 1; 423 while (i >= 0 && Character.isDigit(name.charAt(i))) { 424 i--; 425 } 426 return ((i > CGLIB_RENAMED_METHOD_PREFIX.length()) && 427 (i < name.length() - 1) && name.charAt(i) == '$'); 428 } 429 return false; 430 } 431 432 /** 433 * Make the given field accessible, explicitly setting it accessible if 434 * necessary. The {@code setAccessible(true)} method is only called 435 * when actually necessary, to avoid unnecessary conflicts with a JVM 436 * SecurityManager (if active). 437 * @param field the field to make accessible 438 * @see java.lang.reflect.Field#setAccessible 439 */ 440 public static void makeAccessible(Field field) { 441 if ((!Modifier.isPublic(field.getModifiers()) || 442 !Modifier.isPublic(field.getDeclaringClass().getModifiers()) || 443 Modifier.isFinal(field.getModifiers())) && !field.isAccessible()) { 444 field.setAccessible(true); 445 } 446 } 447 448 /** 449 * Make the given method accessible, explicitly setting it accessible if 450 * necessary. The {@code setAccessible(true)} method is only called 451 * when actually necessary, to avoid unnecessary conflicts with a JVM 452 * SecurityManager (if active). 453 * @param method the method to make accessible 454 * @see java.lang.reflect.Method#setAccessible 455 */ 456 public static void makeAccessible(Method method) { 457 if ((!Modifier.isPublic(method.getModifiers()) || 458 !Modifier.isPublic(method.getDeclaringClass().getModifiers())) && !method.isAccessible()) { 459 method.setAccessible(true); 460 } 461 } 462 463 /** 464 * Make the given constructor accessible, explicitly setting it accessible 465 * if necessary. The {@code setAccessible(true)} method is only called 466 * when actually necessary, to avoid unnecessary conflicts with a JVM 467 * SecurityManager (if active). 468 * @param ctor the constructor to make accessible 469 * @see java.lang.reflect.Constructor#setAccessible 470 */ 471 public static void makeAccessible(Constructor<?> ctor) { 472 if ((!Modifier.isPublic(ctor.getModifiers()) || 473 !Modifier.isPublic(ctor.getDeclaringClass().getModifiers())) && !ctor.isAccessible()) { 474 ctor.setAccessible(true); 475 } 476 } 477 478 /** 479 * Perform the given callback operation on all matching methods of the given 480 * class, as locally declared or equivalent thereof (such as default methods 481 * on Java 8 based interfaces that the given class implements). 482 * @param clazz the class to introspect 483 * @param mc the callback to invoke for each method 484 * @since 4.2 485 * @see #doWithMethods 486 */ 487 public static void doWithLocalMethods(Class<?> clazz, MethodCallback mc) { 488 Method[] methods = getDeclaredMethods(clazz); 489 for (Method method : methods) { 490 try { 491 mc.doWith(method); 492 } 493 catch (IllegalAccessException ex) { 494 throw new IllegalStateException("Not allowed to access method '" + method.getName() + "': " + ex); 495 } 496 } 497 } 498 499 /** 500 * Perform the given callback operation on all matching methods of the given 501 * class and superclasses. 502 * <p>The same named method occurring on subclass and superclass will appear 503 * twice, unless excluded by a {@link MethodFilter}. 504 * @param clazz the class to introspect 505 * @param mc the callback to invoke for each method 506 * @see #doWithMethods(Class, MethodCallback, MethodFilter) 507 */ 508 public static void doWithMethods(Class<?> clazz, MethodCallback mc) { 509 doWithMethods(clazz, mc, null); 510 } 511 512 /** 513 * Perform the given callback operation on all matching methods of the given 514 * class and superclasses (or given interface and super-interfaces). 515 * <p>The same named method occurring on subclass and superclass will appear 516 * twice, unless excluded by the specified {@link MethodFilter}. 517 * @param clazz the class to introspect 518 * @param mc the callback to invoke for each method 519 * @param mf the filter that determines the methods to apply the callback to 520 */ 521 public static void doWithMethods(Class<?> clazz, MethodCallback mc, MethodFilter mf) { 522 // Keep backing up the inheritance hierarchy. 523 Method[] methods = getDeclaredMethods(clazz); 524 for (Method method : methods) { 525 if (mf != null && !mf.matches(method)) { 526 continue; 527 } 528 try { 529 mc.doWith(method); 530 } 531 catch (IllegalAccessException ex) { 532 throw new IllegalStateException("Not allowed to access method '" + method.getName() + "': " + ex); 533 } 534 } 535 if (clazz.getSuperclass() != null) { 536 doWithMethods(clazz.getSuperclass(), mc, mf); 537 } 538 else if (clazz.isInterface()) { 539 for (Class<?> superIfc : clazz.getInterfaces()) { 540 doWithMethods(superIfc, mc, mf); 541 } 542 } 543 } 544 545 /** 546 * Get all declared methods on the leaf class and all superclasses. 547 * Leaf class methods are included first. 548 * @param leafClass the class to introspect 549 */ 550 public static Method[] getAllDeclaredMethods(Class<?> leafClass) { 551 final List<Method> methods = new ArrayList<Method>(32); 552 doWithMethods(leafClass, new MethodCallback() { 553 @Override 554 public void doWith(Method method) { 555 methods.add(method); 556 } 557 }); 558 return methods.toArray(new Method[methods.size()]); 559 } 560 561 /** 562 * Get the unique set of declared methods on the leaf class and all superclasses. 563 * Leaf class methods are included first and while traversing the superclass hierarchy 564 * any methods found with signatures matching a method already included are filtered out. 565 * @param leafClass the class to introspect 566 */ 567 public static Method[] getUniqueDeclaredMethods(Class<?> leafClass) { 568 final List<Method> methods = new ArrayList<Method>(32); 569 doWithMethods(leafClass, new MethodCallback() { 570 @Override 571 public void doWith(Method method) { 572 boolean knownSignature = false; 573 Method methodBeingOverriddenWithCovariantReturnType = null; 574 for (Method existingMethod : methods) { 575 if (method.getName().equals(existingMethod.getName()) && 576 Arrays.equals(method.getParameterTypes(), existingMethod.getParameterTypes())) { 577 // Is this a covariant return type situation? 578 if (existingMethod.getReturnType() != method.getReturnType() && 579 existingMethod.getReturnType().isAssignableFrom(method.getReturnType())) { 580 methodBeingOverriddenWithCovariantReturnType = existingMethod; 581 } 582 else { 583 knownSignature = true; 584 } 585 break; 586 } 587 } 588 if (methodBeingOverriddenWithCovariantReturnType != null) { 589 methods.remove(methodBeingOverriddenWithCovariantReturnType); 590 } 591 if (!knownSignature && !isCglibRenamedMethod(method)) { 592 methods.add(method); 593 } 594 } 595 }); 596 return methods.toArray(new Method[methods.size()]); 597 } 598 599 /** 600 * This variant retrieves {@link Class#getDeclaredMethods()} from a local cache 601 * in order to avoid the JVM's SecurityManager check and defensive array copying. 602 * In addition, it also includes Java 8 default methods from locally implemented 603 * interfaces, since those are effectively to be treated just like declared methods. 604 * @param clazz the class to introspect 605 * @return the cached array of methods 606 * @see Class#getDeclaredMethods() 607 */ 608 private static Method[] getDeclaredMethods(Class<?> clazz) { 609 Assert.notNull(clazz, "Class must not be null"); 610 Method[] result = declaredMethodsCache.get(clazz); 611 if (result == null) { 612 Method[] declaredMethods = clazz.getDeclaredMethods(); 613 List<Method> defaultMethods = findConcreteMethodsOnInterfaces(clazz); 614 if (defaultMethods != null) { 615 result = new Method[declaredMethods.length + defaultMethods.size()]; 616 System.arraycopy(declaredMethods, 0, result, 0, declaredMethods.length); 617 int index = declaredMethods.length; 618 for (Method defaultMethod : defaultMethods) { 619 result[index] = defaultMethod; 620 index++; 621 } 622 } 623 else { 624 result = declaredMethods; 625 } 626 declaredMethodsCache.put(clazz, (result.length == 0 ? NO_METHODS : result)); 627 } 628 return result; 629 } 630 631 private static List<Method> findConcreteMethodsOnInterfaces(Class<?> clazz) { 632 List<Method> result = null; 633 for (Class<?> ifc : clazz.getInterfaces()) { 634 for (Method ifcMethod : ifc.getMethods()) { 635 if (!Modifier.isAbstract(ifcMethod.getModifiers())) { 636 if (result == null) { 637 result = new ArrayList<Method>(); 638 } 639 result.add(ifcMethod); 640 } 641 } 642 } 643 return result; 644 } 645 646 /** 647 * Invoke the given callback on all locally declared fields in the given class. 648 * @param clazz the target class to analyze 649 * @param fc the callback to invoke for each field 650 * @since 4.2 651 * @see #doWithFields 652 */ 653 public static void doWithLocalFields(Class<?> clazz, FieldCallback fc) { 654 for (Field field : getDeclaredFields(clazz)) { 655 try { 656 fc.doWith(field); 657 } 658 catch (IllegalAccessException ex) { 659 throw new IllegalStateException("Not allowed to access field '" + field.getName() + "': " + ex); 660 } 661 } 662 } 663 664 /** 665 * Invoke the given callback on all fields in the target class, going up the 666 * class hierarchy to get all declared fields. 667 * @param clazz the target class to analyze 668 * @param fc the callback to invoke for each field 669 */ 670 public static void doWithFields(Class<?> clazz, FieldCallback fc) { 671 doWithFields(clazz, fc, null); 672 } 673 674 /** 675 * Invoke the given callback on all fields in the target class, going up the 676 * class hierarchy to get all declared fields. 677 * @param clazz the target class to analyze 678 * @param fc the callback to invoke for each field 679 * @param ff the filter that determines the fields to apply the callback to 680 */ 681 public static void doWithFields(Class<?> clazz, FieldCallback fc, FieldFilter ff) { 682 // Keep backing up the inheritance hierarchy. 683 Class<?> targetClass = clazz; 684 do { 685 Field[] fields = getDeclaredFields(targetClass); 686 for (Field field : fields) { 687 if (ff != null && !ff.matches(field)) { 688 continue; 689 } 690 try { 691 fc.doWith(field); 692 } 693 catch (IllegalAccessException ex) { 694 throw new IllegalStateException("Not allowed to access field '" + field.getName() + "': " + ex); 695 } 696 } 697 targetClass = targetClass.getSuperclass(); 698 } 699 while (targetClass != null && targetClass != Object.class); 700 } 701 702 /** 703 * This variant retrieves {@link Class#getDeclaredFields()} from a local cache 704 * in order to avoid the JVM's SecurityManager check and defensive array copying. 705 * @param clazz the class to introspect 706 * @return the cached array of fields 707 * @see Class#getDeclaredFields() 708 */ 709 private static Field[] getDeclaredFields(Class<?> clazz) { 710 Assert.notNull(clazz, "Class must not be null"); 711 Field[] result = declaredFieldsCache.get(clazz); 712 if (result == null) { 713 result = clazz.getDeclaredFields(); 714 declaredFieldsCache.put(clazz, (result.length == 0 ? NO_FIELDS : result)); 715 } 716 return result; 717 } 718 719 /** 720 * Given the source object and the destination, which must be the same class 721 * or a subclass, copy all fields, including inherited fields. Designed to 722 * work on objects with public no-arg constructors. 723 */ 724 public static void shallowCopyFieldState(final Object src, final Object dest) { 725 Assert.notNull(src, "Source for field copy cannot be null"); 726 Assert.notNull(dest, "Destination for field copy cannot be null"); 727 if (!src.getClass().isAssignableFrom(dest.getClass())) { 728 throw new IllegalArgumentException("Destination class [" + dest.getClass().getName() + 729 "] must be same or subclass as source class [" + src.getClass().getName() + "]"); 730 } 731 doWithFields(src.getClass(), new FieldCallback() { 732 @Override 733 public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException { 734 makeAccessible(field); 735 Object srcValue = field.get(src); 736 field.set(dest, srcValue); 737 } 738 }, COPYABLE_FIELDS); 739 } 740 741 /** 742 * Clear the internal method/field cache. 743 * @since 4.2.4 744 */ 745 public static void clearCache() { 746 declaredMethodsCache.clear(); 747 declaredFieldsCache.clear(); 748 } 749 750 751 /** 752 * Action to take on each method. 753 */ 754 public interface MethodCallback { 755 756 /** 757 * Perform an operation using the given method. 758 * @param method the method to operate on 759 */ 760 void doWith(Method method) throws IllegalArgumentException, IllegalAccessException; 761 } 762 763 764 /** 765 * Callback optionally used to filter methods to be operated on by a method callback. 766 */ 767 public interface MethodFilter { 768 769 /** 770 * Determine whether the given method matches. 771 * @param method the method to check 772 */ 773 boolean matches(Method method); 774 } 775 776 777 /** 778 * Callback interface invoked on each field in the hierarchy. 779 */ 780 public interface FieldCallback { 781 782 /** 783 * Perform an operation using the given field. 784 * @param field the field to operate on 785 */ 786 void doWith(Field field) throws IllegalArgumentException, IllegalAccessException; 787 } 788 789 790 /** 791 * Callback optionally used to filter fields to be operated on by a field callback. 792 */ 793 public interface FieldFilter { 794 795 /** 796 * Determine whether the given field matches. 797 * @param field the field to check 798 */ 799 boolean matches(Field field); 800 } 801 802 803 /** 804 * Pre-built MethodFilter that matches all non-bridge methods. 805 * @since 3.0 806 */ 807 public static final MethodFilter NON_BRIDGED_METHODS = new MethodFilter() { 808 809 @Override 810 public boolean matches(Method method) { 811 return !method.isBridge(); 812 } 813 }; 814 815 816 /** 817 * Pre-built MethodFilter that matches all non-bridge methods 818 * which are not declared on {@code java.lang.Object}. 819 * @since 3.0.5 820 */ 821 public static final MethodFilter USER_DECLARED_METHODS = new MethodFilter() { 822 823 @Override 824 public boolean matches(Method method) { 825 return (!method.isBridge() && method.getDeclaringClass() != Object.class); 826 } 827 }; 828 829 830 /** 831 * Pre-built FieldFilter that matches all non-static, non-final fields. 832 */ 833 public static final FieldFilter COPYABLE_FIELDS = new FieldFilter() { 834 835 @Override 836 public boolean matches(Field field) { 837 return !(Modifier.isStatic(field.getModifiers()) || Modifier.isFinal(field.getModifiers())); 838 } 839 }; 840 841}