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