1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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ç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 * 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
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
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
707 if (method == null) {
708 return (null);
709 }
710
711
712 if (!Modifier.isPublic(method.getModifiers())) {
713 return (null);
714 }
715
716
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
726 method =
727 getAccessibleMethodFromInterfaceNest(clazz,
728 methodName,
729 parameterTypes);
730
731
732 if (method == null) {
733 method = getAccessibleMethodFromSuperclass(clazz,
734 methodName,
735 parameterTypes);
736 }
737
738 return (method);
739
740 }
741
742
743
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
791 for (; clazz != null; clazz = clazz.getSuperclass()) {
792
793
794 Class[] interfaces = clazz.getInterfaces();
795 for (int i = 0; i < interfaces.length; i++) {
796
797
798 if (!Modifier.isPublic(interfaces[i].getModifiers())) {
799 continue;
800 }
801
802
803 try {
804 method = interfaces[i].getDeclaredMethod(methodName,
805 parameterTypes);
806 } catch (NoSuchMethodException e) {
807
808
809
810 }
811 if (method != null) {
812 break;
813 }
814
815
816 method =
817 getAccessibleMethodFromInterfaceNest(interfaces[i],
818 methodName,
819 parameterTypes);
820 if (method != null) {
821 break;
822 }
823
824 }
825
826 }
827
828
829 if (method != null) {
830 return (method);
831 }
832
833
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
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
873
874 try {
875
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
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904 method.setAccessible(true);
905
906 } catch (SecurityException se) {
907
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
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) {
939
940
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
949 if (log.isTraceEnabled()) {
950 log.trace("Found matching name:");
951 log.trace(methods[i]);
952 }
953
954
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
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
985
986
987 method.setAccessible(true);
988
989 } catch (SecurityException se) {
990
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
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
1055
1056
1057
1058 cost += 0.25f;
1059 break;
1060 }
1061 cost++;
1062 destClass = destClass.getSuperclass();
1063 }
1064
1065
1066
1067
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
1096 if (parameterType.isAssignableFrom(parameterization)) {
1097 return true;
1098 }
1099
1100 if (parameterType.isPrimitive()) {
1101
1102
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
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
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
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 }