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