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