• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one or more
3  * contributor license agreements.  See the NOTICE file distributed with
4  * this work for additional information regarding copyright ownership.
5  * The ASF licenses this file to You under the Apache License, Version 2.0
6  * (the "License"); you may not use this file except in compliance with
7  * the License.  You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17 package org.apache.commons.lang3.reflect;
18 
19 import java.lang.annotation.Annotation;
20 import java.lang.reflect.Array;
21 import java.lang.reflect.InvocationTargetException;
22 import java.lang.reflect.Method;
23 import java.lang.reflect.Type;
24 import java.lang.reflect.TypeVariable;
25 import java.util.ArrayList;
26 import java.util.Arrays;
27 import java.util.Comparator;
28 import java.util.Iterator;
29 import java.util.LinkedHashSet;
30 import java.util.List;
31 import java.util.Map;
32 import java.util.Objects;
33 import java.util.Set;
34 import java.util.TreeMap;
35 import java.util.stream.Collectors;
36 import java.util.stream.Stream;
37 
38 import org.apache.commons.lang3.ArrayUtils;
39 import org.apache.commons.lang3.ClassUtils;
40 import org.apache.commons.lang3.ClassUtils.Interfaces;
41 import org.apache.commons.lang3.Validate;
42 
43 /**
44  * Utility reflection methods focused on {@link Method}s, originally from Commons BeanUtils.
45  * Differences from the BeanUtils version may be noted, especially where similar functionality
46  * already existed within Lang.
47  *
48  * <h2>Known Limitations</h2>
49  * <h3>Accessing Public Methods In A Default Access Superclass</h3>
50  * <p>There is an issue when invoking {@code public} methods contained in a default access superclass on JREs prior to 1.4.
51  * Reflection locates these methods fine and correctly assigns them as {@code public}.
52  * However, an {@link IllegalAccessException} is thrown if the method is invoked.</p>
53  *
54  * <p>{@link MethodUtils} contains a workaround for this situation.
55  * It will attempt to call {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)} on this method.
56  * If this call succeeds, then the method can be invoked as normal.
57  * This call will only succeed when the application has sufficient security privileges.
58  * If this call fails then the method may fail.</p>
59  *
60  * @since 2.5
61  */
62 public class MethodUtils {
63 
64     private static final Comparator<Method> METHOD_BY_SIGNATURE = Comparator.comparing(Method::toString);
65 
66     /**
67      * {@link MethodUtils} instances should NOT be constructed in standard programming.
68      * Instead, the class should be used as
69      * {@code MethodUtils.getAccessibleMethod(method)}.
70      *
71      * <p>This constructor is {@code public} to permit tools that require a JavaBean
72      * instance to operate.</p>
73      */
MethodUtils()74     public MethodUtils() {
75     }
76 
77     /**
78      * Invokes a named method without parameters.
79      *
80      * <p>This method delegates the method search to {@link #getMatchingAccessibleMethod(Class, String, Class[])}.</p>
81      *
82      * <p>This is a convenient wrapper for
83      * {@link #invokeMethod(Object object, String methodName, Object[] args, Class[] parameterTypes)}.
84      * </p>
85      *
86      * @param object invoke method on this object
87      * @param methodName get method with this name
88      * @return The value returned by the invoked method
89      *
90      * @throws NoSuchMethodException if there is no such accessible method
91      * @throws InvocationTargetException wraps an exception thrown by the method invoked
92      * @throws IllegalAccessException if the requested method is not accessible via reflection
93      *
94      *  @since 3.4
95      */
invokeMethod(final Object object, final String methodName)96     public static Object invokeMethod(final Object object, final String methodName) throws NoSuchMethodException,
97             IllegalAccessException, InvocationTargetException {
98         return invokeMethod(object, methodName, ArrayUtils.EMPTY_OBJECT_ARRAY, null);
99     }
100 
101     /**
102      * Invokes a named method without parameters.
103      *
104      * <p>This is a convenient wrapper for
105      * {@link #invokeMethod(Object object, boolean forceAccess, String methodName, Object[] args, Class[] parameterTypes)}.
106      * </p>
107      *
108      * @param object invoke method on this object
109      * @param forceAccess force access to invoke method even if it's not accessible
110      * @param methodName get method with this name
111      * @return The value returned by the invoked method
112      *
113      * @throws NoSuchMethodException if there is no such accessible method
114      * @throws InvocationTargetException wraps an exception thrown by the method invoked
115      * @throws IllegalAccessException if the requested method is not accessible via reflection
116      *
117      * @since 3.5
118      */
invokeMethod(final Object object, final boolean forceAccess, final String methodName)119     public static Object invokeMethod(final Object object, final boolean forceAccess, final String methodName)
120             throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
121         return invokeMethod(object, forceAccess, methodName, ArrayUtils.EMPTY_OBJECT_ARRAY, null);
122     }
123 
124     /**
125      * Invokes a named method whose parameter type matches the object type.
126      *
127      * <p>This method delegates the method search to {@link #getMatchingAccessibleMethod(Class, String, Class[])}.</p>
128      *
129      * <p>This method supports calls to methods taking primitive parameters
130      * via passing in wrapping classes. So, for example, a {@link Boolean} object
131      * would match a {@code boolean} primitive.</p>
132      *
133      * <p>This is a convenient wrapper for
134      * {@link #invokeMethod(Object object, String methodName, Object[] args, Class[] parameterTypes)}.
135      * </p>
136      *
137      * @param object invoke method on this object
138      * @param methodName get method with this name
139      * @param args use these arguments - treat null as empty array
140      * @return The value returned by the invoked method
141      *
142      * @throws NoSuchMethodException if there is no such accessible method
143      * @throws InvocationTargetException wraps an exception thrown by the method invoked
144      * @throws IllegalAccessException if the requested method is not accessible via reflection
145      */
invokeMethod(final Object object, final String methodName, Object... args)146     public static Object invokeMethod(final Object object, final String methodName,
147             Object... args) throws NoSuchMethodException,
148             IllegalAccessException, InvocationTargetException {
149         args = ArrayUtils.nullToEmpty(args);
150         return invokeMethod(object, methodName, args, ClassUtils.toClass(args));
151     }
152 
153     /**
154      * Invokes a named method whose parameter type matches the object type.
155      *
156      * <p>This method supports calls to methods taking primitive parameters
157      * via passing in wrapping classes. So, for example, a {@link Boolean} object
158      * would match a {@code boolean} primitive.</p>
159      *
160      * <p>This is a convenient wrapper for
161      * {@link #invokeMethod(Object object, boolean forceAccess, String methodName, Object[] args, Class[] parameterTypes)}.
162      * </p>
163      *
164      * @param object invoke method on this object
165      * @param forceAccess force access to invoke method even if it's not accessible
166      * @param methodName get method with this name
167      * @param args use these arguments - treat null as empty array
168      * @return The value returned by the invoked method
169      *
170      * @throws NoSuchMethodException if there is no such accessible method
171      * @throws InvocationTargetException wraps an exception thrown by the method invoked
172      * @throws IllegalAccessException if the requested method is not accessible via reflection
173      *
174      * @since 3.5
175      */
invokeMethod(final Object object, final boolean forceAccess, final String methodName, Object... args)176     public static Object invokeMethod(final Object object, final boolean forceAccess, final String methodName,
177             Object... args) throws NoSuchMethodException,
178             IllegalAccessException, InvocationTargetException {
179         args = ArrayUtils.nullToEmpty(args);
180         return invokeMethod(object, forceAccess, methodName, args, ClassUtils.toClass(args));
181     }
182 
183     /**
184      * Invokes a named method whose parameter type matches the object type.
185      *
186      * <p>This method supports calls to methods taking primitive parameters
187      * via passing in wrapping classes. So, for example, a {@link Boolean} object
188      * would match a {@code boolean} primitive.</p>
189      *
190      * @param object invoke method on this object
191      * @param forceAccess force access to invoke method even if it's not accessible
192      * @param methodName get method with this name
193      * @param args use these arguments - treat null as empty array
194      * @param parameterTypes match these parameters - treat null as empty array
195      * @return The value returned by the invoked method
196      *
197      * @throws NoSuchMethodException if there is no such accessible method
198      * @throws InvocationTargetException wraps an exception thrown by the method invoked
199      * @throws IllegalAccessException if the requested method is not accessible via reflection
200      * @since 3.5
201      */
invokeMethod(final Object object, final boolean forceAccess, final String methodName, Object[] args, Class<?>[] parameterTypes)202     public static Object invokeMethod(final Object object, final boolean forceAccess, final String methodName, Object[] args, Class<?>[] parameterTypes)
203         throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
204         Objects.requireNonNull(object, "object");
205         parameterTypes = ArrayUtils.nullToEmpty(parameterTypes);
206         args = ArrayUtils.nullToEmpty(args);
207 
208         final String messagePrefix;
209         final Method method;
210 
211         final Class<? extends Object> cls = object.getClass();
212         if (forceAccess) {
213             messagePrefix = "No such method: ";
214             method = getMatchingMethod(cls, methodName, parameterTypes);
215             if (method != null && !method.isAccessible()) {
216                 method.setAccessible(true);
217             }
218         } else {
219             messagePrefix = "No such accessible method: ";
220             method = getMatchingAccessibleMethod(cls, methodName, parameterTypes);
221         }
222 
223         if (method == null) {
224             throw new NoSuchMethodException(messagePrefix + methodName + "() on object: " + cls.getName());
225         }
226         args = toVarArgs(method, args);
227 
228         return method.invoke(object, args);
229     }
230 
231     /**
232      * Invokes a named method whose parameter type matches the object type.
233      *
234      * <p>This method delegates the method search to {@link #getMatchingAccessibleMethod(Class, String, Class[])}.</p>
235      *
236      * <p>This method supports calls to methods taking primitive parameters
237      * via passing in wrapping classes. So, for example, a {@link Boolean} object
238      * would match a {@code boolean} primitive.</p>
239      *
240      * @param object invoke method on this object
241      * @param methodName get method with this name
242      * @param args use these arguments - treat null as empty array
243      * @param parameterTypes match these parameters - treat null as empty array
244      * @return The value returned by the invoked method
245      *
246      * @throws NoSuchMethodException if there is no such accessible method
247      * @throws InvocationTargetException wraps an exception thrown by the method invoked
248      * @throws IllegalAccessException if the requested method is not accessible via reflection
249      */
invokeMethod(final Object object, final String methodName, final Object[] args, final Class<?>[] parameterTypes)250     public static Object invokeMethod(final Object object, final String methodName,
251             final Object[] args, final Class<?>[] parameterTypes)
252             throws NoSuchMethodException, IllegalAccessException,
253             InvocationTargetException {
254         return invokeMethod(object, false, methodName, args, parameterTypes);
255     }
256 
257     /**
258      * Invokes a method whose parameter types match exactly the object
259      * types.
260      *
261      * <p>This uses reflection to invoke the method obtained from a call to
262      * {@link #getAccessibleMethod}(Class, String, Class[])}.</p>
263      *
264      * @param object invoke method on this object
265      * @param methodName get method with this name
266      * @return The value returned by the invoked method
267      *
268      * @throws NoSuchMethodException if there is no such accessible method
269      * @throws InvocationTargetException wraps an exception thrown by the
270      *  method invoked
271      * @throws IllegalAccessException if the requested method is not accessible
272      *  via reflection
273      *
274      * @since 3.4
275      */
invokeExactMethod(final Object object, final String methodName)276     public static Object invokeExactMethod(final Object object, final String methodName) throws NoSuchMethodException,
277             IllegalAccessException, InvocationTargetException {
278         return invokeExactMethod(object, methodName, ArrayUtils.EMPTY_OBJECT_ARRAY, null);
279     }
280 
281     /**
282      * Invokes a method with no parameters.
283      *
284      * <p>This uses reflection to invoke the method obtained from a call to
285      * {@link #getAccessibleMethod}(Class, String, Class[])}.</p>
286      *
287      * @param object invoke method on this object
288      * @param methodName get method with this name
289      * @param args use these arguments - treat null as empty array
290      * @return The value returned by the invoked method
291      *
292      * @throws NoSuchMethodException if there is no such accessible method
293      * @throws InvocationTargetException wraps an exception thrown by the
294      *  method invoked
295      * @throws IllegalAccessException if the requested method is not accessible
296      *  via reflection
297      */
invokeExactMethod(final Object object, final String methodName, Object... args)298     public static Object invokeExactMethod(final Object object, final String methodName,
299             Object... args) throws NoSuchMethodException,
300             IllegalAccessException, InvocationTargetException {
301         args = ArrayUtils.nullToEmpty(args);
302         return invokeExactMethod(object, methodName, args, ClassUtils.toClass(args));
303     }
304 
305     /**
306      * Invokes a method whose parameter types match exactly the parameter
307      * types given.
308      *
309      * <p>This uses reflection to invoke the method obtained from a call to
310      * {@link #getAccessibleMethod(Class, String, Class[])}.</p>
311      *
312      * @param object invoke method on this object
313      * @param methodName get method with this name
314      * @param args use these arguments - treat null as empty array
315      * @param parameterTypes match these parameters - treat {@code null} as empty array
316      * @return The value returned by the invoked method
317      *
318      * @throws NoSuchMethodException if there is no such accessible method
319      * @throws InvocationTargetException wraps an exception thrown by the
320      *  method invoked
321      * @throws IllegalAccessException if the requested method is not accessible
322      *  via reflection
323      */
invokeExactMethod(final Object object, final String methodName, Object[] args, Class<?>[] parameterTypes)324     public static Object invokeExactMethod(final Object object, final String methodName, Object[] args, Class<?>[] parameterTypes)
325         throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
326         Objects.requireNonNull(object, "object");
327         args = ArrayUtils.nullToEmpty(args);
328         parameterTypes = ArrayUtils.nullToEmpty(parameterTypes);
329         final Class<?> cls = object.getClass();
330         final Method method = getAccessibleMethod(cls, methodName, parameterTypes);
331         if (method == null) {
332             throw new NoSuchMethodException("No such accessible method: " + methodName + "() on object: " + cls.getName());
333         }
334         return method.invoke(object, args);
335     }
336 
337     /**
338      * Invokes a {@code static} method whose parameter types match exactly the parameter
339      * types given.
340      *
341      * <p>This uses reflection to invoke the method obtained from a call to
342      * {@link #getAccessibleMethod(Class, String, Class[])}.</p>
343      *
344      * @param cls invoke static method on this class
345      * @param methodName get method with this name
346      * @param args use these arguments - treat {@code null} as empty array
347      * @param parameterTypes match these parameters - treat {@code null} as empty array
348      * @return The value returned by the invoked method
349      *
350      * @throws NoSuchMethodException if there is no such accessible method
351      * @throws InvocationTargetException wraps an exception thrown by the
352      *  method invoked
353      * @throws IllegalAccessException if the requested method is not accessible
354      *  via reflection
355      */
invokeExactStaticMethod(final Class<?> cls, final String methodName, Object[] args, Class<?>[] parameterTypes)356     public static Object invokeExactStaticMethod(final Class<?> cls, final String methodName,
357             Object[] args, Class<?>[] parameterTypes)
358             throws NoSuchMethodException, IllegalAccessException,
359             InvocationTargetException {
360         args = ArrayUtils.nullToEmpty(args);
361         parameterTypes = ArrayUtils.nullToEmpty(parameterTypes);
362         final Method method = getAccessibleMethod(cls, methodName, parameterTypes);
363         if (method == null) {
364             throw new NoSuchMethodException("No such accessible method: "
365                     + methodName + "() on class: " + cls.getName());
366         }
367         return method.invoke(null, args);
368     }
369 
370     /**
371      * Invokes a named {@code static} method whose parameter type matches the object type.
372      *
373      * <p>This method delegates the method search to {@link #getMatchingAccessibleMethod(Class, String, Class[])}.</p>
374      *
375      * <p>This method supports calls to methods taking primitive parameters
376      * via passing in wrapping classes. So, for example, a {@link Boolean} class
377      * would match a {@code boolean} primitive.</p>
378      *
379      * <p>This is a convenient wrapper for
380      * {@link #invokeStaticMethod(Class, String, Object[], Class[])}.
381      * </p>
382      *
383      * @param cls invoke static method on this class
384      * @param methodName get method with this name
385      * @param args use these arguments - treat {@code null} as empty array
386      * @return The value returned by the invoked method
387      *
388      * @throws NoSuchMethodException if there is no such accessible method
389      * @throws InvocationTargetException wraps an exception thrown by the
390      *  method invoked
391      * @throws IllegalAccessException if the requested method is not accessible
392      *  via reflection
393      */
invokeStaticMethod(final Class<?> cls, final String methodName, Object... args)394     public static Object invokeStaticMethod(final Class<?> cls, final String methodName,
395             Object... args) throws NoSuchMethodException,
396             IllegalAccessException, InvocationTargetException {
397         args = ArrayUtils.nullToEmpty(args);
398         return invokeStaticMethod(cls, methodName, args, ClassUtils.toClass(args));
399     }
400 
401     /**
402      * Invokes a named {@code static} method whose parameter type matches the object type.
403      *
404      * <p>This method delegates the method search to {@link #getMatchingAccessibleMethod(Class, String, Class[])}.</p>
405      *
406      * <p>This method supports calls to methods taking primitive parameters
407      * via passing in wrapping classes. So, for example, a {@link Boolean} class
408      * would match a {@code boolean} primitive.</p>
409      *
410      * @param cls invoke static method on this class
411      * @param methodName get method with this name
412      * @param args use these arguments - treat {@code null} as empty array
413      * @param parameterTypes match these parameters - treat {@code null} as empty array
414      * @return The value returned by the invoked method
415      *
416      * @throws NoSuchMethodException if there is no such accessible method
417      * @throws InvocationTargetException wraps an exception thrown by the
418      *  method invoked
419      * @throws IllegalAccessException if the requested method is not accessible
420      *  via reflection
421      */
invokeStaticMethod(final Class<?> cls, final String methodName, Object[] args, Class<?>[] parameterTypes)422     public static Object invokeStaticMethod(final Class<?> cls, final String methodName,
423             Object[] args, Class<?>[] parameterTypes)
424             throws NoSuchMethodException, IllegalAccessException,
425             InvocationTargetException {
426         args = ArrayUtils.nullToEmpty(args);
427         parameterTypes = ArrayUtils.nullToEmpty(parameterTypes);
428         final Method method = getMatchingAccessibleMethod(cls, methodName,
429                 parameterTypes);
430         if (method == null) {
431             throw new NoSuchMethodException("No such accessible method: "
432                     + methodName + "() on class: " + cls.getName());
433         }
434         args = toVarArgs(method, args);
435         return method.invoke(null, args);
436     }
437 
toVarArgs(final Method method, Object[] args)438     private static Object[] toVarArgs(final Method method, Object[] args) {
439         if (method.isVarArgs()) {
440             final Class<?>[] methodParameterTypes = method.getParameterTypes();
441             args = getVarArgs(args, methodParameterTypes);
442         }
443         return args;
444     }
445 
446     /**
447      * Given an arguments array passed to a varargs method, return an array of arguments in the canonical form,
448      * i.e. an array with the declared number of parameters, and whose last parameter is an array of the varargs type.
449      *
450      * @param args the array of arguments passed to the varags method
451      * @param methodParameterTypes the declared array of method parameter types
452      * @return an array of the variadic arguments passed to the method
453      * @since 3.5
454      */
getVarArgs(final Object[] args, final Class<?>[] methodParameterTypes)455     static Object[] getVarArgs(final Object[] args, final Class<?>[] methodParameterTypes) {
456         if (args.length == methodParameterTypes.length && (args[args.length - 1] == null ||
457                 args[args.length - 1].getClass().equals(methodParameterTypes[methodParameterTypes.length - 1]))) {
458             // The args array is already in the canonical form for the method.
459             return args;
460         }
461 
462         // Construct a new array matching the method's declared parameter types.
463         final Object[] newArgs = new Object[methodParameterTypes.length];
464 
465         // Copy the normal (non-varargs) parameters
466         System.arraycopy(args, 0, newArgs, 0, methodParameterTypes.length - 1);
467 
468         // Construct a new array for the variadic parameters
469         final Class<?> varArgComponentType = methodParameterTypes[methodParameterTypes.length - 1].getComponentType();
470         final int varArgLength = args.length - methodParameterTypes.length + 1;
471 
472         Object varArgsArray = Array.newInstance(ClassUtils.primitiveToWrapper(varArgComponentType), varArgLength);
473         // Copy the variadic arguments into the varargs array.
474         System.arraycopy(args, methodParameterTypes.length - 1, varArgsArray, 0, varArgLength);
475 
476         if (varArgComponentType.isPrimitive()) {
477             // unbox from wrapper type to primitive type
478             varArgsArray = ArrayUtils.toPrimitive(varArgsArray);
479         }
480 
481         // Store the varargs array in the last position of the array to return
482         newArgs[methodParameterTypes.length - 1] = varArgsArray;
483 
484         // Return the canonical varargs array.
485         return newArgs;
486     }
487 
488     /**
489      * Invokes a {@code static} method whose parameter types match exactly the object
490      * types.
491      *
492      * <p>This uses reflection to invoke the method obtained from a call to
493      * {@link #getAccessibleMethod(Class, String, Class[])}.</p>
494      *
495      * @param cls invoke static method on this class
496      * @param methodName get method with this name
497      * @param args use these arguments - treat {@code null} as empty array
498      * @return The value returned by the invoked method
499      *
500      * @throws NoSuchMethodException if there is no such accessible method
501      * @throws InvocationTargetException wraps an exception thrown by the
502      *  method invoked
503      * @throws IllegalAccessException if the requested method is not accessible
504      *  via reflection
505      */
invokeExactStaticMethod(final Class<?> cls, final String methodName, Object... args)506     public static Object invokeExactStaticMethod(final Class<?> cls, final String methodName,
507             Object... args) throws NoSuchMethodException,
508             IllegalAccessException, InvocationTargetException {
509         args = ArrayUtils.nullToEmpty(args);
510         return invokeExactStaticMethod(cls, methodName, args, ClassUtils.toClass(args));
511     }
512 
513     /**
514      * Returns an accessible method (that is, one that can be invoked via
515      * reflection) with given name and parameters. If no such method
516      * can be found, return {@code null}.
517      * This is just a convenience wrapper for
518      * {@link #getAccessibleMethod(Method)}.
519      *
520      * @param cls get method from this class
521      * @param methodName get method with this name
522      * @param parameterTypes with these parameters types
523      * @return The accessible method
524      */
getAccessibleMethod(final Class<?> cls, final String methodName, final Class<?>... parameterTypes)525     public static Method getAccessibleMethod(final Class<?> cls, final String methodName,
526         final Class<?>... parameterTypes) {
527         try {
528             return getAccessibleMethod(cls.getMethod(methodName, parameterTypes));
529         } catch (final NoSuchMethodException e) {
530             return null;
531         }
532     }
533 
534     /**
535      * Returns an accessible method (that is, one that can be invoked via
536      * reflection) that implements the specified Method. If no such method
537      * can be found, return {@code null}.
538      *
539      * @param method The method that we wish to call
540      * @return The accessible method
541      */
getAccessibleMethod(Method method)542     public static Method getAccessibleMethod(Method method) {
543         if (!MemberUtils.isAccessible(method)) {
544             return null;
545         }
546         // If the declaring class is public, we are done
547         final Class<?> cls = method.getDeclaringClass();
548         if (ClassUtils.isPublic(cls)) {
549             return method;
550         }
551         final String methodName = method.getName();
552         final Class<?>[] parameterTypes = method.getParameterTypes();
553 
554         // Check the implemented interfaces and subinterfaces
555         method = getAccessibleMethodFromInterfaceNest(cls, methodName,
556                 parameterTypes);
557 
558         // Check the superclass chain
559         if (method == null) {
560             method = getAccessibleMethodFromSuperclass(cls, methodName,
561                     parameterTypes);
562         }
563         return method;
564     }
565 
566     /**
567      * Returns an accessible method (that is, one that can be invoked via
568      * reflection) by scanning through the superclasses. If no such method
569      * can be found, return {@code null}.
570      *
571      * @param cls Class to be checked
572      * @param methodName Method name of the method we wish to call
573      * @param parameterTypes The parameter type signatures
574      * @return the accessible method or {@code null} if not found
575      */
getAccessibleMethodFromSuperclass(final Class<?> cls, final String methodName, final Class<?>... parameterTypes)576     private static Method getAccessibleMethodFromSuperclass(final Class<?> cls,
577             final String methodName, final Class<?>... parameterTypes) {
578         Class<?> parentClass = cls.getSuperclass();
579         while (parentClass != null) {
580             if (ClassUtils.isPublic(parentClass)) {
581                 try {
582                     return parentClass.getMethod(methodName, parameterTypes);
583                 } catch (final NoSuchMethodException e) {
584                     return null;
585                 }
586             }
587             parentClass = parentClass.getSuperclass();
588         }
589         return null;
590     }
591 
592     /**
593      * Returns an accessible method (that is, one that can be invoked via
594      * reflection) that implements the specified method, by scanning through
595      * all implemented interfaces and subinterfaces. If no such method
596      * can be found, return {@code null}.
597      *
598      * <p>There isn't any good reason why this method must be {@code private}.
599      * It is because there doesn't seem any reason why other classes should
600      * call this rather than the higher level methods.</p>
601      *
602      * @param cls Parent class for the interfaces to be checked
603      * @param methodName Method name of the method we wish to call
604      * @param parameterTypes The parameter type signatures
605      * @return the accessible method or {@code null} if not found
606      */
getAccessibleMethodFromInterfaceNest(Class<?> cls, final String methodName, final Class<?>... parameterTypes)607     private static Method getAccessibleMethodFromInterfaceNest(Class<?> cls,
608             final String methodName, final Class<?>... parameterTypes) {
609         // Search up the superclass chain
610         for (; cls != null; cls = cls.getSuperclass()) {
611 
612             // Check the implemented interfaces of the parent class
613             final Class<?>[] interfaces = cls.getInterfaces();
614             for (final Class<?> anInterface : interfaces) {
615                 // Is this interface public?
616                 if (!ClassUtils.isPublic(anInterface)) {
617                     continue;
618                 }
619                 // Does the method exist on this interface?
620                 try {
621                     return anInterface.getDeclaredMethod(methodName,
622                             parameterTypes);
623                 } catch (final NoSuchMethodException ignored) {
624                     /*
625                      * Swallow, if no method is found after the loop then this
626                      * method returns null.
627                      */
628                 }
629                 // Recursively check our parent interfaces
630                 final Method method = getAccessibleMethodFromInterfaceNest(anInterface,
631                         methodName, parameterTypes);
632                 if (method != null) {
633                     return method;
634                 }
635             }
636         }
637         return null;
638     }
639 
640     /**
641      * Finds an accessible method that matches the given name and has compatible parameters.
642      * Compatible parameters mean that every method parameter is assignable from
643      * the given parameters.
644      * In other words, it finds a method with the given name
645      * that will take the parameters given.
646      *
647      * <p>This method is used by
648      * {@link
649      * #invokeMethod(Object object, String methodName, Object[] args, Class[] parameterTypes)}.
650      * </p>
651      *
652      * <p>This method can match primitive parameter by passing in wrapper classes.
653      * For example, a {@link Boolean} will match a primitive {@code boolean}
654      * parameter.
655      * </p>
656      *
657      * @param cls find method in this class
658      * @param methodName find method with this name
659      * @param parameterTypes find method with most compatible parameters
660      * @return The accessible method
661      */
getMatchingAccessibleMethod(final Class<?> cls, final String methodName, final Class<?>... parameterTypes)662     public static Method getMatchingAccessibleMethod(final Class<?> cls,
663         final String methodName, final Class<?>... parameterTypes) {
664         try {
665             return MemberUtils.setAccessibleWorkaround(cls.getMethod(methodName, parameterTypes));
666         } catch (final NoSuchMethodException ignored) {
667             // Swallow the exception
668         }
669         // search through all methods
670         final Method[] methods = cls.getMethods();
671         final List<Method> matchingMethods = Stream.of(methods)
672             .filter(method -> method.getName().equals(methodName) && MemberUtils.isMatchingMethod(method, parameterTypes)).collect(Collectors.toList());
673 
674         // Sort methods by signature to force deterministic result
675         matchingMethods.sort(METHOD_BY_SIGNATURE);
676 
677         Method bestMatch = null;
678         for (final Method method : matchingMethods) {
679             // get accessible version of method
680             final Method accessibleMethod = getAccessibleMethod(method);
681             if (accessibleMethod != null && (bestMatch == null || MemberUtils.compareMethodFit(accessibleMethod, bestMatch, parameterTypes) < 0)) {
682                 bestMatch = accessibleMethod;
683             }
684         }
685         if (bestMatch != null) {
686             MemberUtils.setAccessibleWorkaround(bestMatch);
687         }
688 
689         if (bestMatch != null && bestMatch.isVarArgs() && bestMatch.getParameterTypes().length > 0 && parameterTypes.length > 0) {
690             final Class<?>[] methodParameterTypes = bestMatch.getParameterTypes();
691             final Class<?> methodParameterComponentType = methodParameterTypes[methodParameterTypes.length - 1].getComponentType();
692             final String methodParameterComponentTypeName = ClassUtils.primitiveToWrapper(methodParameterComponentType).getName();
693 
694             final Class<?> lastParameterType = parameterTypes[parameterTypes.length - 1];
695             final String parameterTypeName = lastParameterType == null ? null : lastParameterType.getName();
696             final String parameterTypeSuperClassName = lastParameterType == null ? null : lastParameterType.getSuperclass().getName();
697 
698             if (parameterTypeName != null && parameterTypeSuperClassName != null && !methodParameterComponentTypeName.equals(parameterTypeName)
699                 && !methodParameterComponentTypeName.equals(parameterTypeSuperClassName)) {
700                 return null;
701             }
702         }
703 
704         return bestMatch;
705     }
706 
707     /**
708      * Retrieves a method whether or not it's accessible. If no such method
709      * can be found, return {@code null}.
710      * @param cls The class that will be subjected to the method search
711      * @param methodName The method that we wish to call
712      * @param parameterTypes Argument class types
713      * @throws IllegalStateException if there is no unique result
714      * @return The method
715      *
716      * @since 3.5
717      */
getMatchingMethod(final Class<?> cls, final String methodName, final Class<?>... parameterTypes)718     public static Method getMatchingMethod(final Class<?> cls, final String methodName,
719             final Class<?>... parameterTypes) {
720         Objects.requireNonNull(cls, "cls");
721         Validate.notEmpty(methodName, "methodName");
722 
723         final List<Method> methods = Stream.of(cls.getDeclaredMethods())
724                 .filter(method -> method.getName().equals(methodName))
725                 .collect(Collectors.toList());
726 
727         ClassUtils.getAllSuperclasses(cls).stream()
728                 .map(Class::getDeclaredMethods)
729                 .flatMap(Stream::of)
730                 .filter(method -> method.getName().equals(methodName))
731                 .forEach(methods::add);
732 
733         for (final Method method : methods) {
734             if (Arrays.deepEquals(method.getParameterTypes(), parameterTypes)) {
735                 return method;
736             }
737         }
738 
739         final TreeMap<Integer, List<Method>> candidates = new TreeMap<>();
740 
741         methods.stream()
742                 .filter(method -> ClassUtils.isAssignable(parameterTypes, method.getParameterTypes(), true))
743                 .forEach(method -> {
744                     final int distance = distance(parameterTypes, method.getParameterTypes());
745                     final List<Method> candidatesAtDistance = candidates.computeIfAbsent(distance, k -> new ArrayList<>());
746                     candidatesAtDistance.add(method);
747                 });
748 
749         if (candidates.isEmpty()) {
750             return null;
751         }
752 
753         final List<Method> bestCandidates = candidates.values().iterator().next();
754         if (bestCandidates.size() == 1) {
755             return bestCandidates.get(0);
756         }
757 
758         throw new IllegalStateException(
759                 String.format("Found multiple candidates for method %s on class %s : %s",
760                         methodName + Stream.of(parameterTypes).map(String::valueOf).collect(Collectors.joining(",", "(", ")")),
761                         cls.getName(),
762                         bestCandidates.stream().map(Method::toString).collect(Collectors.joining(",", "[", "]")))
763         );
764     }
765 
766     /**
767      * Returns the aggregate number of inheritance hops between assignable argument class types.  Returns -1
768      * if the arguments aren't assignable.  Fills a specific purpose for getMatchingMethod and is not generalized.
769      * @param fromClassArray the Class array to calculate the distance from.
770      * @param toClassArray the Class array to calculate the distance to.
771      * @return the aggregate number of inheritance hops between assignable argument class types.
772      */
distance(final Class<?>[] fromClassArray, final Class<?>[] toClassArray)773     private static int distance(final Class<?>[] fromClassArray, final Class<?>[] toClassArray) {
774         int answer = 0;
775 
776         if (!ClassUtils.isAssignable(fromClassArray, toClassArray, true)) {
777             return -1;
778         }
779         for (int offset = 0; offset < fromClassArray.length; offset++) {
780             // Note InheritanceUtils.distance() uses different scoring system.
781             final Class<?> aClass = fromClassArray[offset];
782             final Class<?> toClass = toClassArray[offset];
783             if (aClass == null || aClass.equals(toClass)) {
784                 continue;
785             }
786             if (ClassUtils.isAssignable(aClass, toClass, true)
787                     && !ClassUtils.isAssignable(aClass, toClass, false)) {
788                 answer++;
789             } else {
790                 answer = answer + 2;
791             }
792         }
793 
794         return answer;
795     }
796 
797     /**
798      * Gets the hierarchy of overridden methods down to {@code result} respecting generics.
799      * @param method lowest to consider
800      * @param interfacesBehavior whether to search interfaces, {@code null} {@code implies} false
801      * @return Set&lt;Method&gt; in ascending order from sub- to superclass
802      * @throws NullPointerException if the specified method is {@code null}
803      * @since 3.2
804      */
getOverrideHierarchy(final Method method, final Interfaces interfacesBehavior)805     public static Set<Method> getOverrideHierarchy(final Method method, final Interfaces interfacesBehavior) {
806         Objects.requireNonNull(method, "method");
807         final Set<Method> result = new LinkedHashSet<>();
808         result.add(method);
809 
810         final Class<?>[] parameterTypes = method.getParameterTypes();
811 
812         final Class<?> declaringClass = method.getDeclaringClass();
813 
814         final Iterator<Class<?>> hierarchy = ClassUtils.hierarchy(declaringClass, interfacesBehavior).iterator();
815         //skip the declaring class :P
816         hierarchy.next();
817         hierarchyTraversal: while (hierarchy.hasNext()) {
818             final Class<?> c = hierarchy.next();
819             final Method m = getMatchingAccessibleMethod(c, method.getName(), parameterTypes);
820             if (m == null) {
821                 continue;
822             }
823             if (Arrays.equals(m.getParameterTypes(), parameterTypes)) {
824                 // matches without generics
825                 result.add(m);
826                 continue;
827             }
828             // necessary to get arguments every time in the case that we are including interfaces
829             final Map<TypeVariable<?>, Type> typeArguments = TypeUtils.getTypeArguments(declaringClass, m.getDeclaringClass());
830             for (int i = 0; i < parameterTypes.length; i++) {
831                 final Type childType = TypeUtils.unrollVariables(typeArguments, method.getGenericParameterTypes()[i]);
832                 final Type parentType = TypeUtils.unrollVariables(typeArguments, m.getGenericParameterTypes()[i]);
833                 if (!TypeUtils.equals(childType, parentType)) {
834                     continue hierarchyTraversal;
835                 }
836             }
837             result.add(m);
838         }
839         return result;
840     }
841 
842     /**
843      * Gets all class level public methods of the given class that are annotated with the given annotation.
844      * @param cls
845      *            the {@link Class} to query
846      * @param annotationCls
847      *            the {@link java.lang.annotation.Annotation} that must be present on a method to be matched
848      * @return an array of Methods (possibly empty).
849      * @throws NullPointerException if the class or annotation are {@code null}
850      * @since 3.4
851      */
getMethodsWithAnnotation(final Class<?> cls, final Class<? extends Annotation> annotationCls)852     public static Method[] getMethodsWithAnnotation(final Class<?> cls, final Class<? extends Annotation> annotationCls) {
853         return getMethodsWithAnnotation(cls, annotationCls, false, false);
854     }
855 
856     /**
857      * Gets all class level public methods of the given class that are annotated with the given annotation.
858      * @param cls
859      *            the {@link Class} to query
860      * @param annotationCls
861      *            the {@link Annotation} that must be present on a method to be matched
862      * @return a list of Methods (possibly empty).
863      * @throws IllegalArgumentException
864      *            if the class or annotation are {@code null}
865      * @since 3.4
866      */
getMethodsListWithAnnotation(final Class<?> cls, final Class<? extends Annotation> annotationCls)867     public static List<Method> getMethodsListWithAnnotation(final Class<?> cls, final Class<? extends Annotation> annotationCls) {
868         return getMethodsListWithAnnotation(cls, annotationCls, false, false);
869     }
870 
871     /**
872      * Gets all methods of the given class that are annotated with the given annotation.
873      * @param cls
874      *            the {@link Class} to query
875      * @param annotationCls
876      *            the {@link java.lang.annotation.Annotation} that must be present on a method to be matched
877      * @param searchSupers
878      *            determines if a lookup in the entire inheritance hierarchy of the given class should be performed
879      * @param ignoreAccess
880      *            determines if non-public methods should be considered
881      * @return an array of Methods (possibly empty).
882      * @throws NullPointerException if the class or annotation are {@code null}
883      * @since 3.6
884      */
getMethodsWithAnnotation(final Class<?> cls, final Class<? extends Annotation> annotationCls, final boolean searchSupers, final boolean ignoreAccess)885     public static Method[] getMethodsWithAnnotation(final Class<?> cls, final Class<? extends Annotation> annotationCls,
886         final boolean searchSupers, final boolean ignoreAccess) {
887         return getMethodsListWithAnnotation(cls, annotationCls, searchSupers, ignoreAccess).toArray(ArrayUtils.EMPTY_METHOD_ARRAY);
888     }
889 
890     /**
891      * Gets all methods of the given class that are annotated with the given annotation.
892      * @param cls
893      *            the {@link Class} to query
894      * @param annotationCls
895      *            the {@link Annotation} that must be present on a method to be matched
896      * @param searchSupers
897      *            determines if a lookup in the entire inheritance hierarchy of the given class should be performed
898      * @param ignoreAccess
899      *            determines if non-public methods should be considered
900      * @return a list of Methods (possibly empty).
901      * @throws NullPointerException if either the class or annotation class is {@code null}
902      * @since 3.6
903      */
getMethodsListWithAnnotation(final Class<?> cls, final Class<? extends Annotation> annotationCls, final boolean searchSupers, final boolean ignoreAccess)904     public static List<Method> getMethodsListWithAnnotation(final Class<?> cls,
905                                                             final Class<? extends Annotation> annotationCls,
906                                                             final boolean searchSupers, final boolean ignoreAccess) {
907 
908         Objects.requireNonNull(cls, "cls");
909         Objects.requireNonNull(annotationCls, "annotationCls");
910         final List<Class<?>> classes = searchSupers ? getAllSuperclassesAndInterfaces(cls) : new ArrayList<>();
911         classes.add(0, cls);
912         final List<Method> annotatedMethods = new ArrayList<>();
913         classes.forEach(acls -> {
914             final Method[] methods = ignoreAccess ? acls.getDeclaredMethods() : acls.getMethods();
915             Stream.of(methods).filter(method -> method.isAnnotationPresent(annotationCls)).forEachOrdered(annotatedMethods::add);
916         });
917         return annotatedMethods;
918     }
919 
920     /**
921      * Gets the annotation object with the given annotation type that is present on the given method
922      * or optionally on any equivalent method in super classes and interfaces. Returns null if the annotation
923      * type was not present.
924      *
925      * <p>Stops searching for an annotation once the first annotation of the specified type has been
926      * found. Additional annotations of the specified type will be silently ignored.</p>
927      * @param <A>
928      *            the annotation type
929      * @param method
930      *            the {@link Method} to query
931      * @param annotationCls
932      *            the {@link Annotation} to check if is present on the method
933      * @param searchSupers
934      *            determines if a lookup in the entire inheritance hierarchy of the given class is performed
935      *            if the annotation was not directly present
936      * @param ignoreAccess
937      *            determines if underlying method has to be accessible
938      * @return the first matching annotation, or {@code null} if not found
939      * @throws NullPointerException if either the method or annotation class is {@code null}
940      * @since 3.6
941      */
getAnnotation(final Method method, final Class<A> annotationCls, final boolean searchSupers, final boolean ignoreAccess)942     public static <A extends Annotation> A getAnnotation(final Method method, final Class<A> annotationCls,
943                                                          final boolean searchSupers, final boolean ignoreAccess) {
944 
945         Objects.requireNonNull(method, "method");
946         Objects.requireNonNull(annotationCls, "annotationCls");
947         if (!ignoreAccess && !MemberUtils.isAccessible(method)) {
948             return null;
949         }
950 
951         A annotation = method.getAnnotation(annotationCls);
952 
953         if (annotation == null && searchSupers) {
954             final Class<?> mcls = method.getDeclaringClass();
955             final List<Class<?>> classes = getAllSuperclassesAndInterfaces(mcls);
956             for (final Class<?> acls : classes) {
957                 final Method equivalentMethod = ignoreAccess ? MethodUtils.getMatchingMethod(acls, method.getName(), method.getParameterTypes())
958                     : MethodUtils.getMatchingAccessibleMethod(acls, method.getName(), method.getParameterTypes());
959                 if (equivalentMethod != null) {
960                     annotation = equivalentMethod.getAnnotation(annotationCls);
961                     if (annotation != null) {
962                         break;
963                     }
964                 }
965             }
966         }
967 
968         return annotation;
969     }
970 
971     /**
972      * Gets a combination of {@link ClassUtils#getAllSuperclasses(Class)} and
973      * {@link ClassUtils#getAllInterfaces(Class)}, one from superclasses, one
974      * from interfaces, and so on in a breadth first way.
975      *
976      * @param cls  the class to look up, may be {@code null}
977      * @return the combined {@link List} of superclasses and interfaces in order
978      * going up from this one
979      *  {@code null} if null input
980      */
getAllSuperclassesAndInterfaces(final Class<?> cls)981     private static List<Class<?>> getAllSuperclassesAndInterfaces(final Class<?> cls) {
982         if (cls == null) {
983             return null;
984         }
985 
986         final List<Class<?>> allSuperClassesAndInterfaces = new ArrayList<>();
987         final List<Class<?>> allSuperclasses = ClassUtils.getAllSuperclasses(cls);
988         int superClassIndex = 0;
989         final List<Class<?>> allInterfaces = ClassUtils.getAllInterfaces(cls);
990         int interfaceIndex = 0;
991         while (interfaceIndex < allInterfaces.size() ||
992                 superClassIndex < allSuperclasses.size()) {
993             final Class<?> acls;
994             if (interfaceIndex >= allInterfaces.size()) {
995                 acls = allSuperclasses.get(superClassIndex++);
996             } else if (superClassIndex >= allSuperclasses.size() || !(superClassIndex < interfaceIndex)) {
997                 acls = allInterfaces.get(interfaceIndex++);
998             } else {
999                 acls = allSuperclasses.get(superClassIndex++);
1000             }
1001             allSuperClassesAndInterfaces.add(acls);
1002         }
1003         return allSuperClassesAndInterfaces;
1004     }
1005 }
1006