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