001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.lang3.reflect;
018
019import java.lang.reflect.InvocationTargetException;
020import java.lang.reflect.Method;
021import java.lang.reflect.Modifier;
022import java.lang.reflect.Type;
023import java.lang.reflect.TypeVariable;
024import java.util.Arrays;
025import java.util.Iterator;
026import java.util.LinkedHashSet;
027import java.util.Map;
028import java.util.Set;
029
030import org.apache.commons.lang3.ArrayUtils;
031import org.apache.commons.lang3.ClassUtils;
032import org.apache.commons.lang3.ClassUtils.Interfaces;
033import org.apache.commons.lang3.Validate;
034
035/**
036 * <p>Utility reflection methods focused on {@link Method}s, originally from Commons BeanUtils.
037 * Differences from the BeanUtils version may be noted, especially where similar functionality
038 * already existed within Lang.
039 * </p>
040 *
041 * <h3>Known Limitations</h3>
042 * <h4>Accessing Public Methods In A Default Access Superclass</h4>
043 * <p>There is an issue when invoking {@code public} methods contained in a default access superclass on JREs prior to 1.4.
044 * Reflection locates these methods fine and correctly assigns them as {@code public}.
045 * However, an {@link IllegalAccessException} is thrown if the method is invoked.</p>
046 *
047 * <p>{@link MethodUtils} contains a workaround for this situation. 
048 * It will attempt to call {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)} on this method.
049 * If this call succeeds, then the method can be invoked as normal.
050 * This call will only succeed when the application has sufficient security privileges. 
051 * If this call fails then the method may fail.</p>
052 *
053 * @since 2.5
054 * @version $Id: MethodUtils.java 1583482 2014-03-31 22:54:57Z niallp $
055 */
056public class MethodUtils {
057
058    /**
059     * <p>{@link MethodUtils} instances should NOT be constructed in standard programming.
060     * Instead, the class should be used as
061     * {@code MethodUtils.getAccessibleMethod(method)}.</p>
062     *
063     * <p>This constructor is {@code public} to permit tools that require a JavaBean
064     * instance to operate.</p>
065     */
066    public MethodUtils() {
067        super();
068    }
069
070    /**
071     * <p>Invokes a named method whose parameter type matches the object type.</p>
072     *
073     * <p>This method delegates the method search to {@link #getMatchingAccessibleMethod(Class, String, Class[])}.</p>
074     *
075     * <p>This method supports calls to methods taking primitive parameters 
076     * via passing in wrapping classes. So, for example, a {@code Boolean} object
077     * would match a {@code boolean} primitive.</p>
078     *
079     * <p>This is a convenient wrapper for
080     * {@link #invokeMethod(Object object,String methodName, Object[] args, Class[] parameterTypes)}.
081     * </p>
082     *
083     * @param object invoke method on this object
084     * @param methodName get method with this name
085     * @param args use these arguments - treat null as empty array
086     * @return The value returned by the invoked method
087     *
088     * @throws NoSuchMethodException if there is no such accessible method
089     * @throws InvocationTargetException wraps an exception thrown by the method invoked
090     * @throws IllegalAccessException if the requested method is not accessible via reflection
091     */
092    public static Object invokeMethod(final Object object, final String methodName,
093            Object... args) throws NoSuchMethodException,
094            IllegalAccessException, InvocationTargetException {
095        args = ArrayUtils.nullToEmpty(args);
096        final Class<?>[] parameterTypes = ClassUtils.toClass(args);
097        return invokeMethod(object, methodName, args, parameterTypes);
098    }
099
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}