    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 1166253 2011-09-07 16:27:42Z 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                // compare name and parameters
    521                if (method.getName().equals(methodName) && ClassUtils.isAssignable(parameterTypes, method.getParameterTypes(), true)) {
    522                    // get accessible version of method
    523                    Method accessibleMethod = getAccessibleMethod(method);
    524                    if (accessibleMethod != null && (bestMatch == null || MemberUtils.compareParameterTypes(
    525                                accessibleMethod.getParameterTypes(),
    526                                bestMatch.getParameterTypes(),
    527                                parameterTypes) < 0)) {
    528                            bestMatch = accessibleMethod;
    529                     }
    530                }
    531            }
    532            if (bestMatch != null) {
    533                MemberUtils.setAccessibleWorkaround(bestMatch);
    534            }
    535            return bestMatch;
    536        }
    537    }