com.feilong.lib.beanutils.MethodUtils Maven / Gradle / Ivy
Show all versions of feilong Show documentation
/*
* 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 com.feilong.lib.beanutils;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
*
* Utility reflection methods focused on methods in general rather than properties in particular.
*
*
* Known Limitations
* Accessing Public Methods In A Default Access Superclass
*
* 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 IllegalAccessException
is thrown if the method is invoked.
*
*
*
* MethodUtils
contains a workaround for this situation.
* It will attempt to call setAccessible
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.
*
*
* @version $Id$
* @deprecated may be can use com.feilong.lib.lang3.reflect.MethodUtils
*/
@Deprecated
public class MethodUtils{
/** The Constant log. */
private static final Logger LOGGER = LoggerFactory.getLogger(MethodUtils.class);
// --------------------------------------------------------- Private Methods
/**
* Only log warning about accessibility work around once.
*
* 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.
*
* 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.
*
* 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).
*
* 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> cache = Collections
.synchronizedMap(new WeakHashMap>());
// --------------------------------------------------------- Public Methods
/**
* Set whether methods should be cached for greater performance or not,
* default is true
.
*
* @param cacheMethods
* true
if methods should be
* cached for greater performance, otherwise false
* @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;
}
/**
*
* Invoke a named method whose parameter type matches the object type.
*
*
*
* The behaviour of this method is less deterministic
* than invokeExactMethod()
.
* It loops through all methods with names that match
* and then executes the first it finds with compatible parameters.
*
*
*
* This method supports calls to methods taking primitive parameters
* via passing in wrapping classes. So, for example, a Boolean
class
* would match a boolean
primitive.
*
*
*
* This is a convenient wrapper for
* {@link #invokeMethod(Object object,String methodName,Object [] args)}.
*
*
* @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);
}
/**
*
* Invoke a named method whose parameter type matches the object type.
*
*
*
* 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.
*
*
*
* This method supports calls to methods taking primitive parameters
* via passing in wrapping classes. So, for example, a Boolean
class
* would match a boolean
primitive.
*
*
*
* This is a convenient wrapper for
* {@link #invokeMethod(Object object,String methodName,Object [] args,Class[] parameterTypes)}.
*
*
* @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);
}
/**
*
* Invoke a named method whose parameter type matches the object type.
*
*
*
* 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.
*
*
*
* This method supports calls to methods taking primitive parameters
* via passing in wrapping classes. So, for example, a Boolean
class
* would match a boolean
primitive.
*
*
*
* @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);
}
/**
*
* Invoke a method whose parameter type matches exactly the object
* type.
*
*
*
* This is a convenient wrapper for
* {@link #invokeExactMethod(Object object,String methodName,Object [] args)}.
*
*
* @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);
}
/**
*
* Invoke a method whose parameter types match exactly the object
* types.
*
*
*
* This uses reflection to invoke the method obtained from a call to
* getAccessibleMethod()
.
*
*
* @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);
}
/**
*
* Invoke a method whose parameter types match exactly the parameter
* types given.
*
*
*
* This uses reflection to invoke the method obtained from a call to
* getAccessibleMethod()
.
*
*
* @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);
}
/**
*
* Invoke a static method whose parameter types match exactly the parameter
* types given.
*
*
*
* This uses reflection to invoke the method obtained from a call to
* {@link #getAccessibleMethod(Class, String, Class[])}.
*
*
* @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);
}
/**
*
* Invoke a named static method whose parameter type matches the object type.
*
*
*
* 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.
*
*
*
* This method supports calls to methods taking primitive parameters
* via passing in wrapping classes. So, for example, a Boolean
class
* would match a boolean
primitive.
*
*
*
* This is a convenient wrapper for
* {@link #invokeStaticMethod(Class objectClass,String methodName,Object [] args)}.
*
*
* @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);
}
/**
*
* Invoke a named static method whose parameter type matches the object type.
*
*
*
* 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.
*
*
*
* This method supports calls to methods taking primitive parameters
* via passing in wrapping classes. So, for example, a Boolean
class
* would match a boolean
primitive.
*
*
*
* This is a convenient wrapper for
* {@link #invokeStaticMethod(Class objectClass,String methodName,Object [] args,Class[] parameterTypes)}.
*
*
* @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);
}
/**
*
* Invoke a named static method whose parameter type matches the object type.
*
*
*
* 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.
*
*
*
* This method supports calls to methods taking primitive parameters
* via passing in wrapping classes. So, for example, a Boolean
class
* would match a boolean
primitive.
*
*
*
* @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);
}
/**
*
* Invoke a static method whose parameter type matches exactly the object
* type.
*
*
*
* This is a convenient wrapper for
* {@link #invokeExactStaticMethod(Class objectClass,String methodName,Object [] args)}.
*
*
* @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);
}
/**
*
* Invoke a static method whose parameter types match exactly the object
* types.
*
*
*
* This uses reflection to invoke the method obtained from a call to
* {@link #getAccessibleMethod(Class, String, Class[])}.
*
*
* @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;
}
/**
*
* 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 null
.
* Basically, a convenience wrapper that constructs a Class
* array for you.
*
*
* @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);
}
/**
*
* 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 null
.
* This is just a convenient wrapper for
* {@link #getAccessibleMethod(Method method)}.
*
*
* @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);
}
}
/**
*
* 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 null
.
*
*
* @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);
}
/**
*
* 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 null
.
*
*
* @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
/**
*
* 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 null
.
*
*
* @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;
}
/**
*
* 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 null
.
*
*
*
* 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.
*
*
* @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 (Class> interface1 : interfaces){
// Is this interface public?
if (!Modifier.isPublic(interface1.getModifiers())){
continue;
}
// Does the method exist on this interface?
try{
method = interface1.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(interface1, methodName, parameterTypes);
if (method != null){
return method;
}
}
}
// We did not find anything
return (null);
}
/**
*
* 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.
*
*
*
* This method is slightly undeterministic since it loops
* through methods names and return the first matching method.
*
*
*
* This method is used by
* {@link
* #invokeMethod(Object object,String methodName,Object [] args,Class[] parameterTypes)}.
*
*
* This method can match primitive parameter by passing in wrapper classes.
* For example, a Boolean
will match a primitive boolean
* 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){
if (LOGGER.isTraceEnabled()){
LOGGER.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 (LOGGER.isTraceEnabled()){
LOGGER.trace("Found straight match: " + method);
LOGGER.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 (Method method2 : methods){
if (method2.getName().equals(methodName)){
// log some trace information
if (LOGGER.isTraceEnabled()){
LOGGER.trace("Found matching name:");
LOGGER.trace(method2.toString());
}
// 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 (LOGGER.isTraceEnabled()){
LOGGER.trace("Param=" + parameterTypes[n].getName());
LOGGER.trace("Method=" + methodsParams[n].getName());
}
if (!isAssignmentCompatible(methodsParams[n], parameterTypes[n])){
if (LOGGER.isTraceEnabled()){
LOGGER.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 (LOGGER.isTraceEnabled()){
LOGGER.trace(method + " accessible version of " + method2);
}
setMethodAccessible(method); // Default access superclass workaround
myCost = getTotalTransformationCost(parameterTypes, method.getParameterTypes());
if (myCost < bestMatchCost){
bestMatch = method;
bestMatchCost = myCost;
}
}
LOGGER.trace("Couldn't find accessible method.");
}
}
}
}
if (bestMatch != null){
cacheMethod(md, bestMatch);
}else{
// didn't find a match
LOGGER.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
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){
LOGGER.warn("Current Security Manager restricts use of workarounds for reflection bugs " + " in pre-1.4 JVMs.");
}
loggedAccessibleWarning = true;
}
LOGGER.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;
}
/**
*
* Determine whether a type can be used as a parameter in a method invocation.
* This method handles primitive conversions correctly.
*
*
*
* In order words, it will match a Boolean
to a boolean
,
* a Long
to a long
,
* a Float
to a float
,
* a Integer
to a int
,
* and a Double
to a double
.
* Now logic widening matches are allowed.
* For example, a Long
will not match a int
.
*
* @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 boolean.class
returns Boolean.class
*
* @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 Boolean.class
returns a boolean.class
.
*
* @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{
if (LOGGER.isDebugEnabled()){
LOGGER.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;
}
return clazz;
}
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 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));
}
}
}
/**
* 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;
this.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;
}
}
}