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     */
017    package org.apache.commons.lang3.reflect;
018    
019    import java.lang.reflect.InvocationTargetException;
020    import java.lang.reflect.Method;
021    import java.lang.reflect.Modifier;
022    
023    import org.apache.commons.lang3.ArrayUtils;
024    import org.apache.commons.lang3.ClassUtils;
025    
026    /**
027     * <p> Utility reflection methods focused on methods, originally from Commons BeanUtils.
028     * Differences from the BeanUtils version may be noted, especially where similar functionality
029     * already existed within Lang.
030     * </p>
031     *
032     * <h3>Known Limitations</h3>
033     * <h4>Accessing Public Methods In A Default Access Superclass</h4>
034     * <p>There is an issue when invoking public methods contained in a default access superclass on JREs prior to 1.4.
035     * Reflection locates these methods fine and correctly assigns them as public.
036     * However, an <code>IllegalAccessException</code> is thrown if the method is invoked.</p>
037     *
038     * <p><code>MethodUtils</code> contains a workaround for this situation. 
039     * It will attempt to call <code>setAccessible</code> on this method.
040     * If this call succeeds, then the method can be invoked as normal.
041     * This call will only succeed when the application has sufficient security privileges. 
042     * If this call fails then the method may fail.</p>
043     *
044     * @author Apache Software Foundation
045     * @author Craig R. McClanahan
046     * @author Ralph Schaer
047     * @author Chris Audley
048     * @author Rey Fran&#231;ois
049     * @author Gregor Ra&#253;man
050     * @author Jan Sorensen
051     * @author Robert Burrell Donkin
052     * @author Matt Benson
053     * @since 2.5
054     * @version $Id: MethodUtils.java 925961 2010-03-22 05:59:31Z bayard $
055     */
056    public class MethodUtils {
057    
058        /**
059         * <p>MethodUtils instances should NOT be constructed in standard programming.
060         * Instead, the class should be used as
061         * <code>MethodUtils.getAccessibleMethod(method)</code>.</p>
062         *
063         * <p>This constructor is public to permit tools that require a JavaBean
064         * instance to operate.</p>
065         */
066        public MethodUtils() {
067            super();
068        }
069    
070        /**
071         * <p>Invoke 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</code> object
077         * would match a <code>boolean</code> 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(Object object, String methodName,
093                Object... args) throws NoSuchMethodException,
094                IllegalAccessException, InvocationTargetException {
095            if (args == null) {
096                args = ArrayUtils.EMPTY_OBJECT_ARRAY;
097            }
098            int arguments = args.length;
099            Class<?>[] parameterTypes = new Class[arguments];
100            for (int i = 0; i < arguments; i++) {
101                parameterTypes[i] = args[i].getClass();
102            }
103            return invokeMethod(object, methodName, args, parameterTypes);
104        }
105    
106        /**
107         * <p>Invoke a named method whose parameter type matches the object type.</p>
108         *
109         * <p>This method delegates the method search to {@link #getMatchingAccessibleMethod(Class, String, Class[])}.</p>
110         *
111         * <p>This method supports calls to methods taking primitive parameters 
112         * via passing in wrapping classes. So, for example, a <code>Boolean</code> object
113         * would match a <code>boolean</code> primitive.</p>
114         *
115         * @param object invoke method on this object
116         * @param methodName get method with this name
117         * @param args use these arguments - treat null as empty array
118         * @param parameterTypes match these parameters - treat null as empty array
119         * @return The value returned by the invoked method
120         *
121         * @throws NoSuchMethodException if there is no such accessible method
122         * @throws InvocationTargetException wraps an exception thrown by the method invoked
123         * @throws IllegalAccessException if the requested method is not accessible via reflection
124         */
125        public static Object invokeMethod(Object object, String methodName,
126                Object[] args, Class<?>[] parameterTypes)
127                throws NoSuchMethodException, IllegalAccessException,
128                InvocationTargetException {
129            if (parameterTypes == null) {
130                parameterTypes = ArrayUtils.EMPTY_CLASS_ARRAY;
131            }
132            if (args == null) {
133                args = ArrayUtils.EMPTY_OBJECT_ARRAY;
134            }
135            Method method = getMatchingAccessibleMethod(object.getClass(),
136                    methodName, parameterTypes);
137            if (method == null) {
138                throw new NoSuchMethodException("No such accessible method: "
139                        + methodName + "() on object: "
140                        + object.getClass().getName());
141            }
142            return method.invoke(object, args);
143        }
144    
145        /**
146         * <p>Invoke a method whose parameter types match exactly the object
147         * types.</p>
148         *
149         * <p> This uses reflection to invoke the method obtained from a call to
150         * <code>getAccessibleMethod()</code>.</p>
151         *
152         * @param object invoke method on this object
153         * @param methodName get method with this name
154         * @param args use these arguments - treat null as empty array
155         * @return The value returned by the invoked method
156         *
157         * @throws NoSuchMethodException if there is no such accessible method
158         * @throws InvocationTargetException wraps an exception thrown by the
159         *  method invoked
160         * @throws IllegalAccessException if the requested method is not accessible
161         *  via reflection
162         */
163        public static Object invokeExactMethod(Object object, String methodName,
164                Object... args) throws NoSuchMethodException,
165                IllegalAccessException, InvocationTargetException {
166            if (args == null) {
167                args = ArrayUtils.EMPTY_OBJECT_ARRAY;
168            }
169            int arguments = args.length;
170            Class<?>[] parameterTypes = new Class[arguments];
171            for (int i = 0; i < arguments; i++) {
172                parameterTypes[i] = args[i].getClass();
173            }
174            return invokeExactMethod(object, methodName, args, parameterTypes);
175        }
176    
177        /**
178         * <p>Invoke a method whose parameter types match exactly the parameter
179         * types given.</p>
180         *
181         * <p>This uses reflection to invoke the method obtained from a call to
182         * <code>getAccessibleMethod()</code>.</p>
183         *
184         * @param object invoke method on this object
185         * @param methodName get method with this name
186         * @param args use these arguments - treat null as empty array
187         * @param parameterTypes match these parameters - treat null as empty array
188         * @return The value returned by the invoked method
189         *
190         * @throws NoSuchMethodException if there is no such accessible method
191         * @throws InvocationTargetException wraps an exception thrown by the
192         *  method invoked
193         * @throws IllegalAccessException if the requested method is not accessible
194         *  via reflection
195         */
196        public static Object invokeExactMethod(Object object, String methodName,
197                Object[] args, Class<?>[] parameterTypes)
198                throws NoSuchMethodException, IllegalAccessException,
199                InvocationTargetException {
200            if (args == null) {
201                args = ArrayUtils.EMPTY_OBJECT_ARRAY;
202            }
203            if (parameterTypes == null) {
204                parameterTypes = ArrayUtils.EMPTY_CLASS_ARRAY;
205            }
206            Method method = getAccessibleMethod(object.getClass(), methodName,
207                    parameterTypes);
208            if (method == null) {
209                throw new NoSuchMethodException("No such accessible method: "
210                        + methodName + "() on object: "
211                        + object.getClass().getName());
212            }
213            return method.invoke(object, args);
214        }
215    
216        /**
217         * <p>Invoke a static method whose parameter types match exactly the parameter
218         * types given.</p>
219         *
220         * <p>This uses reflection to invoke the method obtained from a call to
221         * {@link #getAccessibleMethod(Class, String, Class[])}.</p>
222         *
223         * @param cls invoke static method on this class
224         * @param methodName get method with this name
225         * @param args use these arguments - treat null as empty array
226         * @param parameterTypes match these parameters - treat null as empty array
227         * @return The value returned by the invoked method
228         *
229         * @throws NoSuchMethodException if there is no such accessible method
230         * @throws InvocationTargetException wraps an exception thrown by the
231         *  method invoked
232         * @throws IllegalAccessException if the requested method is not accessible
233         *  via reflection
234         */
235        public static Object invokeExactStaticMethod(Class<?> cls, String methodName,
236                Object[] args, Class<?>[] parameterTypes)
237                throws NoSuchMethodException, IllegalAccessException,
238                InvocationTargetException {
239            if (args == null) {
240                args = ArrayUtils.EMPTY_OBJECT_ARRAY;
241            }
242            if (parameterTypes == null) {
243                parameterTypes = ArrayUtils.EMPTY_CLASS_ARRAY;
244            }
245            Method method = getAccessibleMethod(cls, methodName, parameterTypes);
246            if (method == null) {
247                throw new NoSuchMethodException("No such accessible method: "
248                        + methodName + "() on class: " + cls.getName());
249            }
250            return method.invoke(null, args);
251        }
252    
253        /**
254         * <p>Invoke a named static method whose parameter type matches the object type.</p>
255         *
256         * <p>This method delegates the method search to {@link #getMatchingAccessibleMethod(Class, String, Class[])}.</p>
257         *
258         * <p>This method supports calls to methods taking primitive parameters 
259         * via passing in wrapping classes. So, for example, a <code>Boolean</code> class
260         * would match a <code>boolean</code> primitive.</p>
261         *
262         * <p> This is a convenient wrapper for
263         * {@link #invokeStaticMethod(Class objectClass,String methodName,Object [] args,Class[] parameterTypes)}.
264         * </p>
265         *
266         * @param cls invoke static method on this class
267         * @param methodName get method with this name
268         * @param args use these arguments - treat null as empty array
269         * @return The value returned by the invoked method
270         *
271         * @throws NoSuchMethodException if there is no such accessible method
272         * @throws InvocationTargetException wraps an exception thrown by the
273         *  method invoked
274         * @throws IllegalAccessException if the requested method is not accessible
275         *  via reflection
276         */
277        public static Object invokeStaticMethod(Class<?> cls, String methodName,
278                Object... args) throws NoSuchMethodException,
279                IllegalAccessException, InvocationTargetException {
280            if (args == null) {
281                args = ArrayUtils.EMPTY_OBJECT_ARRAY;
282            }
283            int arguments = args.length;
284            Class<?>[] parameterTypes = new Class[arguments];
285            for (int i = 0; i < arguments; i++) {
286                parameterTypes[i] = args[i].getClass();
287            }
288            return invokeStaticMethod(cls, methodName, args, parameterTypes);
289        }
290    
291        /**
292         * <p>Invoke a named static method whose parameter type matches the object type.</p>
293         *
294         * <p>This method delegates the method search to {@link #getMatchingAccessibleMethod(Class, String, Class[])}.</p>
295         *
296         * <p>This method supports calls to methods taking primitive parameters 
297         * via passing in wrapping classes. So, for example, a <code>Boolean</code> class
298         * would match a <code>boolean</code> primitive.</p>
299         *
300         *
301         * @param cls invoke static method on this class
302         * @param methodName get method with this name
303         * @param args use these arguments - treat null as empty array
304         * @param parameterTypes match these parameters - treat null as empty array
305         * @return The value returned by the invoked method
306         *
307         * @throws NoSuchMethodException if there is no such accessible method
308         * @throws InvocationTargetException wraps an exception thrown by the
309         *  method invoked
310         * @throws IllegalAccessException if the requested method is not accessible
311         *  via reflection
312         */
313        public static Object invokeStaticMethod(Class<?> cls, String methodName,
314                Object[] args, Class<?>[] parameterTypes)
315                throws NoSuchMethodException, IllegalAccessException,
316                InvocationTargetException {
317            if (parameterTypes == null) {
318                parameterTypes = ArrayUtils.EMPTY_CLASS_ARRAY;
319            }
320            if (args == null) {
321                args = ArrayUtils.EMPTY_OBJECT_ARRAY;
322            }
323            Method method = getMatchingAccessibleMethod(cls, methodName,
324                    parameterTypes);
325            if (method == null) {
326                throw new NoSuchMethodException("No such accessible method: "
327                        + methodName + "() on class: " + cls.getName());
328            }
329            return method.invoke(null, args);
330        }
331    
332        /**
333         * <p>Invoke a static method whose parameter types match exactly the object
334         * types.</p>
335         *
336         * <p> This uses reflection to invoke the method obtained from a call to
337         * {@link #getAccessibleMethod(Class, String, Class[])}.</p>
338         *
339         * @param cls invoke static method on this class
340         * @param methodName get method with this name
341         * @param args use these arguments - treat null as empty array
342         * @return The value returned by the invoked method
343         *
344         * @throws NoSuchMethodException if there is no such accessible method
345         * @throws InvocationTargetException wraps an exception thrown by the
346         *  method invoked
347         * @throws IllegalAccessException if the requested method is not accessible
348         *  via reflection
349         */
350        public static Object invokeExactStaticMethod(Class<?> cls, String methodName,
351                Object... args) throws NoSuchMethodException,
352                IllegalAccessException, InvocationTargetException {
353            if (args == null) {
354                args = ArrayUtils.EMPTY_OBJECT_ARRAY;
355            }
356            int arguments = args.length;
357            Class<?>[] parameterTypes = new Class[arguments];
358            for (int i = 0; i < arguments; i++) {
359                parameterTypes[i] = args[i].getClass();
360            }
361            return invokeExactStaticMethod(cls, methodName, args, parameterTypes);
362        }
363    
364        /**
365         * <p>Return an accessible method (that is, one that can be invoked via
366         * reflection) with given name and parameters.  If no such method
367         * can be found, return <code>null</code>.
368         * This is just a convenient wrapper for
369         * {@link #getAccessibleMethod(Method method)}.</p>
370         *
371         * @param cls get method from this class
372         * @param methodName get method with this name
373         * @param parameterTypes with these parameters types
374         * @return The accessible method
375         */
376        public static Method getAccessibleMethod(Class<?> cls, String methodName,
377                Class<?>... parameterTypes) {
378            try {
379                return getAccessibleMethod(cls.getMethod(methodName,
380                        parameterTypes));
381            } catch (NoSuchMethodException e) {
382                return (null);
383            }
384        }
385    
386        /**
387         * <p>Return an accessible method (that is, one that can be invoked via
388         * reflection) that implements the specified Method.  If no such method
389         * can be found, return <code>null</code>.</p>
390         *
391         * @param method The method that we wish to call
392         * @return The accessible method
393         */
394        public static Method getAccessibleMethod(Method method) {
395            if (!MemberUtils.isAccessible(method)) {
396                return null;
397            }
398            // If the declaring class is public, we are done
399            Class<?> cls = method.getDeclaringClass();
400            if (Modifier.isPublic(cls.getModifiers())) {
401                return method;
402            }
403            String methodName = method.getName();
404            Class<?>[] parameterTypes = method.getParameterTypes();
405    
406            // Check the implemented interfaces and subinterfaces
407            method = getAccessibleMethodFromInterfaceNest(cls, methodName,
408                    parameterTypes);
409    
410            // Check the superclass chain
411            if (method == null) {
412                method = getAccessibleMethodFromSuperclass(cls, methodName,
413                        parameterTypes);
414            }
415            return method;
416        }
417    
418        /**
419         * <p>Return an accessible method (that is, one that can be invoked via
420         * reflection) by scanning through the superclasses. If no such method
421         * can be found, return <code>null</code>.</p>
422         *
423         * @param cls Class to be checked
424         * @param methodName Method name of the method we wish to call
425         * @param parameterTypes The parameter type signatures
426         * @return the accessible method or <code>null</code> if not found
427         */
428        private static Method getAccessibleMethodFromSuperclass(Class<?> cls,
429                String methodName, Class<?>... parameterTypes) {
430            Class<?> parentClass = cls.getSuperclass();
431            while (parentClass != null) {
432                if (Modifier.isPublic(parentClass.getModifiers())) {
433                    try {
434                        return parentClass.getMethod(methodName, parameterTypes);
435                    } catch (NoSuchMethodException e) {
436                        return null;
437                    }
438                }
439                parentClass = parentClass.getSuperclass();
440            }
441            return null;
442        }
443    
444        /**
445         * <p>Return an accessible method (that is, one that can be invoked via
446         * reflection) that implements the specified method, by scanning through
447         * all implemented interfaces and subinterfaces.  If no such method
448         * can be found, return <code>null</code>.</p>
449         *
450         * <p> There isn't any good reason why this method must be private.
451         * It is because there doesn't seem any reason why other classes should
452         * call this rather than the higher level methods.</p>
453         *
454         * @param cls Parent class for the interfaces to be checked
455         * @param methodName Method name of the method we wish to call
456         * @param parameterTypes The parameter type signatures
457         * @return the accessible method or <code>null</code> if not found
458         */
459        private static Method getAccessibleMethodFromInterfaceNest(Class<?> cls,
460                String methodName, Class<?>... parameterTypes) {
461            Method method = null;
462    
463            // Search up the superclass chain
464            for (; cls != null; cls = cls.getSuperclass()) {
465    
466                // Check the implemented interfaces of the parent class
467                Class<?>[] interfaces = cls.getInterfaces();
468                for (int i = 0; i < interfaces.length; i++) {
469                    // Is this interface public?
470                    if (!Modifier.isPublic(interfaces[i].getModifiers())) {
471                        continue;
472                    }
473                    // Does the method exist on this interface?
474                    try {
475                        method = interfaces[i].getDeclaredMethod(methodName,
476                                parameterTypes);
477                    } catch (NoSuchMethodException e) {
478                        /*
479                         * Swallow, if no method is found after the loop then this
480                         * method returns null.
481                         */
482                    }
483                    if (method != null) {
484                        break;
485                    }
486                    // Recursively check our parent interfaces
487                    method = getAccessibleMethodFromInterfaceNest(interfaces[i],
488                            methodName, parameterTypes);
489                    if (method != null) {
490                        break;
491                    }
492                }
493            }
494            return method;
495        }
496    
497        /**
498         * <p>Find an accessible method that matches the given name and has compatible parameters.
499         * Compatible parameters mean that every method parameter is assignable from 
500         * the given parameters.
501         * In other words, it finds a method with the given name 
502         * that will take the parameters given.<p>
503         *
504         * <p>This method is used by 
505         * {@link 
506         * #invokeMethod(Object object, String methodName, Object[] args, Class[] parameterTypes)}.
507         *
508         * <p>This method can match primitive parameter by passing in wrapper classes.
509         * For example, a <code>Boolean</code> will match a primitive <code>boolean</code>
510         * parameter.
511         *
512         * @param cls find method in this class
513         * @param methodName find method with this name
514         * @param parameterTypes find method with most compatible parameters 
515         * @return The accessible method
516         */
517        public static Method getMatchingAccessibleMethod(Class<?> cls,
518                String methodName, Class<?>... parameterTypes) {
519            try {
520                Method method = cls.getMethod(methodName, parameterTypes);
521                MemberUtils.setAccessibleWorkaround(method);
522                return method;
523            } catch (NoSuchMethodException e) { /* SWALLOW */
524            }
525            // search through all methods
526            Method bestMatch = null;
527            Method[] methods = cls.getMethods();
528            for (int i = 0, size = methods.length; i < size; i++) {
529                if (methods[i].getName().equals(methodName)) {
530                    // compare parameters
531                    if (ClassUtils.isAssignable(parameterTypes, methods[i]
532                            .getParameterTypes(), true)) {
533                        // get accessible version of method
534                        Method accessibleMethod = getAccessibleMethod(methods[i]);
535                        if (accessibleMethod != null) {
536                            if (bestMatch == null
537                                    || MemberUtils.compareParameterTypes(
538                                            accessibleMethod.getParameterTypes(),
539                                            bestMatch.getParameterTypes(),
540                                            parameterTypes) < 0) {
541                                bestMatch = accessibleMethod;
542                            }
543                        }
544                    }
545                }
546            }
547            if (bestMatch != null) {
548                MemberUtils.setAccessibleWorkaround(bestMatch);
549            }
550            return bestMatch;
551        }
552    }