MethodUtils.java
- /*
- * 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 org.apache.commons.beanutils2;
- 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.Arrays;
- import java.util.Collections;
- import java.util.Map;
- import java.util.Objects;
- import java.util.WeakHashMap;
- import org.apache.commons.lang3.SystemProperties;
- 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>
- *
- * <h2>Known Limitations</h2>
- * <h3>Accessing Public Methods In A Default Access Superclass</h3>
- * <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} is thrown if the method is invoked.
- * </p>
- *
- * <p>
- * {@code MethodUtils} contains a workaround for this situation. It will attempt to call {@code 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 privileges. If this call fails then a warning will
- * be logged and the method may fail.
- * </p>
- */
- public final class MethodUtils {
- /**
- * Represents the key to looking up a Method by reflection.
- */
- private static final 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, final Class<?>[] paramTypes, final boolean exact) {
- this.cls = Objects.requireNonNull(cls, "cls");
- this.methodName = Objects.requireNonNull(methodName, "methodName");
- this.paramTypes = paramTypes != null ? paramTypes : BeanUtils.EMPTY_CLASS_ARRAY;
- 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) && Arrays.equals(paramTypes, md.paramTypes);
- }
- /**
- * Returns the string length of method name. I.e. if the hash codes are different, the objects are different. If the hash codes 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;
- }
- }
- private static final Log LOG = LogFactory.getLog(MethodUtils.class);
- /**
- * 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;
- /**
- * 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;
- /**
- * 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, that is, 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 class
- * loaders 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<>());
- /**
- * 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 && method != null) {
- cache.put(md, new WeakReference<>(method));
- }
- }
- /**
- * 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>
- * 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}.
- * </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 {
- if (!method.getDeclaringClass().isAssignableFrom(clazz)) {
- throw new IllegalArgumentException(clazz.getName() + " is not assignable from " + method.getDeclaringClass().getName());
- }
- sameClass = clazz.equals(method.getDeclaringClass());
- }
- // 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;
- }
- /**
- * <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}. Basically, a convenience wrapper that constructs a {@code Class} 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}. 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}.
- * </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, by scanning through all implemented
- * interfaces and subinterfaces. If no such method can be found, return {@code null}.
- * </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 (final Class<?> anInterface : interfaces) {
- // Is this interface public?
- if (!Modifier.isPublic(anInterface.getModifiers())) {
- continue;
- }
- // Does the method exist on this interface?
- try {
- method = anInterface.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(anInterface, methodName, parameterTypes);
- if (method != null) {
- return method;
- }
- }
- }
- // We did not find anything
- return null;
- }
- /**
- * <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}.
- * </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;
- }
- /**
- * Gets 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;
- }
- /**
- * <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 indeterministic 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}
- * 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
- 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;
- }
- /**
- * 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 traveled 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;
- }
- /**
- * 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}.
- *
- * @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;
- }
- if (Float.class.equals(wrapperType)) {
- return float.class;
- }
- if (Long.class.equals(wrapperType)) {
- return long.class;
- }
- if (Integer.class.equals(wrapperType)) {
- return int.class;
- }
- if (Short.class.equals(wrapperType)) {
- return short.class;
- }
- if (Byte.class.equals(wrapperType)) {
- return byte.class;
- }
- if (Double.class.equals(wrapperType)) {
- return double.class;
- }
- if (Character.class.equals(wrapperType)) {
- return char.class;
- }
- if (LOG.isDebugEnabled()) {
- LOG.debug("Not a known primitive wrapper class: " + wrapperType);
- }
- return null;
- }
- /**
- * Gets the wrapper object class for the given primitive type class. For example, passing {@code boolean.class</code> returns <code>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;
- }
- if (float.class.equals(primitiveType)) {
- return Float.class;
- }
- if (long.class.equals(primitiveType)) {
- return Long.class;
- }
- if (int.class.equals(primitiveType)) {
- return Integer.class;
- }
- if (short.class.equals(primitiveType)) {
- return Short.class;
- }
- if (byte.class.equals(primitiveType)) {
- return Byte.class;
- }
- if (double.class.equals(primitiveType)) {
- return Double.class;
- }
- if (char.class.equals(primitiveType)) {
- return Character.class;
- }
- return null;
- }
- /**
- * 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;
- }
- /**
- * <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()}.
- * </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 = BeanUtils.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()}.
- * </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 = BeanUtils.EMPTY_OBJECT_ARRAY;
- }
- if (parameterTypes == null) {
- parameterTypes = BeanUtils.EMPTY_CLASS_ARRAY;
- }
- 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 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 = BeanUtils.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);
- }
- /**
- * <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 = BeanUtils.EMPTY_OBJECT_ARRAY;
- }
- if (parameterTypes == null) {
- parameterTypes = BeanUtils.EMPTY_CLASS_ARRAY;
- }
- 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 method whose parameter type matches the object type.
- * </p>
- *
- * <p>
- * The behavior of this method is less deterministic than {@code invokeExactMethod()}. 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} class would match a
- * {@code boolean} 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 behavior 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} class would match a
- * {@code boolean} 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 = BeanUtils.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 behavior 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} class would match a
- * {@code boolean} 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 = BeanUtils.EMPTY_CLASS_ARRAY;
- }
- if (args == null) {
- args = BeanUtils.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 named static method whose parameter type matches the object type.
- * </p>
- *
- * <p>
- * The behavior 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} class would match a
- * {@code boolean} 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 behavior 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} class would match a
- * {@code boolean} 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 = BeanUtils.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 behavior 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} class would match a
- * {@code boolean} 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 = BeanUtils.EMPTY_CLASS_ARRAY;
- }
- if (args == null) {
- args = BeanUtils.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>
- * 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},
- * a {@code Long</code> to a <code>long},
- * a {@code Float</code> to a <code>float},
- * a {@code Integer</code> to a <code>int},
- * and a {@code Double</code> to a <code>double}.
- * Now logic widening matches are allowed.
- * For example, a {@code Long</code> will not match a <code>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 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 behavior?
- final Class<?> parameterWrapperClazz = getPrimitiveWrapper(parameterType);
- if (parameterWrapperClazz != null) {
- return parameterWrapperClazz.equals(parameterization);
- }
- }
- return false;
- }
- /**
- * Sets whether methods should be cached for greater performance or not, default is {@code true}.
- *
- * @param cacheMethods {@code true} if methods should be cached for greater performance, otherwise {@code false}
- * @since 1.8.0
- */
- public static synchronized void setCacheMethods(final boolean cacheMethods) {
- CACHE_METHODS = cacheMethods;
- if (!CACHE_METHODS) {
- clearCache();
- }
- }
- /**
- * 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
- // modifier is public.
- //
- // The following workaround solves the problem but will only
- // work from sufficiently privileges code.
- //
- // Better workarounds would be gratefully 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 = SystemProperties.getJavaSpecificationVersion();
- 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);
- }
- }
- private static Object[] toArray(final Object arg) {
- Object[] args = null;
- if (arg != null) {
- args = new Object[] { arg };
- }
- return args;
- }
- /**
- * 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;
- }
- private MethodUtils() {
- // empty
- }
- }