0001: /*
0002: * Licensed to the Apache Software Foundation (ASF) under one or more
0003: * contributor license agreements. See the NOTICE file distributed with
0004: * this work for additional information regarding copyright ownership.
0005: * The ASF licenses this file to You under the Apache License, Version 2.0
0006: * (the "License"); you may not use this file except in compliance with
0007: * the License. You may obtain a copy of the License at
0008: *
0009: * http://www.apache.org/licenses/LICENSE-2.0
0010: *
0011: * Unless required by applicable law or agreed to in writing, software
0012: * distributed under the License is distributed on an "AS IS" BASIS,
0013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0014: * See the License for the specific language governing permissions and
0015: * limitations under the License.
0016: */
0017:
0018: package org.apache.commons.beanutils;
0019:
0020: import java.lang.reflect.InvocationTargetException;
0021: import java.lang.reflect.Method;
0022: import java.lang.reflect.Modifier;
0023:
0024: import java.util.WeakHashMap;
0025:
0026: import org.apache.commons.logging.Log;
0027: import org.apache.commons.logging.LogFactory;
0028:
0029: /**
0030: * <p> Utility reflection methods focussed on methods in general rather than properties in particular. </p>
0031: *
0032: * <h3>Known Limitations</h3>
0033: * <h4>Accessing Public Methods In A Default Access Superclass</h4>
0034: * <p>There is an issue when invoking public methods contained in a default access superclass.
0035: * Reflection locates these methods fine and correctly assigns them as public.
0036: * However, an <code>IllegalAccessException</code> is thrown if the method is invoked.</p>
0037: *
0038: * <p><code>MethodUtils</code> contains a workaround for this situation.
0039: * It will attempt to call <code>setAccessible</code> on this method.
0040: * If this call succeeds, then the method can be invoked as normal.
0041: * This call will only succeed when the application has sufficient security privilages.
0042: * If this call fails then a warning will be logged and the method may fail.</p>
0043: *
0044: * @author Craig R. McClanahan
0045: * @author Ralph Schaer
0046: * @author Chris Audley
0047: * @author Rey François
0048: * @author Gregor Raýman
0049: * @author Jan Sorensen
0050: * @author Robert Burrell Donkin
0051: */
0052:
0053: public class MethodUtils {
0054:
0055: // --------------------------------------------------------- Private Methods
0056:
0057: /**
0058: * Only log warning about accessibility work around once.
0059: * <p>
0060: * Note that this is broken when this class is deployed via a shared
0061: * classloader in a container, as the warning message will be emitted
0062: * only once, not once per webapp. However making the warning appear
0063: * once per webapp means having a map keyed by context classloader
0064: * which introduces nasty memory-leak problems. As this warning is
0065: * really optional we can ignore this problem; only one of the webapps
0066: * will get the warning in its logs but that should be good enough.
0067: */
0068: private static boolean loggedAccessibleWarning = false;
0069:
0070: /** An empty class array */
0071: private static final Class[] EMPTY_CLASS_PARAMETERS = new Class[0];
0072: /** An empty object array */
0073: private static final Object[] EMPTY_OBJECT_ARRAY = new Object[0];
0074:
0075: /**
0076: * Stores a cache of MethodDescriptor -> Method in a WeakHashMap.
0077: * <p>
0078: * The keys into this map only ever exist as temporary variables within
0079: * methods of this class, and are never exposed to users of this class.
0080: * This means that the WeakHashMap is used only as a mechanism for
0081: * limiting the size of the cache, ie a way to tell the garbage collector
0082: * that the contents of the cache can be completely garbage-collected
0083: * whenever it needs the memory. Whether this is a good approach to
0084: * this problem is doubtful; something like the commons-collections
0085: * LRUMap may be more appropriate (though of course selecting an
0086: * appropriate size is an issue).
0087: * <p>
0088: * This static variable is safe even when this code is deployed via a
0089: * shared classloader because it is keyed via a MethodDescriptor object
0090: * which has a Class as one of its members and that member is used in
0091: * the MethodDescriptor.equals method. So two components that load the same
0092: * class via different classloaders will generate non-equal MethodDescriptor
0093: * objects and hence end up with different entries in the map.
0094: */
0095: private static WeakHashMap cache = new WeakHashMap();
0096:
0097: // --------------------------------------------------------- Public Methods
0098:
0099: /**
0100: * <p>Invoke a named method whose parameter type matches the object type.</p>
0101: *
0102: * <p>The behaviour of this method is less deterministic
0103: * than <code>invokeExactMethod()</code>.
0104: * It loops through all methods with names that match
0105: * and then executes the first it finds with compatable parameters.</p>
0106: *
0107: * <p>This method supports calls to methods taking primitive parameters
0108: * via passing in wrapping classes. So, for example, a <code>Boolean</code> class
0109: * would match a <code>boolean</code> primitive.</p>
0110: *
0111: * <p> This is a convenient wrapper for
0112: * {@link #invokeMethod(Object object,String methodName,Object [] args)}.
0113: * </p>
0114: *
0115: * @param object invoke method on this object
0116: * @param methodName get method with this name
0117: * @param arg use this argument
0118: * @return The value returned by the invoked method
0119: *
0120: * @throws NoSuchMethodException if there is no such accessible method
0121: * @throws InvocationTargetException wraps an exception thrown by the
0122: * method invoked
0123: * @throws IllegalAccessException if the requested method is not accessible
0124: * via reflection
0125: */
0126: public static Object invokeMethod(Object object, String methodName,
0127: Object arg) throws NoSuchMethodException,
0128: IllegalAccessException, InvocationTargetException {
0129:
0130: Object[] args = { arg };
0131: return invokeMethod(object, methodName, args);
0132:
0133: }
0134:
0135: /**
0136: * <p>Invoke a named method whose parameter type matches the object type.</p>
0137: *
0138: * <p>The behaviour of this method is less deterministic
0139: * than {@link #invokeExactMethod(Object object,String methodName,Object [] args)}.
0140: * It loops through all methods with names that match
0141: * and then executes the first it finds with compatable parameters.</p>
0142: *
0143: * <p>This method supports calls to methods taking primitive parameters
0144: * via passing in wrapping classes. So, for example, a <code>Boolean</code> class
0145: * would match a <code>boolean</code> primitive.</p>
0146: *
0147: * <p> This is a convenient wrapper for
0148: * {@link #invokeMethod(Object object,String methodName,Object [] args,Class[] parameterTypes)}.
0149: * </p>
0150: *
0151: * @param object invoke method on this object
0152: * @param methodName get method with this name
0153: * @param args use these arguments - treat null as empty array
0154: * @return The value returned by the invoked method
0155: *
0156: * @throws NoSuchMethodException if there is no such accessible method
0157: * @throws InvocationTargetException wraps an exception thrown by the
0158: * method invoked
0159: * @throws IllegalAccessException if the requested method is not accessible
0160: * via reflection
0161: */
0162: public static Object invokeMethod(Object object, String methodName,
0163: Object[] args) throws NoSuchMethodException,
0164: IllegalAccessException, InvocationTargetException {
0165:
0166: if (args == null) {
0167: args = EMPTY_OBJECT_ARRAY;
0168: }
0169: int arguments = args.length;
0170: Class[] parameterTypes = new Class[arguments];
0171: for (int i = 0; i < arguments; i++) {
0172: parameterTypes[i] = args[i].getClass();
0173: }
0174: return invokeMethod(object, methodName, args, parameterTypes);
0175:
0176: }
0177:
0178: /**
0179: * <p>Invoke a named method whose parameter type matches the object type.</p>
0180: *
0181: * <p>The behaviour of this method is less deterministic
0182: * than {@link
0183: * #invokeExactMethod(Object object,String methodName,Object [] args,Class[] parameterTypes)}.
0184: * It loops through all methods with names that match
0185: * and then executes the first it finds with compatable parameters.</p>
0186: *
0187: * <p>This method supports calls to methods taking primitive parameters
0188: * via passing in wrapping classes. So, for example, a <code>Boolean</code> class
0189: * would match a <code>boolean</code> primitive.</p>
0190: *
0191: *
0192: * @param object invoke method on this object
0193: * @param methodName get method with this name
0194: * @param args use these arguments - treat null as empty array
0195: * @param parameterTypes match these parameters - treat null as empty array
0196: * @return The value returned by the invoked method
0197: *
0198: * @throws NoSuchMethodException if there is no such accessible method
0199: * @throws InvocationTargetException wraps an exception thrown by the
0200: * method invoked
0201: * @throws IllegalAccessException if the requested method is not accessible
0202: * via reflection
0203: */
0204: public static Object invokeMethod(Object object, String methodName,
0205: Object[] args, Class[] parameterTypes)
0206: throws NoSuchMethodException, IllegalAccessException,
0207: InvocationTargetException {
0208:
0209: if (parameterTypes == null) {
0210: parameterTypes = EMPTY_CLASS_PARAMETERS;
0211: }
0212: if (args == null) {
0213: args = EMPTY_OBJECT_ARRAY;
0214: }
0215:
0216: Method method = getMatchingAccessibleMethod(object.getClass(),
0217: methodName, parameterTypes);
0218: if (method == null) {
0219: throw new NoSuchMethodException(
0220: "No such accessible method: " + methodName
0221: + "() on object: "
0222: + object.getClass().getName());
0223: }
0224: return method.invoke(object, args);
0225: }
0226:
0227: /**
0228: * <p>Invoke a method whose parameter type matches exactly the object
0229: * type.</p>
0230: *
0231: * <p> This is a convenient wrapper for
0232: * {@link #invokeExactMethod(Object object,String methodName,Object [] args)}.
0233: * </p>
0234: *
0235: * @param object invoke method on this object
0236: * @param methodName get method with this name
0237: * @param arg use this argument
0238: * @return The value returned by the invoked method
0239: *
0240: * @throws NoSuchMethodException if there is no such accessible method
0241: * @throws InvocationTargetException wraps an exception thrown by the
0242: * method invoked
0243: * @throws IllegalAccessException if the requested method is not accessible
0244: * via reflection
0245: */
0246: public static Object invokeExactMethod(Object object,
0247: String methodName, Object arg)
0248: throws NoSuchMethodException, IllegalAccessException,
0249: InvocationTargetException {
0250:
0251: Object[] args = { arg };
0252: return invokeExactMethod(object, methodName, args);
0253:
0254: }
0255:
0256: /**
0257: * <p>Invoke a method whose parameter types match exactly the object
0258: * types.</p>
0259: *
0260: * <p> This uses reflection to invoke the method obtained from a call to
0261: * <code>getAccessibleMethod()</code>.</p>
0262: *
0263: * @param object invoke method on this object
0264: * @param methodName get method with this name
0265: * @param args use these arguments - treat null as empty array
0266: * @return The value returned by the invoked method
0267: *
0268: * @throws NoSuchMethodException if there is no such accessible method
0269: * @throws InvocationTargetException wraps an exception thrown by the
0270: * method invoked
0271: * @throws IllegalAccessException if the requested method is not accessible
0272: * via reflection
0273: */
0274: public static Object invokeExactMethod(Object object,
0275: String methodName, Object[] args)
0276: throws NoSuchMethodException, IllegalAccessException,
0277: InvocationTargetException {
0278: if (args == null) {
0279: args = EMPTY_OBJECT_ARRAY;
0280: }
0281: int arguments = args.length;
0282: Class[] parameterTypes = new Class[arguments];
0283: for (int i = 0; i < arguments; i++) {
0284: parameterTypes[i] = args[i].getClass();
0285: }
0286: return invokeExactMethod(object, methodName, args,
0287: parameterTypes);
0288:
0289: }
0290:
0291: /**
0292: * <p>Invoke a method whose parameter types match exactly the parameter
0293: * types given.</p>
0294: *
0295: * <p>This uses reflection to invoke the method obtained from a call to
0296: * <code>getAccessibleMethod()</code>.</p>
0297: *
0298: * @param object invoke method on this object
0299: * @param methodName get method with this name
0300: * @param args use these arguments - treat null as empty array
0301: * @param parameterTypes match these parameters - treat null as empty array
0302: * @return The value returned by the invoked method
0303: *
0304: * @throws NoSuchMethodException if there is no such accessible method
0305: * @throws InvocationTargetException wraps an exception thrown by the
0306: * method invoked
0307: * @throws IllegalAccessException if the requested method is not accessible
0308: * via reflection
0309: */
0310: public static Object invokeExactMethod(Object object,
0311: String methodName, Object[] args, Class[] parameterTypes)
0312: throws NoSuchMethodException, IllegalAccessException,
0313: InvocationTargetException {
0314:
0315: if (args == null) {
0316: args = EMPTY_OBJECT_ARRAY;
0317: }
0318:
0319: if (parameterTypes == null) {
0320: parameterTypes = EMPTY_CLASS_PARAMETERS;
0321: }
0322:
0323: Method method = getAccessibleMethod(object.getClass(),
0324: methodName, parameterTypes);
0325: if (method == null) {
0326: throw new NoSuchMethodException(
0327: "No such accessible method: " + methodName
0328: + "() on object: "
0329: + object.getClass().getName());
0330: }
0331: return method.invoke(object, args);
0332:
0333: }
0334:
0335: /**
0336: * <p>Invoke a static method whose parameter types match exactly the parameter
0337: * types given.</p>
0338: *
0339: * <p>This uses reflection to invoke the method obtained from a call to
0340: * {@link #getAccessibleMethod(Class, String, Class[])}.</p>
0341: *
0342: * @param objectClass invoke static method on this class
0343: * @param methodName get method with this name
0344: * @param args use these arguments - treat null as empty array
0345: * @param parameterTypes match these parameters - treat null as empty array
0346: * @return The value returned by the invoked method
0347: *
0348: * @throws NoSuchMethodException if there is no such accessible method
0349: * @throws InvocationTargetException wraps an exception thrown by the
0350: * method invoked
0351: * @throws IllegalAccessException if the requested method is not accessible
0352: * via reflection
0353: */
0354: public static Object invokeExactStaticMethod(Class objectClass,
0355: String methodName, Object[] args, Class[] parameterTypes)
0356: throws NoSuchMethodException, IllegalAccessException,
0357: InvocationTargetException {
0358:
0359: if (args == null) {
0360: args = EMPTY_OBJECT_ARRAY;
0361: }
0362:
0363: if (parameterTypes == null) {
0364: parameterTypes = EMPTY_CLASS_PARAMETERS;
0365: }
0366:
0367: Method method = getAccessibleMethod(objectClass, methodName,
0368: parameterTypes);
0369: if (method == null) {
0370: throw new NoSuchMethodException(
0371: "No such accessible method: " + methodName
0372: + "() on class: " + objectClass.getName());
0373: }
0374: return method.invoke(null, args);
0375:
0376: }
0377:
0378: /**
0379: * <p>Invoke a named static method whose parameter type matches the object type.</p>
0380: *
0381: * <p>The behaviour of this method is less deterministic
0382: * than {@link #invokeExactMethod(Object, String, Object[], Class[])}.
0383: * It loops through all methods with names that match
0384: * and then executes the first it finds with compatable parameters.</p>
0385: *
0386: * <p>This method supports calls to methods taking primitive parameters
0387: * via passing in wrapping classes. So, for example, a <code>Boolean</code> class
0388: * would match a <code>boolean</code> primitive.</p>
0389: *
0390: * <p> This is a convenient wrapper for
0391: * {@link #invokeStaticMethod(Class objectClass,String methodName,Object [] args)}.
0392: * </p>
0393: *
0394: * @param objectClass invoke static method on this class
0395: * @param methodName get method with this name
0396: * @param arg use this argument
0397: * @return The value returned by the invoked method
0398: *
0399: * @throws NoSuchMethodException if there is no such accessible method
0400: * @throws InvocationTargetException wraps an exception thrown by the
0401: * method invoked
0402: * @throws IllegalAccessException if the requested method is not accessible
0403: * via reflection
0404: */
0405: public static Object invokeStaticMethod(Class objectClass,
0406: String methodName, Object arg)
0407: throws NoSuchMethodException, IllegalAccessException,
0408: InvocationTargetException {
0409:
0410: Object[] args = { arg };
0411: return invokeStaticMethod(objectClass, methodName, args);
0412:
0413: }
0414:
0415: /**
0416: * <p>Invoke a named static method whose parameter type matches the object type.</p>
0417: *
0418: * <p>The behaviour of this method is less deterministic
0419: * than {@link #invokeExactMethod(Object object,String methodName,Object [] args)}.
0420: * It loops through all methods with names that match
0421: * and then executes the first it finds with compatable parameters.</p>
0422: *
0423: * <p>This method supports calls to methods taking primitive parameters
0424: * via passing in wrapping classes. So, for example, a <code>Boolean</code> class
0425: * would match a <code>boolean</code> primitive.</p>
0426: *
0427: * <p> This is a convenient wrapper for
0428: * {@link #invokeStaticMethod(Class objectClass,String methodName,Object [] args,Class[] parameterTypes)}.
0429: * </p>
0430: *
0431: * @param objectClass invoke static method on this class
0432: * @param methodName get method with this name
0433: * @param args use these arguments - treat null as empty array
0434: * @return The value returned by the invoked method
0435: *
0436: * @throws NoSuchMethodException if there is no such accessible method
0437: * @throws InvocationTargetException wraps an exception thrown by the
0438: * method invoked
0439: * @throws IllegalAccessException if the requested method is not accessible
0440: * via reflection
0441: */
0442: public static Object invokeStaticMethod(Class objectClass,
0443: String methodName, Object[] args)
0444: throws NoSuchMethodException, IllegalAccessException,
0445: InvocationTargetException {
0446:
0447: if (args == null) {
0448: args = EMPTY_OBJECT_ARRAY;
0449: }
0450: int arguments = args.length;
0451: Class[] parameterTypes = new Class[arguments];
0452: for (int i = 0; i < arguments; i++) {
0453: parameterTypes[i] = args[i].getClass();
0454: }
0455: return invokeStaticMethod(objectClass, methodName, args,
0456: parameterTypes);
0457:
0458: }
0459:
0460: /**
0461: * <p>Invoke a named static method whose parameter type matches the object type.</p>
0462: *
0463: * <p>The behaviour of this method is less deterministic
0464: * than {@link
0465: * #invokeExactStaticMethod(Class objectClass,String methodName,Object [] args,Class[] parameterTypes)}.
0466: * It loops through all methods with names that match
0467: * and then executes the first it finds with compatable parameters.</p>
0468: *
0469: * <p>This method supports calls to methods taking primitive parameters
0470: * via passing in wrapping classes. So, for example, a <code>Boolean</code> class
0471: * would match a <code>boolean</code> primitive.</p>
0472: *
0473: *
0474: * @param objectClass invoke static method on this class
0475: * @param methodName get method with this name
0476: * @param args use these arguments - treat null as empty array
0477: * @param parameterTypes match these parameters - treat null as empty array
0478: * @return The value returned by the invoked method
0479: *
0480: * @throws NoSuchMethodException if there is no such accessible method
0481: * @throws InvocationTargetException wraps an exception thrown by the
0482: * method invoked
0483: * @throws IllegalAccessException if the requested method is not accessible
0484: * via reflection
0485: */
0486: public static Object invokeStaticMethod(Class objectClass,
0487: String methodName, Object[] args, Class[] parameterTypes)
0488: throws NoSuchMethodException, IllegalAccessException,
0489: InvocationTargetException {
0490:
0491: if (parameterTypes == null) {
0492: parameterTypes = EMPTY_CLASS_PARAMETERS;
0493: }
0494: if (args == null) {
0495: args = EMPTY_OBJECT_ARRAY;
0496: }
0497:
0498: Method method = getMatchingAccessibleMethod(objectClass,
0499: methodName, parameterTypes);
0500: if (method == null) {
0501: throw new NoSuchMethodException(
0502: "No such accessible method: " + methodName
0503: + "() on class: " + objectClass.getName());
0504: }
0505: return method.invoke(null, args);
0506: }
0507:
0508: /**
0509: * <p>Invoke a static method whose parameter type matches exactly the object
0510: * type.</p>
0511: *
0512: * <p> This is a convenient wrapper for
0513: * {@link #invokeExactStaticMethod(Class objectClass,String methodName,Object [] args)}.
0514: * </p>
0515: *
0516: * @param objectClass invoke static method on this class
0517: * @param methodName get method with this name
0518: * @param arg use this argument
0519: * @return The value returned by the invoked method
0520: *
0521: * @throws NoSuchMethodException if there is no such accessible method
0522: * @throws InvocationTargetException wraps an exception thrown by the
0523: * method invoked
0524: * @throws IllegalAccessException if the requested method is not accessible
0525: * via reflection
0526: */
0527: public static Object invokeExactStaticMethod(Class objectClass,
0528: String methodName, Object arg)
0529: throws NoSuchMethodException, IllegalAccessException,
0530: InvocationTargetException {
0531:
0532: Object[] args = { arg };
0533: return invokeExactStaticMethod(objectClass, methodName, args);
0534:
0535: }
0536:
0537: /**
0538: * <p>Invoke a static method whose parameter types match exactly the object
0539: * types.</p>
0540: *
0541: * <p> This uses reflection to invoke the method obtained from a call to
0542: * {@link #getAccessibleMethod(Class, String, Class[])}.</p>
0543: *
0544: * @param objectClass invoke static method on this class
0545: * @param methodName get method with this name
0546: * @param args use these arguments - treat null as empty array
0547: * @return The value returned by the invoked method
0548: *
0549: * @throws NoSuchMethodException if there is no such accessible method
0550: * @throws InvocationTargetException wraps an exception thrown by the
0551: * method invoked
0552: * @throws IllegalAccessException if the requested method is not accessible
0553: * via reflection
0554: */
0555: public static Object invokeExactStaticMethod(Class objectClass,
0556: String methodName, Object[] args)
0557: throws NoSuchMethodException, IllegalAccessException,
0558: InvocationTargetException {
0559: if (args == null) {
0560: args = EMPTY_OBJECT_ARRAY;
0561: }
0562: int arguments = args.length;
0563: Class[] parameterTypes = new Class[arguments];
0564: for (int i = 0; i < arguments; i++) {
0565: parameterTypes[i] = args[i].getClass();
0566: }
0567: return invokeExactStaticMethod(objectClass, methodName, args,
0568: parameterTypes);
0569:
0570: }
0571:
0572: /**
0573: * <p>Return an accessible method (that is, one that can be invoked via
0574: * reflection) with given name and a single parameter. If no such method
0575: * can be found, return <code>null</code>.
0576: * Basically, a convenience wrapper that constructs a <code>Class</code>
0577: * array for you.</p>
0578: *
0579: * @param clazz get method from this class
0580: * @param methodName get method with this name
0581: * @param parameterType taking this type of parameter
0582: * @return The accessible method
0583: */
0584: public static Method getAccessibleMethod(Class clazz,
0585: String methodName, Class parameterType) {
0586:
0587: Class[] parameterTypes = { parameterType };
0588: return getAccessibleMethod(clazz, methodName, parameterTypes);
0589:
0590: }
0591:
0592: /**
0593: * <p>Return an accessible method (that is, one that can be invoked via
0594: * reflection) with given name and parameters. If no such method
0595: * can be found, return <code>null</code>.
0596: * This is just a convenient wrapper for
0597: * {@link #getAccessibleMethod(Method method)}.</p>
0598: *
0599: * @param clazz get method from this class
0600: * @param methodName get method with this name
0601: * @param parameterTypes with these parameters types
0602: * @return The accessible method
0603: */
0604: public static Method getAccessibleMethod(Class clazz,
0605: String methodName, Class[] parameterTypes) {
0606:
0607: try {
0608: MethodDescriptor md = new MethodDescriptor(clazz,
0609: methodName, parameterTypes, true);
0610: // Check the cache first
0611: Method method = (Method) cache.get(md);
0612: if (method != null) {
0613: return method;
0614: }
0615:
0616: method = getAccessibleMethod(clazz.getMethod(methodName,
0617: parameterTypes));
0618: cache.put(md, method);
0619: return method;
0620: } catch (NoSuchMethodException e) {
0621: return (null);
0622: }
0623:
0624: }
0625:
0626: /**
0627: * <p>Return an accessible method (that is, one that can be invoked via
0628: * reflection) that implements the specified Method. If no such method
0629: * can be found, return <code>null</code>.</p>
0630: *
0631: * @param method The method that we wish to call
0632: * @return The accessible method
0633: */
0634: public static Method getAccessibleMethod(Method method) {
0635:
0636: // Make sure we have a method to check
0637: if (method == null) {
0638: return (null);
0639: }
0640:
0641: // If the requested method is not public we cannot call it
0642: if (!Modifier.isPublic(method.getModifiers())) {
0643: return (null);
0644: }
0645:
0646: // If the declaring class is public, we are done
0647: Class clazz = method.getDeclaringClass();
0648: if (Modifier.isPublic(clazz.getModifiers())) {
0649: return (method);
0650: }
0651:
0652: String methodName = method.getName();
0653: Class[] parameterTypes = method.getParameterTypes();
0654:
0655: // Check the implemented interfaces and subinterfaces
0656: method = getAccessibleMethodFromInterfaceNest(clazz,
0657: methodName, parameterTypes);
0658:
0659: // Check the superclass chain
0660: if (method == null) {
0661: method = getAccessibleMethodFromSuperclass(clazz,
0662: methodName, parameterTypes);
0663: }
0664:
0665: return (method);
0666:
0667: }
0668:
0669: // -------------------------------------------------------- Private Methods
0670:
0671: /**
0672: * <p>Return an accessible method (that is, one that can be invoked via
0673: * reflection) by scanning through the superclasses. If no such method
0674: * can be found, return <code>null</code>.</p>
0675: *
0676: * @param clazz Class to be checked
0677: * @param methodName Method name of the method we wish to call
0678: * @param parameterTypes The parameter type signatures
0679: */
0680: private static Method getAccessibleMethodFromSuperclass(
0681: Class clazz, String methodName, Class[] parameterTypes) {
0682:
0683: Class parentClazz = clazz.getSuperclass();
0684: while (parentClazz != null) {
0685: if (Modifier.isPublic(parentClazz.getModifiers())) {
0686: try {
0687: return parentClazz.getMethod(methodName,
0688: parameterTypes);
0689: } catch (NoSuchMethodException e) {
0690: return null;
0691: }
0692: }
0693: parentClazz = parentClazz.getSuperclass();
0694: }
0695: return null;
0696: }
0697:
0698: /**
0699: * <p>Return an accessible method (that is, one that can be invoked via
0700: * reflection) that implements the specified method, by scanning through
0701: * all implemented interfaces and subinterfaces. If no such method
0702: * can be found, return <code>null</code>.</p>
0703: *
0704: * <p> There isn't any good reason why this method must be private.
0705: * It is because there doesn't seem any reason why other classes should
0706: * call this rather than the higher level methods.</p>
0707: *
0708: * @param clazz Parent class for the interfaces to be checked
0709: * @param methodName Method name of the method we wish to call
0710: * @param parameterTypes The parameter type signatures
0711: */
0712: private static Method getAccessibleMethodFromInterfaceNest(
0713: Class clazz, String methodName, Class[] parameterTypes) {
0714:
0715: Method method = null;
0716:
0717: // Search up the superclass chain
0718: for (; clazz != null; clazz = clazz.getSuperclass()) {
0719:
0720: // Check the implemented interfaces of the parent class
0721: Class[] interfaces = clazz.getInterfaces();
0722: for (int i = 0; i < interfaces.length; i++) {
0723:
0724: // Is this interface public?
0725: if (!Modifier.isPublic(interfaces[i].getModifiers())) {
0726: continue;
0727: }
0728:
0729: // Does the method exist on this interface?
0730: try {
0731: method = interfaces[i].getDeclaredMethod(
0732: methodName, parameterTypes);
0733: } catch (NoSuchMethodException e) {
0734: /* Swallow, if no method is found after the loop then this
0735: * method returns null.
0736: */
0737: }
0738: if (method != null) {
0739: break;
0740: }
0741:
0742: // Recursively check our parent interfaces
0743: method = getAccessibleMethodFromInterfaceNest(
0744: interfaces[i], methodName, parameterTypes);
0745: if (method != null) {
0746: break;
0747: }
0748:
0749: }
0750:
0751: }
0752:
0753: // If we found a method return it
0754: if (method != null) {
0755: return (method);
0756: }
0757:
0758: // We did not find anything
0759: return (null);
0760:
0761: }
0762:
0763: /**
0764: * <p>Find an accessible method that matches the given name and has compatible parameters.
0765: * Compatible parameters mean that every method parameter is assignable from
0766: * the given parameters.
0767: * In other words, it finds a method with the given name
0768: * that will take the parameters given.<p>
0769: *
0770: * <p>This method is slightly undeterminstic since it loops
0771: * through methods names and return the first matching method.</p>
0772: *
0773: * <p>This method is used by
0774: * {@link
0775: * #invokeMethod(Object object,String methodName,Object [] args,Class[] parameterTypes)}.
0776: *
0777: * <p>This method can match primitive parameter by passing in wrapper classes.
0778: * For example, a <code>Boolean</code> will match a primitive <code>boolean</code>
0779: * parameter.
0780: *
0781: * @param clazz find method in this class
0782: * @param methodName find method with this name
0783: * @param parameterTypes find method with compatible parameters
0784: * @return The accessible method
0785: */
0786: public static Method getMatchingAccessibleMethod(Class clazz,
0787: String methodName, Class[] parameterTypes) {
0788: // trace logging
0789: Log log = LogFactory.getLog(MethodUtils.class);
0790: if (log.isTraceEnabled()) {
0791: log.trace("Matching name=" + methodName + " on " + clazz);
0792: }
0793: MethodDescriptor md = new MethodDescriptor(clazz, methodName,
0794: parameterTypes, false);
0795:
0796: // see if we can find the method directly
0797: // most of the time this works and it's much faster
0798: try {
0799: // Check the cache first
0800: Method method = (Method) cache.get(md);
0801: if (method != null) {
0802: return method;
0803: }
0804:
0805: method = clazz.getMethod(methodName, parameterTypes);
0806: if (log.isTraceEnabled()) {
0807: log.trace("Found straight match: " + method);
0808: log.trace("isPublic:"
0809: + Modifier.isPublic(method.getModifiers()));
0810: }
0811:
0812: try {
0813: //
0814: // XXX Default access superclass workaround
0815: //
0816: // When a public class has a default access superclass
0817: // with public methods, these methods are accessible.
0818: // Calling them from compiled code works fine.
0819: //
0820: // Unfortunately, using reflection to invoke these methods
0821: // seems to (wrongly) to prevent access even when the method
0822: // modifer is public.
0823: //
0824: // The following workaround solves the problem but will only
0825: // work from sufficiently privilages code.
0826: //
0827: // Better workarounds would be greatfully accepted.
0828: //
0829: method.setAccessible(true);
0830:
0831: } catch (SecurityException se) {
0832: // log but continue just in case the method.invoke works anyway
0833: if (!loggedAccessibleWarning) {
0834: boolean vulnerableJVM = false;
0835: try {
0836: String specVersion = System
0837: .getProperty("java.specification.version");
0838: if (specVersion.charAt(0) == '1'
0839: && (specVersion.charAt(2) == '0'
0840: || specVersion.charAt(2) == '1'
0841: || specVersion.charAt(2) == '2' || specVersion
0842: .charAt(2) == '3')) {
0843:
0844: vulnerableJVM = true;
0845: }
0846: } catch (SecurityException e) {
0847: // don't know - so display warning
0848: vulnerableJVM = true;
0849: }
0850: if (vulnerableJVM) {
0851: log
0852: .warn("Current Security Manager restricts use of workarounds for reflection bugs "
0853: + " in pre-1.4 JVMs.");
0854: }
0855: loggedAccessibleWarning = true;
0856: }
0857: log
0858: .debug(
0859: "Cannot setAccessible on method. Therefore cannot use jvm access bug workaround.",
0860: se);
0861: }
0862: cache.put(md, method);
0863: return method;
0864:
0865: } catch (NoSuchMethodException e) { /* SWALLOW */
0866: }
0867:
0868: // search through all methods
0869: int paramSize = parameterTypes.length;
0870: Method bestMatch = null;
0871: Method[] methods = clazz.getMethods();
0872: float bestMatchCost = Float.MAX_VALUE;
0873: float myCost = Float.MAX_VALUE;
0874: for (int i = 0, size = methods.length; i < size; i++) {
0875: if (methods[i].getName().equals(methodName)) {
0876: // log some trace information
0877: if (log.isTraceEnabled()) {
0878: log.trace("Found matching name:");
0879: log.trace(methods[i]);
0880: }
0881:
0882: // compare parameters
0883: Class[] methodsParams = methods[i].getParameterTypes();
0884: int methodParamSize = methodsParams.length;
0885: if (methodParamSize == paramSize) {
0886: boolean match = true;
0887: for (int n = 0; n < methodParamSize; n++) {
0888: if (log.isTraceEnabled()) {
0889: log.trace("Param="
0890: + parameterTypes[n].getName());
0891: log.trace("Method="
0892: + methodsParams[n].getName());
0893: }
0894: if (!isAssignmentCompatible(methodsParams[n],
0895: parameterTypes[n])) {
0896: if (log.isTraceEnabled()) {
0897: log.trace(methodsParams[n]
0898: + " is not assignable from "
0899: + parameterTypes[n]);
0900: }
0901: match = false;
0902: break;
0903: }
0904: }
0905:
0906: if (match) {
0907: // get accessible version of method
0908: Method method = getAccessibleMethod(methods[i]);
0909: if (method != null) {
0910: if (log.isTraceEnabled()) {
0911: log.trace(method
0912: + " accessible version of "
0913: + methods[i]);
0914: }
0915: try {
0916: //
0917: // XXX Default access superclass workaround
0918: // (See above for more details.)
0919: //
0920: method.setAccessible(true);
0921:
0922: } catch (SecurityException se) {
0923: // log but continue just in case the method.invoke works anyway
0924: if (!loggedAccessibleWarning) {
0925: log
0926: .warn("Cannot use JVM pre-1.4 access bug workaround due to restrictive security manager.");
0927: loggedAccessibleWarning = true;
0928: }
0929: log
0930: .debug(
0931: "Cannot setAccessible on method. Therefore cannot use jvm access bug workaround.",
0932: se);
0933: }
0934: myCost = getTotalTransformationCost(
0935: parameterTypes, method
0936: .getParameterTypes());
0937: if (myCost < bestMatchCost) {
0938: bestMatch = method;
0939: bestMatchCost = myCost;
0940: }
0941: }
0942:
0943: log.trace("Couldn't find accessible method.");
0944: }
0945: }
0946: }
0947: }
0948: if (bestMatch != null) {
0949: cache.put(md, bestMatch);
0950: } else {
0951: // didn't find a match
0952: log.trace("No match found.");
0953: }
0954:
0955: return bestMatch;
0956: }
0957:
0958: /**
0959: * Returns the sum of the object transformation cost for each class in the source
0960: * argument list.
0961: * @param srcArgs The source arguments
0962: * @param destArgs The destination arguments
0963: * @return The total transformation cost
0964: */
0965: private static float getTotalTransformationCost(Class[] srcArgs,
0966: Class[] destArgs) {
0967:
0968: float totalCost = 0.0f;
0969: for (int i = 0; i < srcArgs.length; i++) {
0970: Class srcClass, destClass;
0971: srcClass = srcArgs[i];
0972: destClass = destArgs[i];
0973: totalCost += getObjectTransformationCost(srcClass,
0974: destClass);
0975: }
0976:
0977: return totalCost;
0978: }
0979:
0980: /**
0981: * Gets the number of steps required needed to turn the source class into the
0982: * destination class. This represents the number of steps in the object hierarchy
0983: * graph.
0984: * @param srcClass The source class
0985: * @param destClass The destination class
0986: * @return The cost of transforming an object
0987: */
0988: private static float getObjectTransformationCost(Class srcClass,
0989: Class destClass) {
0990: float cost = 0.0f;
0991: while (destClass != null && !destClass.equals(srcClass)) {
0992: if (destClass.isInterface()
0993: && isAssignmentCompatible(destClass, srcClass)) {
0994: // slight penalty for interface match.
0995: // we still want an exact match to override an interface match, but
0996: // an interface match should override anything where we have to get a
0997: // superclass.
0998: cost += 0.25f;
0999: break;
1000: }
1001: cost++;
1002: destClass = destClass.getSuperclass();
1003: }
1004:
1005: /*
1006: * If the destination class is null, we've travelled all the way up to
1007: * an Object match. We'll penalize this by adding 1.5 to the cost.
1008: */
1009: if (destClass == null) {
1010: cost += 1.5f;
1011: }
1012:
1013: return cost;
1014: }
1015:
1016: /**
1017: * <p>Determine whether a type can be used as a parameter in a method invocation.
1018: * This method handles primitive conversions correctly.</p>
1019: *
1020: * <p>In order words, it will match a <code>Boolean</code> to a <code>boolean</code>,
1021: * a <code>Long</code> to a <code>long</code>,
1022: * a <code>Float</code> to a <code>float</code>,
1023: * a <code>Integer</code> to a <code>int</code>,
1024: * and a <code>Double</code> to a <code>double</code>.
1025: * Now logic widening matches are allowed.
1026: * For example, a <code>Long</code> will not match a <code>int</code>.
1027: *
1028: * @param parameterType the type of parameter accepted by the method
1029: * @param parameterization the type of parameter being tested
1030: *
1031: * @return true if the assignement is compatible.
1032: */
1033: public static final boolean isAssignmentCompatible(
1034: Class parameterType, Class parameterization) {
1035: // try plain assignment
1036: if (parameterType.isAssignableFrom(parameterization)) {
1037: return true;
1038: }
1039:
1040: if (parameterType.isPrimitive()) {
1041: // this method does *not* do widening - you must specify exactly
1042: // is this the right behaviour?
1043: Class parameterWrapperClazz = getPrimitiveWrapper(parameterType);
1044: if (parameterWrapperClazz != null) {
1045: return parameterWrapperClazz.equals(parameterization);
1046: }
1047: }
1048:
1049: return false;
1050: }
1051:
1052: /**
1053: * Gets the wrapper object class for the given primitive type class.
1054: * For example, passing <code>boolean.class</code> returns <code>Boolean.class</code>
1055: * @param primitiveType the primitive type class for which a match is to be found
1056: * @return the wrapper type associated with the given primitive
1057: * or null if no match is found
1058: */
1059: public static Class getPrimitiveWrapper(Class primitiveType) {
1060: // does anyone know a better strategy than comparing names?
1061: if (boolean.class.equals(primitiveType)) {
1062: return Boolean.class;
1063: } else if (float.class.equals(primitiveType)) {
1064: return Float.class;
1065: } else if (long.class.equals(primitiveType)) {
1066: return Long.class;
1067: } else if (int.class.equals(primitiveType)) {
1068: return Integer.class;
1069: } else if (short.class.equals(primitiveType)) {
1070: return Short.class;
1071: } else if (byte.class.equals(primitiveType)) {
1072: return Byte.class;
1073: } else if (double.class.equals(primitiveType)) {
1074: return Double.class;
1075: } else if (char.class.equals(primitiveType)) {
1076: return Character.class;
1077: } else {
1078:
1079: return null;
1080: }
1081: }
1082:
1083: /**
1084: * Gets the class for the primitive type corresponding to the primitive wrapper class given.
1085: * For example, an instance of <code>Boolean.class</code> returns a <code>boolean.class</code>.
1086: * @param wrapperType the
1087: * @return the primitive type class corresponding to the given wrapper class,
1088: * null if no match is found
1089: */
1090: public static Class getPrimitiveType(Class wrapperType) {
1091: // does anyone know a better strategy than comparing names?
1092: if (Boolean.class.equals(wrapperType)) {
1093: return boolean.class;
1094: } else if (Float.class.equals(wrapperType)) {
1095: return float.class;
1096: } else if (Long.class.equals(wrapperType)) {
1097: return long.class;
1098: } else if (Integer.class.equals(wrapperType)) {
1099: return int.class;
1100: } else if (Short.class.equals(wrapperType)) {
1101: return short.class;
1102: } else if (Byte.class.equals(wrapperType)) {
1103: return byte.class;
1104: } else if (Double.class.equals(wrapperType)) {
1105: return double.class;
1106: } else if (Character.class.equals(wrapperType)) {
1107: return char.class;
1108: } else {
1109: Log log = LogFactory.getLog(MethodUtils.class);
1110: if (log.isDebugEnabled()) {
1111: log.debug("Not a known primitive wrapper class: "
1112: + wrapperType);
1113: }
1114: return null;
1115: }
1116: }
1117:
1118: /**
1119: * Find a non primitive representation for given primitive class.
1120: *
1121: * @param clazz the class to find a representation for, not null
1122: * @return the original class if it not a primitive. Otherwise the wrapper class. Not null
1123: */
1124: public static Class toNonPrimitiveClass(Class clazz) {
1125: if (clazz.isPrimitive()) {
1126: Class primitiveClazz = MethodUtils
1127: .getPrimitiveWrapper(clazz);
1128: // the above method returns
1129: if (primitiveClazz != null) {
1130: return primitiveClazz;
1131: } else {
1132: return clazz;
1133: }
1134: } else {
1135: return clazz;
1136: }
1137: }
1138:
1139: /**
1140: * Represents the key to looking up a Method by reflection.
1141: */
1142: private static class MethodDescriptor {
1143: private Class cls;
1144: private String methodName;
1145: private Class[] paramTypes;
1146: private boolean exact;
1147: private int hashCode;
1148:
1149: /**
1150: * The sole constructor.
1151: *
1152: * @param cls the class to reflect, must not be null
1153: * @param methodName the method name to obtain
1154: * @param paramTypes the array of classes representing the paramater types
1155: * @param exact whether the match has to be exact.
1156: */
1157: public MethodDescriptor(Class cls, String methodName,
1158: Class[] paramTypes, boolean exact) {
1159: if (cls == null) {
1160: throw new IllegalArgumentException(
1161: "Class cannot be null");
1162: }
1163: if (methodName == null) {
1164: throw new IllegalArgumentException(
1165: "Method Name cannot be null");
1166: }
1167: if (paramTypes == null) {
1168: paramTypes = EMPTY_CLASS_PARAMETERS;
1169: }
1170:
1171: this .cls = cls;
1172: this .methodName = methodName;
1173: this .paramTypes = paramTypes;
1174: this .exact = exact;
1175:
1176: this .hashCode = methodName.length();
1177: }
1178:
1179: /**
1180: * Checks for equality.
1181: * @param obj object to be tested for equality
1182: * @return true, if the object describes the same Method.
1183: */
1184: public boolean equals(Object obj) {
1185: if (!(obj instanceof MethodDescriptor)) {
1186: return false;
1187: }
1188: MethodDescriptor md = (MethodDescriptor) obj;
1189:
1190: return (exact == md.exact
1191: && methodName.equals(md.methodName)
1192: && cls.equals(md.cls) && java.util.Arrays.equals(
1193: paramTypes, md.paramTypes));
1194: }
1195:
1196: /**
1197: * Returns the string length of method name. I.e. if the
1198: * hashcodes are different, the objects are different. If the
1199: * hashcodes are the same, need to use the equals method to
1200: * determine equality.
1201: * @return the string length of method name.
1202: */
1203: public int hashCode() {
1204: return hashCode;
1205: }
1206: }
1207: }
|