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