View Javadoc

    1   /*
    2    * Licensed to the Apache Software Foundation (ASF) under one or more
    3    * contributor license agreements.  See the NOTICE file distributed with
    4    * this work for additional information regarding copyright ownership.
    5    * The ASF licenses this file to You under the Apache License, Version 2.0
    6    * (the "License"); you may not use this file except in compliance with
    7    * the License.  You may obtain a copy of the License at
    8    *
    9    *      http://www.apache.org/licenses/LICENSE-2.0
    10   *
    11   * Unless required by applicable law or agreed to in writing, software
    12   * distributed under the License is distributed on an "AS IS" BASIS,
    13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14   * See the License for the specific language governing permissions and
    15   * limitations under the License.
    16   */
    17
    18  package org.apache.commons.beanutils;
    19
    20
    21  import java.lang.reflect.InvocationTargetException;
    22  import java.lang.reflect.Method;
    23  import java.lang.reflect.Modifier;
    24
    25  import java.util.WeakHashMap;
    26
    27  import org.apache.commons.logging.Log;
    28  import org.apache.commons.logging.LogFactory;
    29
    30
    31  /***
    32   * <p> Utility reflection methods focussed on methods in general rather than properties in particular. </p>
    33   *
    34   * <h3>Known Limitations</h3>
    35   * <h4>Accessing Public Methods In A Default Access Superclass</h4>
    36   * <p>There is an issue when invoking public methods contained in a default access superclass.
    37   * Reflection locates these methods fine and correctly assigns them as public.
    38   * However, an <code>IllegalAccessException</code> is thrown if the method is invoked.</p>
    39   *
    40   * <p><code>MethodUtils</code> contains a workaround for this situation.
    41   * It will attempt to call <code>setAccessible</code> on this method.
    42   * If this call succeeds, then the method can be invoked as normal.
    43   * This call will only succeed when the application has sufficient security privilages.
    44   * If this call fails then a warning will be logged and the method may fail.</p>
    45   *
    46   * @author Craig R. McClanahan
    47   * @author Ralph Schaer
    48   * @author Chris Audley
    49   * @author Rey Fran&#231;ois
    50   * @author Gregor Ra&#253;man
    51   * @author Jan Sorensen
    52   * @author Robert Burrell Donkin
    53   */
    54
    55  public class MethodUtils {
    56
    57      // --------------------------------------------------------- Private Methods
    58
    59      /***
    60       * Only log warning about accessibility work around once.
    61       * <p>
    62       * Note that this is broken when this class is deployed via a shared
    63       * classloader in a container, as the warning message will be emitted
    64       * only once, not once per webapp. However making the warning appear
    65       * once per webapp means having a map keyed by context classloader
    66       * which introduces nasty memory-leak problems. As this warning is
    67       * really optional we can ignore this problem; only one of the webapps
    68       * will get the warning in its logs but that should be good enough.
    69       */
    70      private static boolean loggedAccessibleWarning = false;
    71
    72      /*** An empty class array */
    73      private static final Class[] EMPTY_CLASS_PARAMETERS = new Class[0];
    74      /*** An empty object array */
    75      private static final Object[] EMPTY_OBJECT_ARRAY = new Object[0];
    76
    77      /***
    78       * Stores a cache of MethodDescriptor -> Method in a WeakHashMap.
    79       * <p>
    80       * The keys into this map only ever exist as temporary variables within
    81       * methods of this class, and are never exposed to users of this class.
    82       * This means that the WeakHashMap is used only as a mechanism for
    83       * limiting the size of the cache, ie a way to tell the garbage collector
    84       * that the contents of the cache can be completely garbage-collected
    85       * whenever it needs the memory. Whether this is a good approach to
    86       * this problem is doubtful; something like the commons-collections
    87       * LRUMap may be more appropriate (though of course selecting an
    88       * appropriate size is an issue).
    89       * <p>
    90       * This static variable is safe even when this code is deployed via a
    91       * shared classloader because it is keyed via a MethodDescriptor object
    92       * which has a Class as one of its members and that member is used in
    93       * the MethodDescriptor.equals method. So two components that load the same
    94       * class via different classloaders will generate non-equal MethodDescriptor
    95       * objects and hence end up with different entries in the map.
    96       */
    97      private static WeakHashMap cache = new WeakHashMap();
    98
    99      // --------------------------------------------------------- Public Methods
    100
    101     /***
    102      * <p>Invoke a named method whose parameter type matches the object type.</p>
    103      *
    104      * <p>The behaviour of this method is less deterministic
    105      * than <code>invokeExactMethod()</code>.
    106      * It loops through all methods with names that match
    107      * and then executes the first it finds with compatable parameters.</p>
    108      *
    109      * <p>This method supports calls to methods taking primitive parameters
    110      * via passing in wrapping classes. So, for example, a <code>Boolean</code> class
    111      * would match a <code>boolean</code> primitive.</p>
    112      *
    113      * <p> This is a convenient wrapper for
    114      * {@link #invokeMethod(Object object,String methodName,Object [] args)}.
    115      * </p>
    116      *
    117      * @param object invoke method on this object
    118      * @param methodName get method with this name
    119      * @param arg use this argument
    120      * @return The value returned by the invoked method
    121      *
    122      * @throws NoSuchMethodException if there is no such accessible method
    123      * @throws InvocationTargetException wraps an exception thrown by the
    124      *  method invoked
    125      * @throws IllegalAccessException if the requested method is not accessible
    126      *  via reflection
    127      */
    128     public static Object invokeMethod(
    129             Object object,
    130             String methodName,
    131             Object arg)
    132             throws
    133             NoSuchMethodException,
    134             IllegalAccessException,
    135             InvocationTargetException {
    136
    137         Object[] args = {arg};
    138         return invokeMethod(object, methodName, args);
    139
    140     }
    141
    142
    143     /***
    144      * <p>Invoke a named method whose parameter type matches the object type.</p>
    145      *
    146      * <p>The behaviour of this method is less deterministic
    147      * than {@link #invokeExactMethod(Object object,String methodName,Object [] args)}.
    148      * It loops through all methods with names that match
    149      * and then executes the first it finds with compatable parameters.</p>
    150      *
    151      * <p>This method supports calls to methods taking primitive parameters
    152      * via passing in wrapping classes. So, for example, a <code>Boolean</code> class
    153      * would match a <code>boolean</code> primitive.</p>
    154      *
    155      * <p> This is a convenient wrapper for
    156      * {@link #invokeMethod(Object object,String methodName,Object [] args,Class[] parameterTypes)}.
    157      * </p>
    158      *
    159      * @param object invoke method on this object
    160      * @param methodName get method with this name
    161      * @param args use these arguments - treat null as empty array
    162      * @return The value returned by the invoked method
    163      *
    164      * @throws NoSuchMethodException if there is no such accessible method
    165      * @throws InvocationTargetException wraps an exception thrown by the
    166      *  method invoked
    167      * @throws IllegalAccessException if the requested method is not accessible
    168      *  via reflection
    169      */
    170     public static Object invokeMethod(
    171             Object object,
    172             String methodName,
    173             Object[] args)
    174             throws
    175             NoSuchMethodException,
    176             IllegalAccessException,
    177             InvocationTargetException {
    178
    179         if (args == null) {
    180             args = EMPTY_OBJECT_ARRAY;
    181         }
    182         int arguments = args.length;
    183         Class[] parameterTypes = new Class[arguments];
    184         for (int i = 0; i < arguments; i++) {
    185             parameterTypes[i] = args[i].getClass();
    186         }
    187         return invokeMethod(object, methodName, args, parameterTypes);
    188
    189     }
    190
    191
    192     /***
    193      * <p>Invoke a named method whose parameter type matches the object type.</p>
    194      *
    195      * <p>The behaviour of this method is less deterministic
    196      * than {@link
    197      * #invokeExactMethod(Object object,String methodName,Object [] args,Class[] parameterTypes)}.
    198      * It loops through all methods with names that match
    199      * and then executes the first it finds with compatable parameters.</p>
    200      *
    201      * <p>This method supports calls to methods taking primitive parameters
    202      * via passing in wrapping classes. So, for example, a <code>Boolean</code> class
    203      * would match a <code>boolean</code> primitive.</p>
    204      *
    205      *
    206      * @param object invoke method on this object
    207      * @param methodName get method with this name
    208      * @param args use these arguments - treat null as empty array
    209      * @param parameterTypes match these parameters - treat null as empty array
    210      * @return The value returned by the invoked method
    211      *
    212      * @throws NoSuchMethodException if there is no such accessible method
    213      * @throws InvocationTargetException wraps an exception thrown by the
    214      *  method invoked
    215      * @throws IllegalAccessException if the requested method is not accessible
    216      *  via reflection
    217      */
    218     public static Object invokeMethod(
    219             Object object,
    220             String methodName,
    221             Object[] args,
    222             Class[] parameterTypes)
    223                 throws
    224                     NoSuchMethodException,
    225                     IllegalAccessException,
    226                     InvocationTargetException {
    227
    228         if (parameterTypes == null) {
    229             parameterTypes = EMPTY_CLASS_PARAMETERS;
    230         }
    231         if (args == null) {
    232             args = EMPTY_OBJECT_ARRAY;
    233         }
    234
    235         Method method = getMatchingAccessibleMethod(
    236                 object.getClass(),
    237                 methodName,
    238                 parameterTypes);
    239         if (method == null) {
    240             throw new NoSuchMethodException("No such accessible method: " +
    241                     methodName + "() on object: " + object.getClass().getName());
    242         }
    243         return method.invoke(object, args);
    244     }
    245
    246
    247     /***
    248      * <p>Invoke a method whose parameter type matches exactly the object
    249      * type.</p>
    250      *
    251      * <p> This is a convenient wrapper for
    252      * {@link #invokeExactMethod(Object object,String methodName,Object [] args)}.
    253      * </p>
    254      *
    255      * @param object invoke method on this object
    256      * @param methodName get method with this name
    257      * @param arg use this argument
    258      * @return The value returned by the invoked method
    259      *
    260      * @throws NoSuchMethodException if there is no such accessible method
    261      * @throws InvocationTargetException wraps an exception thrown by the
    262      *  method invoked
    263      * @throws IllegalAccessException if the requested method is not accessible
    264      *  via reflection
    265      */
    266     public static Object invokeExactMethod(
    267             Object object,
    268             String methodName,
    269             Object arg)
    270             throws
    271             NoSuchMethodException,
    272             IllegalAccessException,
    273             InvocationTargetException {
    274
    275         Object[] args = {arg};
    276         return invokeExactMethod(object, methodName, args);
    277
    278     }
    279
    280
    281     /***
    282      * <p>Invoke a method whose parameter types match exactly the object
    283      * types.</p>
    284      *
    285      * <p> This uses reflection to invoke the method obtained from a call to
    286      * <code>getAccessibleMethod()</code>.</p>
    287      *
    288      * @param object invoke method on this object
    289      * @param methodName get method with this name
    290      * @param args use these arguments - treat null as empty array
    291      * @return The value returned by the invoked method
    292      *
    293      * @throws NoSuchMethodException if there is no such accessible method
    294      * @throws InvocationTargetException wraps an exception thrown by the
    295      *  method invoked
    296      * @throws IllegalAccessException if the requested method is not accessible
    297      *  via reflection
    298      */
    299     public static Object invokeExactMethod(
    300             Object object,
    301             String methodName,
    302             Object[] args)
    303             throws
    304             NoSuchMethodException,
    305             IllegalAccessException,
    306             InvocationTargetException {
    307         if (args == null) {
    308             args = EMPTY_OBJECT_ARRAY;
    309         }
    310         int arguments = args.length;
    311         Class[] parameterTypes = new Class[arguments];
    312         for (int i = 0; i < arguments; i++) {
    313             parameterTypes[i] = args[i].getClass();
    314         }
    315         return invokeExactMethod(object, methodName, args, parameterTypes);
    316
    317     }
    318
    319
    320     /***
    321      * <p>Invoke a method whose parameter types match exactly the parameter
    322      * types given.</p>
    323      *
    324      * <p>This uses reflection to invoke the method obtained from a call to
    325      * <code>getAccessibleMethod()</code>.</p>
    326      *
    327      * @param object invoke method on this object
    328      * @param methodName get method with this name
    329      * @param args use these arguments - treat null as empty array
    330      * @param parameterTypes match these parameters - treat null as empty array
    331      * @return The value returned by the invoked method
    332      *
    333      * @throws NoSuchMethodException if there is no such accessible method
    334      * @throws InvocationTargetException wraps an exception thrown by the
    335      *  method invoked
    336      * @throws IllegalAccessException if the requested method is not accessible
    337      *  via reflection
    338      */
    339     public static Object invokeExactMethod(
    340             Object object,
    341             String methodName,
    342             Object[] args,
    343             Class[] parameterTypes)
    344             throws
    345             NoSuchMethodException,
    346             IllegalAccessException,
    347             InvocationTargetException {
    348
    349         if (args == null) {
    350             args = EMPTY_OBJECT_ARRAY;
    351         }
    352
    353         if (parameterTypes == null) {
    354             parameterTypes = EMPTY_CLASS_PARAMETERS;
    355         }
    356
    357         Method method = getAccessibleMethod(
    358                 object.getClass(),
    359                 methodName,
    360                 parameterTypes);
    361         if (method == null) {
    362             throw new NoSuchMethodException("No such accessible method: " +
    363                     methodName + "() on object: " + object.getClass().getName());
    364         }
    365         return method.invoke(object, args);
    366
    367     }
    368
    369     /***
    370      * <p>Invoke a static method whose parameter types match exactly the parameter
    371      * types given.</p>
    372      *
    373      * <p>This uses reflection to invoke the method obtained from a call to
    374      * {@link #getAccessibleMethod(Class, String, Class[])}.</p>
    375      *
    376      * @param objectClass invoke static method on this class
    377      * @param methodName get method with this name
    378      * @param args use these arguments - treat null as empty array
    379      * @param parameterTypes match these parameters - treat null as empty array
    380      * @return The value returned by the invoked method
    381      *
    382      * @throws NoSuchMethodException if there is no such accessible method
    383      * @throws InvocationTargetException wraps an exception thrown by the
    384      *  method invoked
    385      * @throws IllegalAccessException if the requested method is not accessible
    386      *  via reflection
    387      */
    388     public static Object invokeExactStaticMethod(
    389             Class objectClass,
    390             String methodName,
    391             Object[] args,
    392             Class[] parameterTypes)
    393             throws
    394             NoSuchMethodException,
    395             IllegalAccessException,
    396             InvocationTargetException {
    397
    398         if (args == null) {
    399             args = EMPTY_OBJECT_ARRAY;
    400         }
    401
    402         if (parameterTypes == null) {
    403             parameterTypes = EMPTY_CLASS_PARAMETERS;
    404         }
    405
    406         Method method = getAccessibleMethod(
    407                 objectClass,
    408                 methodName,
    409                 parameterTypes);
    410         if (method == null) {
    411             throw new NoSuchMethodException("No such accessible method: " +
    412                     methodName + "() on class: " + objectClass.getName());
    413         }
    414         return method.invoke(null, args);
    415
    416     }
    417
    418     /***
    419      * <p>Invoke a named static method whose parameter type matches the object type.</p>
    420      *
    421      * <p>The behaviour of this method is less deterministic
    422      * than {@link #invokeExactMethod(Object, String, Object[], Class[])}.
    423      * It loops through all methods with names that match
    424      * and then executes the first it finds with compatable parameters.</p>
    425      *
    426      * <p>This method supports calls to methods taking primitive parameters
    427      * via passing in wrapping classes. So, for example, a <code>Boolean</code> class
    428      * would match a <code>boolean</code> primitive.</p>
    429      *
    430      * <p> This is a convenient wrapper for
    431      * {@link #invokeStaticMethod(Class objectClass,String methodName,Object [] args)}.
    432      * </p>
    433      *
    434      * @param objectClass invoke static method on this class
    435      * @param methodName get method with this name
    436      * @param arg use this argument
    437      * @return The value returned by the invoked method
    438      *
    439      * @throws NoSuchMethodException if there is no such accessible method
    440      * @throws InvocationTargetException wraps an exception thrown by the
    441      *  method invoked
    442      * @throws IllegalAccessException if the requested method is not accessible
    443      *  via reflection
    444      */
    445     public static Object invokeStaticMethod(
    446             Class objectClass,
    447             String methodName,
    448             Object arg)
    449             throws
    450             NoSuchMethodException,
    451             IllegalAccessException,
    452             InvocationTargetException {
    453
    454         Object[] args = {arg};
    455         return invokeStaticMethod (objectClass, methodName, args);
    456
    457     }
    458
    459
    460     /***
    461      * <p>Invoke a named static method whose parameter type matches the object type.</p>
    462      *
    463      * <p>The behaviour of this method is less deterministic
    464      * than {@link #invokeExactMethod(Object object,String methodName,Object [] args)}.
    465      * It loops through all methods with names that match
    466      * and then executes the first it finds with compatable parameters.</p>
    467      *
    468      * <p>This method supports calls to methods taking primitive parameters
    469      * via passing in wrapping classes. So, for example, a <code>Boolean</code> class
    470      * would match a <code>boolean</code> primitive.</p>
    471      *
    472      * <p> This is a convenient wrapper for
    473      * {@link #invokeStaticMethod(Class objectClass,String methodName,Object [] args,Class[] parameterTypes)}.
    474      * </p>
    475      *
    476      * @param objectClass invoke static method on this class
    477      * @param methodName get method with this name
    478      * @param args use these arguments - treat null as empty array
    479      * @return The value returned by the invoked method
    480      *
    481      * @throws NoSuchMethodException if there is no such accessible method
    482      * @throws InvocationTargetException wraps an exception thrown by the
    483      *  method invoked
    484      * @throws IllegalAccessException if the requested method is not accessible
    485      *  via reflection
    486      */
    487     public static Object invokeStaticMethod(
    488             Class objectClass,
    489             String methodName,
    490             Object[] args)
    491             throws
    492             NoSuchMethodException,
    493             IllegalAccessException,
    494             InvocationTargetException {
    495
    496         if (args == null) {
    497             args = EMPTY_OBJECT_ARRAY;
    498         }
    499         int arguments = args.length;
    500         Class[] parameterTypes = new Class[arguments];
    501         for (int i = 0; i < arguments; i++) {
    502             parameterTypes[i] = args[i].getClass();
    503         }
    504         return invokeStaticMethod (objectClass, methodName, args, parameterTypes);
    505
    506     }
    507
    508
    509     /***
    510      * <p>Invoke a named static method whose parameter type matches the object type.</p>
    511      *
    512      * <p>The behaviour of this method is less deterministic
    513      * than {@link
    514      * #invokeExactStaticMethod(Class objectClass,String methodName,Object [] args,Class[] parameterTypes)}.
    515      * It loops through all methods with names that match
    516      * and then executes the first it finds with compatable parameters.</p>
    517      *
    518      * <p>This method supports calls to methods taking primitive parameters
    519      * via passing in wrapping classes. So, for example, a <code>Boolean</code> class
    520      * would match a <code>boolean</code> primitive.</p>
    521      *
    522      *
    523      * @param objectClass invoke static method on this class
    524      * @param methodName get method with this name
    525      * @param args use these arguments - treat null as empty array
    526      * @param parameterTypes match these parameters - treat null as empty array
    527      * @return The value returned by the invoked method
    528      *
    529      * @throws NoSuchMethodException if there is no such accessible method
    530      * @throws InvocationTargetException wraps an exception thrown by the
    531      *  method invoked
    532      * @throws IllegalAccessException if the requested method is not accessible
    533      *  via reflection
    534      */
    535     public static Object invokeStaticMethod(
    536             Class objectClass,
    537             String methodName,
    538             Object[] args,
    539             Class[] parameterTypes)
    540                 throws
    541                     NoSuchMethodException,
    542                     IllegalAccessException,
    543                     InvocationTargetException {
    544
    545         if (parameterTypes == null) {
    546             parameterTypes = EMPTY_CLASS_PARAMETERS;
    547         }
    548         if (args == null) {
    549             args = EMPTY_OBJECT_ARRAY;
    550         }
    551
    552         Method method = getMatchingAccessibleMethod(
    553                 objectClass,
    554                 methodName,
    555                 parameterTypes);
    556         if (method == null) {
    557             throw new NoSuchMethodException("No such accessible method: " +
    558                     methodName + "() on class: " + objectClass.getName());
    559         }
    560         return method.invoke(null, args);
    561     }
    562
    563
    564     /***
    565      * <p>Invoke a static method whose parameter type matches exactly the object
    566      * type.</p>
    567      *
    568      * <p> This is a convenient wrapper for
    569      * {@link #invokeExactStaticMethod(Class objectClass,String methodName,Object [] args)}.
    570      * </p>
    571      *
    572      * @param objectClass invoke static method on this class
    573      * @param methodName get method with this name
    574      * @param arg use this argument
    575      * @return The value returned by the invoked method
    576      *
    577      * @throws NoSuchMethodException if there is no such accessible method
    578      * @throws InvocationTargetException wraps an exception thrown by the
    579      *  method invoked
    580      * @throws IllegalAccessException if the requested method is not accessible
    581      *  via reflection
    582      */
    583     public static Object invokeExactStaticMethod(
    584             Class objectClass,
    585             String methodName,
    586             Object arg)
    587             throws
    588             NoSuchMethodException,
    589             IllegalAccessException,
    590             InvocationTargetException {
    591
    592         Object[] args = {arg};
    593         return invokeExactStaticMethod (objectClass, methodName, args);
    594
    595     }
    596
    597
    598     /***
    599      * <p>Invoke a static method whose parameter types match exactly the object
    600      * types.</p>
    601      *
    602      * <p> This uses reflection to invoke the method obtained from a call to
    603      * {@link #getAccessibleMethod(Class, String, Class[])}.</p>
    604      *
    605      * @param objectClass invoke static method on this class
    606      * @param methodName get method with this name
    607      * @param args use these arguments - treat null as empty array
    608      * @return The value returned by the invoked method
    609      *
    610      * @throws NoSuchMethodException if there is no such accessible method
    611      * @throws InvocationTargetException wraps an exception thrown by the
    612      *  method invoked
    613      * @throws IllegalAccessException if the requested method is not accessible
    614      *  via reflection
    615      */
    616     public static Object invokeExactStaticMethod(
    617             Class objectClass,
    618             String methodName,
    619             Object[] args)
    620             throws
    621             NoSuchMethodException,
    622             IllegalAccessException,
    623             InvocationTargetException {
    624         if (args == null) {
    625             args = EMPTY_OBJECT_ARRAY;
    626         }
    627         int arguments = args.length;
    628         Class[] parameterTypes = new Class[arguments];
    629         for (int i = 0; i < arguments; i++) {
    630             parameterTypes[i] = args[i].getClass();
    631         }
    632         return invokeExactStaticMethod(objectClass, methodName, args, parameterTypes);
    633
    634     }
    635
    636
    637     /***
    638      * <p>Return an accessible method (that is, one that can be invoked via
    639      * reflection) with given name and a single parameter.  If no such method
    640      * can be found, return <code>null</code>.
    641      * Basically, a convenience wrapper that constructs a <code>Class</code>
    642      * array for you.</p>
    643      *
    644      * @param clazz get method from this class
    645      * @param methodName get method with this name
    646      * @param parameterType taking this type of parameter
    647      * @return The accessible method
    648      */
    649     public static Method getAccessibleMethod(
    650             Class clazz,
    651             String methodName,
    652             Class parameterType) {
    653
    654         Class[] parameterTypes = {parameterType};
    655         return getAccessibleMethod(clazz, methodName, parameterTypes);
    656
    657     }
    658
    659
    660     /***
    661      * <p>Return an accessible method (that is, one that can be invoked via
    662      * reflection) with given name and parameters.  If no such method
    663      * can be found, return <code>null</code>.
    664      * This is just a convenient wrapper for
    665      * {@link #getAccessibleMethod(Method method)}.</p>
    666      *
    667      * @param clazz get method from this class
    668      * @param methodName get method with this name
    669      * @param parameterTypes with these parameters types
    670      * @return The accessible method
    671      */
    672     public static Method getAccessibleMethod(
    673             Class clazz,
    674             String methodName,
    675             Class[] parameterTypes) {
    676
    677         try {
    678             MethodDescriptor md = new MethodDescriptor(clazz, methodName, parameterTypes, true);
    679             // Check the cache first
    680             Method method = (Method)cache.get(md);
    681             if (method != null) {
    682                 return method;
    683             }
    684
    685             method =  getAccessibleMethod
    686                     (clazz.getMethod(methodName, parameterTypes));
    687             cache.put(md, method);
    688             return method;
    689         } catch (NoSuchMethodException e) {
    690             return (null);
    691         }
    692
    693     }
    694
    695
    696     /***
    697      * <p>Return an accessible method (that is, one that can be invoked via
    698      * reflection) that implements the specified Method.  If no such method
    699      * can be found, return <code>null</code>.</p>
    700      *
    701      * @param method The method that we wish to call
    702      * @return The accessible method
    703      */
    704     public static Method getAccessibleMethod(Method method) {
    705
    706         // Make sure we have a method to check
    707         if (method == null) {
    708             return (null);
    709         }
    710
    711         // If the requested method is not public we cannot call it
    712         if (!Modifier.isPublic(method.getModifiers())) {
    713             return (null);
    714         }
    715
    716         // If the declaring class is public, we are done
    717         Class clazz = method.getDeclaringClass();
    718         if (Modifier.isPublic(clazz.getModifiers())) {
    719             return (method);
    720         }
    721
    722         String methodName      = method.getName();
    723         Class[] parameterTypes = method.getParameterTypes();
    724
    725         // Check the implemented interfaces and subinterfaces
    726         method =
    727                 getAccessibleMethodFromInterfaceNest(clazz,
    728                         methodName,
    729                         parameterTypes);
    730
    731         // Check the superclass chain
    732         if (method == null) {
    733             method = getAccessibleMethodFromSuperclass(clazz,
    734                         methodName,
    735                         parameterTypes);
    736         }
    737
    738         return (method);
    739
    740     }
    741
    742
    743     // -------------------------------------------------------- Private Methods
    744
    745     /***
    746      * <p>Return an accessible method (that is, one that can be invoked via
    747      * reflection) by scanning through the superclasses. If no such method
    748      * can be found, return <code>null</code>.</p>
    749      *
    750      * @param clazz Class to be checked
    751      * @param methodName Method name of the method we wish to call
    752      * @param parameterTypes The parameter type signatures
    753      */
    754     private static Method getAccessibleMethodFromSuperclass
    755             (Class clazz, String methodName, Class[] parameterTypes) {
    756
    757         Class parentClazz = clazz.getSuperclass();
    758         while (parentClazz != null) {
    759             if (Modifier.isPublic(parentClazz.getModifiers())) {
    760                 try {
    761                     return parentClazz.getMethod(methodName, parameterTypes);
    762                 } catch (NoSuchMethodException e) {
    763                     return null;
    764                 }
    765             }
    766             parentClazz = parentClazz.getSuperclass();
    767         }
    768         return null;
    769     }
    770
    771     /***
    772      * <p>Return an accessible method (that is, one that can be invoked via
    773      * reflection) that implements the specified method, by scanning through
    774      * all implemented interfaces and subinterfaces.  If no such method
    775      * can be found, return <code>null</code>.</p>
    776      *
    777      * <p> There isn't any good reason why this method must be private.
    778      * It is because there doesn't seem any reason why other classes should
    779      * call this rather than the higher level methods.</p>
    780      *
    781      * @param clazz Parent class for the interfaces to be checked
    782      * @param methodName Method name of the method we wish to call
    783      * @param parameterTypes The parameter type signatures
    784      */
    785     private static Method getAccessibleMethodFromInterfaceNest
    786             (Class clazz, String methodName, Class[] parameterTypes) {
    787
    788         Method method = null;
    789
    790         // Search up the superclass chain
    791         for (; clazz != null; clazz = clazz.getSuperclass()) {
    792
    793             // Check the implemented interfaces of the parent class
    794             Class[] interfaces = clazz.getInterfaces();
    795             for (int i = 0; i < interfaces.length; i++) {
    796
    797                 // Is this interface public?
    798                 if (!Modifier.isPublic(interfaces[i].getModifiers())) {
    799                     continue;
    800                 }
    801
    802                 // Does the method exist on this interface?
    803                 try {
    804                     method = interfaces[i].getDeclaredMethod(methodName,
    805                             parameterTypes);
    806                 } catch (NoSuchMethodException e) {
    807                     /* Swallow, if no method is found after the loop then this
    808                      * method returns null.
    809                      */
    810                 }
    811                 if (method != null) {
    812                     break;
    813                 }
    814
    815                 // Recursively check our parent interfaces
    816                 method =
    817                         getAccessibleMethodFromInterfaceNest(interfaces[i],
    818                                 methodName,
    819                                 parameterTypes);
    820                 if (method != null) {
    821                     break;
    822                 }
    823
    824             }
    825
    826         }
    827
    828         // If we found a method return it
    829         if (method != null) {
    830             return (method);
    831         }
    832
    833         // We did not find anything
    834         return (null);
    835
    836     }
    837
    838     /***
    839      * <p>Find an accessible method that matches the given name and has compatible parameters.
    840      * Compatible parameters mean that every method parameter is assignable from
    841      * the given parameters.
    842      * In other words, it finds a method with the given name
    843      * that will take the parameters given.<p>
    844      *
    845      * <p>This method is slightly undeterminstic since it loops
    846      * through methods names and return the first matching method.</p>
    847      *
    848      * <p>This method is used by
    849      * {@link
    850      * #invokeMethod(Object object,String methodName,Object [] args,Class[] parameterTypes)}.
    851      *
    852      * <p>This method can match primitive parameter by passing in wrapper classes.
    853      * For example, a <code>Boolean</code> will match a primitive <code>boolean</code>
    854      * parameter.
    855      *
    856      * @param clazz find method in this class
    857      * @param methodName find method with this name
    858      * @param parameterTypes find method with compatible parameters
    859      * @return The accessible method
    860      */
    861     public static Method getMatchingAccessibleMethod(
    862                                                 Class clazz,
    863                                                 String methodName,
    864                                                 Class[] parameterTypes) {
    865         // trace logging
    866         Log log = LogFactory.getLog(MethodUtils.class);
    867         if (log.isTraceEnabled()) {
    868             log.trace("Matching name=" + methodName + " on " + clazz);
    869         }
    870         MethodDescriptor md = new MethodDescriptor(clazz, methodName, parameterTypes, false);
    871
    872         // see if we can find the method directly
    873         // most of the time this works and it's much faster
    874         try {
    875             // Check the cache first
    876             Method method = (Method)cache.get(md);
    877             if (method != null) {
    878                 return method;
    879             }
    880
    881             method = clazz.getMethod(methodName, parameterTypes);
    882             if (log.isTraceEnabled()) {
    883                 log.trace("Found straight match: " + method);
    884                 log.trace("isPublic:" + Modifier.isPublic(method.getModifiers()));
    885             }
    886
    887             try {
    888                 //
    889                 // XXX Default access superclass workaround
    890                 //
    891                 // When a public class has a default access superclass
    892                 // with public methods, these methods are accessible.
    893                 // Calling them from compiled code works fine.
    894                 //
    895                 // Unfortunately, using reflection to invoke these methods
    896                 // seems to (wrongly) to prevent access even when the method
    897                 // modifer is public.
    898                 //
    899                 // The following workaround solves the problem but will only
    900                 // work from sufficiently privilages code.
    901                 //
    902                 // Better workarounds would be greatfully accepted.
    903                 //
    904                 method.setAccessible(true);
    905
    906             } catch (SecurityException se) {
    907                 // log but continue just in case the method.invoke works anyway
    908                 if (!loggedAccessibleWarning) {
    909                     boolean vulnerableJVM = false;
    910                     try {
    911                         String specVersion = System.getProperty("java.specification.version");
    912                         if (specVersion.charAt(0) == '1' &&
    913                                 (specVersion.charAt(2) == '0' ||
    914                                  specVersion.charAt(2) == '1' ||
    915                                  specVersion.charAt(2) == '2' ||
    916                                  specVersion.charAt(2) == '3')) {
    917
    918                             vulnerableJVM = true;
    919                         }
    920                     } catch (SecurityException e) {
    921                         // don't know - so display warning
    922                         vulnerableJVM = true;
    923                     }
    924                     if (vulnerableJVM) {
    925                         log.warn(
    926                             "Current Security Manager restricts use of workarounds for reflection bugs "
    927                             + " in pre-1.4 JVMs.");
    928                     }
    929                     loggedAccessibleWarning = true;
    930                 }
    931                 log.debug(
    932                         "Cannot setAccessible on method. Therefore cannot use jvm access bug workaround.",
    933                         se);
    934             }
    935             cache.put(md, method);
    936             return method;
    937
    938         } catch (NoSuchMethodException e) { /* SWALLOW */ }
    939
    940         // search through all methods
    941         int paramSize = parameterTypes.length;
    942         Method bestMatch = null;
    943         Method[] methods = clazz.getMethods();
    944         float bestMatchCost = Float.MAX_VALUE;
    945         float myCost = Float.MAX_VALUE;
    946         for (int i = 0, size = methods.length; i < size ; i++) {
    947             if (methods[i].getName().equals(methodName)) {
    948                 // log some trace information
    949                 if (log.isTraceEnabled()) {
    950                     log.trace("Found matching name:");
    951                     log.trace(methods[i]);
    952                 }
    953
    954                 // compare parameters
    955                 Class[] methodsParams = methods[i].getParameterTypes();
    956                 int methodParamSize = methodsParams.length;
    957                 if (methodParamSize == paramSize) {
    958                     boolean match = true;
    959                     for (int n = 0 ; n < methodParamSize; n++) {
    960                         if (log.isTraceEnabled()) {
    961                             log.trace("Param=" + parameterTypes[n].getName());
    962                             log.trace("Method=" + methodsParams[n].getName());
    963                         }
    964                         if (!isAssignmentCompatible(methodsParams[n], parameterTypes[n])) {
    965                             if (log.isTraceEnabled()) {
    966                                 log.trace(methodsParams[n] + " is not assignable from "
    967                                             + parameterTypes[n]);
    968                             }
    969                             match = false;
    970                             break;
    971                         }
    972                     }
    973
    974                     if (match) {
    975                         // get accessible version of method
    976                         Method method = getAccessibleMethod(methods[i]);
    977                         if (method != null) {
    978                             if (log.isTraceEnabled()) {
    979                                 log.trace(method + " accessible version of "
    980                                             + methods[i]);
    981                             }
    982                             try {
    983                                 //
    984                                 // XXX Default access superclass workaround
    985                                 // (See above for more details.)
    986                                 //
    987                                 method.setAccessible(true);
    988
    989                             } catch (SecurityException se) {
    990                                 // log but continue just in case the method.invoke works anyway
    991                                 if (!loggedAccessibleWarning) {
    992                                     log.warn(
    993             "Cannot use JVM pre-1.4 access bug workaround due to restrictive security manager.");
    994                                     loggedAccessibleWarning = true;
    995                                 }
    996                                 log.debug(
    997             "Cannot setAccessible on method. Therefore cannot use jvm access bug workaround.",
    998                                         se);
    999                             }
    1000                             myCost = getTotalTransformationCost(parameterTypes,method.getParameterTypes());
    1001                             if ( myCost < bestMatchCost ) {
    1002                                bestMatch = method;
    1003                                bestMatchCost = myCost;
    1004                             }
    1005                         }
    1006
    1007                         log.trace("Couldn't find accessible method.");
    1008                     }
    1009                 }
    1010             }
    1011         }
    1012         if ( bestMatch != null ){
    1013                  cache.put(md, bestMatch);
    1014         } else {
    1015         // didn't find a match
    1016                log.trace("No match found.");
    1017         }
    1018
    1019         return bestMatch;
    1020     }
    1021
    1022     /***
    1023      * Returns the sum of the object transformation cost for each class in the source
    1024      * argument list.
    1025      * @param srcArgs The source arguments
    1026      * @param destArgs The destination arguments
    1027      * @return The total transformation cost
    1028      */
    1029     private static float getTotalTransformationCost(Class[] srcArgs, Class[] destArgs) {
    1030
    1031         float totalCost = 0.0f;
    1032         for (int i = 0; i < srcArgs.length; i++) {
    1033             Class srcClass, destClass;
    1034             srcClass = srcArgs[i];
    1035             destClass = destArgs[i];
    1036             totalCost += getObjectTransformationCost(srcClass, destClass);
    1037         }
    1038
    1039         return totalCost;
    1040     }
    1041
    1042     /***
    1043      * Gets the number of steps required needed to turn the source class into the
    1044      * destination class. This represents the number of steps in the object hierarchy
    1045      * graph.
    1046      * @param srcClass The source class
    1047      * @param destClass The destination class
    1048      * @return The cost of transforming an object
    1049      */
    1050     private static float getObjectTransformationCost(Class srcClass, Class destClass) {
    1051         float cost = 0.0f;
    1052         while (destClass != null && !destClass.equals(srcClass)) {
    1053             if (destClass.isInterface() && isAssignmentCompatible(destClass,srcClass)) {
    1054                 // slight penalty for interface match.
    1055                 // we still want an exact match to override an interface match, but
    1056                 // an interface match should override anything where we have to get a
    1057                 // superclass.
    1058                 cost += 0.25f;
    1059                 break;
    1060             }
    1061             cost++;
    1062             destClass = destClass.getSuperclass();
    1063         }
    1064
    1065         /*
    1066          * If the destination class is null, we've travelled all the way up to
    1067          * an Object match. We'll penalize this by adding 1.5 to the cost.
    1068          */
    1069         if (destClass == null) {
    1070             cost += 1.5f;
    1071         }
    1072
    1073         return cost;
    1074     }
    1075
    1076
    1077     /***
    1078      * <p>Determine whether a type can be used as a parameter in a method invocation.
    1079      * This method handles primitive conversions correctly.</p>
    1080      *
    1081      * <p>In order words, it will match a <code>Boolean</code> to a <code>boolean</code>,
    1082      * a <code>Long</code> to a <code>long</code>,
    1083      * a <code>Float</code> to a <code>float</code>,
    1084      * a <code>Integer</code> to a <code>int</code>,
    1085      * and a <code>Double</code> to a <code>double</code>.
    1086      * Now logic widening matches are allowed.
    1087      * For example, a <code>Long</code> will not match a <code>int</code>.
    1088      *
    1089      * @param parameterType the type of parameter accepted by the method
    1090      * @param parameterization the type of parameter being tested
    1091      *
    1092      * @return true if the assignement is compatible.
    1093      */
    1094     public static final boolean isAssignmentCompatible(Class parameterType, Class parameterization) {
    1095         // try plain assignment
    1096         if (parameterType.isAssignableFrom(parameterization)) {
    1097             return true;
    1098         }
    1099
    1100         if (parameterType.isPrimitive()) {
    1101             // this method does *not* do widening - you must specify exactly
    1102             // is this the right behaviour?
    1103             Class parameterWrapperClazz = getPrimitiveWrapper(parameterType);
    1104             if (parameterWrapperClazz != null) {
    1105                 return parameterWrapperClazz.equals(parameterization);
    1106             }
    1107         }
    1108
    1109         return false;
    1110     }
    1111
    1112     /***
    1113      * Gets the wrapper object class for the given primitive type class.
    1114      * For example, passing <code>boolean.class</code> returns <code>Boolean.class</code>
    1115      * @param primitiveType the primitive type class for which a match is to be found
    1116      * @return the wrapper type associated with the given primitive
    1117      * or null if no match is found
    1118      */
    1119     public static Class getPrimitiveWrapper(Class primitiveType) {
    1120         // does anyone know a better strategy than comparing names?
    1121         if (boolean.class.equals(primitiveType)) {
    1122             return Boolean.class;
    1123         } else if (float.class.equals(primitiveType)) {
    1124             return Float.class;
    1125         } else if (long.class.equals(primitiveType)) {
    1126             return Long.class;
    1127         } else if (int.class.equals(primitiveType)) {
    1128             return Integer.class;
    1129         } else if (short.class.equals(primitiveType)) {
    1130             return Short.class;
    1131         } else if (byte.class.equals(primitiveType)) {
    1132             return Byte.class;
    1133         } else if (double.class.equals(primitiveType)) {
    1134             return Double.class;
    1135         } else if (char.class.equals(primitiveType)) {
    1136             return Character.class;
    1137         } else {
    1138
    1139             return null;
    1140         }
    1141     }
    1142
    1143     /***
    1144      * Gets the class for the primitive type corresponding to the primitive wrapper class given.
    1145      * For example, an instance of <code>Boolean.class</code> returns a <code>boolean.class</code>.
    1146      * @param wrapperType the
    1147      * @return the primitive type class corresponding to the given wrapper class,
    1148      * null if no match is found
    1149      */
    1150     public static Class getPrimitiveType(Class wrapperType) {
    1151         // does anyone know a better strategy than comparing names?
    1152         if (Boolean.class.equals(wrapperType)) {
    1153             return boolean.class;
    1154         } else if (Float.class.equals(wrapperType)) {
    1155             return float.class;
    1156         } else if (Long.class.equals(wrapperType)) {
    1157             return long.class;
    1158         } else if (Integer.class.equals(wrapperType)) {
    1159             return int.class;
    1160         } else if (Short.class.equals(wrapperType)) {
    1161             return short.class;
    1162         } else if (Byte.class.equals(wrapperType)) {
    1163             return byte.class;
    1164         } else if (Double.class.equals(wrapperType)) {
    1165             return double.class;
    1166         } else if (Character.class.equals(wrapperType)) {
    1167             return char.class;
    1168         } else {
    1169             Log log = LogFactory.getLog(MethodUtils.class);
    1170             if (log.isDebugEnabled()) {
    1171                 log.debug("Not a known primitive wrapper class: " + wrapperType);
    1172             }
    1173             return null;
    1174         }
    1175     }
    1176
    1177     /***
    1178      * Find a non primitive representation for given primitive class.
    1179      *
    1180      * @param clazz the class to find a representation for, not null
    1181      * @return the original class if it not a primitive. Otherwise the wrapper class. Not null
    1182      */
    1183     public static Class toNonPrimitiveClass(Class clazz) {
    1184         if (clazz.isPrimitive()) {
    1185             Class primitiveClazz = MethodUtils.getPrimitiveWrapper(clazz);
    1186             // the above method returns
    1187             if (primitiveClazz != null) {
    1188                 return primitiveClazz;
    1189             } else {
    1190                 return clazz;
    1191             }
    1192         } else {
    1193             return clazz;
    1194         }
    1195     }
    1196
    1197
    1198     /***
    1199      * Represents the key to looking up a Method by reflection.
    1200      */
    1201     private static class MethodDescriptor {
    1202         private Class cls;
    1203         private String methodName;
    1204         private Class[] paramTypes;
    1205         private boolean exact;
    1206         private int hashCode;
    1207
    1208         /***
    1209          * The sole constructor.
    1210          *
    1211          * @param cls  the class to reflect, must not be null
    1212          * @param methodName  the method name to obtain
    1213          * @param paramTypes the array of classes representing the paramater types
    1214          * @param exact whether the match has to be exact.
    1215          */
    1216         public MethodDescriptor(Class cls, String methodName, Class[] paramTypes, boolean exact) {
    1217             if (cls == null) {
    1218                 throw new IllegalArgumentException("Class cannot be null");
    1219             }
    1220             if (methodName == null) {
    1221                 throw new IllegalArgumentException("Method Name cannot be null");
    1222             }
    1223             if (paramTypes == null) {
    1224                 paramTypes = EMPTY_CLASS_PARAMETERS;
    1225             }
    1226
    1227             this.cls = cls;
    1228             this.methodName = methodName;
    1229             this.paramTypes = paramTypes;
    1230             this.exact= exact;
    1231
    1232             this.hashCode = methodName.length();
    1233         }
    1234         /***
    1235          * Checks for equality.
    1236          * @param obj object to be tested for equality
    1237          * @return true, if the object describes the same Method.
    1238          */
    1239         public boolean equals(Object obj) {
    1240             if (!(obj instanceof MethodDescriptor)) {
    1241                 return false;
    1242             }
    1243             MethodDescriptor md = (MethodDescriptor)obj;
    1244
    1245             return (
    1246                 exact == md.exact &&
    1247                 methodName.equals(md.methodName) &&
    1248                 cls.equals(md.cls) &&
    1249                 java.util.Arrays.equals(paramTypes, md.paramTypes)
    1250             );
    1251         }
    1252         /***
    1253          * Returns the string length of method name. I.e. if the
    1254          * hashcodes are different, the objects are different. If the
    1255          * hashcodes are the same, need to use the equals method to
    1256          * determine equality.
    1257          * @return the string length of method name.
    1258          */
    1259         public int hashCode() {
    1260             return hashCode;
    1261         }
    1262     }
    1263 }