001: /*
002: * The Apache Software License, Version 1.1
003: *
004: * Copyright (c) 2001-2003 The Apache Software Foundation. All rights
005: * reserved.
006: *
007: * Redistribution and use in source and binary forms, with or without
008: * modification, are permitted provided that the following conditions
009: * are met:
010: *
011: * 1. Redistributions of source code must retain the above copyright
012: * notice, this list of conditions and the following disclaimer.
013: *
014: * 2. Redistributions in binary form must reproduce the above copyright
015: * notice, this list of conditions and the following disclaimer in
016: * the documentation and/or other materials provided with the
017: * distribution.
018: *
019: * 3. The end-user documentation included with the redistribution,
020: * if any, must include the following acknowledgement:
021: * "This product includes software developed by the
022: * Apache Software Foundation (http://www.apache.org/)."
023: * Alternately, this acknowledgement may appear in the software itself,
024: * if and wherever such third-party acknowledgements normally appear.
025: *
026: * 4. The names "Apache", "The Jakarta Project", "Commons", and "Apache Software
027: * Foundation" must not be used to endorse or promote products derived
028: * from this software without prior written permission. For written
029: * permission, please contact apache@apache.org.
030: *
031: * 5. Products derived from this software may not be called "Apache",
032: * "Apache" nor may "Apache" appear in their names without prior
033: * written permission of the Apache Software Foundation.
034: *
035: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
036: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
037: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
038: * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
039: * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
040: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
041: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
042: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
043: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
044: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
045: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
046: * SUCH DAMAGE.
047: * ====================================================================
048: *
049: * This software consists of voluntary contributions made by many
050: * individuals on behalf of the Apache Software Foundation. For more
051: * information on the Apache Software Foundation, please see
052: * <http://www.apache.org/>.
053: *
054: */
055:
056: package com.opensymphony.workflow.designer.beanutils;
057:
058: import java.lang.reflect.InvocationTargetException;
059: import java.lang.reflect.Method;
060: import java.lang.reflect.Modifier;
061:
062: import java.util.WeakHashMap;
063:
064: /**
065: * <p> Utility reflection methods focussed on methods in general rather than properties in particular. </p>
066: *
067: * <h3>Known Limitations</h3>
068: * <h4>Accessing Public Methods In A Default Access Superclass</h4>
069: * <p>There is an issue when invoking public methods contained in a default access superclass.
070: * Reflection locates these methods fine and correctly assigns them as public.
071: * However, an <code>IllegalAccessException</code> is thrown if the method is invoked.</p>
072: *
073: * <p><code>MethodUtils</code> contains a workaround for this situation.
074: * It will attempt to call <code>setAccessible</code> on this method.
075: * If this call succeeds, then the method can be invoked as normal.
076: * This call will only succeed when the application has sufficient security privilages.
077: * If this call fails then a warning will be logged and the method may fail.</p>
078: *
079: * @author Craig R. McClanahan
080: * @author Ralph Schaer
081: * @author Chris Audley
082: * @author Rey Fran�ois
083: * @author Gregor Ra�man
084: * @author Jan Sorensen
085: * @author Robert Burrell Donkin
086: */
087:
088: public class MethodUtils {
089:
090: // --------------------------------------------------------- Private Methods
091:
092: /** Only log warning about accessibility work around once */
093: private static boolean loggedAccessibleWarning = false;
094:
095: /** An empty class array */
096: private static final Class[] emptyClassArray = new Class[0];
097: /** An empty object array */
098: private static final Object[] emptyObjectArray = new Object[0];
099:
100: /**
101: * Stores a cache of Methods against MethodDescriptors, in a WeakHashMap.
102: */
103: private static WeakHashMap cache = new WeakHashMap();
104:
105: // --------------------------------------------------------- Public Methods
106:
107: /**
108: * <p>Invoke a named method whose parameter type matches the object type.</p>
109: *
110: * <p>The behaviour of this method is less deterministic
111: * than {@link #invokeExactMethod}.
112: * It loops through all methods with names that match
113: * and then executes the first it finds with compatable parameters.</p>
114: *
115: * <p>This method supports calls to methods taking primitive parameters
116: * via passing in wrapping classes. So, for example, a <code>Boolean</code> class
117: * would match a <code>boolean</code> primitive.</p>
118: *
119: * <p> This is a convenient wrapper for
120: * {@link #invokeMethod(Object object,String methodName,Object [] args)}.
121: * </p>
122: *
123: * @param object invoke method on this object
124: * @param methodName get method with this name
125: * @param arg use this argument
126: *
127: * @throws NoSuchMethodException if there is no such accessible method
128: * @throws InvocationTargetException wraps an exception thrown by the
129: * method invoked
130: * @throws IllegalAccessException if the requested method is not accessible
131: * via reflection
132: */
133: public static Object invokeMethod(Object object, String methodName,
134: Object arg) throws NoSuchMethodException,
135: IllegalAccessException, InvocationTargetException {
136:
137: Object[] args = { arg };
138: return invokeMethod(object, methodName, args);
139:
140: }
141:
142: /**
143: * <p>Invoke a named method whose parameter type matches the object type.</p>
144: *
145: * <p>The behaviour of this method is less deterministic
146: * than {@link #invokeExactMethod(Object object,String methodName,Object [] args)}.
147: * It loops through all methods with names that match
148: * and then executes the first it finds with compatable parameters.</p>
149: *
150: * <p>This method supports calls to methods taking primitive parameters
151: * via passing in wrapping classes. So, for example, a <code>Boolean</code> class
152: * would match a <code>boolean</code> primitive.</p>
153: *
154: * <p> This is a convenient wrapper for
155: * {@link #invokeMethod(Object, String, Object[], Class[])} } }.
156: * </p>
157: *
158: * @param object invoke method on this object
159: * @param methodName get method with this name
160: * @param args use these arguments - treat null as empty array
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(Object object, String methodName,
169: Object[] args) throws NoSuchMethodException,
170: IllegalAccessException, InvocationTargetException {
171:
172: if (args == null) {
173: args = emptyObjectArray;
174: }
175: int arguments = args.length;
176: Class parameterTypes[] = new Class[arguments];
177: for (int i = 0; i < arguments; i++) {
178: parameterTypes[i] = args[i].getClass();
179: }
180: return invokeMethod(object, methodName, args, parameterTypes);
181:
182: }
183:
184: /**
185: * <p>Invoke a named method whose parameter type matches the object type.</p>
186: *
187: * <p>The behaviour of this method is less deterministic
188: * than {@link
189: * #invokeExactMethod(Object object,String methodName,Object [] args,Class[] parameterTypes)}.
190: * It loops through all methods with names that match
191: * and then executes the first it finds with compatable parameters.</p>
192: *
193: * <p>This method supports calls to methods taking primitive parameters
194: * via passing in wrapping classes. So, for example, a <code>Boolean</code> class
195: * would match a <code>boolean</code> primitive.</p>
196: *
197: *
198: * @param object invoke method on this object
199: * @param methodName get method with this name
200: * @param args use these arguments - treat null as empty array
201: * @param parameterTypes match these parameters - treat null as empty array
202: *
203: * @throws NoSuchMethodException if there is no such accessible method
204: * @throws InvocationTargetException wraps an exception thrown by the
205: * method invoked
206: * @throws IllegalAccessException if the requested method is not accessible
207: * via reflection
208: */
209: public static Object invokeMethod(Object object, String methodName,
210: Object[] args, Class[] parameterTypes)
211: throws NoSuchMethodException, IllegalAccessException,
212: InvocationTargetException {
213:
214: if (parameterTypes == null) {
215: parameterTypes = emptyClassArray;
216: }
217: if (args == null) {
218: args = emptyObjectArray;
219: }
220:
221: Method method = getMatchingAccessibleMethod(object.getClass(),
222: methodName, parameterTypes);
223: if (method == null)
224: throw new NoSuchMethodException(
225: "No such accessible method: " + methodName
226: + "() on object: "
227: + object.getClass().getName());
228: return method.invoke(object, args);
229: }
230:
231: /**
232: * <p>Invoke a method whose parameter type matches exactly the object
233: * type.</p>
234: *
235: * <p> This is a convenient wrapper for
236: * {@link #invokeExactMethod(Object object,String methodName,Object [] args)}.
237: * </p>
238: *
239: * @param object invoke method on this object
240: * @param methodName get method with this name
241: * @param arg use this argument
242: *
243: * @throws NoSuchMethodException if there is no such accessible method
244: * @throws InvocationTargetException wraps an exception thrown by the
245: * method invoked
246: * @throws IllegalAccessException if the requested method is not accessible
247: * via reflection
248: */
249: public static Object invokeExactMethod(Object object,
250: String methodName, Object arg)
251: throws NoSuchMethodException, IllegalAccessException,
252: InvocationTargetException {
253:
254: Object[] args = { arg };
255: return invokeExactMethod(object, methodName, args);
256:
257: }
258:
259: /**
260: * <p>Invoke a method whose parameter types match exactly the object
261: * types.</p>
262: *
263: * <p> This uses reflection to invoke the method obtained from a call to
264: * {@link #getAccessibleMethod}.</p>
265: *
266: * @param object invoke method on this object
267: * @param methodName get method with this name
268: * @param args use these arguments - treat null as empty array
269: *
270: * @throws NoSuchMethodException if there is no such accessible method
271: * @throws InvocationTargetException wraps an exception thrown by the
272: * method invoked
273: * @throws IllegalAccessException if the requested method is not accessible
274: * via reflection
275: */
276: public static Object invokeExactMethod(Object object,
277: String methodName, Object[] args)
278: throws NoSuchMethodException, IllegalAccessException,
279: InvocationTargetException {
280: if (args == null) {
281: args = emptyObjectArray;
282: }
283: int arguments = args.length;
284: Class parameterTypes[] = new Class[arguments];
285: for (int i = 0; i < arguments; i++) {
286: parameterTypes[i] = args[i].getClass();
287: }
288: return invokeExactMethod(object, methodName, args,
289: parameterTypes);
290:
291: }
292:
293: /**
294: * <p>Invoke a method whose parameter types match exactly the parameter
295: * types given.</p>
296: *
297: * <p>This uses reflection to invoke the method obtained from a call to
298: * {@link #getAccessibleMethod}.</p>
299: *
300: * @param object invoke method on this object
301: * @param methodName get method with this name
302: * @param args use these arguments - treat null as empty array
303: * @param parameterTypes match these parameters - treat null as empty array
304: *
305: * @throws NoSuchMethodException if there is no such accessible method
306: * @throws InvocationTargetException wraps an exception thrown by the
307: * method invoked
308: * @throws IllegalAccessException if the requested method is not accessible
309: * via reflection
310: */
311: public static Object invokeExactMethod(Object object,
312: String methodName, Object[] args, Class[] parameterTypes)
313: throws NoSuchMethodException, IllegalAccessException,
314: InvocationTargetException {
315:
316: if (args == null) {
317: args = emptyObjectArray;
318: }
319:
320: if (parameterTypes == null) {
321: parameterTypes = emptyClassArray;
322: }
323:
324: Method method = getAccessibleMethod(object.getClass(),
325: methodName, parameterTypes);
326: if (method == null)
327: throw new NoSuchMethodException(
328: "No such accessible method: " + methodName
329: + "() on object: "
330: + object.getClass().getName());
331: return method.invoke(object, args);
332:
333: }
334:
335: /**
336: * <p>Return an accessible method (that is, one that can be invoked via
337: * reflection) with given name and a single parameter. If no such method
338: * can be found, return <code>null</code>.
339: * Basically, a convenience wrapper that constructs a <code>Class</code>
340: * array for you.</p>
341: *
342: * @param clazz get method from this class
343: * @param methodName get method with this name
344: * @param parameterType taking this type of parameter
345: */
346: public static Method getAccessibleMethod(Class clazz,
347: String methodName, Class parameterType) {
348:
349: Class[] parameterTypes = { parameterType };
350: return getAccessibleMethod(clazz, methodName, parameterTypes);
351:
352: }
353:
354: /**
355: * <p>Return an accessible method (that is, one that can be invoked via
356: * reflection) with given name and parameters. If no such method
357: * can be found, return <code>null</code>.
358: * This is just a convenient wrapper for
359: * {@link #getAccessibleMethod(Method method)}.</p>
360: *
361: * @param clazz get method from this class
362: * @param methodName get method with this name
363: * @param parameterTypes with these parameters types
364: */
365: public static Method getAccessibleMethod(Class clazz,
366: String methodName, Class[] parameterTypes) {
367:
368: try {
369: MethodDescriptor md = new MethodDescriptor(clazz,
370: methodName, parameterTypes, true);
371: // Check the cache first
372: Method method = (Method) cache.get(md);
373: if (method != null) {
374: return method;
375: }
376:
377: method = getAccessibleMethod(clazz.getMethod(methodName,
378: parameterTypes));
379: cache.put(md, method);
380: return method;
381: } catch (NoSuchMethodException e) {
382: return (null);
383: }
384:
385: }
386:
387: /**
388: * <p>Return an accessible method (that is, one that can be invoked via
389: * reflection) that implements the specified Method. If no such method
390: * can be found, return <code>null</code>.</p>
391: *
392: * @param method The method that we wish to call
393: */
394: public static Method getAccessibleMethod(Method method) {
395:
396: // Make sure we have a method to check
397: if (method == null) {
398: return (null);
399: }
400:
401: // If the requested method is not public we cannot call it
402: if (!Modifier.isPublic(method.getModifiers())) {
403: return (null);
404: }
405:
406: // If the declaring class is public, we are done
407: Class clazz = method.getDeclaringClass();
408: if (Modifier.isPublic(clazz.getModifiers())) {
409: return (method);
410: }
411:
412: // Check the implemented interfaces and subinterfaces
413: method = getAccessibleMethodFromInterfaceNest(clazz, method
414: .getName(), method.getParameterTypes());
415: return (method);
416:
417: }
418:
419: // -------------------------------------------------------- Private Methods
420:
421: /**
422: * <p>Return an accessible method (that is, one that can be invoked via
423: * reflection) that implements the specified method, by scanning through
424: * all implemented interfaces and subinterfaces. If no such method
425: * can be found, return <code>null</code>.</p>
426: *
427: * <p> There isn't any good reason why this method must be private.
428: * It is because there doesn't seem any reason why other classes should
429: * call this rather than the higher level methods.</p>
430: *
431: * @param clazz Parent class for the interfaces to be checked
432: * @param methodName Method name of the method we wish to call
433: * @param parameterTypes The parameter type signatures
434: */
435: private static Method getAccessibleMethodFromInterfaceNest(
436: Class clazz, String methodName, Class parameterTypes[]) {
437:
438: Method method = null;
439:
440: // Search up the superclass chain
441: for (; clazz != null; clazz = clazz.getSuperclass()) {
442:
443: // Check the implemented interfaces of the parent class
444: Class interfaces[] = clazz.getInterfaces();
445: for (int i = 0; i < interfaces.length; i++) {
446:
447: // Is this interface public?
448: if (!Modifier.isPublic(interfaces[i].getModifiers()))
449: continue;
450:
451: // Does the method exist on this interface?
452: try {
453: method = interfaces[i].getDeclaredMethod(
454: methodName, parameterTypes);
455: } catch (NoSuchMethodException e) {
456: }
457: if (method != null)
458: break;
459:
460: // Recursively check our parent interfaces
461: method = getAccessibleMethodFromInterfaceNest(
462: interfaces[i], methodName, parameterTypes);
463: if (method != null)
464: break;
465:
466: }
467:
468: }
469:
470: // If we found a method return it
471: if (method != null)
472: return (method);
473:
474: // We did not find anything
475: return (null);
476:
477: }
478:
479: /**
480: * <p>Find an accessible method that matches the given name and has compatible parameters.
481: * Compatible parameters mean that every method parameter is assignable from
482: * the given parameters.
483: * In other words, it finds a method with the given name
484: * that will take the parameters given.<p>
485: *
486: * <p>This method is slightly undeterminstic since it loops
487: * through methods names and return the first matching method.</p>
488: *
489: * <p>This method is used by
490: * {@link
491: * #invokeMethod(Object object,String methodName,Object [] args,Class[] parameterTypes)}.
492: *
493: * <p>This method can match primitive parameter by passing in wrapper classes.
494: * For example, a <code>Boolean</code> will match a primitive <code>boolean</code>
495: * parameter.
496: *
497: * @param clazz find method in this class
498: * @param methodName find method with this name
499: * @param parameterTypes find method with compatible parameters
500: */
501: public static Method getMatchingAccessibleMethod(Class clazz,
502: String methodName, Class[] parameterTypes) {
503: MethodDescriptor md = new MethodDescriptor(clazz, methodName,
504: parameterTypes, false);
505:
506: // see if we can find the method directly
507: // most of the time this works and it's much faster
508: try {
509: // Check the cache first
510: Method method = (Method) cache.get(md);
511: if (method != null) {
512: return method;
513: }
514:
515: method = clazz.getMethod(methodName, parameterTypes);
516:
517: try {
518: //
519: // XXX Default access superclass workaround
520: //
521: // When a public class has a default access superclass
522: // with public methods, these methods are accessible.
523: // Calling them from compiled code works fine.
524: //
525: // Unfortunately, using reflection to invoke these methods
526: // seems to (wrongly) to prevent access even when the method
527: // modifer is public.
528: //
529: // The following workaround solves the problem but will only
530: // work from sufficiently privilages code.
531: //
532: // Better workarounds would be greatfully accepted.
533: //
534: method.setAccessible(true);
535:
536: } catch (SecurityException se) {
537: // log but continue just in case the method.invoke works anyway
538: if (!loggedAccessibleWarning) {
539: boolean vunerableJVM = false;
540: try {
541: String specVersion = System
542: .getProperty("java.specification.version");
543: if (specVersion.charAt(0) == '1'
544: && (specVersion.charAt(0) == '0'
545: || specVersion.charAt(0) == '1'
546: || specVersion.charAt(0) == '2' || specVersion
547: .charAt(0) == '3')) {
548:
549: vunerableJVM = true;
550: }
551: } catch (SecurityException e) {
552: // don't know - so display warning
553: vunerableJVM = true;
554: }
555: loggedAccessibleWarning = true;
556: }
557: }
558: cache.put(md, method);
559: return method;
560:
561: } catch (NoSuchMethodException e) { /* SWALLOW */
562: }
563:
564: // search through all methods
565: int paramSize = parameterTypes.length;
566: Method[] methods = clazz.getMethods();
567: for (int i = 0, size = methods.length; i < size; i++) {
568: if (methods[i].getName().equals(methodName)) {
569:
570: // compare parameters
571: Class[] methodsParams = methods[i].getParameterTypes();
572: int methodParamSize = methodsParams.length;
573: if (methodParamSize == paramSize) {
574: boolean match = true;
575: for (int n = 0; n < methodParamSize; n++) {
576: if (!isAssignmentCompatible(methodsParams[n],
577: parameterTypes[n])) {
578: match = false;
579: break;
580: }
581: }
582:
583: if (match) {
584: // get accessible version of method
585: Method method = getAccessibleMethod(methods[i]);
586: if (method != null) {
587: try {
588: //
589: // XXX Default access superclass workaround
590: // (See above for more details.)
591: //
592: method.setAccessible(true);
593:
594: } catch (SecurityException se) {
595: // log but continue just in case the method.invoke works anyway
596: }
597: cache.put(md, method);
598: return method;
599: }
600:
601: }
602: }
603: }
604: }
605:
606: // didn't find a match
607: return null;
608: }
609:
610: /**
611: * <p>Determine whether a type can be used as a parameter in a method invocation.
612: * This method handles primitive conversions correctly.</p>
613: *
614: * <p>In order words, it will match a <code>Boolean</code> to a <code>boolean</code>,
615: * a <code>Long</code> to a <code>long</code>,
616: * a <code>Float</code> to a <code>float</code>,
617: * a <code>Integer</code> to a <code>int</code>,
618: * and a <code>Double</code> to a <code>double</code>.
619: * Now logic widening matches are allowed.
620: * For example, a <code>Long</code> will not match a <code>int</code>.
621: *
622: * @param parameterType the type of parameter accepted by the method
623: * @param parameterization the type of parameter being tested
624: *
625: * @return true if the assignement is compatible.
626: */
627: public static final boolean isAssignmentCompatible(
628: Class parameterType, Class parameterization) {
629: // try plain assignment
630: if (parameterType.isAssignableFrom(parameterization)) {
631: return true;
632: }
633:
634: if (parameterType.isPrimitive()) {
635: // this method does *not* do widening - you must specify exactly
636: // is this the right behaviour?
637: Class parameterWrapperClazz = getPrimitiveWrapper(parameterType);
638: if (parameterWrapperClazz != null) {
639: return parameterWrapperClazz.equals(parameterization);
640: }
641: }
642:
643: return false;
644: }
645:
646: /**
647: * Gets the wrapper object class for the given primitive type class.
648: * For example, passing <code>boolean.class</code> returns <code>Boolean.class</code>
649: * @param primitiveType the primitive type class for which a match is to be found
650: * @return the wrapper type associated with the given primitive
651: * or null if no match is found
652: */
653: public static Class getPrimitiveWrapper(Class primitiveType) {
654: // does anyone know a better strategy than comparing names?
655: if (boolean.class.equals(primitiveType)) {
656: return Boolean.class;
657: } else if (float.class.equals(primitiveType)) {
658: return Float.class;
659: } else if (long.class.equals(primitiveType)) {
660: return Long.class;
661: } else if (int.class.equals(primitiveType)) {
662: return Integer.class;
663: } else if (short.class.equals(primitiveType)) {
664: return Short.class;
665: } else if (byte.class.equals(primitiveType)) {
666: return Byte.class;
667: } else if (double.class.equals(primitiveType)) {
668: return Double.class;
669: } else if (char.class.equals(primitiveType)) {
670: return Character.class;
671: } else {
672:
673: return null;
674: }
675: }
676:
677: /**
678: * Gets the class for the primitive type corresponding to the primitive wrapper class given.
679: * For example, an instance of <code>Boolean.class</code> returns a <code>boolean.class</code>.
680: * @param wrapperType the
681: * @return the primitive type class corresponding to the given wrapper class,
682: * null if no match is found
683: */
684: public static Class getPrimitiveType(Class wrapperType) {
685: // does anyone know a better strategy than comparing names?
686: if (Boolean.class.equals(wrapperType)) {
687: return boolean.class;
688: } else if (Float.class.equals(wrapperType)) {
689: return float.class;
690: } else if (Long.class.equals(wrapperType)) {
691: return long.class;
692: } else if (Integer.class.equals(wrapperType)) {
693: return int.class;
694: } else if (Short.class.equals(wrapperType)) {
695: return short.class;
696: } else if (Byte.class.equals(wrapperType)) {
697: return byte.class;
698: } else if (Double.class.equals(wrapperType)) {
699: return double.class;
700: } else if (Character.class.equals(wrapperType)) {
701: return char.class;
702: } else {
703: return null;
704: }
705: }
706:
707: /**
708: * Find a non primitive representation for given primitive class.
709: *
710: * @param clazz the class to find a representation for, not null
711: * @return the original class if it not a primitive. Otherwise the wrapper class. Not null
712: */
713: public static Class toNonPrimitiveClass(Class clazz) {
714: if (clazz.isPrimitive()) {
715: Class primitiveClazz = MethodUtils
716: .getPrimitiveWrapper(clazz);
717: // the above method returns
718: if (primitiveClazz != null) {
719: return primitiveClazz;
720: } else {
721: return clazz;
722: }
723: } else {
724: return clazz;
725: }
726: }
727:
728: /**
729: * Represents the key to looking up a Method by reflection.
730: */
731: private static class MethodDescriptor {
732: private Class cls;
733: private String methodName;
734: private Class[] paramTypes;
735: private boolean exact;
736: private int hashCode;
737:
738: /**
739: * The sole constructor.
740: *
741: * @param cls the class to reflect, must not be null
742: * @param methodName the method name to obtain
743: * @param paramTypes the array of classes representing the paramater types
744: * @param exact whether the match has to be exact.
745: */
746: public MethodDescriptor(Class cls, String methodName,
747: Class[] paramTypes, boolean exact) {
748: if (cls == null) {
749: throw new IllegalArgumentException(
750: "Class cannot be null");
751: }
752: if (methodName == null) {
753: throw new IllegalArgumentException(
754: "Method Name cannot be null");
755: }
756: if (paramTypes == null) {
757: paramTypes = emptyClassArray;
758: }
759:
760: this .cls = cls;
761: this .methodName = methodName;
762: this .paramTypes = paramTypes;
763: this .exact = exact;
764:
765: this .hashCode = methodName.length();
766: }
767:
768: /**
769: * Checks for equality.
770: * @param obj object to be tested for equality
771: * @return true, if the object describes the same Method.
772: */
773: public boolean equals(Object obj) {
774: if (!(obj instanceof MethodDescriptor)) {
775: return false;
776: }
777: MethodDescriptor md = (MethodDescriptor) obj;
778:
779: return (exact == md.exact
780: && methodName.equals(md.methodName)
781: && cls.equals(md.cls) && java.util.Arrays.equals(
782: paramTypes, md.paramTypes));
783: }
784:
785: /**
786: * Returns the string length of method name. I.e. if the
787: * hashcodes are different, the objects are different. If the
788: * hashcodes are the same, need to use the equals method to
789: * determine equality.
790: * @return the string length of method name.
791: */
792: public int hashCode() {
793: return hashCode;
794: }
795: }
796: }
|