    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    }