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 }