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&#231;ois
050     * @author Gregor Ra&#253;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    }