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 // If we found a method return it
911 if (method != null) {
912 return (method);
913 }
914
915 // We did not find anything
916 return (null);
917
918 }
919
920 /**
921 * <p>Find an accessible method that matches the given name and has compatible parameters.
922 * Compatible parameters mean that every method parameter is assignable from
923 * the given parameters.
924 * In other words, it finds a method with the given name
925 * that will take the parameters given.<p>
926 *
927 * <p>This method is slightly undeterminstic since it loops
928 * through methods names and return the first matching method.</p>
929 *
930 * <p>This method is used by
931 * {@link
932 * #invokeMethod(Object object,String methodName,Object [] args,Class[] parameterTypes)}.
933 *
934 * <p>This method can match primitive parameter by passing in wrapper classes.
935 * For example, a <code>Boolean</code> will match a primitive <code>boolean</code>
936 * parameter.
937 *
938 * @param clazz find method in this class
939 * @param methodName find method with this name
940 * @param parameterTypes find method with compatible parameters
941 * @return The accessible method
942 */
943 public static Method getMatchingAccessibleMethod(
944 Class clazz,
945 String methodName,
946 Class[] parameterTypes) {
947 // trace logging
948 Log log = LogFactory.getLog(MethodUtils.class);
949 if (log.isTraceEnabled()) {
950 log.trace("Matching name=" + methodName + " on " + clazz);
951 }
952 MethodDescriptor md = new MethodDescriptor(clazz, methodName, parameterTypes, false);
953
954 // see if we can find the method directly
955 // most of the time this works and it's much faster
956 try {
957 // Check the cache first
958 Method method = getCachedMethod(md);
959 if (method != null) {
960 return method;
961 }
962
963 method = clazz.getMethod(methodName, parameterTypes);
964 if (log.isTraceEnabled()) {
965 log.trace("Found straight match: " + method);
966 log.trace("isPublic:" + Modifier.isPublic(method.getModifiers()));
967 }
968
969 setMethodAccessible(method); // Default access superclass workaround
970
971 cacheMethod(md, method);
972 return method;
973
974 } catch (NoSuchMethodException e) { /* SWALLOW */ }
975
976 // search through all methods
977 int paramSize = parameterTypes.length;
978 Method bestMatch = null;
979 Method[] methods = clazz.getMethods();
980 float bestMatchCost = Float.MAX_VALUE;
981 float myCost = Float.MAX_VALUE;
982 for (int i = 0, size = methods.length; i < size ; i++) {
983 if (methods[i].getName().equals(methodName)) {
984 // log some trace information
985 if (log.isTraceEnabled()) {
986 log.trace("Found matching name:");
987 log.trace(methods[i]);
988 }
989
990 // compare parameters
991 Class[] methodsParams = methods[i].getParameterTypes();
992 int methodParamSize = methodsParams.length;
993 if (methodParamSize == paramSize) {
994 boolean match = true;
995 for (int n = 0 ; n < methodParamSize; n++) {
996 if (log.isTraceEnabled()) {
997 log.trace("Param=" + parameterTypes[n].getName());
998 log.trace("Method=" + methodsParams[n].getName());
999 }
1000 if (!isAssignmentCompatible(methodsParams[n], parameterTypes[n])) {
1001 if (log.isTraceEnabled()) {
1002 log.trace(methodsParams[n] + " is not assignable from "
1003 + parameterTypes[n]);
1004 }
1005 match = false;
1006 break;
1007 }
1008 }
1009
1010 if (match) {
1011 // get accessible version of method
1012 Method method = getAccessibleMethod(clazz, methods[i]);
1013 if (method != null) {
1014 if (log.isTraceEnabled()) {
1015 log.trace(method + " accessible version of "
1016 + methods[i]);
1017 }
1018 setMethodAccessible(method); // Default access superclass workaround
1019 myCost = getTotalTransformationCost(parameterTypes,method.getParameterTypes());
1020 if ( myCost < bestMatchCost ) {
1021 bestMatch = method;
1022 bestMatchCost = myCost;
1023 }
1024 }
1025
1026 log.trace("Couldn't find accessible method.");
1027 }
1028 }
1029 }
1030 }
1031 if ( bestMatch != null ){
1032 cacheMethod(md, bestMatch);
1033 } else {
1034 // didn't find a match
1035 log.trace("No match found.");
1036 }
1037
1038 return bestMatch;
1039 }
1040
1041 /**
1042 * Try to make the method accessible
1043 * @param method The source arguments
1044 */
1045 private static void setMethodAccessible(Method method) {
1046 try {
1047 //
1048 // XXX Default access superclass workaround
1049 //
1050 // When a public class has a default access superclass
1051 // with public methods, these methods are accessible.
1052 // Calling them from compiled code works fine.
1053 //
1054 // Unfortunately, using reflection to invoke these methods
1055 // seems to (wrongly) to prevent access even when the method
1056 // modifer is public.
1057 //
1058 // The following workaround solves the problem but will only
1059 // work from sufficiently privilages code.
1060 //
1061 // Better workarounds would be greatfully accepted.
1062 //
1063 method.setAccessible(true);
1064
1065 } catch (SecurityException se) {
1066 // log but continue just in case the method.invoke works anyway
1067 Log log = LogFactory.getLog(MethodUtils.class);
1068 if (!loggedAccessibleWarning) {
1069 boolean vulnerableJVM = false;
1070 try {
1071 String specVersion = System.getProperty("java.specification.version");
1072 if (specVersion.charAt(0) == '1' &&
1073 (specVersion.charAt(2) == '0' ||
1074 specVersion.charAt(2) == '1' ||
1075 specVersion.charAt(2) == '2' ||
1076 specVersion.charAt(2) == '3')) {
1077
1078 vulnerableJVM = true;
1079 }
1080 } catch (SecurityException e) {
1081 // don't know - so display warning
1082 vulnerableJVM = true;
1083 }
1084 if (vulnerableJVM) {
1085 log.warn(
1086 "Current Security Manager restricts use of workarounds for reflection bugs "
1087 + " in pre-1.4 JVMs.");
1088 }
1089 loggedAccessibleWarning = true;
1090 }
1091 log.debug("Cannot setAccessible on method. Therefore cannot use jvm access bug workaround.", se);
1092 }
1093 }
1094
1095 /**
1096 * Returns the sum of the object transformation cost for each class in the source
1097 * argument list.
1098 * @param srcArgs The source arguments
1099 * @param destArgs The destination arguments
1100 * @return The total transformation cost
1101 */
1102 private static float getTotalTransformationCost(Class[] srcArgs, Class[] destArgs) {
1103
1104 float totalCost = 0.0f;
1105 for (int i = 0; i < srcArgs.length; i++) {
1106 Class srcClass, destClass;
1107 srcClass = srcArgs[i];
1108 destClass = destArgs[i];
1109 totalCost += getObjectTransformationCost(srcClass, destClass);
1110 }
1111
1112 return totalCost;
1113 }
1114
1115 /**
1116 * Gets the number of steps required needed to turn the source class into the
1117 * destination class. This represents the number of steps in the object hierarchy
1118 * graph.
1119 * @param srcClass The source class
1120 * @param destClass The destination class
1121 * @return The cost of transforming an object
1122 */
1123 private static float getObjectTransformationCost(Class srcClass, Class destClass) {
1124 float cost = 0.0f;
1125 while (destClass != null && !destClass.equals(srcClass)) {
1126 if (destClass.isInterface() && isAssignmentCompatible(destClass,srcClass)) {
1127 // slight penalty for interface match.
1128 // we still want an exact match to override an interface match, but
1129 // an interface match should override anything where we have to get a
1130 // superclass.
1131 cost += 0.25f;
1132 break;
1133 }
1134 cost++;
1135 destClass = destClass.getSuperclass();
1136 }
1137
1138 /*
1139 * If the destination class is null, we've travelled all the way up to
1140 * an Object match. We'll penalize this by adding 1.5 to the cost.
1141 */
1142 if (destClass == null) {
1143 cost += 1.5f;
1144 }
1145
1146 return cost;
1147 }
1148
1149
1150 /**
1151 * <p>Determine whether a type can be used as a parameter in a method invocation.
1152 * This method handles primitive conversions correctly.</p>
1153 *
1154 * <p>In order words, it will match a <code>Boolean</code> to a <code>boolean</code>,
1155 * a <code>Long</code> to a <code>long</code>,
1156 * a <code>Float</code> to a <code>float</code>,
1157 * a <code>Integer</code> to a <code>int</code>,
1158 * and a <code>Double</code> to a <code>double</code>.
1159 * Now logic widening matches are allowed.
1160 * For example, a <code>Long</code> will not match a <code>int</code>.
1161 *
1162 * @param parameterType the type of parameter accepted by the method
1163 * @param parameterization the type of parameter being tested
1164 *
1165 * @return true if the assignement is compatible.
1166 */
1167 public static final boolean isAssignmentCompatible(Class parameterType, Class parameterization) {
1168 // try plain assignment
1169 if (parameterType.isAssignableFrom(parameterization)) {
1170 return true;
1171 }
1172
1173 if (parameterType.isPrimitive()) {
1174 // this method does *not* do widening - you must specify exactly
1175 // is this the right behaviour?
1176 Class parameterWrapperClazz = getPrimitiveWrapper(parameterType);
1177 if (parameterWrapperClazz != null) {
1178 return parameterWrapperClazz.equals(parameterization);
1179 }
1180 }
1181
1182 return false;
1183 }
1184
1185 /**
1186 * Gets the wrapper object class for the given primitive type class.
1187 * For example, passing <code>boolean.class</code> returns <code>Boolean.class</code>
1188 * @param primitiveType the primitive type class for which a match is to be found
1189 * @return the wrapper type associated with the given primitive
1190 * or null if no match is found
1191 */
1192 public static Class getPrimitiveWrapper(Class primitiveType) {
1193 // does anyone know a better strategy than comparing names?
1194 if (boolean.class.equals(primitiveType)) {
1195 return Boolean.class;
1196 } else if (float.class.equals(primitiveType)) {
1197 return Float.class;
1198 } else if (long.class.equals(primitiveType)) {
1199 return Long.class;
1200 } else if (int.class.equals(primitiveType)) {
1201 return Integer.class;
1202 } else if (short.class.equals(primitiveType)) {
1203 return Short.class;
1204 } else if (byte.class.equals(primitiveType)) {
1205 return Byte.class;
1206 } else if (double.class.equals(primitiveType)) {
1207 return Double.class;
1208 } else if (char.class.equals(primitiveType)) {
1209 return Character.class;
1210 } else {
1211
1212 return null;
1213 }
1214 }
1215
1216 /**
1217 * Gets the class for the primitive type corresponding to the primitive wrapper class given.
1218 * For example, an instance of <code>Boolean.class</code> returns a <code>boolean.class</code>.
1219 * @param wrapperType the
1220 * @return the primitive type class corresponding to the given wrapper class,
1221 * null if no match is found
1222 */
1223 public static Class getPrimitiveType(Class wrapperType) {
1224 // does anyone know a better strategy than comparing names?
1225 if (Boolean.class.equals(wrapperType)) {
1226 return boolean.class;
1227 } else if (Float.class.equals(wrapperType)) {
1228 return float.class;
1229 } else if (Long.class.equals(wrapperType)) {
1230 return long.class;
1231 } else if (Integer.class.equals(wrapperType)) {
1232 return int.class;
1233 } else if (Short.class.equals(wrapperType)) {
1234 return short.class;
1235 } else if (Byte.class.equals(wrapperType)) {
1236 return byte.class;
1237 } else if (Double.class.equals(wrapperType)) {
1238 return double.class;
1239 } else if (Character.class.equals(wrapperType)) {
1240 return char.class;
1241 } else {
1242 Log log = LogFactory.getLog(MethodUtils.class);
1243 if (log.isDebugEnabled()) {
1244 log.debug("Not a known primitive wrapper class: " + wrapperType);
1245 }
1246 return null;
1247 }
1248 }
1249
1250 /**
1251 * Find a non primitive representation for given primitive class.
1252 *
1253 * @param clazz the class to find a representation for, not null
1254 * @return the original class if it not a primitive. Otherwise the wrapper class. Not null
1255 */
1256 public static Class toNonPrimitiveClass(Class clazz) {
1257 if (clazz.isPrimitive()) {
1258 Class primitiveClazz = MethodUtils.getPrimitiveWrapper(clazz);
1259 // the above method returns
1260 if (primitiveClazz != null) {
1261 return primitiveClazz;
1262 } else {
1263 return clazz;
1264 }
1265 } else {
1266 return clazz;
1267 }
1268 }
1269
1270
1271 /**
1272 * Return the method from the cache, if present.
1273 *
1274 * @param md The method descriptor
1275 * @return The cached method
1276 */
1277 private static Method getCachedMethod(MethodDescriptor md) {
1278 if (CACHE_METHODS) {
1279 Reference methodRef = (Reference)cache.get(md);
1280 if (methodRef != null) {
1281 return (Method)methodRef.get();
1282 }
1283 }
1284 return null;
1285 }
1286
1287 /**
1288 * Add a method to the cache.
1289 *
1290 * @param md The method descriptor
1291 * @param method The method to cache
1292 */
1293 private static void cacheMethod(MethodDescriptor md, Method method) {
1294 if (CACHE_METHODS) {
1295 if (method != null) {
1296 cache.put(md, new WeakReference(method));
1297 }
1298 }
1299 }
1300
1301 /**
1302 * Represents the key to looking up a Method by reflection.
1303 */
1304 private static class MethodDescriptor {
1305 private Class cls;
1306 private String methodName;
1307 private Class[] paramTypes;
1308 private boolean exact;
1309 private int hashCode;
1310
1311 /**
1312 * The sole constructor.
1313 *
1314 * @param cls the class to reflect, must not be null
1315 * @param methodName the method name to obtain
1316 * @param paramTypes the array of classes representing the paramater types
1317 * @param exact whether the match has to be exact.
1318 */
1319 public MethodDescriptor(Class cls, String methodName, Class[] paramTypes, boolean exact) {
1320 if (cls == null) {
1321 throw new IllegalArgumentException("Class cannot be null");
1322 }
1323 if (methodName == null) {
1324 throw new IllegalArgumentException("Method Name cannot be null");
1325 }
1326 if (paramTypes == null) {
1327 paramTypes = EMPTY_CLASS_PARAMETERS;
1328 }
1329
1330 this.cls = cls;
1331 this.methodName = methodName;
1332 this.paramTypes = paramTypes;
1333 this.exact= exact;
1334
1335 this.hashCode = methodName.length();
1336 }
1337 /**
1338 * Checks for equality.
1339 * @param obj object to be tested for equality
1340 * @return true, if the object describes the same Method.
1341 */
1342 public boolean equals(Object obj) {
1343 if (!(obj instanceof MethodDescriptor)) {
1344 return false;
1345 }
1346 MethodDescriptor md = (MethodDescriptor)obj;
1347
1348 return (
1349 exact == md.exact &&
1350 methodName.equals(md.methodName) &&
1351 cls.equals(md.cls) &&
1352 java.util.Arrays.equals(paramTypes, md.paramTypes)
1353 );
1354 }
1355 /**
1356 * Returns the string length of method name. I.e. if the
1357 * hashcodes are different, the objects are different. If the
1358 * hashcodes are the same, need to use the equals method to
1359 * determine equality.
1360 * @return the string length of method name.
1361 */
1362 public int hashCode() {
1363 return hashCode;
1364 }
1365 }
1366 }