You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by pa...@apache.org on 2016/06/05 18:52:46 UTC
[1/2] [lang] LANG-1195: Enhance MethodUtils to allow invocation of
private methods (closes #141)
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 <da...@force66.com>
Authored: Sat Jun 4 08:53:29 2016 -0500
Committer: pascalschumacher <pa...@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);
+ }
}
[2/2] [lang] LANG-1195: add changes.xml entry
Posted by pa...@apache.org.
LANG-1195: add changes.xml entry
Project: http://git-wip-us.apache.org/repos/asf/commons-lang/repo
Commit: http://git-wip-us.apache.org/repos/asf/commons-lang/commit/de0819cb
Tree: http://git-wip-us.apache.org/repos/asf/commons-lang/tree/de0819cb
Diff: http://git-wip-us.apache.org/repos/asf/commons-lang/diff/de0819cb
Branch: refs/heads/master
Commit: de0819cb86fcd597961b99fd8cb71b8b74fd8cec
Parents: 5fef957
Author: pascalschumacher <pa...@gmx.net>
Authored: Sun Jun 5 20:47:18 2016 +0200
Committer: pascalschumacher <pa...@gmx.net>
Committed: Sun Jun 5 20:47:18 2016 +0200
----------------------------------------------------------------------
src/changes/changes.xml | 1 +
1 file changed, 1 insertion(+)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/commons-lang/blob/de0819cb/src/changes/changes.xml
----------------------------------------------------------------------
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 6c10abf..1b4ad50 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -46,6 +46,7 @@ The <action> type attribute can be add,update,fix,remove.
<body>
<release version="3.5" date="tba" description="tba">
+ <action issue="LANG-1195" type="fix" dev="pschumacher" due-to="Derek C. Ashmore">Enhance MethodUtils to allow invocation of private methods</action>
<action issue="LANG-1199" type="fix" dev="pschumacher" due-to="M. Steiger">Fix implementation of StringUtils.getJaroWinklerDistance()</action>
<action issue="LANG-1244" type="fix" dev="pschumacher" due-to="jjbankert">Fix dead links in StringUtils.getLevenshteinDistance() javadoc</action>
<action issue="LANG-1242" type="fix" dev="pschumacher" due-to="Neal Stewart">"\u2284":"⊄" mapping missing from EntityArrays#HTML40_EXTENDED_ESCAPE</action>