package com.comphenix.protocol.reflect; /* * 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. */ 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 java.util.logging.Logger; import org.bukkit.Bukkit; /** *
Utility reflection methods focussed on methods in general rather than properties in particular.
* *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.
* 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.
*/
@SuppressWarnings("unchecked")
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(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() {
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 compatable 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 * @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( Object object, String methodName, Object arg) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { Object[] args = {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 compatable 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 * @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( Object object, String methodName, Object[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { if (args == null) { args = EMPTY_OBJECT_ARRAY; } int arguments = args.length; 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 compatable 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.
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 * @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( Object object, String methodName, Object arg) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { Object[] args = {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()
.
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()
.
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 * @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( Class objectClass, String methodName, Object[] args, Class[] parameterTypes) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { if (args == null) { args = EMPTY_OBJECT_ARRAY; } if (parameterTypes == null) { parameterTypes = EMPTY_CLASS_PARAMETERS; } 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 compatable 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 * @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( Class objectClass, String methodName, Object arg) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { Object[] args = {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 compatable 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 * @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( Class objectClass, String methodName, Object[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { if (args == null) { args = EMPTY_OBJECT_ARRAY; } int arguments = args.length; 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 compatable 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.
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 * @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( Class objectClass, String methodName, Object arg) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { Object[] args = {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 * @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( Class objectClass, String methodName, Object[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { if (args == null) { args = EMPTY_OBJECT_ARRAY; } int arguments = args.length; Class[] parameterTypes = new Class[arguments]; for (int i = 0; i < arguments; i++) { parameterTypes[i] = args[i].getClass(); } return invokeExactStaticMethod(objectClass, methodName, args, 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)}.
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
.
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
.
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
.
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 */ @SuppressWarnings("unchecked") private static Method getAccessibleMethodFromInterfaceNest (Class clazz, String methodName, Class[] parameterTypes) { Method method = null; // Search up the superclass chain for (; clazz != null; clazz = clazz.getSuperclass()) { // Check the implemented interfaces of the parent class 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 (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); } /** *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 undeterminstic 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
*/
@SuppressWarnings("unchecked")
public static Method getMatchingAccessibleMethod(
Class clazz,
String methodName,
Class[] parameterTypes) {
MethodDescriptor md = new MethodDescriptor(clazz, methodName, parameterTypes, false);
Logger log = tryGetLogger();
// 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);
setMethodAccessible(method); // Default access superclass workaround
cacheMethod(md, method);
return method;
} catch (NoSuchMethodException e) { /* SWALLOW */ }
// search through all methods
int paramSize = parameterTypes.length;
Method bestMatch = null;
Method[] methods = clazz.getMethods();
float bestMatchCost = Float.MAX_VALUE;
float myCost = Float.MAX_VALUE;
for (int i = 0, size = methods.length; i < size ; i++) {
if (methods[i].getName().equals(methodName)) {
// compare parameters
Class[] methodsParams = methods[i].getParameterTypes();
int methodParamSize = methodsParams.length;
if (methodParamSize == paramSize) {
boolean match = true;
for (int n = 0 ; n < methodParamSize; n++) {
if (!isAssignmentCompatible(methodsParams[n], parameterTypes[n])) {
match = false;
break;
}
}
if (match) {
// get accessible version of method
Method method = getAccessibleMethod(clazz, methods[i]);
if (method != null) {
setMethodAccessible(method); // Default access superclass workaround
myCost = getTotalTransformationCost(parameterTypes,method.getParameterTypes());
if ( myCost < bestMatchCost ) {
bestMatch = method;
bestMatchCost = myCost;
}
}
if (log != null) {
//log.severe("Couldn't find accessible method.");
}
}
}
}
}
if ( bestMatch != null ){
cacheMethod(md, bestMatch);
} else {
if (log != null) {
log.severe("No match found.");
}
}
return bestMatch;
}
/**
* Attempt to get the default logger from Bukkit.
* @return Bukkit default logger.
*/
private static Logger tryGetLogger() {
try {
return Bukkit.getLogger();
} catch (Exception e) {
return null;
}
}
/**
* Try to make the method accessible
* @param method The source arguments
*/
private static void setMethodAccessible(Method method) {
try {
//
//
// 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 (SecurityException se) {
if (!loggedAccessibleWarning) {
boolean vulnerableJVM = false;
try {
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 (SecurityException e) {
// don't know - so display warning
vulnerableJVM = true;
}
if (vulnerableJVM && tryGetLogger() != null) {
tryGetLogger().info("Vulnerable JVM!");
}
loggedAccessibleWarning = true;
}
}
}
/**
* 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(Class[] srcArgs, 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, Class destClass) {
float cost = 0.0f;
while (destClass != null && !destClass.equals(srcClass)) {
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++;
destClass = destClass.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 (destClass == 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 assignement is compatible.
*/
@SuppressWarnings("unchecked")
public static final boolean isAssignmentCompatible(Class parameterType, 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?
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(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(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 {
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(Class clazz) {
if (clazz.isPrimitive()) {
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(MethodDescriptor md) {
if (CACHE_METHODS) {
Reference methodRef = (Reference)cache.get(md);
if (methodRef != null) {
return (Method)methodRef.get();
}
}
return null;
}
/**
* Add a method to the cache.
*
* @param md The method descriptor
* @param method The method to cache
*/
@SuppressWarnings("unchecked")
private static void cacheMethod(MethodDescriptor md, 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 Class cls;
private String methodName;
private Class[] paramTypes;
private boolean exact;
private 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 paramater types
* @param exact whether the match has to be exact.
*/
public MethodDescriptor(Class cls, String methodName, Class[] paramTypes, 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(Object obj) {
if (!(obj instanceof MethodDescriptor)) {
return false;
}
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;
}
}
}