LANG-1115: Add support for varargs in ConstructorUtils, MemberUtils, and MethodUtils

This closes #89 from github.
This commit is contained in:
Chas Honton 2016-04-23 20:22:08 -07:00
parent 77d187eefc
commit 5e62bf80f3
8 changed files with 513 additions and 42 deletions

View File

@ -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>

View File

@ -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
// ----------------------------------------------------------------------
/**

View File

@ -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

View File

@ -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; }
}
}

View File

@ -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,12 +587,13 @@ 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;
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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;
@ -88,6 +90,14 @@ public class MethodUtilsTest {
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
}
@ -121,9 +131,77 @@ public class MethodUtilsTest {
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",
@ -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
@ -236,6 +404,19 @@ 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");
@ -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",
@ -490,6 +675,8 @@ public class MethodUtilsTest {
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()));
@ -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);
}
}