View Javadoc

1   /*
2    * Copyright 2001-2004 The Apache Software Foundation.
3    * 
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * 
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    * 
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */ 
16  
17  package org.apache.commons.beanutils;
18  
19  
20  import java.lang.reflect.InvocationTargetException;
21  import java.lang.reflect.Method;
22  import java.lang.reflect.Modifier;
23  
24  import java.util.WeakHashMap;
25  
26  import org.apache.commons.logging.Log;
27  import org.apache.commons.logging.LogFactory;
28  
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?ois
50   * @author Gregor Ra?man
51   * @author Jan Sorensen
52   * @author Robert Burrell Donkin
53   */
54  
55  public class MethodUtils {
56  
57      // --------------------------------------------------------- Private Methods
58      
59      /**
60       * All logging goes through this logger
61       */
62      private static Log log = LogFactory.getLog(MethodUtils.class);
63      /** Only log warning about accessibility work around once */
64      private static boolean loggedAccessibleWarning = false;
65  
66      /** An empty class array */
67      private static final Class[] emptyClassArray = new Class[0];
68      /** An empty object array */
69      private static final Object[] emptyObjectArray = new Object[0];
70  
71      /**
72       * Stores a cache of Methods against MethodDescriptors, in a WeakHashMap.
73       */
74      private static WeakHashMap cache = new WeakHashMap();
75      
76      // --------------------------------------------------------- Public Methods
77      
78      /**
79       * <p>Invoke a named method whose parameter type matches the object type.</p>
80       *
81       * <p>The behaviour of this method is less deterministic 
82       * than {@link #invokeExactMethod}. 
83       * It loops through all methods with names that match
84       * and then executes the first it finds with compatable parameters.</p>
85       *
86       * <p>This method supports calls to methods taking primitive parameters 
87       * via passing in wrapping classes. So, for example, a <code>Boolean</code> class
88       * would match a <code>boolean</code> primitive.</p>
89       *
90       * <p> This is a convenient wrapper for
91       * {@link #invokeMethod(Object object,String methodName,Object [] args)}.
92       * </p>
93       *
94       * @param object invoke method on this object
95       * @param methodName get method with this name
96       * @param arg use this argument
97       *
98       * @throws NoSuchMethodException if there is no such accessible method
99       * @throws InvocationTargetException wraps an exception thrown by the
100      *  method invoked
101      * @throws IllegalAccessException if the requested method is not accessible
102      *  via reflection
103      */
104     public static Object invokeMethod(
105             Object object,
106             String methodName,
107             Object arg)
108             throws
109             NoSuchMethodException,
110             IllegalAccessException,
111             InvocationTargetException {
112 
113         Object[] args = {arg};
114         return invokeMethod(object, methodName, args);
115 
116     }
117 
118 
119     /**
120      * <p>Invoke a named method whose parameter type matches the object type.</p>
121      *
122      * <p>The behaviour of this method is less deterministic 
123      * than {@link #invokeExactMethod(Object object,String methodName,Object [] args)}. 
124      * It loops through all methods with names that match
125      * and then executes the first it finds with compatable parameters.</p>
126      *
127      * <p>This method supports calls to methods taking primitive parameters 
128      * via passing in wrapping classes. So, for example, a <code>Boolean</code> class
129      * would match a <code>boolean</code> primitive.</p>
130      *
131      * <p> This is a convenient wrapper for
132      * {@link #invokeMethod(Object object,String methodName,Object [] args,Class[] parameterTypes)}.
133      * </p>
134      *
135      * @param object invoke method on this object
136      * @param methodName get method with this name
137      * @param args use these arguments - treat null as empty array
138      *
139      * @throws NoSuchMethodException if there is no such accessible method
140      * @throws InvocationTargetException wraps an exception thrown by the
141      *  method invoked
142      * @throws IllegalAccessException if the requested method is not accessible
143      *  via reflection
144      */
145     public static Object invokeMethod(
146             Object object,
147             String methodName,
148             Object[] args)
149             throws
150             NoSuchMethodException,
151             IllegalAccessException,
152             InvocationTargetException {
153         
154         if (args == null) {
155             args = emptyObjectArray;
156         }  
157         int arguments = args.length;
158         Class parameterTypes [] = new Class[arguments];
159         for (int i = 0; i < arguments; i++) {
160             parameterTypes[i] = args[i].getClass();
161         }
162         return invokeMethod(object, methodName, args, parameterTypes);
163 
164     }
165 
166 
167     /**
168      * <p>Invoke a named method whose parameter type matches the object type.</p>
169      *
170      * <p>The behaviour of this method is less deterministic 
171      * than {@link 
172      * #invokeExactMethod(Object object,String methodName,Object [] args,Class[] parameterTypes)}. 
173      * It loops through all methods with names that match
174      * and then executes the first it finds with compatable parameters.</p>
175      *
176      * <p>This method supports calls to methods taking primitive parameters 
177      * via passing in wrapping classes. So, for example, a <code>Boolean</code> class
178      * would match a <code>boolean</code> primitive.</p>
179      *
180      *
181      * @param object invoke method on this object
182      * @param methodName get method with this name
183      * @param args use these arguments - treat null as empty array
184      * @param parameterTypes match these parameters - treat null as empty array
185      *
186      * @throws NoSuchMethodException if there is no such accessible method
187      * @throws InvocationTargetException wraps an exception thrown by the
188      *  method invoked
189      * @throws IllegalAccessException if the requested method is not accessible
190      *  via reflection
191      */
192     public static Object invokeMethod(
193             Object object,
194             String methodName,
195             Object[] args,
196             Class[] parameterTypes)
197                 throws
198                     NoSuchMethodException,
199                     IllegalAccessException,
200                     InvocationTargetException {
201                     
202         if (parameterTypes == null) {
203             parameterTypes = emptyClassArray;
204         }        
205         if (args == null) {
206             args = emptyObjectArray;
207         }  
208 
209         Method method = getMatchingAccessibleMethod(
210                 object.getClass(),
211                 methodName,
212                 parameterTypes);
213         if (method == null)
214             throw new NoSuchMethodException("No such accessible method: " +
215                     methodName + "() on object: " + object.getClass().getName());
216         return method.invoke(object, args);
217     }
218 
219 
220     /**
221      * <p>Invoke a method whose parameter type matches exactly the object
222      * type.</p>
223      *
224      * <p> This is a convenient wrapper for
225      * {@link #invokeExactMethod(Object object,String methodName,Object [] args)}.
226      * </p>
227      *
228      * @param object invoke method on this object
229      * @param methodName get method with this name
230      * @param arg use this argument
231      *
232      * @throws NoSuchMethodException if there is no such accessible method
233      * @throws InvocationTargetException wraps an exception thrown by the
234      *  method invoked
235      * @throws IllegalAccessException if the requested method is not accessible
236      *  via reflection
237      */
238     public static Object invokeExactMethod(
239             Object object,
240             String methodName,
241             Object arg)
242             throws
243             NoSuchMethodException,
244             IllegalAccessException,
245             InvocationTargetException {
246 
247         Object[] args = {arg};
248         return invokeExactMethod(object, methodName, args);
249 
250     }
251 
252 
253     /**
254      * <p>Invoke a method whose parameter types match exactly the object
255      * types.</p>
256      *
257      * <p> This uses reflection to invoke the method obtained from a call to
258      * {@link #getAccessibleMethod}.</p>
259      *
260      * @param object invoke method on this object
261      * @param methodName get method with this name
262      * @param args use these arguments - treat null as empty array
263      *
264      * @throws NoSuchMethodException if there is no such accessible method
265      * @throws InvocationTargetException wraps an exception thrown by the
266      *  method invoked
267      * @throws IllegalAccessException if the requested method is not accessible
268      *  via reflection
269      */
270     public static Object invokeExactMethod(
271             Object object,
272             String methodName,
273             Object[] args)
274             throws
275             NoSuchMethodException,
276             IllegalAccessException,
277             InvocationTargetException {
278         if (args == null) {
279             args = emptyObjectArray;
280         }  
281         int arguments = args.length;
282         Class parameterTypes [] = new Class[arguments];
283         for (int i = 0; i < arguments; i++) {
284             parameterTypes[i] = args[i].getClass();
285         }
286         return invokeExactMethod(object, methodName, args, parameterTypes);
287 
288     }
289 
290 
291     /**
292      * <p>Invoke a method whose parameter types match exactly the parameter
293      * types given.</p>
294      *
295      * <p>This uses reflection to invoke the method obtained from a call to
296      * {@link #getAccessibleMethod}.</p>
297      *
298      * @param object invoke method on this object
299      * @param methodName get method with this name
300      * @param args use these arguments - treat null as empty array
301      * @param parameterTypes match these parameters - treat null as empty array
302      *
303      * @throws NoSuchMethodException if there is no such accessible method
304      * @throws InvocationTargetException wraps an exception thrown by the
305      *  method invoked
306      * @throws IllegalAccessException if the requested method is not accessible
307      *  via reflection
308      */
309     public static Object invokeExactMethod(
310             Object object,
311             String methodName,
312             Object[] args,
313             Class[] parameterTypes)
314             throws
315             NoSuchMethodException,
316             IllegalAccessException,
317             InvocationTargetException {
318         
319         if (args == null) {
320             args = emptyObjectArray;
321         }  
322                 
323         if (parameterTypes == null) {
324             parameterTypes = emptyClassArray;
325         }
326 
327         Method method = getAccessibleMethod(
328                 object.getClass(),
329                 methodName,
330                 parameterTypes);
331         if (method == null)
332             throw new NoSuchMethodException("No such accessible method: " +
333                     methodName + "() on object: " + object.getClass().getName());
334         return method.invoke(object, args);
335 
336     }
337 
338 
339     /**
340      * <p>Return an accessible method (that is, one that can be invoked via
341      * reflection) with given name and a single parameter.  If no such method
342      * can be found, return <code>null</code>.
343      * Basically, a convenience wrapper that constructs a <code>Class</code>
344      * array for you.</p>
345      *
346      * @param clazz get method from this class
347      * @param methodName get method with this name
348      * @param parameterType taking this type of parameter
349      */
350     public static Method getAccessibleMethod(
351             Class clazz,
352             String methodName,
353             Class parameterType) {
354 
355         Class[] parameterTypes = {parameterType};
356         return getAccessibleMethod(clazz, methodName, parameterTypes);
357 
358     }
359 
360 
361     /**
362      * <p>Return an accessible method (that is, one that can be invoked via
363      * reflection) with given name and parameters.  If no such method
364      * can be found, return <code>null</code>.
365      * This is just a convenient wrapper for
366      * {@link #getAccessibleMethod(Method method)}.</p>
367      *
368      * @param clazz get method from this class
369      * @param methodName get method with this name
370      * @param parameterTypes with these parameters types
371      */
372     public static Method getAccessibleMethod(
373             Class clazz,
374             String methodName,
375             Class[] parameterTypes) {
376 
377         try {
378             MethodDescriptor md = new MethodDescriptor(clazz, methodName, parameterTypes, true);
379             // Check the cache first
380             Method method = (Method)cache.get(md);
381             if (method != null) {
382                 return method;
383             }
384             
385             method =  getAccessibleMethod
386                     (clazz.getMethod(methodName, parameterTypes));
387             cache.put(md, method);
388             return method;
389         } catch (NoSuchMethodException e) {
390             return (null);
391         }
392 
393     }
394 
395 
396     /**
397      * <p>Return an accessible method (that is, one that can be invoked via
398      * reflection) that implements the specified Method.  If no such method
399      * can be found, return <code>null</code>.</p>
400      *
401      * @param method The method that we wish to call
402      */
403     public static Method getAccessibleMethod(Method method) {
404 
405         // Make sure we have a method to check
406         if (method == null) {
407             return (null);
408         }
409 
410         // If the requested method is not public we cannot call it
411         if (!Modifier.isPublic(method.getModifiers())) {
412             return (null);
413         }
414 
415         // If the declaring class is public, we are done
416         Class clazz = method.getDeclaringClass();
417         if (Modifier.isPublic(clazz.getModifiers())) {
418             return (method);
419         }
420 
421         // Check the implemented interfaces and subinterfaces
422         method =
423                 getAccessibleMethodFromInterfaceNest(clazz,
424                         method.getName(),
425                         method.getParameterTypes());
426         return (method);
427 
428     }
429 
430 
431     // -------------------------------------------------------- Private Methods
432 
433     /**
434      * <p>Return an accessible method (that is, one that can be invoked via
435      * reflection) that implements the specified method, by scanning through
436      * all implemented interfaces and subinterfaces.  If no such method
437      * can be found, return <code>null</code>.</p>
438      *
439      * <p> There isn't any good reason why this method must be private.
440      * It is because there doesn't seem any reason why other classes should
441      * call this rather than the higher level methods.</p>
442      *
443      * @param clazz Parent class for the interfaces to be checked
444      * @param methodName Method name of the method we wish to call
445      * @param parameterTypes The parameter type signatures
446      */
447     private static Method getAccessibleMethodFromInterfaceNest
448             (Class clazz, String methodName, Class parameterTypes[]) {
449 
450         Method method = null;
451 
452         // Search up the superclass chain
453         for (; clazz != null; clazz = clazz.getSuperclass()) {
454 
455             // Check the implemented interfaces of the parent class
456             Class interfaces[] = clazz.getInterfaces();
457             for (int i = 0; i < interfaces.length; i++) {
458 
459                 // Is this interface public?
460                 if (!Modifier.isPublic(interfaces[i].getModifiers()))
461                     continue;
462 
463                 // Does the method exist on this interface?
464                 try {
465                     method = interfaces[i].getDeclaredMethod(methodName,
466                             parameterTypes);
467                 } catch (NoSuchMethodException e) {
468                     ;
469                 }
470                 if (method != null)
471                     break;
472 
473                 // Recursively check our parent interfaces
474                 method =
475                         getAccessibleMethodFromInterfaceNest(interfaces[i],
476                                 methodName,
477                                 parameterTypes);
478                 if (method != null)
479                     break;
480 
481             }
482 
483         }
484 
485         // If we found a method return it
486         if (method != null)
487             return (method);
488 
489         // We did not find anything
490         return (null);
491 
492     }
493 
494     /**
495      * <p>Find an accessible method that matches the given name and has compatible parameters.
496      * Compatible parameters mean that every method parameter is assignable from 
497      * the given parameters.
498      * In other words, it finds a method with the given name 
499      * that will take the parameters given.<p>
500      *
501      * <p>This method is slightly undeterminstic since it loops 
502      * through methods names and return the first matching method.</p>
503      * 
504      * <p>This method is used by 
505      * {@link 
506      * #invokeMethod(Object object,String methodName,Object [] args,Class[] parameterTypes)}.
507      *
508      * <p>This method can match primitive parameter by passing in wrapper classes.
509      * For example, a <code>Boolean</code> will match a primitive <code>boolean</code>
510      * parameter.
511      *
512      * @param clazz find method in this class
513      * @param methodName find method with this name
514      * @param parameterTypes find method with compatible parameters 
515      */
516     public static Method getMatchingAccessibleMethod(
517                                                 Class clazz,
518                                                 String methodName,
519                                                 Class[] parameterTypes) {
520         // trace logging
521         if (log.isTraceEnabled()) {
522             log.trace("Matching name=" + methodName + " on " + clazz);
523         }
524         MethodDescriptor md = new MethodDescriptor(clazz, methodName, parameterTypes, false);
525         
526         // see if we can find the method directly
527         // most of the time this works and it's much faster
528         try {
529             // Check the cache first
530             Method method = (Method)cache.get(md);
531             if (method != null) {
532                 return method;
533             }
534 
535             method = clazz.getMethod(methodName, parameterTypes);
536             if (log.isTraceEnabled()) {
537                 log.trace("Found straight match: " + method);
538                 log.trace("isPublic:" + Modifier.isPublic(method.getModifiers()));
539             }
540             
541             try {
542                 //
543                 // XXX Default access superclass workaround
544                 //
545                 // When a public class has a default access superclass
546                 // with public methods, these methods are accessible.
547                 // Calling them from compiled code works fine.
548                 //
549                 // Unfortunately, using reflection to invoke these methods
550                 // seems to (wrongly) to prevent access even when the method
551                 // modifer is public.
552                 //
553                 // The following workaround solves the problem but will only
554                 // work from sufficiently privilages code. 
555                 //
556                 // Better workarounds would be greatfully accepted.
557                 //
558                 method.setAccessible(true);
559                 
560             } catch (SecurityException se) {
561                 // log but continue just in case the method.invoke works anyway
562                 if (!loggedAccessibleWarning) {
563                     boolean vunerableJVM = false;
564                     try {
565                         String specVersion = System.getProperty("java.specification.version");
566                         if (specVersion.charAt(0) == '1' && 
567                                 (specVersion.charAt(0) == '0' ||
568                                  specVersion.charAt(0) == '1' ||
569                                  specVersion.charAt(0) == '2' ||
570                                  specVersion.charAt(0) == '3')) {
571                                  
572                             vunerableJVM = true;
573                         }
574                     } catch (SecurityException e) {
575                         // don't know - so display warning
576                         vunerableJVM = true;
577                     }
578                     if (vunerableJVM) {
579                         log.warn(
580                             "Current Security Manager restricts use of workarounds for reflection bugs "
581                             + " in pre-1.4 JVMs.");
582                     }
583                     loggedAccessibleWarning = true;
584                 }
585                 log.debug(
586                         "Cannot setAccessible on method. Therefore cannot use jvm access bug workaround.", 
587                         se);
588             }
589             cache.put(md, method);
590             return method;
591             
592         } catch (NoSuchMethodException e) { /* SWALLOW */ }
593         
594         // search through all methods 
595         int paramSize = parameterTypes.length;
596         Method[] methods = clazz.getMethods();
597         for (int i = 0, size = methods.length; i < size ; i++) {
598             if (methods[i].getName().equals(methodName)) {	
599                 // log some trace information
600                 if (log.isTraceEnabled()) {
601                     log.trace("Found matching name:");
602                     log.trace(methods[i]);
603                 }                
604                 
605                 // compare parameters
606                 Class[] methodsParams = methods[i].getParameterTypes();
607                 int methodParamSize = methodsParams.length;
608                 if (methodParamSize == paramSize) {          
609                     boolean match = true;
610                     for (int n = 0 ; n < methodParamSize; n++) {
611                         if (log.isTraceEnabled()) {
612                             log.trace("Param=" + parameterTypes[n].getName());
613                             log.trace("Method=" + methodsParams[n].getName());
614                         }
615                         if (!isAssignmentCompatible(methodsParams[n], parameterTypes[n])) {
616                             if (log.isTraceEnabled()) {
617                                 log.trace(methodsParams[n] + " is not assignable from " 
618                                             + parameterTypes[n]);
619                             }    
620                             match = false;
621                             break;
622                         }
623                     }
624                     
625                     if (match) {
626                         // get accessible version of method
627                         Method method = getAccessibleMethod(methods[i]);
628                         if (method != null) {
629                             if (log.isTraceEnabled()) {
630                                 log.trace(method + " accessible version of " 
631                                             + methods[i]);
632                             }
633                             try {
634                                 //
635                                 // XXX Default access superclass workaround
636                                 // (See above for more details.)
637                                 //
638                                 method.setAccessible(true);
639                                 
640                             } catch (SecurityException se) {
641                                 // log but continue just in case the method.invoke works anyway
642                                 if (!loggedAccessibleWarning) {
643                                     log.warn(
644             "Cannot use JVM pre-1.4 access bug workaround due to restrictive security manager.");
645                                     loggedAccessibleWarning = true;
646                                 }
647                                 log.debug(
648             "Cannot setAccessible on method. Therefore cannot use jvm access bug workaround.", 
649                                         se);
650                             }
651                             cache.put(md, method);
652                             return method;
653                         }
654                         
655                         log.trace("Couldn't find accessible method.");
656                     }
657                 }
658             }
659         }
660         
661         // didn't find a match
662         log.trace("No match found.");
663         return null;                                        
664     }
665 
666     /**
667      * <p>Determine whether a type can be used as a parameter in a method invocation.
668      * This method handles primitive conversions correctly.</p>
669      *
670      * <p>In order words, it will match a <code>Boolean</code> to a <code>boolean</code>,
671      * a <code>Long</code> to a <code>long</code>,
672      * a <code>Float</code> to a <code>float</code>,
673      * a <code>Integer</code> to a <code>int</code>,
674      * and a <code>Double</code> to a <code>double</code>.
675      * Now logic widening matches are allowed.
676      * For example, a <code>Long</code> will not match a <code>int</code>.
677      *
678      * @param parameterType the type of parameter accepted by the method
679      * @param parameterization the type of parameter being tested 
680      *
681      * @return true if the assignement is compatible.
682      */
683     public static final boolean isAssignmentCompatible(Class parameterType, Class parameterization) {
684         // try plain assignment
685         if (parameterType.isAssignableFrom(parameterization)) {
686             return true;
687         }
688         
689         if (parameterType.isPrimitive()) {
690             // this method does *not* do widening - you must specify exactly
691             // is this the right behaviour?
692             Class parameterWrapperClazz = getPrimitiveWrapper(parameterType);
693             if (parameterWrapperClazz != null) {
694                 return parameterWrapperClazz.equals(parameterization);
695             }
696         }
697         
698         return false;
699     }
700     
701     /**
702      * Gets the wrapper object class for the given primitive type class.
703      * For example, passing <code>boolean.class</code> returns <code>Boolean.class</code>
704      * @param primitiveType the primitive type class for which a match is to be found
705      * @return the wrapper type associated with the given primitive 
706      * or null if no match is found
707      */
708     public static Class getPrimitiveWrapper(Class primitiveType) {
709         // does anyone know a better strategy than comparing names?
710         if (boolean.class.equals(primitiveType)) {
711             return Boolean.class;
712         } else if (float.class.equals(primitiveType)) {
713             return Float.class;
714         } else if (long.class.equals(primitiveType)) {
715             return Long.class;
716         } else if (int.class.equals(primitiveType)) {
717             return Integer.class;
718         } else if (short.class.equals(primitiveType)) {
719             return Short.class;
720         } else if (byte.class.equals(primitiveType)) {
721             return Byte.class;
722         } else if (double.class.equals(primitiveType)) {
723             return Double.class;
724         } else if (char.class.equals(primitiveType)) {
725             return Character.class;
726         } else {
727             
728             return null;
729         }
730     }
731 
732     /**
733      * Gets the class for the primitive type corresponding to the primitive wrapper class given.
734      * For example, an instance of <code>Boolean.class</code> returns a <code>boolean.class</code>. 
735      * @param wrapperType the 
736      * @return the primitive type class corresponding to the given wrapper class,
737      * null if no match is found
738      */
739     public static Class getPrimitiveType(Class wrapperType) {
740         // does anyone know a better strategy than comparing names?
741         if (Boolean.class.equals(wrapperType)) {
742             return boolean.class;
743         } else if (Float.class.equals(wrapperType)) {
744             return float.class;
745         } else if (Long.class.equals(wrapperType)) {
746             return long.class;
747         } else if (Integer.class.equals(wrapperType)) {
748             return int.class;
749         } else if (Short.class.equals(wrapperType)) {
750             return short.class;
751         } else if (Byte.class.equals(wrapperType)) {
752             return byte.class;
753         } else if (Double.class.equals(wrapperType)) {
754             return double.class;
755         } else if (Character.class.equals(wrapperType)) {
756             return char.class;
757         } else {
758             if (log.isDebugEnabled()) {
759                 log.debug("Not a known primitive wrapper class: " + wrapperType);
760             }
761             return null;
762         }
763     }
764     
765     /**
766      * Find a non primitive representation for given primitive class.
767      *
768      * @param clazz the class to find a representation for, not null
769      * @return the original class if it not a primitive. Otherwise the wrapper class. Not null
770      */
771     public static Class toNonPrimitiveClass(Class clazz) {
772         if (clazz.isPrimitive()) {
773             Class primitiveClazz = MethodUtils.getPrimitiveWrapper(clazz);
774             // the above method returns 
775             if (primitiveClazz != null) {
776                 return primitiveClazz;
777             } else {
778                 return clazz;
779             }
780         } else {
781             return clazz;
782         }
783     }
784     
785 
786     /**
787      * Represents the key to looking up a Method by reflection.
788      */
789     private static class MethodDescriptor {
790         private Class cls;
791         private String methodName;
792         private Class[] paramTypes;
793         private boolean exact;
794         private int hashCode;
795 
796         /**
797          * The sole constructor.
798          *
799          * @param cls  the class to reflect, must not be null
800          * @param methodName  the method name to obtain
801          * @param paramTypes the array of classes representing the paramater types
802          * @param exact whether the match has to be exact.
803          */
804         public MethodDescriptor(Class cls, String methodName, Class[] paramTypes, boolean exact) {
805             if (cls == null) {
806                 throw new IllegalArgumentException("Class cannot be null");
807             }
808             if (methodName == null) {
809                 throw new IllegalArgumentException("Method Name cannot be null");
810             }
811             if (paramTypes == null) {
812                 paramTypes = emptyClassArray;
813             }
814 
815             this.cls = cls;
816             this.methodName = methodName;
817             this.paramTypes = paramTypes;
818             this.exact= exact;
819 
820             this.hashCode = methodName.length();
821         }
822         /**
823          * Checks for equality.
824          * @param obj object to be tested for equality
825          * @return true, if the object describes the same Method.
826          */
827         public boolean equals(Object obj) {
828             if (!(obj instanceof MethodDescriptor)) {
829                 return false;
830             }
831             MethodDescriptor md = (MethodDescriptor)obj;
832 
833             return (
834                 exact == md.exact &&
835                 methodName.equals(md.methodName) &&
836                 cls.equals(md.cls) &&
837                 java.util.Arrays.equals(paramTypes, md.paramTypes)
838             );
839         }
840         /**
841          * Returns the string length of method name. I.e. if the
842          * hashcodes are different, the objects are different. If the
843          * hashcodes are the same, need to use the equals method to
844          * determine equality.
845          * @return the string length of method name.
846          */
847         public int hashCode() {
848             return hashCode;
849         }
850     }
851 }