commons-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From pascalschumac...@apache.org
Subject [1/2] [lang] LANG-1195: Enhance MethodUtils to allow invocation of private methods (closes #141)
Date Sun, 05 Jun 2016 18:52:46 GMT
Repository: commons-lang
Updated Branches:
  refs/heads/master 078e512e6 -> de0819cb8


LANG-1195: Enhance MethodUtils to allow invocation of private methods (closes #141)


Project: http://git-wip-us.apache.org/repos/asf/commons-lang/repo
Commit: http://git-wip-us.apache.org/repos/asf/commons-lang/commit/5fef9575
Tree: http://git-wip-us.apache.org/repos/asf/commons-lang/tree/5fef9575
Diff: http://git-wip-us.apache.org/repos/asf/commons-lang/diff/5fef9575

Branch: refs/heads/master
Commit: 5fef9575646f6583fd2d9ee01368b3deefe6ce82
Parents: 078e512
Author: Derek Ashmore <dashmore@force66.com>
Authored: Sat Jun 4 08:53:29 2016 -0500
Committer: pascalschumacher <pascalschumacher@gmx.net>
Committed: Sun Jun 5 20:42:52 2016 +0200

----------------------------------------------------------------------
 .../commons/lang3/reflect/MethodUtils.java      | 199 ++++++++++++++++++-
 .../commons/lang3/reflect/MethodUtilsTest.java  |  63 +++++-
 2 files changed, 250 insertions(+), 12 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/commons-lang/blob/5fef9575/src/main/java/org/apache/commons/lang3/reflect/MethodUtils.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/commons/lang3/reflect/MethodUtils.java b/src/main/java/org/apache/commons/lang3/reflect/MethodUtils.java
index c938cb6..296a2db 100644
--- a/src/main/java/org/apache/commons/lang3/reflect/MethodUtils.java
+++ b/src/main/java/org/apache/commons/lang3/reflect/MethodUtils.java
@@ -93,6 +93,29 @@ public class MethodUtils {
             IllegalAccessException, InvocationTargetException {
         return invokeMethod(object, methodName, ArrayUtils.EMPTY_OBJECT_ARRAY, null);
     }
+    
+    /**
+     * <p>Invokes a named method without parameters.</p>
+     *
+     * <p>This is a convenient wrapper for
+     * {@link #invokeMethod(Object object,boolean forceAccess,String methodName, Object[]
args, Class[] parameterTypes)}.
+     * </p>
+     *
+     * @param object invoke method on this object
+     * @param forceAccess force access to invoke method even if it's not accessible
+     * @param methodName get method with this name
+     * @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 3.5
+     */
+    public static Object invokeMethod(final Object object, final boolean forceAccess, final
String methodName) 
+    		throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
+        return invokeMethod(object, forceAccess, methodName, ArrayUtils.EMPTY_OBJECT_ARRAY,
null);
+    }
 
     /**
      * <p>Invokes a named method whose parameter type matches the object type.</p>
@@ -123,17 +146,46 @@ public class MethodUtils {
         final Class<?>[] parameterTypes = ClassUtils.toClass(args);
         return invokeMethod(object, methodName, args, parameterTypes);
     }
-
+    
     /**
      * <p>Invokes a named method whose parameter type matches the object type.</p>
      *
-     * <p>This method delegates the method search to {@link #getMatchingAccessibleMethod(Class,
String, Class[])}.</p>
+     * <p>This method supports calls to methods taking primitive parameters 
+     * via passing in wrapping classes. So, for example, a {@code Boolean} object
+     * would match a {@code boolean} primitive.</p>
+     *
+     * <p>This is a convenient wrapper for
+     * {@link #invokeMethod(Object object,boolean forceAccess,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
+     * @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 3.5
+     */
+    public static Object invokeMethod(final Object object, final boolean forceAccess, final
String methodName,
+            Object... args) throws NoSuchMethodException,
+            IllegalAccessException, InvocationTargetException {
+        args = ArrayUtils.nullToEmpty(args);
+        final Class<?>[] parameterTypes = ClassUtils.toClass(args);
+        return invokeMethod(object, forceAccess, methodName, args, parameterTypes);
+    }
+
+    /**
+     * <p>Invokes a named method whose parameter type matches the object type.</p>
      *
      * <p>This method supports calls to methods taking primitive parameters 
      * via passing in wrapping classes. So, for example, a {@code Boolean} object
      * would match a {@code boolean} primitive.</p>
      *
      * @param object invoke method on this object
+     * @param forceAccess force access to invoke method even if it's not accessible
      * @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
@@ -143,21 +195,77 @@ public class MethodUtils {
      * @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,
+    public static Object invokeMethod(final Object object, final boolean forceAccess, final
String methodName,
             Object[] args, Class<?>[] parameterTypes)
             throws NoSuchMethodException, IllegalAccessException,
             InvocationTargetException {
         parameterTypes = ArrayUtils.nullToEmpty(parameterTypes);
         args = ArrayUtils.nullToEmpty(args);
-        final Method method = getMatchingAccessibleMethod(object.getClass(),
-                methodName, parameterTypes);
-        if (method == null) {
-            throw new NoSuchMethodException("No such accessible method: "
-                    + methodName + "() on object: "
-                    + object.getClass().getName());
+        
+        final String messagePrefix;
+        Method method = null;
+        boolean isOriginallyAccessible = false;
+        Object result = null;
+        
+        try {
+            if (forceAccess) {
+            	messagePrefix = "No such method: ";
+            	method = getMatchingMethod(object.getClass(),
+                        methodName, parameterTypes);
+            	if (method != null) {
+            	    isOriginallyAccessible = method.isAccessible();
+            	    if (!isOriginallyAccessible) {
+            	        method.setAccessible(true);
+            	    }
+            	}
+            }  else {
+            	messagePrefix = "No such accessible method: ";
+            	method = getMatchingAccessibleMethod(object.getClass(),
+                        methodName, parameterTypes);
+            }
+            
+            if (method == null) {
+                throw new NoSuchMethodException(messagePrefix
+                        + methodName + "() on object: "
+                        + object.getClass().getName());
+            }
+            args = toVarArgs(method, args);
+            
+            result = method.invoke(object, args);
         }
-        args = toVarArgs(method, args);
-        return method.invoke(object, args);
+        finally {
+            if (method != null && forceAccess && method.isAccessible() !=
isOriginallyAccessible) {
+                method.setAccessible(isOriginallyAccessible);
+            }
+        }
+        
+        return result;
+    }
+    
+    /**
+     * <p>Invokes a named method whose parameter type matches the object type.</p>
+     *
+     * <p>This method delegates the method search to {@link #getMatchingAccessibleMethod(Class,
String, Class[])}.</p>
+     *
+     * <p>This method supports calls to methods taking primitive parameters 
+     * via passing in wrapping classes. So, for example, a {@code Boolean} object
+     * 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
+     * @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 {
+    	return invokeMethod(object, false, methodName, args, parameterTypes);
     }
 
     /**
@@ -604,6 +712,75 @@ public class MethodUtils {
         }
         return bestMatch;
     }
+    
+    /**
+     * <p>Retrieves a method whether or not it's accessible. If no such method
+     * can be found, return {@code null}.</p>
+     * @param cls The class that will be subjected to the method search
+     * @param methodName The method that we wish to call
+     * @param parameterTypes Argument class types
+     * @return The method
+     * 
+     * @since 3.5
+     */
+    public static Method getMatchingMethod(final Class<?> cls, final String methodName,
+            final Class<?>... parameterTypes) {
+    	Validate.notNull(cls, "Null class not allowed.");
+    	Validate.notEmpty(methodName, "Null or blank methodName not allowed.");
+    	
+    	// Address methods in superclasses
+    	Method[] methodArray = cls.getDeclaredMethods();
+    	List<Class<?>> superclassList = ClassUtils.getAllSuperclasses(cls);
+    	for (Class<?> klass: superclassList) {
+    		methodArray = ArrayUtils.addAll(methodArray, klass.getDeclaredMethods());
+    	}
+    	
+    	Method inexactMatch = null;
+    	for (Method method: methodArray) {
+    		if (methodName.equals(method.getName()) && 
+    				ArrayUtils.isEquals(parameterTypes, method.getParameterTypes())) {
+    			return method;
+    		} else if (methodName.equals(method.getName()) &&  
+    				ClassUtils.isAssignable(parameterTypes, method.getParameterTypes(), true)) {
+    			if (inexactMatch == null) {
+    				inexactMatch = method;
+    			} else if (distance(parameterTypes, method.getParameterTypes()) 
+    					< distance(parameterTypes, inexactMatch.getParameterTypes())) {
+    				inexactMatch = method;
+    			}
+    		}
+    		
+    	}
+    	return inexactMatch;
+    }
+    
+    /**
+     * <p>Returns the aggregate number of inheritance hops between assignable argument
class types.  Returns -1
+     * if the arguments aren't assignable.  Fills a specific purpose for getMatchingMethod
and is not generalized.</p>
+     * @param classArray
+     * @param toClassArray
+     * @return the aggregate number of inheritance hops between assignable argument class
types.
+     */
+    private static int distance(Class<?>[] classArray, Class<?>[] toClassArray)
{
+    	int answer=0;
+    	
+    	if (!ClassUtils.isAssignable(classArray, toClassArray, true)) {
+    		return -1;
+    	}
+    	for (int offset = 0; offset < classArray.length; offset++) {
+    		// Note InheritanceUtils.distance() uses different scoring system.
+    		if (classArray[offset].equals(toClassArray[offset])) {
+    			continue;
+    		} else if (ClassUtils.isAssignable(classArray[offset], toClassArray[offset], true)

+    				&& !ClassUtils.isAssignable(classArray[offset], toClassArray[offset], false))
{
+    			answer++;
+    		} else {
+    			answer = answer+2;
+    		}
+    	}
+    	
+    	return answer;
+    }
 
     /**
      * Get the hierarchy of overridden methods down to {@code result} respecting generics.

http://git-wip-us.apache.org/repos/asf/commons-lang/blob/5fef9575/src/test/java/org/apache/commons/lang3/reflect/MethodUtilsTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/commons/lang3/reflect/MethodUtilsTest.java b/src/test/java/org/apache/commons/lang3/reflect/MethodUtilsTest.java
index c7c3a69..ec755f2 100644
--- a/src/test/java/org/apache/commons/lang3/reflect/MethodUtilsTest.java
+++ b/src/test/java/org/apache/commons/lang3/reflect/MethodUtilsTest.java
@@ -32,12 +32,14 @@ import static org.junit.Assert.fail;
 import java.lang.reflect.Method;
 import java.lang.reflect.Type;
 import java.util.Arrays;
+import java.util.Date;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 
 import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.ClassUtils;
 import org.apache.commons.lang3.ClassUtils.Interfaces;
 import org.apache.commons.lang3.math.NumberUtils;
 import org.apache.commons.lang3.mutable.Mutable;
@@ -101,11 +103,41 @@ public class MethodUtilsTest {
         public static void oneParameterStatic(final String s) {
             // empty
         }
-
+        
         @SuppressWarnings("unused")
         private void privateStuff() {
         }
 
+        @SuppressWarnings("unused")
+        private String privateStringStuff() {
+        	return "privateStringStuff()";
+        }
+        
+        @SuppressWarnings("unused")
+        private String privateStringStuff(final int i) {
+        	return "privateStringStuff(int)";
+        }
+        
+        @SuppressWarnings("unused")
+        private String privateStringStuff(final Integer i) {
+        	return "privateStringStuff(Integer)";
+        }
+        
+        @SuppressWarnings("unused")
+        private String privateStringStuff(final double d) {
+        	return "privateStringStuff(double)";
+        }
+        
+        @SuppressWarnings("unused")
+        private String privateStringStuff(final String s) {
+        	return "privateStringStuff(String)";
+        }
+        
+        @SuppressWarnings("unused")
+        private String privateStringStuff(final Object s) {
+        	return "privateStringStuff(Object)";
+        }
+
 
         public String foo() {
             return "foo()";
@@ -728,4 +760,33 @@ public class MethodUtilsTest {
         int[] actual = (int[])MethodUtils.invokeMethod(testBean, "unboxing", Integer.valueOf(1),
Integer.valueOf(2));
         Assert.assertArrayEquals(new int[]{1, 2}, actual);
     }
+    
+    @Test
+    public void testInvokeMethodForceAccessNoArgs() throws Exception {
+        Method privateStringStuffMethod = MethodUtils.getMatchingMethod(TestBean.class, "privateStringStuff");
+        Assert.assertFalse(privateStringStuffMethod.isAccessible());
+        Assert.assertEquals("privateStringStuff()", MethodUtils.invokeMethod(testBean, true,
"privateStringStuff"));
+        Assert.assertFalse(privateStringStuffMethod.isAccessible());
+    }
+    
+    @Test
+    public void testInvokeMethodForceAccessWithArgs() throws Exception {
+        Assert.assertEquals("privateStringStuff(Integer)", MethodUtils.invokeMethod(testBean,
true, "privateStringStuff", 5));
+        Assert.assertEquals("privateStringStuff(double)", MethodUtils.invokeMethod(testBean,
true, "privateStringStuff", 5.0d));
+        Assert.assertEquals("privateStringStuff(String)", MethodUtils.invokeMethod(testBean,
true, "privateStringStuff", "Hi There"));
+        Assert.assertEquals("privateStringStuff(Object)", MethodUtils.invokeMethod(testBean,
true, "privateStringStuff", new Date()));
+    }
+    
+    @Test
+    public void testDistance() throws Exception {
+        Method distanceMethod = MethodUtils.getMatchingMethod(MethodUtils.class, "distance",
Class[].class, Class[].class);
+        distanceMethod.setAccessible(true);
+        
+        Assert.assertEquals(-1, distanceMethod.invoke(null, new Class[]{String.class}, new
Class[]{Date.class}));
+        Assert.assertEquals(0, distanceMethod.invoke(null, new Class[]{Date.class}, new Class[]{Date.class}));
+        Assert.assertEquals(1, distanceMethod.invoke(null, new Class[]{Integer.class}, new
Class[]{ClassUtils.wrapperToPrimitive(Integer.class)}));
+        Assert.assertEquals(2, distanceMethod.invoke(null, new Class[]{Integer.class}, new
Class[]{Object.class}));
+        
+        distanceMethod.setAccessible(false);
+    }
 }


Mime
View raw message