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