View Javadoc
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.reflect.InvocationTargetException;
20  import java.lang.reflect.Method;
21  import java.lang.reflect.Modifier;
22  import java.lang.reflect.Type;
23  import java.lang.reflect.TypeVariable;
24  import java.util.Arrays;
25  import java.util.Iterator;
26  import java.util.LinkedHashSet;
27  import java.util.Map;
28  import java.util.Set;
29  
30  import org.apache.commons.lang3.ArrayUtils;
31  import org.apache.commons.lang3.ClassUtils;
32  import org.apache.commons.lang3.ClassUtils.Interfaces;
33  import org.apache.commons.lang3.Validate;
34  
35  /**
36   * <p>Utility reflection methods focused on {@link Method}s, originally from Commons BeanUtils.
37   * Differences from the BeanUtils version may be noted, especially where similar functionality
38   * already existed within Lang.
39   * </p>
40   *
41   * <h3>Known Limitations</h3>
42   * <h4>Accessing Public Methods In A Default Access Superclass</h4>
43   * <p>There is an issue when invoking {@code public} methods contained in a default access superclass on JREs prior to 1.4.
44   * Reflection locates these methods fine and correctly assigns them as {@code public}.
45   * However, an {@link IllegalAccessException} is thrown if the method is invoked.</p>
46   *
47   * <p>{@link MethodUtils} contains a workaround for this situation. 
48   * It will attempt to call {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)} on this method.
49   * If this call succeeds, then the method can be invoked as normal.
50   * This call will only succeed when the application has sufficient security privileges. 
51   * If this call fails then the method may fail.</p>
52   *
53   * @since 2.5
54   * @version $Id: MethodUtils.java 1583482 2014-03-31 22:54:57Z niallp $
55   */
56  public class MethodUtils {
57  
58      /**
59       * <p>{@link MethodUtils} instances should NOT be constructed in standard programming.
60       * Instead, the class should be used as
61       * {@code MethodUtils.getAccessibleMethod(method)}.</p>
62       *
63       * <p>This constructor is {@code public} to permit tools that require a JavaBean
64       * instance to operate.</p>
65       */
66      public MethodUtils() {
67          super();
68      }
69  
70      /**
71       * <p>Invokes a named method whose parameter type matches the object type.</p>
72       *
73       * <p>This method delegates the method search to {@link #getMatchingAccessibleMethod(Class, String, Class[])}.</p>
74       *
75       * <p>This method supports calls to methods taking primitive parameters 
76       * via passing in wrapping classes. So, for example, a {@code Boolean} object
77       * would match a {@code boolean} primitive.</p>
78       *
79       * <p>This is a convenient wrapper for
80       * {@link #invokeMethod(Object object,String methodName, Object[] args, Class[] parameterTypes)}.
81       * </p>
82       *
83       * @param object invoke method on this object
84       * @param methodName get method with this name
85       * @param args use these arguments - treat null as empty array
86       * @return The value returned by the invoked method
87       *
88       * @throws NoSuchMethodException if there is no such accessible method
89       * @throws InvocationTargetException wraps an exception thrown by the method invoked
90       * @throws IllegalAccessException if the requested method is not accessible via reflection
91       */
92      public static Object invokeMethod(final Object object, final String methodName,
93              Object... args) throws NoSuchMethodException,
94              IllegalAccessException, InvocationTargetException {
95          args = ArrayUtils.nullToEmpty(args);
96          final Class<?>[] parameterTypes = ClassUtils.toClass(args);
97          return invokeMethod(object, methodName, args, parameterTypes);
98      }
99  
100     /**
101      * <p>Invokes a named method whose parameter type matches the object type.</p>
102      *
103      * <p>This method delegates the method search to {@link #getMatchingAccessibleMethod(Class, String, Class[])}.</p>
104      *
105      * <p>This method supports calls to methods taking primitive parameters 
106      * via passing in wrapping classes. So, for example, a {@code Boolean} object
107      * would match a {@code boolean} primitive.</p>
108      *
109      * @param object invoke method on this object
110      * @param methodName get method with this name
111      * @param args use these arguments - treat null as empty array
112      * @param parameterTypes match these parameters - treat null as empty array
113      * @return The value returned by the invoked method
114      *
115      * @throws NoSuchMethodException if there is no such accessible method
116      * @throws InvocationTargetException wraps an exception thrown by the method invoked
117      * @throws IllegalAccessException if the requested method is not accessible via reflection
118      */
119     public static Object invokeMethod(final Object object, final String methodName,
120             Object[] args, Class<?>[] parameterTypes)
121             throws NoSuchMethodException, IllegalAccessException,
122             InvocationTargetException {
123         parameterTypes = ArrayUtils.nullToEmpty(parameterTypes);
124         args = ArrayUtils.nullToEmpty(args);
125         final Method method = getMatchingAccessibleMethod(object.getClass(),
126                 methodName, parameterTypes);
127         if (method == null) {
128             throw new NoSuchMethodException("No such accessible method: "
129                     + methodName + "() on object: "
130                     + object.getClass().getName());
131         }
132         return method.invoke(object, args);
133     }
134 
135     /**
136      * <p>Invokes a method whose parameter types match exactly the object
137      * types.</p>
138      *
139      * <p>This uses reflection to invoke the method obtained from a call to
140      * {@link #getAccessibleMethod}(Class,String,Class[])}.</p>
141      *
142      * @param object invoke method on this object
143      * @param methodName get method with this name
144      * @param args use these arguments - treat {@code null} as empty array
145      * @return The value returned by the invoked method
146      *
147      * @throws NoSuchMethodException if there is no such accessible method
148      * @throws InvocationTargetException wraps an exception thrown by the
149      *  method invoked
150      * @throws IllegalAccessException if the requested method is not accessible
151      *  via reflection
152      */
153     public static Object invokeExactMethod(final Object object, final String methodName,
154             Object... args) throws NoSuchMethodException,
155             IllegalAccessException, InvocationTargetException {
156         args = ArrayUtils.nullToEmpty(args);
157         final Class<?>[] parameterTypes = ClassUtils.toClass(args);
158         return invokeExactMethod(object, methodName, args, parameterTypes);
159     }
160 
161     /**
162      * <p>Invokes a method whose parameter types match exactly the parameter
163      * types given.</p>
164      *
165      * <p>This uses reflection to invoke the method obtained from a call to
166      * {@link #getAccessibleMethod(Class,String,Class[])}.</p>
167      *
168      * @param object invoke method on this object
169      * @param methodName get method with this name
170      * @param args use these arguments - treat null as empty array
171      * @param parameterTypes match these parameters - treat {@code null} as empty array
172      * @return The value returned by the invoked method
173      *
174      * @throws NoSuchMethodException if there is no such accessible method
175      * @throws InvocationTargetException wraps an exception thrown by the
176      *  method invoked
177      * @throws IllegalAccessException if the requested method is not accessible
178      *  via reflection
179      */
180     public static Object invokeExactMethod(final Object object, final String methodName,
181             Object[] args, Class<?>[] parameterTypes)
182             throws NoSuchMethodException, IllegalAccessException,
183             InvocationTargetException {
184         args = ArrayUtils.nullToEmpty(args);
185         parameterTypes = ArrayUtils.nullToEmpty(parameterTypes);
186         final Method method = getAccessibleMethod(object.getClass(), methodName,
187                 parameterTypes);
188         if (method == null) {
189             throw new NoSuchMethodException("No such accessible method: "
190                     + methodName + "() on object: "
191                     + object.getClass().getName());
192         }
193         return method.invoke(object, args);
194     }
195 
196     /**
197      * <p>Invokes a {@code static} method whose parameter types match exactly the parameter
198      * types given.</p>
199      *
200      * <p>This uses reflection to invoke the method obtained from a call to
201      * {@link #getAccessibleMethod(Class, String, Class[])}.</p>
202      *
203      * @param cls invoke static method on this class
204      * @param methodName get method with this name
205      * @param args use these arguments - treat {@code null} as empty array
206      * @param parameterTypes match these parameters - treat {@code null} as empty array
207      * @return The value returned by the invoked method
208      *
209      * @throws NoSuchMethodException if there is no such accessible method
210      * @throws InvocationTargetException wraps an exception thrown by the
211      *  method invoked
212      * @throws IllegalAccessException if the requested method is not accessible
213      *  via reflection
214      */
215     public static Object invokeExactStaticMethod(final Class<?> cls, final String methodName,
216             Object[] args, Class<?>[] parameterTypes)
217             throws NoSuchMethodException, IllegalAccessException,
218             InvocationTargetException {
219         args = ArrayUtils.nullToEmpty(args);
220         parameterTypes = ArrayUtils.nullToEmpty(parameterTypes);
221         final Method method = getAccessibleMethod(cls, methodName, parameterTypes);
222         if (method == null) {
223             throw new NoSuchMethodException("No such accessible method: "
224                     + methodName + "() on class: " + cls.getName());
225         }
226         return method.invoke(null, args);
227     }
228 
229     /**
230      * <p>Invokes a named {@code static} method whose parameter type matches the object type.</p>
231      *
232      * <p>This method delegates the method search to {@link #getMatchingAccessibleMethod(Class, String, Class[])}.</p>
233      *
234      * <p>This method supports calls to methods taking primitive parameters 
235      * via passing in wrapping classes. So, for example, a {@code Boolean} class
236      * would match a {@code boolean} primitive.</p>
237      *
238      * <p>This is a convenient wrapper for
239      * {@link #invokeStaticMethod(Class, String, Object[], Class[])}.
240      * </p>
241      *
242      * @param cls invoke static method on this class
243      * @param methodName get method with this name
244      * @param args use these arguments - treat {@code null} as empty array
245      * @return The value returned by the invoked method
246      *
247      * @throws NoSuchMethodException if there is no such accessible method
248      * @throws InvocationTargetException wraps an exception thrown by the
249      *  method invoked
250      * @throws IllegalAccessException if the requested method is not accessible
251      *  via reflection
252      */
253     public static Object invokeStaticMethod(final Class<?> cls, final String methodName,
254             Object... args) throws NoSuchMethodException,
255             IllegalAccessException, InvocationTargetException {
256         args = ArrayUtils.nullToEmpty(args);
257         final Class<?>[] parameterTypes = ClassUtils.toClass(args);
258         return invokeStaticMethod(cls, methodName, args, parameterTypes);
259     }
260 
261     /**
262      * <p>Invokes a named {@code static} method whose parameter type matches the object type.</p>
263      *
264      * <p>This method delegates the method search to {@link #getMatchingAccessibleMethod(Class, String, Class[])}.</p>
265      *
266      * <p>This method supports calls to methods taking primitive parameters 
267      * via passing in wrapping classes. So, for example, a {@code Boolean} class
268      * would match a {@code boolean} primitive.</p>
269      *
270      *
271      * @param cls invoke static method on this class
272      * @param methodName get method with this name
273      * @param args use these arguments - treat {@code null} as empty array
274      * @param parameterTypes match these parameters - treat {@code null} as empty array
275      * @return The value returned by the invoked method
276      *
277      * @throws NoSuchMethodException if there is no such accessible method
278      * @throws InvocationTargetException wraps an exception thrown by the
279      *  method invoked
280      * @throws IllegalAccessException if the requested method is not accessible
281      *  via reflection
282      */
283     public static Object invokeStaticMethod(final Class<?> cls, final String methodName,
284             Object[] args, Class<?>[] parameterTypes)
285             throws NoSuchMethodException, IllegalAccessException,
286             InvocationTargetException {
287         args = ArrayUtils.nullToEmpty(args);
288         parameterTypes = ArrayUtils.nullToEmpty(parameterTypes);
289         final Method method = getMatchingAccessibleMethod(cls, methodName,
290                 parameterTypes);
291         if (method == null) {
292             throw new NoSuchMethodException("No such accessible method: "
293                     + methodName + "() on class: " + cls.getName());
294         }
295         return method.invoke(null, args);
296     }
297 
298     /**
299      * <p>Invokes a {@code static} method whose parameter types match exactly the object
300      * types.</p>
301      *
302      * <p>This uses reflection to invoke the method obtained from a call to
303      * {@link #getAccessibleMethod(Class, String, Class[])}.</p>
304      *
305      * @param cls invoke static method on this class
306      * @param methodName get method with this name
307      * @param args use these arguments - treat {@code null} as empty array
308      * @return The value returned by the invoked method
309      *
310      * @throws NoSuchMethodException if there is no such accessible method
311      * @throws InvocationTargetException wraps an exception thrown by the
312      *  method invoked
313      * @throws IllegalAccessException if the requested method is not accessible
314      *  via reflection
315      */
316     public static Object invokeExactStaticMethod(final Class<?> cls, final String methodName,
317             Object... args) throws NoSuchMethodException,
318             IllegalAccessException, InvocationTargetException {
319         args = ArrayUtils.nullToEmpty(args);
320         final Class<?>[] parameterTypes = ClassUtils.toClass(args);
321         return invokeExactStaticMethod(cls, methodName, args, parameterTypes);
322     }
323 
324     /**
325      * <p>Returns an accessible method (that is, one that can be invoked via
326      * reflection) with given name and parameters. If no such method
327      * can be found, return {@code null}.
328      * This is just a convenience wrapper for
329      * {@link #getAccessibleMethod(Method)}.</p>
330      *
331      * @param cls get method from this class
332      * @param methodName get method with this name
333      * @param parameterTypes with these parameters types
334      * @return The accessible method
335      */
336     public static Method getAccessibleMethod(final Class<?> cls, final String methodName,
337             final Class<?>... parameterTypes) {
338         try {
339             return getAccessibleMethod(cls.getMethod(methodName,
340                     parameterTypes));
341         } catch (final NoSuchMethodException e) {
342             return null;
343         }
344     }
345 
346     /**
347      * <p>Returns an accessible method (that is, one that can be invoked via
348      * reflection) that implements the specified Method. If no such method
349      * can be found, return {@code null}.</p>
350      *
351      * @param method The method that we wish to call
352      * @return The accessible method
353      */
354     public static Method getAccessibleMethod(Method method) {
355         if (!MemberUtils.isAccessible(method)) {
356             return null;
357         }
358         // If the declaring class is public, we are done
359         final Class<?> cls = method.getDeclaringClass();
360         if (Modifier.isPublic(cls.getModifiers())) {
361             return method;
362         }
363         final String methodName = method.getName();
364         final Class<?>[] parameterTypes = method.getParameterTypes();
365 
366         // Check the implemented interfaces and subinterfaces
367         method = getAccessibleMethodFromInterfaceNest(cls, methodName,
368                 parameterTypes);
369 
370         // Check the superclass chain
371         if (method == null) {
372             method = getAccessibleMethodFromSuperclass(cls, methodName,
373                     parameterTypes);
374         }
375         return method;
376     }
377 
378     /**
379      * <p>Returns an accessible method (that is, one that can be invoked via
380      * reflection) by scanning through the superclasses. If no such method
381      * can be found, return {@code null}.</p>
382      *
383      * @param cls Class to be checked
384      * @param methodName Method name of the method we wish to call
385      * @param parameterTypes The parameter type signatures
386      * @return the accessible method or {@code null} if not found
387      */
388     private static Method getAccessibleMethodFromSuperclass(final Class<?> cls,
389             final String methodName, final Class<?>... parameterTypes) {
390         Class<?> parentClass = cls.getSuperclass();
391         while (parentClass != null) {
392             if (Modifier.isPublic(parentClass.getModifiers())) {
393                 try {
394                     return parentClass.getMethod(methodName, parameterTypes);
395                 } catch (final NoSuchMethodException e) {
396                     return null;
397                 }
398             }
399             parentClass = parentClass.getSuperclass();
400         }
401         return null;
402     }
403 
404     /**
405      * <p>Returns an accessible method (that is, one that can be invoked via
406      * reflection) that implements the specified method, by scanning through
407      * all implemented interfaces and subinterfaces. If no such method
408      * can be found, return {@code null}.</p>
409      *
410      * <p>There isn't any good reason why this method must be {@code private}.
411      * It is because there doesn't seem any reason why other classes should
412      * call this rather than the higher level methods.</p>
413      *
414      * @param cls Parent class for the interfaces to be checked
415      * @param methodName Method name of the method we wish to call
416      * @param parameterTypes The parameter type signatures
417      * @return the accessible method or {@code null} if not found
418      */
419     private static Method getAccessibleMethodFromInterfaceNest(Class<?> cls,
420             final String methodName, final Class<?>... parameterTypes) {
421         // Search up the superclass chain
422         for (; cls != null; cls = cls.getSuperclass()) {
423 
424             // Check the implemented interfaces of the parent class
425             final Class<?>[] interfaces = cls.getInterfaces();
426             for (int i = 0; i < interfaces.length; i++) {
427                 // Is this interface public?
428                 if (!Modifier.isPublic(interfaces[i].getModifiers())) {
429                     continue;
430                 }
431                 // Does the method exist on this interface?
432                 try {
433                     return interfaces[i].getDeclaredMethod(methodName,
434                             parameterTypes);
435                 } catch (final NoSuchMethodException e) { // NOPMD
436                     /*
437                      * Swallow, if no method is found after the loop then this
438                      * method returns null.
439                      */
440                 }
441                 // Recursively check our parent interfaces
442                 Method method = getAccessibleMethodFromInterfaceNest(interfaces[i],
443                         methodName, parameterTypes);
444                 if (method != null) {
445                     return method;
446                 }
447             }
448         }
449         return null;
450     }
451 
452     /**
453      * <p>Finds an accessible method that matches the given name and has compatible parameters.
454      * Compatible parameters mean that every method parameter is assignable from 
455      * the given parameters.
456      * In other words, it finds a method with the given name 
457      * that will take the parameters given.</p>
458      *
459      * <p>This method is used by 
460      * {@link 
461      * #invokeMethod(Object object, String methodName, Object[] args, Class[] parameterTypes)}.
462      * </p>
463      *
464      * <p>This method can match primitive parameter by passing in wrapper classes.
465      * For example, a {@code Boolean} will match a primitive {@code boolean}
466      * parameter.
467      * </p>
468      *
469      * @param cls find method in this class
470      * @param methodName find method with this name
471      * @param parameterTypes find method with most compatible parameters 
472      * @return The accessible method
473      */
474     public static Method getMatchingAccessibleMethod(final Class<?> cls,
475             final String methodName, final Class<?>... parameterTypes) {
476         try {
477             final Method method = cls.getMethod(methodName, parameterTypes);
478             MemberUtils.setAccessibleWorkaround(method);
479             return method;
480         } catch (final NoSuchMethodException e) { // NOPMD - Swallow the exception
481         }
482         // search through all methods
483         Method bestMatch = null;
484         final Method[] methods = cls.getMethods();
485         for (final Method method : methods) {
486             // compare name and parameters
487             if (method.getName().equals(methodName) && ClassUtils.isAssignable(parameterTypes, method.getParameterTypes(), true)) {
488                 // get accessible version of method
489                 final Method accessibleMethod = getAccessibleMethod(method);
490                 if (accessibleMethod != null && (bestMatch == null || MemberUtils.compareParameterTypes(
491                             accessibleMethod.getParameterTypes(),
492                             bestMatch.getParameterTypes(),
493                             parameterTypes) < 0)) {
494                         bestMatch = accessibleMethod;
495                  }
496             }
497         }
498         if (bestMatch != null) {
499             MemberUtils.setAccessibleWorkaround(bestMatch);
500         }
501         return bestMatch;
502     }
503 
504     /**
505      * Get the hierarchy of overridden methods down to {@code result} respecting generics.
506      * @param method lowest to consider
507      * @param interfacesBehavior whether to search interfaces, {@code null} {@code implies} false
508      * @return Set&lt;Method&gt; in ascending order from sub- to superclass
509      * @throws NullPointerException if the specified method is {@code null}
510      * @since 3.2
511      */
512     public static Set<Method> getOverrideHierarchy(final Method method, Interfaces interfacesBehavior) {
513         Validate.notNull(method);
514         final Set<Method> result = new LinkedHashSet<Method>();
515         result.add(method);
516 
517         final Class<?>[] parameterTypes = method.getParameterTypes();
518 
519         final Class<?> declaringClass = method.getDeclaringClass();
520 
521         final Iterator<Class<?>> hierarchy = ClassUtils.hierarchy(declaringClass, interfacesBehavior).iterator();
522         //skip the declaring class :P
523         hierarchy.next();
524         hierarchyTraversal: while (hierarchy.hasNext()) {
525             final Class<?> c = hierarchy.next();
526             final Method m = getMatchingAccessibleMethod(c, method.getName(), parameterTypes);
527             if (m == null) {
528                 continue;
529             }
530             if (Arrays.equals(m.getParameterTypes(), parameterTypes)) {
531                 // matches without generics
532                 result.add(m);
533                 continue;
534             }
535             // necessary to get arguments every time in the case that we are including interfaces
536             final Map<TypeVariable<?>, Type> typeArguments = TypeUtils.getTypeArguments(declaringClass, m.getDeclaringClass());
537             for (int i = 0; i < parameterTypes.length; i++) {
538                 final Type childType = TypeUtils.unrollVariables(typeArguments, method.getGenericParameterTypes()[i]);
539                 final Type parentType = TypeUtils.unrollVariables(typeArguments, m.getGenericParameterTypes()[i]);
540                 if (!TypeUtils.equals(childType, parentType)) {
541                     continue hierarchyTraversal;
542                 }
543             }
544             result.add(m);
545         }
546         return result;
547     }
548 
549 }