LANG-1115: Add support for varargs in ConstructorUtils, MemberUtils, and MethodUtils
This closes #89 from github.
This commit is contained in:
parent
77d187eefc
commit
5e62bf80f3
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
// ----------------------------------------------------------------------
|
||||
/**
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 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
|
||||
*/
|
||||
static int compareParameterTypes(final Class<?>[] left, final Class<?>[] right, final Class<?>[] actual) {
|
||||
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; }
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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> {
|
||||
|
@ -151,6 +229,81 @@ public class MethodUtilsTest {
|
|||
assertNotNull(MethodUtils.class.newInstance());
|
||||
}
|
||||
|
||||
@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",
|
||||
|
@ -158,7 +311,7 @@ public class MethodUtilsTest {
|
|||
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);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue