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

Save This Page
Home » commons-beanutils-1.8.3-src » org.apache.commons.beanutils » [javadoc | source]