You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by ch...@apache.org on 2016/04/24 05:23:22 UTC
[lang] LANG-1115: Add support for varargs in ConstructorUtils,
MemberUtils, and MethodUtils This closes #89 from github.
Repository: commons-lang
Updated Branches:
refs/heads/master 77d187eef -> 5e62bf80f
LANG-1115: Add support for varargs in ConstructorUtils, MemberUtils, and MethodUtils
This closes #89 from github.
Project: http://git-wip-us.apache.org/repos/asf/commons-lang/repo
Commit: http://git-wip-us.apache.org/repos/asf/commons-lang/commit/5e62bf80
Tree: http://git-wip-us.apache.org/repos/asf/commons-lang/tree/5e62bf80
Diff: http://git-wip-us.apache.org/repos/asf/commons-lang/diff/5e62bf80
Branch: refs/heads/master
Commit: 5e62bf80f345ff28d494c2b407a9e8691a9fb684
Parents: 77d187e
Author: Chas Honton <ch...@apache.org>
Authored: Sat Apr 23 20:22:08 2016 -0700
Committer: Chas Honton <ch...@apache.org>
Committed: Sat Apr 23 20:22:08 2016 -0700
----------------------------------------------------------------------
src/changes/changes.xml | 1 +
.../org/apache/commons/lang3/ArrayUtils.java | 33 +++
.../commons/lang3/reflect/ConstructorUtils.java | 10 +-
.../commons/lang3/reflect/MemberUtils.java | 143 +++++++++++-
.../commons/lang3/reflect/MethodUtils.java | 69 +++++-
.../apache/commons/lang3/ArrayUtilsTest.java | 21 +-
.../lang3/reflect/ConstructorUtilsTest.java | 56 ++++-
.../commons/lang3/reflect/MethodUtilsTest.java | 222 +++++++++++++++++--
8 files changed, 513 insertions(+), 42 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/commons-lang/blob/5e62bf80/src/changes/changes.xml
----------------------------------------------------------------------
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 9a25607..f18253d 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -22,6 +22,7 @@
<body>
<release version="3.5" date="tba" description="tba">
+ <action issue="LANG-1115" type="add" dev="chas" due-to="Jim Lloyd, Joe Ferner">Add support for varargs in ConstructorUtils, MemberUtils, and MethodUtils</action>
<action issue="LANG-1134" type="add" dev="chas" due-to="Alan Smithee">New methods for lang3.Validate</action>
<action issue="LANG-1222" type="fix" dev="ggregory" due-to="Adam J.">Fix for incorrect comment on StringUtils.containsIgnoreCase method</action>
<action issue="LANG-1221" type="fix" dev="ggregory" due-to="Pierre Templier">Fix typo on appendIfMissing javadoc</action>
http://git-wip-us.apache.org/repos/asf/commons-lang/blob/5e62bf80/src/main/java/org/apache/commons/lang3/ArrayUtils.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/commons/lang3/ArrayUtils.java b/src/main/java/org/apache/commons/lang3/ArrayUtils.java
index e8a327e..efd9c4a 100644
--- a/src/main/java/org/apache/commons/lang3/ArrayUtils.java
+++ b/src/main/java/org/apache/commons/lang3/ArrayUtils.java
@@ -4692,6 +4692,39 @@ public class ArrayUtils {
return result;
}
+ /**
+ * <p>Create an array of primitive type from an array of wrapper types.
+ *
+ * <p>This method returns {@code null} for a {@code null} input array.
+ *
+ * @param array an array of wrapper object
+ * @return an array of the corresponding primitive type, or the original array
+ * @since 3.5
+ */
+ public static Object toPrimitive(final Object array) {
+ if (array == null) {
+ return null;
+ }
+ Class<?> ct = array.getClass().getComponentType();
+ Class<?> pt = ClassUtils.wrapperToPrimitive(ct);
+ if(Integer.TYPE.equals(pt)) {
+ return toPrimitive((Integer[]) array);
+ }
+ if(Long.TYPE.equals(pt)) {
+ return toPrimitive((Long[]) array);
+ }
+ if(Short.TYPE.equals(pt)) {
+ return toPrimitive((Short[]) array);
+ }
+ if(Double.TYPE.equals(pt)) {
+ return toPrimitive((Double[]) array);
+ }
+ if(Float.TYPE.equals(pt)) {
+ return toPrimitive((Float[]) array);
+ }
+ return array;
+ }
+
// Boolean array converters
// ----------------------------------------------------------------------
/**
http://git-wip-us.apache.org/repos/asf/commons-lang/blob/5e62bf80/src/main/java/org/apache/commons/lang3/reflect/ConstructorUtils.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/commons/lang3/reflect/ConstructorUtils.java b/src/main/java/org/apache/commons/lang3/reflect/ConstructorUtils.java
index 7d43694..aa6a6ba 100644
--- a/src/main/java/org/apache/commons/lang3/reflect/ConstructorUtils.java
+++ b/src/main/java/org/apache/commons/lang3/reflect/ConstructorUtils.java
@@ -114,6 +114,10 @@ public class ConstructorUtils {
throw new NoSuchMethodException(
"No such accessible constructor on object: " + cls.getName());
}
+ if (ctor.isVarArgs()) {
+ Class<?>[] methodParameterTypes = ctor.getParameterTypes();
+ args = MethodUtils.getVarArgs(args, methodParameterTypes);
+ }
return ctor.newInstance(args);
}
@@ -258,14 +262,12 @@ public class ConstructorUtils {
// return best match:
for (Constructor<?> ctor : ctors) {
// compare parameters
- if (ClassUtils.isAssignable(parameterTypes, ctor.getParameterTypes(), true)) {
+ if (MemberUtils.isMatchingConstructor(ctor, parameterTypes)) {
// get accessible version of constructor
ctor = getAccessibleConstructor(ctor);
if (ctor != null) {
MemberUtils.setAccessibleWorkaround(ctor);
- if (result == null
- || MemberUtils.compareParameterTypes(ctor.getParameterTypes(), result
- .getParameterTypes(), parameterTypes) < 0) {
+ if (result == null || MemberUtils.compareConstructorFit(ctor, result, parameterTypes) < 0) {
// temporary variable for annotation, see comment above (1)
@SuppressWarnings("unchecked")
final
http://git-wip-us.apache.org/repos/asf/commons-lang/blob/5e62bf80/src/main/java/org/apache/commons/lang3/reflect/MemberUtils.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/commons/lang3/reflect/MemberUtils.java b/src/main/java/org/apache/commons/lang3/reflect/MemberUtils.java
index 04c3637..3922bb4 100644
--- a/src/main/java/org/apache/commons/lang3/reflect/MemberUtils.java
+++ b/src/main/java/org/apache/commons/lang3/reflect/MemberUtils.java
@@ -17,7 +17,9 @@
package org.apache.commons.lang3.reflect;
import java.lang.reflect.AccessibleObject;
+import java.lang.reflect.Constructor;
import java.lang.reflect.Member;
+import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import org.apache.commons.lang3.ClassUtils;
@@ -85,18 +87,52 @@ abstract class MemberUtils {
}
/**
- * Compares the relative fitness of two sets of parameter types in terms of
- * matching a third set of runtime parameter types, such that a list ordered
+ * Compares the relative fitness of two Constructors in terms of how well they
+ * match a set of runtime parameter types, such that a list ordered
* by the results of the comparison would return the best match first
* (least).
*
- * @param left the "left" parameter set
- * @param right the "right" parameter set
+ * @param left the "left" Constructor
+ * @param right the "right" Constructor
* @param actual the runtime parameter types to match against
* {@code left}/{@code right}
* @return int consistent with {@code compare} semantics
+ * @since 3.5
*/
- static int compareParameterTypes(final Class<?>[] left, final Class<?>[] right, final Class<?>[] actual) {
+ static int compareConstructorFit(final Constructor<?> left, final Constructor<?> right, final Class<?>[] actual) {
+ return compareParameterTypes(Executable.of(left), Executable.of(right), actual);
+ }
+
+ /**
+ * Compares the relative fitness of two Methods in terms of how well they
+ * match a set of runtime parameter types, such that a list ordered
+ * by the results of the comparison would return the best match first
+ * (least).
+ *
+ * @param left the "left" Method
+ * @param right the "right" Method
+ * @param actual the runtime parameter types to match against
+ * {@code left}/{@code right}
+ * @return int consistent with {@code compare} semantics
+ * @since 3.5
+ */
+ static int compareMethodFit(final Method left, final Method right, final Class<?>[] actual) {
+ return compareParameterTypes(Executable.of(left), Executable.of(right), actual);
+ }
+
+ /**
+ * Compares the relative fitness of two Executables in terms of how well they
+ * match a set of runtime parameter types, such that a list ordered
+ * by the results of the comparison would return the best match first
+ * (least).
+ *
+ * @param left the "left" Executable
+ * @param right the "right" Executable
+ * @param actual the runtime parameter types to match against
+ * {@code left}/{@code right}
+ * @return int consistent with {@code compare} semantics
+ */
+ private static int compareParameterTypes(final Executable left, final Executable right, final Class<?>[] actual) {
final float leftCost = getTotalTransformationCost(actual, left);
final float rightCost = getTotalTransformationCost(actual, right);
return leftCost < rightCost ? -1 : rightCost < leftCost ? 1 : 0;
@@ -107,15 +143,44 @@ abstract class MemberUtils {
* source argument list.
* @param srcArgs The source arguments
* @param destArgs The destination arguments
+ * @param isVarArgs True if the destination arguments are for a varags methods
* @return The total transformation cost
*/
- private static float getTotalTransformationCost(final Class<?>[] srcArgs, final Class<?>[] destArgs) {
+ private static float getTotalTransformationCost(final Class<?>[] srcArgs, final Executable executable) {
+ final Class<?>[] destArgs = executable.getParameterTypes();
+ final boolean isVarArgs = executable.isVarArgs();
+
+ // "source" and "destination" are the actual and declared args respectively.
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);
+ final long normalArgsLen = isVarArgs ? destArgs.length-1 : destArgs.length;
+ if (srcArgs.length < normalArgsLen)
+ return Float.MAX_VALUE;
+ for (int i = 0; i < normalArgsLen; i++) {
+ totalCost += getObjectTransformationCost(srcArgs[i], destArgs[i]);
+ }
+ if (isVarArgs) {
+ // When isVarArgs is true, srcArgs and dstArgs may differ in length.
+ // There are two special cases to consider:
+ final boolean noVarArgsPassed = srcArgs.length < destArgs.length;
+ final boolean explicitArrayForVarags = (srcArgs.length == destArgs.length) && srcArgs[srcArgs.length-1].isArray();
+
+ final float varArgsCost = 0.001f;
+ Class<?> destClass = destArgs[destArgs.length-1].getComponentType();
+ if (noVarArgsPassed) {
+ // When no varargs passed, the best match is the most generic matching type, not the most specific.
+ totalCost += getObjectTransformationCost(destClass, Object.class) + varArgsCost;
+ }
+ else if (explicitArrayForVarags) {
+ Class<?> sourceClass = srcArgs[srcArgs.length-1].getComponentType();
+ totalCost += getObjectTransformationCost(sourceClass, destClass) + varArgsCost;
+ }
+ else {
+ // This is typical varargs case.
+ for (int i = destArgs.length-1; i < srcArgs.length; i++) {
+ Class<?> srcClass = srcArgs[i];
+ totalCost += getObjectTransformationCost(srcClass, destClass) + varArgsCost;
+ }
+ }
}
return totalCost;
}
@@ -147,7 +212,7 @@ abstract class MemberUtils {
srcClass = srcClass.getSuperclass();
}
/*
- * If the destination class is null, we've travelled all the way up to
+ * 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) {
@@ -182,4 +247,58 @@ abstract class MemberUtils {
return cost;
}
+ static boolean isMatchingMethod(Method method, Class<?>[] parameterTypes) {
+ return MemberUtils.isMatchingExecutable(Executable.of(method), parameterTypes);
+ }
+
+ static boolean isMatchingConstructor(Constructor<?> method, Class<?>[] parameterTypes) {
+ return MemberUtils.isMatchingExecutable(Executable.of(method), parameterTypes);
+ }
+
+ private static boolean isMatchingExecutable(Executable method, Class<?>[] parameterTypes) {
+ final Class<?>[] methodParameterTypes = method.getParameterTypes();
+ if (method.isVarArgs()) {
+ int i;
+ for (i = 0; i < methodParameterTypes.length - 1 && i < parameterTypes.length; i++) {
+ if (!ClassUtils.isAssignable(parameterTypes[i], methodParameterTypes[i], true)) {
+ return false;
+ }
+ }
+ Class<?> varArgParameterType = methodParameterTypes[methodParameterTypes.length - 1].getComponentType();
+ for (; i < parameterTypes.length; i++) {
+ if (!ClassUtils.isAssignable(parameterTypes[i], varArgParameterType, true)) {
+ return false;
+ }
+ }
+ return true;
+ }
+ return ClassUtils.isAssignable(parameterTypes, methodParameterTypes, true);
+ }
+
+ /**
+ * <p> A class providing a subset of the API of java.lang.reflect.Executable in Java 1.8,
+ * providing a common representation for function signatures for Constructors and Methods.</p>
+ */
+ private static final class Executable {
+ private final Class<?>[] parameterTypes;
+ private final boolean isVarArgs;
+
+ private static Executable of(Method method) { return new Executable(method); }
+ private static Executable of(Constructor<?> constructor) { return new Executable(constructor); }
+
+ private Executable(Method method) {
+ parameterTypes = method.getParameterTypes();
+ isVarArgs = method.isVarArgs();
+ }
+
+ private Executable(Constructor<?> constructor) {
+ parameterTypes = constructor.getParameterTypes();
+ isVarArgs = constructor.isVarArgs();
+ }
+
+ public Class<?>[] getParameterTypes() { return parameterTypes; }
+
+ public boolean isVarArgs() { return isVarArgs; }
+ }
+
}
http://git-wip-us.apache.org/repos/asf/commons-lang/blob/5e62bf80/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 180c35c..c938cb6 100644
--- a/src/main/java/org/apache/commons/lang3/reflect/MethodUtils.java
+++ b/src/main/java/org/apache/commons/lang3/reflect/MethodUtils.java
@@ -17,13 +17,14 @@
package org.apache.commons.lang3.reflect;
import java.lang.annotation.Annotation;
+import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
-import java.util.Arrays;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
@@ -155,6 +156,7 @@ public class MethodUtils {
+ methodName + "() on object: "
+ object.getClass().getName());
}
+ args = toVarArgs(method, args);
return method.invoke(object, args);
}
@@ -341,9 +343,61 @@ public class MethodUtils {
throw new NoSuchMethodException("No such accessible method: "
+ methodName + "() on class: " + cls.getName());
}
+ args = toVarArgs(method, args);
return method.invoke(null, args);
}
+ private static Object[] toVarArgs(Method method, Object[] args) {
+ if (method.isVarArgs()) {
+ Class<?>[] methodParameterTypes = method.getParameterTypes();
+ args = getVarArgs(args, methodParameterTypes);
+ }
+ return args;
+ }
+
+ /**
+ * <p>Given an arguments array passed to a varargs method, return an array of arguments in the canonical form,
+ * i.e. an array with the declared number of parameters, and whose last parameter is an array of the varargs type.
+ * </p>
+ *
+ * @param args the array of arguments passed to the varags method
+ * @param methodParameterTypes the declared array of method parameter types
+ * @return an array of the variadic arguments passed to the method
+ * @since 3.5
+ */
+ static Object[] getVarArgs(Object[] args, Class<?>[] methodParameterTypes) {
+ if (args.length == methodParameterTypes.length
+ && args[args.length - 1].getClass().equals(methodParameterTypes[methodParameterTypes.length - 1])) {
+ // The args array is already in the canonical form for the method.
+ return args;
+ }
+
+ // Construct a new array matching the method's declared parameter types.
+ Object[] newArgs = new Object[methodParameterTypes.length];
+
+ // Copy the normal (non-varargs) parameters
+ System.arraycopy(args, 0, newArgs, 0, methodParameterTypes.length - 1);
+
+ // Construct a new array for the variadic parameters
+ Class<?> varArgComponentType = methodParameterTypes[methodParameterTypes.length - 1].getComponentType();
+ int varArgLength = args.length - methodParameterTypes.length + 1;
+
+ Object varArgsArray = Array.newInstance(ClassUtils.primitiveToWrapper(varArgComponentType), varArgLength);
+ // Copy the variadic arguments into the varargs array.
+ System.arraycopy(args, methodParameterTypes.length - 1, varArgsArray, 0, varArgLength);
+
+ if(varArgComponentType.isPrimitive()) {
+ // unbox from wrapper type to primitive type
+ varArgsArray = ArrayUtils.toPrimitive(varArgsArray);
+ }
+
+ // Store the varargs array in the last position of the array to return
+ newArgs[methodParameterTypes.length - 1] = varArgsArray;
+
+ // Return the canonical varargs array.
+ return newArgs;
+ }
+
/**
* <p>Invokes a {@code static} method whose parameter types match exactly the object
* types.</p>
@@ -533,15 +587,16 @@ public class MethodUtils {
final Method[] methods = cls.getMethods();
for (final Method method : methods) {
// compare name and parameters
- if (method.getName().equals(methodName) && ClassUtils.isAssignable(parameterTypes, method.getParameterTypes(), true)) {
+ if (method.getName().equals(methodName) &&
+ MemberUtils.isMatchingMethod(method, parameterTypes)) {
// get accessible version of method
final Method accessibleMethod = getAccessibleMethod(method);
- if (accessibleMethod != null && (bestMatch == null || MemberUtils.compareParameterTypes(
- accessibleMethod.getParameterTypes(),
- bestMatch.getParameterTypes(),
+ if (accessibleMethod != null && (bestMatch == null || MemberUtils.compareMethodFit(
+ accessibleMethod,
+ bestMatch,
parameterTypes) < 0)) {
- bestMatch = accessibleMethod;
- }
+ bestMatch = accessibleMethod;
+ }
}
}
if (bestMatch != null) {
http://git-wip-us.apache.org/repos/asf/commons-lang/blob/5e62bf80/src/test/java/org/apache/commons/lang3/ArrayUtilsTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/commons/lang3/ArrayUtilsTest.java b/src/test/java/org/apache/commons/lang3/ArrayUtilsTest.java
index 588068e..a50ed1a 100644
--- a/src/test/java/org/apache/commons/lang3/ArrayUtilsTest.java
+++ b/src/test/java/org/apache/commons/lang3/ArrayUtilsTest.java
@@ -16,7 +16,15 @@
*/
package org.apache.commons.lang3;
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
@@ -25,6 +33,7 @@ import java.util.Comparator;
import java.util.Date;
import java.util.Map;
+import org.junit.Assert;
import org.junit.Test;
/**
@@ -4406,4 +4415,14 @@ public class ArrayUtilsTest {
assertFalse(ArrayUtils.isSorted(array));
}
+ @Test
+ public void testCreatePrimitiveArray() {
+ Assert.assertNull(ArrayUtils.toPrimitive((Object[])null));
+ Assert.assertArrayEquals(new int[]{}, (int[]) ArrayUtils.toPrimitive(new Integer[]{}));
+ Assert.assertArrayEquals(new short[]{2}, (short[]) ArrayUtils.toPrimitive(new Short[]{2}));
+ Assert.assertArrayEquals(new long[]{2, 3}, (long[]) ArrayUtils.toPrimitive(new Long[]{2L, 3L}));
+ Assert.assertArrayEquals(new float[]{3.14f}, (float[]) ArrayUtils.toPrimitive(new Float[]{3.14f}), 0.1f);
+ Assert.assertArrayEquals(new double[]{2.718}, (double[]) ArrayUtils.toPrimitive(new Double[]{2.718}), 0.1);
+ }
+
}
http://git-wip-us.apache.org/repos/asf/commons-lang/blob/5e62bf80/src/test/java/org/apache/commons/lang3/reflect/ConstructorUtilsTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/commons/lang3/reflect/ConstructorUtilsTest.java b/src/test/java/org/apache/commons/lang3/reflect/ConstructorUtilsTest.java
index c40f66f..31d6d64 100644
--- a/src/test/java/org/apache/commons/lang3/reflect/ConstructorUtilsTest.java
+++ b/src/test/java/org/apache/commons/lang3/reflect/ConstructorUtilsTest.java
@@ -16,10 +16,11 @@
*/
package org.apache.commons.lang3.reflect;
-import org.junit.Test;
-import org.junit.Before;
-
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import java.lang.reflect.Constructor;
import java.util.Arrays;
@@ -29,6 +30,9 @@ import java.util.Map;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.apache.commons.lang3.mutable.MutableObject;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
/**
* Unit tests ConstructorUtils
@@ -36,35 +40,65 @@ import org.apache.commons.lang3.mutable.MutableObject;
public class ConstructorUtilsTest {
public static class TestBean {
private final String toString;
+ final String[] varArgs;
public TestBean() {
toString = "()";
+ varArgs = null;
}
public TestBean(final int i) {
toString = "(int)";
+ varArgs = null;
}
public TestBean(final Integer i) {
toString = "(Integer)";
+ varArgs = null;
}
public TestBean(final double d) {
toString = "(double)";
+ varArgs = null;
}
public TestBean(final String s) {
toString = "(String)";
+ varArgs = null;
}
public TestBean(final Object o) {
toString = "(Object)";
+ varArgs = null;
+ }
+
+ public TestBean(final String... s) {
+ toString = "(String...)";
+ varArgs = s;
+ }
+
+ public TestBean(final Integer i, String... s) {
+ toString = "(Integer, String...)";
+ varArgs = s;
+ }
+
+ public TestBean(final Integer first, int... args) {
+ toString = "(Integer, String...)";
+ varArgs = new String[args.length];
+ for(int i = 0; i< args.length; ++i) {
+ varArgs[i] = Integer.toString(args[i]);
+ }
}
@Override
public String toString() {
return toString;
}
+
+ void verify(final String str, final String[] args) {
+ assertEquals(str, toString);
+ assertEquals(args, varArgs);
+ }
}
private static class PrivateClass {
@@ -117,6 +151,12 @@ public class ConstructorUtilsTest {
TestBean.class, NumberUtils.LONG_ONE).toString());
assertEquals("(double)", ConstructorUtils.invokeConstructor(
TestBean.class, NumberUtils.DOUBLE_ONE).toString());
+ ConstructorUtils.invokeConstructor(TestBean.class, NumberUtils.INTEGER_ONE)
+ .verify("(Integer)", null);
+ ConstructorUtils.invokeConstructor(TestBean.class, "a", "b")
+ .verify("(String...)", new String[]{"a", "b"});
+ ConstructorUtils.invokeConstructor(TestBean.class, NumberUtils.INTEGER_ONE, "a", "b")
+ .verify("(Integer, String...)", new String[]{"a", "b"});
}
@Test
@@ -242,4 +282,12 @@ public class ConstructorUtilsTest {
return result;
}
+ @Test
+ public void testVarArgsUnboxing() throws Exception {
+ TestBean testBean = ConstructorUtils.invokeConstructor(
+ TestBean.class, Integer.valueOf(1), Integer.valueOf(2), Integer.valueOf(3));
+
+ Assert.assertArrayEquals(new String[]{"2", "3"}, testBean.varArgs);
+ }
+
}
http://git-wip-us.apache.org/repos/asf/commons-lang/blob/5e62bf80/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 c30088c..b91dae1 100644
--- a/src/test/java/org/apache/commons/lang3/reflect/MethodUtilsTest.java
+++ b/src/test/java/org/apache/commons/lang3/reflect/MethodUtilsTest.java
@@ -38,14 +38,16 @@ import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.ClassUtils.Interfaces;
import org.apache.commons.lang3.math.NumberUtils;
import org.apache.commons.lang3.mutable.Mutable;
import org.apache.commons.lang3.mutable.MutableObject;
-import org.apache.commons.lang3.ClassUtils.Interfaces;
import org.apache.commons.lang3.reflect.testbed.Annotated;
import org.apache.commons.lang3.reflect.testbed.GenericConsumer;
import org.apache.commons.lang3.reflect.testbed.GenericParent;
import org.apache.commons.lang3.reflect.testbed.StringParameterizedChild;
+import org.apache.commons.lang3.tuple.ImmutablePair;
+import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
@@ -53,15 +55,15 @@ import org.junit.Test;
* Unit tests MethodUtils
*/
public class MethodUtilsTest {
-
+
private static interface PrivateInterface {}
-
+
static class TestBeanWithInterfaces implements PrivateInterface {
public String foo() {
return "foo()";
}
}
-
+
public static class TestBean {
public static String bar() {
@@ -87,7 +89,15 @@ public class MethodUtilsTest {
public static String bar(final Object o) {
return "bar(Object)";
}
-
+
+ public static String bar(final String... s) {
+ return "bar(String...)";
+ }
+
+ public static String bar(final Integer i, final String... s) {
+ return "bar(int, String...)";
+ }
+
public static void oneParameterStatic(final String s) {
// empty
}
@@ -120,10 +130,78 @@ public class MethodUtilsTest {
public String foo(final Object o) {
return "foo(Object)";
}
-
+
+ public String foo(final String... s) {
+ return "foo(String...)";
+ }
+
+ public String foo(final Integer i, final String... s) {
+ return "foo(int, String...)";
+ }
+
public void oneParameter(final String s) {
// empty
}
+
+ public String foo(final Object... s) {
+ return "foo(Object...)";
+ }
+
+ public int[] unboxing(int... values) {
+ return values;
+ }
+
+ // This method is overloaded for the wrapper class for every primitive type, plus the common supertypes
+ // Number and Object. This is an acid test since it easily leads to ambiguous methods.
+ public static String varOverload(Byte... args) { return "Byte..."; }
+ public static String varOverload(Character... args) { return "Character..."; }
+ public static String varOverload(Short... args) { return "Short..."; }
+ public static String varOverload(Boolean... args) { return "Boolean..."; }
+ public static String varOverload(Float... args) { return "Float..."; }
+ public static String varOverload(Double... args) { return "Double..."; }
+ public static String varOverload(Integer... args) { return "Integer..."; }
+ public static String varOverload(Long... args) { return "Long..."; }
+ public static String varOverload(Number... args) { return "Number..."; }
+ public static String varOverload(Object... args) { return "Object..."; }
+ public static String varOverload(String... args) { return "String..."; }
+
+ // This method is overloaded for the wrapper class for every numeric primitive type, plus the common
+ // supertype Number
+ public static String numOverload(Byte... args) { return "Byte..."; }
+ public static String numOverload(Short... args) { return "Short..."; }
+ public static String numOverload(Float... args) { return "Float..."; }
+ public static String numOverload(Double... args) { return "Double..."; }
+ public static String numOverload(Integer... args) { return "Integer..."; }
+ public static String numOverload(Long... args) { return "Long..."; }
+ public static String numOverload(Number... args) { return "Number..."; }
+
+ // These varOverloadEcho and varOverloadEchoStatic methods are designed to verify that
+ // not only is the correct overloaded variant invoked, but that the varags arguments
+ // are also delivered correctly to the method.
+ public ImmutablePair<String, Object[]> varOverloadEcho(String... args) {
+ return new ImmutablePair<String, Object[]>("String...", args);
+ }
+ public ImmutablePair<String, Object[]> varOverloadEcho(Number... args) {
+ return new ImmutablePair<String, Object[]>("Number...", args);
+ }
+
+ public static ImmutablePair<String, Object[]> varOverloadEchoStatic(String... args) {
+ return new ImmutablePair<String, Object[]>("String...", args);
+ }
+ public static ImmutablePair<String, Object[]> varOverloadEchoStatic(Number... args) {
+ return new ImmutablePair<String, Object[]>("Number...", args);
+ }
+
+ static void verify(ImmutablePair<String, Object[]> a, ImmutablePair<String, Object[]> b) {
+ assertEquals(a.getLeft(), b.getLeft());
+ assertArrayEquals(a.getRight(), b.getRight());
+ }
+
+ static void verify(ImmutablePair<String, Object[]> a, Object _b) {
+ final ImmutablePair<String, Object[]> b = (ImmutablePair<String, Object[]>) _b;
+ verify(a, b);
+ }
+
}
private static class TestMutable implements Mutable<Object> {
@@ -152,13 +230,88 @@ public class MethodUtilsTest {
}
@Test
+ public void verifyJavaVarargsOverloadingResolution() throws Exception {
+ // This code is not a test of MethodUtils.
+ // Rather it makes explicit the behavior of the Java specification for
+ // various cases of overload resolution.
+ assertEquals("Byte...", TestBean.varOverload((byte) 1, (byte) 2));
+ assertEquals("Short...", TestBean.varOverload((short) 1, (short) 2));
+ assertEquals("Integer...", TestBean.varOverload(1, 2));
+ assertEquals("Long...", TestBean.varOverload(1L, 2L));
+ assertEquals("Float...", TestBean.varOverload(1f, 2f));
+ assertEquals("Double...", TestBean.varOverload(1d, 2d));
+ assertEquals("Character...", TestBean.varOverload('a', 'b'));
+ assertEquals("String...", TestBean.varOverload("a", "b"));
+ assertEquals("Boolean...", TestBean.varOverload(true, false));
+
+ assertEquals("Object...", TestBean.varOverload(1, "s"));
+ assertEquals("Object...", TestBean.varOverload(1, true));
+ assertEquals("Object...", TestBean.varOverload(1.1, true));
+ assertEquals("Object...", TestBean.varOverload('c', true));
+ assertEquals("Number...", TestBean.varOverload(1, 1.1));
+ assertEquals("Number...", TestBean.varOverload(1, 1L));
+ assertEquals("Number...", TestBean.varOverload(1d, 1f));
+ assertEquals("Number...", TestBean.varOverload((short) 1, (byte) 1));
+ assertEquals("Object...", TestBean.varOverload(1, 'c'));
+ assertEquals("Object...", TestBean.varOverload('c', "s"));
+ }
+
+ @Test
+ public void testInvokeJavaVarargsOverloadingResolution() throws Exception {
+ assertEquals("Byte...", MethodUtils.invokeStaticMethod(TestBean.class,
+ "varOverload", (byte) 1, (byte) 2));
+ assertEquals("Short...", MethodUtils.invokeStaticMethod(TestBean.class,
+ "varOverload", (short) 1, (short) 2));
+ assertEquals("Integer...", MethodUtils.invokeStaticMethod(TestBean.class,
+ "varOverload", 1, 2));
+ assertEquals("Long...", MethodUtils.invokeStaticMethod(TestBean.class,
+ "varOverload", 1L, 2L));
+ assertEquals("Float...", MethodUtils.invokeStaticMethod(TestBean.class,
+ "varOverload", 1f, 2f));
+ assertEquals("Double...", MethodUtils.invokeStaticMethod(TestBean.class,
+ "varOverload", 1d, 2d));
+ assertEquals("Character...", MethodUtils.invokeStaticMethod(TestBean.class,
+ "varOverload", 'a', 'b'));
+ assertEquals("String...", MethodUtils.invokeStaticMethod(TestBean.class,
+ "varOverload", "a", "b"));
+ assertEquals("Boolean...", MethodUtils.invokeStaticMethod(TestBean.class,
+ "varOverload", true, false));
+
+ assertEquals("Object...", MethodUtils.invokeStaticMethod(TestBean.class,
+ "varOverload", 1, "s"));
+ assertEquals("Object...", MethodUtils.invokeStaticMethod(TestBean.class,
+ "varOverload", 1, true));
+ assertEquals("Object...", MethodUtils.invokeStaticMethod(TestBean.class,
+ "varOverload", 1.1, true));
+ assertEquals("Object...", MethodUtils.invokeStaticMethod(TestBean.class,
+ "varOverload", 'c', true));
+ assertEquals("Number...", MethodUtils.invokeStaticMethod(TestBean.class,
+ "varOverload", 1, 1.1));
+ assertEquals("Number...", MethodUtils.invokeStaticMethod(TestBean.class,
+ "varOverload", 1, 1L));
+ assertEquals("Number...", MethodUtils.invokeStaticMethod(TestBean.class,
+ "varOverload", 1d, 1f));
+ assertEquals("Number...", MethodUtils.invokeStaticMethod(TestBean.class,
+ "varOverload", (short) 1, (byte) 1));
+ assertEquals("Object...", MethodUtils.invokeStaticMethod(TestBean.class,
+ "varOverload", 1, 'c'));
+ assertEquals("Object...", MethodUtils.invokeStaticMethod(TestBean.class,
+ "varOverload", 'c', "s"));
+
+ assertEquals("Object...", MethodUtils.invokeStaticMethod(TestBean.class, "varOverload",
+ (Object[]) ArrayUtils.EMPTY_CLASS_ARRAY));
+ assertEquals("Number...", MethodUtils.invokeStaticMethod(TestBean.class, "numOverload",
+ (Object[]) ArrayUtils.EMPTY_CLASS_ARRAY));
+ }
+
+ @Test
public void testInvokeMethod() throws Exception {
assertEquals("foo()", MethodUtils.invokeMethod(testBean, "foo",
(Object[]) ArrayUtils.EMPTY_CLASS_ARRAY));
assertEquals("foo()", MethodUtils.invokeMethod(testBean, "foo"));
assertEquals("foo()", MethodUtils.invokeMethod(testBean, "foo",
(Object[]) null));
- assertEquals("foo()", MethodUtils.invokeMethod(testBean, "foo",
+ assertEquals("foo()", MethodUtils.invokeMethod(testBean, "foo",
(Object[]) null, (Class<?>[]) null));
assertEquals("foo(String)", MethodUtils.invokeMethod(testBean, "foo",
""));
@@ -174,6 +327,21 @@ public class MethodUtilsTest {
NumberUtils.LONG_ONE));
assertEquals("foo(double)", MethodUtils.invokeMethod(testBean, "foo",
NumberUtils.DOUBLE_ONE));
+ assertEquals("foo(String...)", MethodUtils.invokeMethod(testBean, "foo",
+ "a", "b", "c"));
+ assertEquals("foo(String...)", MethodUtils.invokeMethod(testBean, "foo",
+ "a", "b", "c"));
+ assertEquals("foo(int, String...)", MethodUtils.invokeMethod(testBean, "foo",
+ 5, "a", "b", "c"));
+
+ TestBean.verify(new ImmutablePair("String...", new String[]{"x", "y"}),
+ MethodUtils.invokeMethod(testBean, "varOverloadEcho", "x", "y"));
+ TestBean.verify(new ImmutablePair("Number...", new Number[]{17, 23, 42}),
+ MethodUtils.invokeMethod(testBean, "varOverloadEcho", 17, 23, 42));
+ TestBean.verify(new ImmutablePair("String...", new String[]{"x", "y"}),
+ MethodUtils.invokeMethod(testBean, "varOverloadEcho", new String[]{"x", "y"}));
+ TestBean.verify(new ImmutablePair("Number...", new Number[]{17, 23, 42}),
+ MethodUtils.invokeMethod(testBean, "varOverloadEcho", new Number[]{17, 23, 42}));
}
@Test
@@ -183,7 +351,7 @@ public class MethodUtilsTest {
assertEquals("foo()", MethodUtils.invokeExactMethod(testBean, "foo"));
assertEquals("foo()", MethodUtils.invokeExactMethod(testBean, "foo",
(Object[]) null));
- assertEquals("foo()", MethodUtils.invokeExactMethod(testBean, "foo",
+ assertEquals("foo()", MethodUtils.invokeExactMethod(testBean, "foo",
(Object[]) null, (Class<?>[]) null));
assertEquals("foo(String)", MethodUtils.invokeExactMethod(testBean,
"foo", ""));
@@ -236,7 +404,20 @@ public class MethodUtilsTest {
TestBean.class, "bar", NumberUtils.LONG_ONE));
assertEquals("bar(double)", MethodUtils.invokeStaticMethod(
TestBean.class, "bar", NumberUtils.DOUBLE_ONE));
-
+ assertEquals("bar(String...)", MethodUtils.invokeStaticMethod(
+ TestBean.class, "bar", "a", "b"));
+ assertEquals("bar(int, String...)", MethodUtils.invokeStaticMethod(
+ TestBean.class, "bar", NumberUtils.INTEGER_ONE, "a", "b"));
+
+ TestBean.verify(new ImmutablePair("String...", new String[]{"x", "y"}),
+ MethodUtils.invokeStaticMethod(TestBean.class, "varOverloadEchoStatic", "x", "y"));
+ TestBean.verify(new ImmutablePair("Number...", new Number[]{17, 23, 42}),
+ MethodUtils.invokeStaticMethod(TestBean.class, "varOverloadEchoStatic", 17, 23, 42));
+ TestBean.verify(new ImmutablePair("String...", new String[]{"x", "y"}),
+ MethodUtils.invokeStaticMethod(TestBean.class, "varOverloadEchoStatic", new String[]{"x", "y"}));
+ TestBean.verify(new ImmutablePair("Number...", new Number[]{17, 23, 42}),
+ MethodUtils.invokeStaticMethod(TestBean.class, "varOverloadEchoStatic", new Number[]{17, 23, 42}));
+
try {
MethodUtils.invokeStaticMethod(TestBean.class, "does_not_exist");
fail("should throw NoSuchMethodException");
@@ -292,7 +473,7 @@ public class MethodUtilsTest {
assertSame(Mutable.class, accessibleMethod.getDeclaringClass());
}
}
-
+
@Test
public void testGetAccessibleMethodPrivateInterface() throws Exception {
final Method expected = TestBeanWithInterfaces.class.getMethod("foo");
@@ -325,7 +506,7 @@ public class MethodUtilsTest {
MutableObject.class, "getValue", ArrayUtils.EMPTY_CLASS_ARRAY)
.getDeclaringClass());
}
-
+
@Test
public void testGetAccessibleMethodInaccessible() throws Exception {
final Method expected = TestBean.class.getDeclaredMethod("privateStuff");
@@ -375,6 +556,10 @@ public class MethodUtilsTest {
singletonArray(Double.TYPE), singletonArray(Double.TYPE));
expectMatchingAccessibleMethodParameterTypes(TestBean.class, "foo",
singletonArray(Double.TYPE), singletonArray(Double.TYPE));
+ expectMatchingAccessibleMethodParameterTypes(TestBean.class, "foo",
+ new Class[] {String.class, String.class}, new Class[] {String[].class});
+ expectMatchingAccessibleMethodParameterTypes(TestBean.class, "foo",
+ new Class[] {Integer.TYPE, String.class, String.class}, new Class[] {Integer.class, String[].class});
expectMatchingAccessibleMethodParameterTypes(InheritanceBean.class, "testOne",
singletonArray(ParentObject.class), singletonArray(ParentObject.class));
expectMatchingAccessibleMethodParameterTypes(InheritanceBean.class, "testOne",
@@ -485,11 +670,13 @@ public class MethodUtilsTest {
public void testGetMethodsListWithAnnotationIllegalArgumentException3() {
MethodUtils.getMethodsListWithAnnotation(null, null);
}
-
+
private void expectMatchingAccessibleMethodParameterTypes(final Class<?> cls,
final String methodName, final Class<?>[] requestTypes, final Class<?>[] actualTypes) {
final Method m = MethodUtils.getMatchingAccessibleMethod(cls, methodName,
requestTypes);
+ assertNotNull("could not find any matches for " + methodName
+ + " (" + (requestTypes == null ? null : toString(requestTypes)) + ")", m);
assertTrue(toString(m.getParameterTypes()) + " not equals "
+ toString(actualTypes), Arrays.equals(actualTypes, m
.getParameterTypes()));
@@ -516,8 +703,8 @@ public class MethodUtilsTest {
public void testTwo(final GrandParentObject obj) {}
public void testTwo(final ChildInterface obj) {}
}
-
- interface ChildInterface {}
+
+ interface ChildInterface {}
public static class GrandParentObject {}
public static class ParentObject extends GrandParentObject {}
public static class ChildObject extends ParentObject implements ChildInterface {}
@@ -533,4 +720,11 @@ public class MethodUtilsTest {
this.parameterTypes = parameterTypes;
}
}
+
+ @Test
+ public void testVarArgsUnboxing() throws Exception {
+ TestBean testBean = new TestBean();
+ int[] actual = (int[])MethodUtils.invokeMethod(testBean, "unboxing", Integer.valueOf(1), Integer.valueOf(2));
+ Assert.assertArrayEquals(new int[]{1, 2}, actual);
+ }
}