1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
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
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
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
406 if (method == null) {
407 return (null);
408 }
409
410
411 if (!Modifier.isPublic(method.getModifiers())) {
412 return (null);
413 }
414
415
416 Class clazz = method.getDeclaringClass();
417 if (Modifier.isPublic(clazz.getModifiers())) {
418 return (method);
419 }
420
421
422 method =
423 getAccessibleMethodFromInterfaceNest(clazz,
424 method.getName(),
425 method.getParameterTypes());
426 return (method);
427
428 }
429
430
431
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
453 for (; clazz != null; clazz = clazz.getSuperclass()) {
454
455
456 Class interfaces[] = clazz.getInterfaces();
457 for (int i = 0; i < interfaces.length; i++) {
458
459
460 if (!Modifier.isPublic(interfaces[i].getModifiers()))
461 continue;
462
463
464 try {
465 method = interfaces[i].getDeclaredMethod(methodName,
466 parameterTypes);
467 } catch (NoSuchMethodException e) {
468 ;
469 }
470 if (method != null)
471 break;
472
473
474 method =
475 getAccessibleMethodFromInterfaceNest(interfaces[i],
476 methodName,
477 parameterTypes);
478 if (method != null)
479 break;
480
481 }
482
483 }
484
485
486 if (method != null)
487 return (method);
488
489
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
521 if (log.isTraceEnabled()) {
522 log.trace("Matching name=" + methodName + " on " + clazz);
523 }
524 MethodDescriptor md = new MethodDescriptor(clazz, methodName, parameterTypes, false);
525
526
527
528 try {
529
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
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558 method.setAccessible(true);
559
560 } catch (SecurityException se) {
561
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
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) {
593
594
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
600 if (log.isTraceEnabled()) {
601 log.trace("Found matching name:");
602 log.trace(methods[i]);
603 }
604
605
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
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
636
637
638 method.setAccessible(true);
639
640 } catch (SecurityException se) {
641
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
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
685 if (parameterType.isAssignableFrom(parameterization)) {
686 return true;
687 }
688
689 if (parameterType.isPrimitive()) {
690
691
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
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
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
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 }