Java tutorial
/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package eu.qualityontime.commons; import java.lang.ref.Reference; import java.lang.ref.WeakReference; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Collections; import java.util.Map; import java.util.WeakHashMap; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** * <p> * Utility reflection methods focused on methods in general rather than * properties in particular. * </p> * * <h3>Known Limitations</h3> * <h4>Accessing Public Methods In A Default Access Superclass</h4> * <p> * There is an issue when invoking public methods contained in a default access * superclass. Reflection locates these methods fine and correctly assigns them * as public. However, an <code>IllegalAccessException</code> is thrown if the * method is invoked. * </p> * * <p> * <code>MethodUtils</code> contains a workaround for this situation. It will * attempt to call <code>setAccessible</code> on this method. If this call * succeeds, then the method can be invoked as normal. This call will only * succeed when the application has sufficient security privilages. If this call * fails then a warning will be logged and the method may fail. * </p> * * @version $Id$ */ public class MethodUtils { // --------------------------------------------------------- Private Methods /** * Only log warning about accessibility work around once. * <p> * Note that this is broken when this class is deployed via a shared * classloader in a container, as the warning message will be emitted only * once, not once per webapp. However making the warning appear once per * webapp means having a map keyed by context classloader which introduces * nasty memory-leak problems. As this warning is really optional we can * ignore this problem; only one of the webapps will get the warning in its * logs but that should be good enough. */ private static boolean loggedAccessibleWarning = false; /** * Indicates whether methods should be cached for improved performance. * <p> * Note that when this class is deployed via a shared classloader in a * container, this will affect all webapps. However making this configurable * per webapp would mean having a map keyed by context classloader which may * introduce memory-leak problems. */ private static boolean CACHE_METHODS = true; /** An empty class array */ private static final Class<?>[] EMPTY_CLASS_PARAMETERS = new Class[0]; /** An empty object array */ private static final Object[] EMPTY_OBJECT_ARRAY = new Object[0]; /** * Stores a cache of MethodDescriptor -> Method in a WeakHashMap. * <p> * The keys into this map only ever exist as temporary variables within * methods of this class, and are never exposed to users of this class. This * means that the WeakHashMap is used only as a mechanism for limiting the * size of the cache, ie a way to tell the garbage collector that the * contents of the cache can be completely garbage-collected whenever it * needs the memory. Whether this is a good approach to this problem is * doubtful; something like the commons-collections LRUMap may be more * appropriate (though of course selecting an appropriate size is an issue). * <p> * This static variable is safe even when this code is deployed via a shared * classloader because it is keyed via a MethodDescriptor object which has a * Class as one of its members and that member is used in the * MethodDescriptor.equals method. So two components that load the same * class via different classloaders will generate non-equal MethodDescriptor * objects and hence end up with different entries in the map. */ private static final Map<MethodDescriptor, Reference<Method>> cache = Collections .synchronizedMap(new WeakHashMap<MethodDescriptor, Reference<Method>>()); // --------------------------------------------------------- Public Methods /** * Set whether methods should be cached for greater performance or not, * default is <code>true</code>. * * @param cacheMethods * <code>true</code> if methods should be cached for greater * performance, otherwise <code>false</code> * @since 1.8.0 */ public static synchronized void setCacheMethods(final boolean cacheMethods) { CACHE_METHODS = cacheMethods; if (!CACHE_METHODS) { clearCache(); } } /** * Clear the method cache. * * @return the number of cached methods cleared * @since 1.8.0 */ public static synchronized int clearCache() { final int size = cache.size(); cache.clear(); return size; } /** * <p> * Invoke a named method whose parameter type matches the object type. * </p> * * <p> * The behaviour of this method is less deterministic than * <code>invokeExactMethod()</code>. It loops through all methods with names * that match and then executes the first it finds with compatible * parameters. * </p> * * <p> * This method supports calls to methods taking primitive parameters via * passing in wrapping classes. So, for example, a <code>Boolean</code> * class would match a <code>boolean</code> primitive. * </p> * * <p> * This is a convenient wrapper for * {@link #invokeMethod(Object object,String methodName,Object [] args)}. * </p> * * @param object * invoke method on this object * @param methodName * get method with this name * @param arg * use this argument. May be null (this will result in calling * the parameterless method with name {@code methodName}). * @return The value returned by the invoked method * * @throws NoSuchMethodException * if there is no such accessible method * @throws InvocationTargetException * wraps an exception thrown by the method invoked * @throws IllegalAccessException * if the requested method is not accessible via reflection */ public static Object invokeMethod(final Object object, final String methodName, final Object arg) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { final Object[] args = toArray(arg); return invokeMethod(object, methodName, args); } /** * <p> * Invoke a named method whose parameter type matches the object type. * </p> * * <p> * The behaviour of this method is less deterministic than * {@link #invokeExactMethod(Object object,String methodName,Object [] args)}. * It loops through all methods with names that match and then executes the * first it finds with compatible parameters. * </p> * * <p> * This method supports calls to methods taking primitive parameters via * passing in wrapping classes. So, for example, a <code>Boolean</code> * class would match a <code>boolean</code> primitive. * </p> * * <p> * This is a convenient wrapper for * {@link #invokeMethod(Object object,String methodName,Object [] args,Class[] parameterTypes)}. * </p> * * @param object * invoke method on this object * @param methodName * get method with this name * @param args * use these arguments - treat null as empty array (passing null * will result in calling the parameterless method with name * {@code methodName}). * @return The value returned by the invoked method * * @throws NoSuchMethodException * if there is no such accessible method * @throws InvocationTargetException * wraps an exception thrown by the method invoked * @throws IllegalAccessException * if the requested method is not accessible via reflection */ public static Object invokeMethod(final Object object, final String methodName, Object[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { if (args == null) { args = EMPTY_OBJECT_ARRAY; } final int arguments = args.length; final Class<?>[] parameterTypes = new Class[arguments]; for (int i = 0; i < arguments; i++) { parameterTypes[i] = args[i].getClass(); } return invokeMethod(object, methodName, args, parameterTypes); } /** * <p> * Invoke a named method whose parameter type matches the object type. * </p> * * <p> * The behaviour of this method is less deterministic than * {@link #invokeExactMethod(Object object,String methodName,Object [] args,Class[] parameterTypes)}. * It loops through all methods with names that match and then executes the * first it finds with compatible parameters. * </p> * * <p> * This method supports calls to methods taking primitive parameters via * passing in wrapping classes. So, for example, a <code>Boolean</code> * class would match a <code>boolean</code> primitive. * </p> * * * @param object * invoke method on this object * @param methodName * get method with this name * @param args * use these arguments - treat null as empty array (passing null * will result in calling the parameterless method with name * {@code methodName}). * @param parameterTypes * match these parameters - treat null as empty array * @return The value returned by the invoked method * * @throws NoSuchMethodException * if there is no such accessible method * @throws InvocationTargetException * wraps an exception thrown by the method invoked * @throws IllegalAccessException * if the requested method is not accessible via reflection */ public static Object invokeMethod(final Object object, final String methodName, Object[] args, Class<?>[] parameterTypes) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { if (parameterTypes == null) { parameterTypes = EMPTY_CLASS_PARAMETERS; } if (args == null) { args = EMPTY_OBJECT_ARRAY; } final Method method = getMatchingAccessibleMethod(object.getClass(), methodName, parameterTypes); if (method == null) { throw new NoSuchMethodException( "No such accessible method: " + methodName + "() on object: " + object.getClass().getName()); } return method.invoke(object, args); } /** * <p> * Invoke a method whose parameter type matches exactly the object type. * </p> * * <p> * This is a convenient wrapper for * {@link #invokeExactMethod(Object object,String methodName,Object [] args)}. * </p> * * @param object * invoke method on this object * @param methodName * get method with this name * @param arg * use this argument. May be null (this will result in calling * the parameterless method with name {@code methodName}). * @return The value returned by the invoked method * * @throws NoSuchMethodException * if there is no such accessible method * @throws InvocationTargetException * wraps an exception thrown by the method invoked * @throws IllegalAccessException * if the requested method is not accessible via reflection */ public static Object invokeExactMethod(final Object object, final String methodName, final Object arg) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { final Object[] args = toArray(arg); return invokeExactMethod(object, methodName, args); } /** * <p> * Invoke a method whose parameter types match exactly the object types. * </p> * * <p> * This uses reflection to invoke the method obtained from a call to * <code>getAccessibleMethod()</code>. * </p> * * @param object * invoke method on this object * @param methodName * get method with this name * @param args * use these arguments - treat null as empty array (passing null * will result in calling the parameterless method with name * {@code methodName}). * @return The value returned by the invoked method * * @throws NoSuchMethodException * if there is no such accessible method * @throws InvocationTargetException * wraps an exception thrown by the method invoked * @throws IllegalAccessException * if the requested method is not accessible via reflection */ public static Object invokeExactMethod(final Object object, final String methodName, Object[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { if (args == null) { args = EMPTY_OBJECT_ARRAY; } final int arguments = args.length; final Class<?>[] parameterTypes = new Class[arguments]; for (int i = 0; i < arguments; i++) { parameterTypes[i] = args[i].getClass(); } return invokeExactMethod(object, methodName, args, parameterTypes); } /** * <p> * Invoke a method whose parameter types match exactly the parameter types * given. * </p> * * <p> * This uses reflection to invoke the method obtained from a call to * <code>getAccessibleMethod()</code>. * </p> * * @param object * invoke method on this object * @param methodName * get method with this name * @param args * use these arguments - treat null as empty array (passing null * will result in calling the parameterless method with name * {@code methodName}). * @param parameterTypes * match these parameters - treat null as empty array * @return The value returned by the invoked method * * @throws NoSuchMethodException * if there is no such accessible method * @throws InvocationTargetException * wraps an exception thrown by the method invoked * @throws IllegalAccessException * if the requested method is not accessible via reflection */ public static Object invokeExactMethod(final Object object, final String methodName, Object[] args, Class<?>[] parameterTypes) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { if (args == null) { args = EMPTY_OBJECT_ARRAY; } if (parameterTypes == null) { parameterTypes = EMPTY_CLASS_PARAMETERS; } final Method method = getAccessibleMethod(object.getClass(), methodName, parameterTypes); if (method == null) { throw new NoSuchMethodException( "No such accessible method: " + methodName + "() on object: " + object.getClass().getName()); } return method.invoke(object, args); } /** * <p> * Invoke a static method whose parameter types match exactly the parameter * types given. * </p> * * <p> * This uses reflection to invoke the method obtained from a call to * {@link #getAccessibleMethod(Class, String, Class[])}. * </p> * * @param objectClass * invoke static method on this class * @param methodName * get method with this name * @param args * use these arguments - treat null as empty array (passing null * will result in calling the parameterless method with name * {@code methodName}). * @param parameterTypes * match these parameters - treat null as empty array * @return The value returned by the invoked method * * @throws NoSuchMethodException * if there is no such accessible method * @throws InvocationTargetException * wraps an exception thrown by the method invoked * @throws IllegalAccessException * if the requested method is not accessible via reflection * @since 1.8.0 */ public static Object invokeExactStaticMethod(final Class<?> objectClass, final String methodName, Object[] args, Class<?>[] parameterTypes) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { if (args == null) { args = EMPTY_OBJECT_ARRAY; } if (parameterTypes == null) { parameterTypes = EMPTY_CLASS_PARAMETERS; } final Method method = getAccessibleMethod(objectClass, methodName, parameterTypes); if (method == null) { throw new NoSuchMethodException( "No such accessible method: " + methodName + "() on class: " + objectClass.getName()); } return method.invoke(null, args); } /** * <p> * Invoke a named static method whose parameter type matches the object * type. * </p> * * <p> * The behaviour of this method is less deterministic than * {@link #invokeExactMethod(Object, String, Object[], Class[])}. It loops * through all methods with names that match and then executes the first it * finds with compatible parameters. * </p> * * <p> * This method supports calls to methods taking primitive parameters via * passing in wrapping classes. So, for example, a <code>Boolean</code> * class would match a <code>boolean</code> primitive. * </p> * * <p> * This is a convenient wrapper for * {@link #invokeStaticMethod(Class objectClass,String methodName,Object [] args)}. * </p> * * @param objectClass * invoke static method on this class * @param methodName * get method with this name * @param arg * use this argument. May be null (this will result in calling * the parameterless method with name {@code methodName}). * @return The value returned by the invoked method * * @throws NoSuchMethodException * if there is no such accessible method * @throws InvocationTargetException * wraps an exception thrown by the method invoked * @throws IllegalAccessException * if the requested method is not accessible via reflection * @since 1.8.0 */ public static Object invokeStaticMethod(final Class<?> objectClass, final String methodName, final Object arg) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { final Object[] args = toArray(arg); return invokeStaticMethod(objectClass, methodName, args); } /** * <p> * Invoke a named static method whose parameter type matches the object * type. * </p> * * <p> * The behaviour of this method is less deterministic than * {@link #invokeExactMethod(Object object,String methodName,Object [] args)}. * It loops through all methods with names that match and then executes the * first it finds with compatible parameters. * </p> * * <p> * This method supports calls to methods taking primitive parameters via * passing in wrapping classes. So, for example, a <code>Boolean</code> * class would match a <code>boolean</code> primitive. * </p> * * <p> * This is a convenient wrapper for * {@link #invokeStaticMethod(Class objectClass,String methodName,Object [] args,Class[] parameterTypes)}. * </p> * * @param objectClass * invoke static method on this class * @param methodName * get method with this name * @param args * use these arguments - treat null as empty array (passing null * will result in calling the parameterless method with name * {@code methodName}). * @return The value returned by the invoked method * * @throws NoSuchMethodException * if there is no such accessible method * @throws InvocationTargetException * wraps an exception thrown by the method invoked * @throws IllegalAccessException * if the requested method is not accessible via reflection * @since 1.8.0 */ public static Object invokeStaticMethod(final Class<?> objectClass, final String methodName, Object[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { if (args == null) { args = EMPTY_OBJECT_ARRAY; } final int arguments = args.length; final Class<?>[] parameterTypes = new Class[arguments]; for (int i = 0; i < arguments; i++) { parameterTypes[i] = args[i].getClass(); } return invokeStaticMethod(objectClass, methodName, args, parameterTypes); } /** * <p> * Invoke a named static method whose parameter type matches the object * type. * </p> * * <p> * The behaviour of this method is less deterministic than * {@link #invokeExactStaticMethod(Class objectClass,String methodName,Object [] args,Class[] parameterTypes)}. * It loops through all methods with names that match and then executes the * first it finds with compatible parameters. * </p> * * <p> * This method supports calls to methods taking primitive parameters via * passing in wrapping classes. So, for example, a <code>Boolean</code> * class would match a <code>boolean</code> primitive. * </p> * * * @param objectClass * invoke static method on this class * @param methodName * get method with this name * @param args * use these arguments - treat null as empty array (passing null * will result in calling the parameterless method with name * {@code methodName}). * @param parameterTypes * match these parameters - treat null as empty array * @return The value returned by the invoked method * * @throws NoSuchMethodException * if there is no such accessible method * @throws InvocationTargetException * wraps an exception thrown by the method invoked * @throws IllegalAccessException * if the requested method is not accessible via reflection * @since 1.8.0 */ public static Object invokeStaticMethod(final Class<?> objectClass, final String methodName, Object[] args, Class<?>[] parameterTypes) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { if (parameterTypes == null) { parameterTypes = EMPTY_CLASS_PARAMETERS; } if (args == null) { args = EMPTY_OBJECT_ARRAY; } final Method method = getMatchingAccessibleMethod(objectClass, methodName, parameterTypes); if (method == null) { throw new NoSuchMethodException( "No such accessible method: " + methodName + "() on class: " + objectClass.getName()); } return method.invoke(null, args); } /** * <p> * Invoke a static method whose parameter type matches exactly the object * type. * </p> * * <p> * This is a convenient wrapper for * {@link #invokeExactStaticMethod(Class objectClass,String methodName,Object [] args)}. * </p> * * @param objectClass * invoke static method on this class * @param methodName * get method with this name * @param arg * use this argument. May be null (this will result in calling * the parameterless method with name {@code methodName}). * @return The value returned by the invoked method * * @throws NoSuchMethodException * if there is no such accessible method * @throws InvocationTargetException * wraps an exception thrown by the method invoked * @throws IllegalAccessException * if the requested method is not accessible via reflection * @since 1.8.0 */ public static Object invokeExactStaticMethod(final Class<?> objectClass, final String methodName, final Object arg) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { final Object[] args = toArray(arg); return invokeExactStaticMethod(objectClass, methodName, args); } /** * <p> * Invoke a static method whose parameter types match exactly the object * types. * </p> * * <p> * This uses reflection to invoke the method obtained from a call to * {@link #getAccessibleMethod(Class, String, Class[])}. * </p> * * @param objectClass * invoke static method on this class * @param methodName * get method with this name * @param args * use these arguments - treat null as empty array (passing null * will result in calling the parameterless method with name * {@code methodName}). * @return The value returned by the invoked method * * @throws NoSuchMethodException * if there is no such accessible method * @throws InvocationTargetException * wraps an exception thrown by the method invoked * @throws IllegalAccessException * if the requested method is not accessible via reflection * @since 1.8.0 */ public static Object invokeExactStaticMethod(final Class<?> objectClass, final String methodName, Object[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { if (args == null) { args = EMPTY_OBJECT_ARRAY; } final int arguments = args.length; final Class<?>[] parameterTypes = new Class[arguments]; for (int i = 0; i < arguments; i++) { parameterTypes[i] = args[i].getClass(); } return invokeExactStaticMethod(objectClass, methodName, args, parameterTypes); } private static Object[] toArray(final Object arg) { Object[] args = null; if (arg != null) { args = new Object[] { arg }; } return args; } /** * <p> * Return an accessible method (that is, one that can be invoked via * reflection) with given name and a single parameter. If no such method can * be found, return <code>null</code>. Basically, a convenience wrapper that * constructs a <code>Class</code> array for you. * </p> * * @param clazz * get method from this class * @param methodName * get method with this name * @param parameterType * taking this type of parameter * @return The accessible method */ public static Method getAccessibleMethod(final Class<?> clazz, final String methodName, final Class<?> parameterType) { final Class<?>[] parameterTypes = { parameterType }; return getAccessibleMethod(clazz, methodName, parameterTypes); } /** * <p> * Return an accessible method (that is, one that can be invoked via * reflection) with given name and parameters. If no such method can be * found, return <code>null</code>. This is just a convenient wrapper for * {@link #getAccessibleMethod(Method method)}. * </p> * * @param clazz * get method from this class * @param methodName * get method with this name * @param parameterTypes * with these parameters types * @return The accessible method */ public static Method getAccessibleMethod(final Class<?> clazz, final String methodName, final Class<?>[] parameterTypes) { try { final MethodDescriptor md = new MethodDescriptor(clazz, methodName, parameterTypes, true); // Check the cache first Method method = getCachedMethod(md); if (method != null) { return method; } method = getAccessibleMethod(clazz, clazz.getMethod(methodName, parameterTypes)); cacheMethod(md, method); return method; } catch (final NoSuchMethodException e) { return null; } } /** * <p> * Return an accessible method (that is, one that can be invoked via * reflection) that implements the specified Method. If no such method can * be found, return <code>null</code>. * </p> * * @param method * The method that we wish to call * @return The accessible method */ public static Method getAccessibleMethod(final Method method) { // Make sure we have a method to check if (method == null) { return null; } return getAccessibleMethod(method.getDeclaringClass(), method); } /** * <p> * Return an accessible method (that is, one that can be invoked via * reflection) that implements the specified Method. If no such method can * be found, return <code>null</code>. * </p> * * @param clazz * The class of the object * @param method * The method that we wish to call * @return The accessible method * @since 1.8.0 */ public static Method getAccessibleMethod(Class<?> clazz, Method method) { // Make sure we have a method to check if (method == null) { return null; } // If the requested method is not public we cannot call it if (!Modifier.isPublic(method.getModifiers())) { return null; } boolean sameClass = true; if (clazz == null) { clazz = method.getDeclaringClass(); } else { sameClass = clazz.equals(method.getDeclaringClass()); if (!method.getDeclaringClass().isAssignableFrom(clazz)) { throw new IllegalArgumentException( clazz.getName() + " is not assignable from " + method.getDeclaringClass().getName()); } } // If the class is public, we are done if (Modifier.isPublic(clazz.getModifiers())) { if (!sameClass && !Modifier.isPublic(method.getDeclaringClass().getModifiers())) { setMethodAccessible(method); // Default access superclass // workaround } return method; } final String methodName = method.getName(); final Class<?>[] parameterTypes = method.getParameterTypes(); // Check the implemented interfaces and subinterfaces method = getAccessibleMethodFromInterfaceNest(clazz, methodName, parameterTypes); // Check the superclass chain if (method == null) { method = getAccessibleMethodFromSuperclass(clazz, methodName, parameterTypes); } return method; } // -------------------------------------------------------- Private Methods /** * <p> * Return an accessible method (that is, one that can be invoked via * reflection) by scanning through the superclasses. If no such method can * be found, return <code>null</code>. * </p> * * @param clazz * Class to be checked * @param methodName * Method name of the method we wish to call * @param parameterTypes * The parameter type signatures */ private static Method getAccessibleMethodFromSuperclass(final Class<?> clazz, final String methodName, final Class<?>[] parameterTypes) { Class<?> parentClazz = clazz.getSuperclass(); while (parentClazz != null) { if (Modifier.isPublic(parentClazz.getModifiers())) { try { return parentClazz.getMethod(methodName, parameterTypes); } catch (final NoSuchMethodException e) { return null; } } parentClazz = parentClazz.getSuperclass(); } return null; } /** * <p> * Return an accessible method (that is, one that can be invoked via * reflection) that implements the specified method, by scanning through all * implemented interfaces and subinterfaces. If no such method can be found, * return <code>null</code>. * </p> * * <p> * There isn't any good reason why this method must be private. It is * because there doesn't seem any reason why other classes should call this * rather than the higher level methods. * </p> * * @param clazz * Parent class for the interfaces to be checked * @param methodName * Method name of the method we wish to call * @param parameterTypes * The parameter type signatures */ private static Method getAccessibleMethodFromInterfaceNest(Class<?> clazz, final String methodName, final Class<?>[] parameterTypes) { Method method = null; // Search up the superclass chain for (; clazz != null; clazz = clazz.getSuperclass()) { // Check the implemented interfaces of the parent class final Class<?>[] interfaces = clazz.getInterfaces(); for (int i = 0; i < interfaces.length; i++) { // Is this interface public? if (!Modifier.isPublic(interfaces[i].getModifiers())) { continue; } // Does the method exist on this interface? try { method = interfaces[i].getDeclaredMethod(methodName, parameterTypes); } catch (final NoSuchMethodException e) { /* * Swallow, if no method is found after the loop then this * method returns null. */ } if (method != null) { return method; } // Recursively check our parent interfaces method = getAccessibleMethodFromInterfaceNest(interfaces[i], methodName, parameterTypes); if (method != null) { return method; } } } // We did not find anything return null; } /** * <p> * Find an accessible method that matches the given name and has compatible * parameters. Compatible parameters mean that every method parameter is * assignable from the given parameters. In other words, it finds a method * with the given name that will take the parameters given. * </p> * * <p> * This method is slightly undeterministic since it loops through methods * names and return the first matching method. * </p> * * <p> * This method is used by * {@link #invokeMethod(Object object,String methodName,Object [] args,Class[] parameterTypes)}. * * <p> * This method can match primitive parameter by passing in wrapper classes. * For example, a <code>Boolean</code> will match a primitive * <code>boolean</code> parameter. * * @param clazz * find method in this class * @param methodName * find method with this name * @param parameterTypes * find method with compatible parameters * @return The accessible method */ public static Method getMatchingAccessibleMethod(final Class<?> clazz, final String methodName, final Class<?>[] parameterTypes) { // trace logging final Log log = LogFactory.getLog(MethodUtils.class); if (log.isTraceEnabled()) { log.trace("Matching name=" + methodName + " on " + clazz); } final MethodDescriptor md = new MethodDescriptor(clazz, methodName, parameterTypes, false); // see if we can find the method directly // most of the time this works and it's much faster try { // Check the cache first Method method = getCachedMethod(md); if (method != null) { return method; } method = clazz.getMethod(methodName, parameterTypes); if (log.isTraceEnabled()) { log.trace("Found straight match: " + method); log.trace("isPublic:" + Modifier.isPublic(method.getModifiers())); } setMethodAccessible(method); // Default access superclass workaround cacheMethod(md, method); return method; } catch (final NoSuchMethodException e) { /* SWALLOW */ } // search through all methods final int paramSize = parameterTypes.length; Method bestMatch = null; final Method[] methods = clazz.getMethods(); float bestMatchCost = Float.MAX_VALUE; float myCost = Float.MAX_VALUE; for (final Method method2 : methods) { if (method2.getName().equals(methodName)) { // log some trace information if (log.isTraceEnabled()) { log.trace("Found matching name:"); log.trace(method2); } // compare parameters final Class<?>[] methodsParams = method2.getParameterTypes(); final int methodParamSize = methodsParams.length; if (methodParamSize == paramSize) { boolean match = true; for (int n = 0; n < methodParamSize; n++) { if (log.isTraceEnabled()) { log.trace("Param=" + parameterTypes[n].getName()); log.trace("Method=" + methodsParams[n].getName()); } if (!isAssignmentCompatible(methodsParams[n], parameterTypes[n])) { if (log.isTraceEnabled()) { log.trace(methodsParams[n] + " is not assignable from " + parameterTypes[n]); } match = false; break; } } if (match) { // get accessible version of method final Method method = getAccessibleMethod(clazz, method2); if (method != null) { if (log.isTraceEnabled()) { log.trace(method + " accessible version of " + method2); } setMethodAccessible(method); // Default access // superclass // workaround myCost = getTotalTransformationCost(parameterTypes, method.getParameterTypes()); if (myCost < bestMatchCost) { bestMatch = method; bestMatchCost = myCost; } } log.trace("Couldn't find accessible method."); } } } } if (bestMatch != null) { cacheMethod(md, bestMatch); } else { // didn't find a match log.trace("No match found."); } return bestMatch; } /** * Try to make the method accessible * * @param method * The source arguments */ private static void setMethodAccessible(final Method method) { try { // // XXX Default access superclass workaround // // When a public class has a default access superclass // with public methods, these methods are accessible. // Calling them from compiled code works fine. // // Unfortunately, using reflection to invoke these methods // seems to (wrongly) to prevent access even when the method // modifer is public. // // The following workaround solves the problem but will only // work from sufficiently privilages code. // // Better workarounds would be greatfully accepted. // if (!method.isAccessible()) { method.setAccessible(true); } } catch (final SecurityException se) { // log but continue just in case the method.invoke works anyway final Log log = LogFactory.getLog(MethodUtils.class); if (!loggedAccessibleWarning) { boolean vulnerableJVM = false; try { final String specVersion = System.getProperty("java.specification.version"); if (specVersion.charAt(0) == '1' && (specVersion.charAt(2) == '0' || specVersion.charAt(2) == '1' || specVersion.charAt(2) == '2' || specVersion.charAt(2) == '3')) { vulnerableJVM = true; } } catch (final SecurityException e) { // don't know - so display warning vulnerableJVM = true; } if (vulnerableJVM) { log.warn("Current Security Manager restricts use of workarounds for reflection bugs " + " in pre-1.4 JVMs."); } loggedAccessibleWarning = true; } log.debug("Cannot setAccessible on method. Therefore cannot use jvm access bug workaround.", se); } } /** * Returns the sum of the object transformation cost for each class in the * source argument list. * * @param srcArgs * The source arguments * @param destArgs * The destination arguments * @return The total transformation cost */ private static float getTotalTransformationCost(final Class<?>[] srcArgs, final Class<?>[] destArgs) { float totalCost = 0.0f; for (int i = 0; i < srcArgs.length; i++) { Class<?> srcClass, destClass; srcClass = srcArgs[i]; destClass = destArgs[i]; totalCost += getObjectTransformationCost(srcClass, destClass); } return totalCost; } /** * Gets the number of steps required needed to turn the source class into * the destination class. This represents the number of steps in the object * hierarchy graph. * * @param srcClass * The source class * @param destClass * The destination class * @return The cost of transforming an object */ private static float getObjectTransformationCost(Class<?> srcClass, final Class<?> destClass) { float cost = 0.0f; while (srcClass != null && !destClass.equals(srcClass)) { if (destClass.isPrimitive()) { final Class<?> destClassWrapperClazz = getPrimitiveWrapper(destClass); if (destClassWrapperClazz != null && destClassWrapperClazz.equals(srcClass)) { cost += 0.25f; break; } } if (destClass.isInterface() && isAssignmentCompatible(destClass, srcClass)) { // slight penalty for interface match. // we still want an exact match to override an interface match, // but // an interface match should override anything where we have to // get a // superclass. cost += 0.25f; break; } cost++; srcClass = srcClass.getSuperclass(); } /* * If the destination class is null, we've travelled all the way up to * an Object match. We'll penalize this by adding 1.5 to the cost. */ if (srcClass == null) { cost += 1.5f; } return cost; } /** * <p> * Determine whether a type can be used as a parameter in a method * invocation. This method handles primitive conversions correctly. * </p> * * <p> * In order words, it will match a <code>Boolean</code> to a * <code>boolean</code>, a <code>Long</code> to a <code>long</code>, a * <code>Float</code> to a <code>float</code>, a <code>Integer</code> to a * <code>int</code>, and a <code>Double</code> to a <code>double</code>. Now * logic widening matches are allowed. For example, a <code>Long</code> will * not match a <code>int</code>. * * @param parameterType * the type of parameter accepted by the method * @param parameterization * the type of parameter being tested * * @return true if the assignment is compatible. */ public static final boolean isAssignmentCompatible(final Class<?> parameterType, final Class<?> parameterization) { // try plain assignment if (parameterType.isAssignableFrom(parameterization)) { return true; } if (parameterType.isPrimitive()) { // this method does *not* do widening - you must specify exactly // is this the right behaviour? final Class<?> parameterWrapperClazz = getPrimitiveWrapper(parameterType); if (parameterWrapperClazz != null) { return parameterWrapperClazz.equals(parameterization); } } return false; } /** * Gets the wrapper object class for the given primitive type class. For * example, passing <code>boolean.class</code> returns * <code>Boolean.class</code> * * @param primitiveType * the primitive type class for which a match is to be found * @return the wrapper type associated with the given primitive or null if * no match is found */ public static Class<?> getPrimitiveWrapper(final Class<?> primitiveType) { // does anyone know a better strategy than comparing names? if (boolean.class.equals(primitiveType)) { return Boolean.class; } else if (float.class.equals(primitiveType)) { return Float.class; } else if (long.class.equals(primitiveType)) { return Long.class; } else if (int.class.equals(primitiveType)) { return Integer.class; } else if (short.class.equals(primitiveType)) { return Short.class; } else if (byte.class.equals(primitiveType)) { return Byte.class; } else if (double.class.equals(primitiveType)) { return Double.class; } else if (char.class.equals(primitiveType)) { return Character.class; } else { return null; } } /** * Gets the class for the primitive type corresponding to the primitive * wrapper class given. For example, an instance of * <code>Boolean.class</code> returns a <code>boolean.class</code>. * * @param wrapperType * the * @return the primitive type class corresponding to the given wrapper * class, null if no match is found */ public static Class<?> getPrimitiveType(final Class<?> wrapperType) { // does anyone know a better strategy than comparing names? if (Boolean.class.equals(wrapperType)) { return boolean.class; } else if (Float.class.equals(wrapperType)) { return float.class; } else if (Long.class.equals(wrapperType)) { return long.class; } else if (Integer.class.equals(wrapperType)) { return int.class; } else if (Short.class.equals(wrapperType)) { return short.class; } else if (Byte.class.equals(wrapperType)) { return byte.class; } else if (Double.class.equals(wrapperType)) { return double.class; } else if (Character.class.equals(wrapperType)) { return char.class; } else { final Log log = LogFactory.getLog(MethodUtils.class); if (log.isDebugEnabled()) { log.debug("Not a known primitive wrapper class: " + wrapperType); } return null; } } /** * Find a non primitive representation for given primitive class. * * @param clazz * the class to find a representation for, not null * @return the original class if it not a primitive. Otherwise the wrapper * class. Not null */ public static Class<?> toNonPrimitiveClass(final Class<?> clazz) { if (clazz.isPrimitive()) { final Class<?> primitiveClazz = MethodUtils.getPrimitiveWrapper(clazz); // the above method returns if (primitiveClazz != null) { return primitiveClazz; } else { return clazz; } } else { return clazz; } } /** * Return the method from the cache, if present. * * @param md * The method descriptor * @return The cached method */ private static Method getCachedMethod(final MethodDescriptor md) { if (CACHE_METHODS) { final Reference<Method> methodRef = cache.get(md); if (methodRef != null) { return methodRef.get(); } } return null; } /** * Add a method to the cache. * * @param md * The method descriptor * @param method * The method to cache */ private static void cacheMethod(final MethodDescriptor md, final Method method) { if (CACHE_METHODS) { if (method != null) { cache.put(md, new WeakReference<Method>(method)); } } } /** * Represents the key to looking up a Method by reflection. */ private static class MethodDescriptor { private final Class<?> cls; private final String methodName; private final Class<?>[] paramTypes; private final boolean exact; private final int hashCode; /** * The sole constructor. * * @param cls * the class to reflect, must not be null * @param methodName * the method name to obtain * @param paramTypes * the array of classes representing the parameter types * @param exact * whether the match has to be exact. */ public MethodDescriptor(final Class<?> cls, final String methodName, Class<?>[] paramTypes, final boolean exact) { if (cls == null) { throw new IllegalArgumentException("Class cannot be null"); } if (methodName == null) { throw new IllegalArgumentException("Method Name cannot be null"); } if (paramTypes == null) { paramTypes = EMPTY_CLASS_PARAMETERS; } this.cls = cls; this.methodName = methodName; this.paramTypes = paramTypes; this.exact = exact; hashCode = methodName.length(); } /** * Checks for equality. * * @param obj * object to be tested for equality * @return true, if the object describes the same Method. */ @Override public boolean equals(final Object obj) { if (!(obj instanceof MethodDescriptor)) { return false; } final MethodDescriptor md = (MethodDescriptor) obj; return exact == md.exact && methodName.equals(md.methodName) && cls.equals(md.cls) && java.util.Arrays.equals(paramTypes, md.paramTypes); } /** * Returns the string length of method name. I.e. if the hashcodes are * different, the objects are different. If the hashcodes are the same, * need to use the equals method to determine equality. * * @return the string length of method name. */ @Override public int hashCode() { return hashCode; } } }