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}