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 d7e2bd2c9..73fa2f1c0 100644 --- a/src/main/java/org/apache/commons/lang3/reflect/MemberUtils.java +++ b/src/main/java/org/apache/commons/lang3/reflect/MemberUtils.java @@ -162,7 +162,7 @@ private static float getTotalTransformationCost(final Class[] srcArgs, final // 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 boolean explicitArrayForVarags = srcArgs.length == destArgs.length && srcArgs[srcArgs.length-1] != null && srcArgs[srcArgs.length-1].isArray(); final float varArgsCost = 0.001f; final Class destClass = destArgs[destArgs.length-1].getComponentType(); @@ -227,6 +227,9 @@ private static float getObjectTransformationCost(Class srcClass, final Class< * @return The cost of promoting the primitive */ private static float getPrimitivePromotionCost(final Class srcClass, final Class destClass) { + if (srcClass == null) { + return 1.5f; + } float cost = 0.0f; Class cls = srcClass; if (!cls.isPrimitive()) { 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 8ebc790a4..c014450b0 100644 --- a/src/main/java/org/apache/commons/lang3/reflect/MethodUtils.java +++ b/src/main/java/org/apache/commons/lang3/reflect/MethodUtils.java @@ -25,6 +25,8 @@ import java.lang.reflect.TypeVariable; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; @@ -59,6 +61,13 @@ */ public class MethodUtils { + private static final Comparator METHOD_BY_SIGNATURE = new Comparator() { + @Override + public int compare(final Method m1, final Method m2) { + return m1.toString ().compareTo (m2.toString ()); + } + }; + /** *

{@link MethodUtils} instances should NOT be constructed in standard programming. * Instead, the class should be used as @@ -462,8 +471,8 @@ private static Object[] toVarArgs(final Method method, Object[] args) { * @since 3.5 */ static Object[] getVarArgs(final Object[] args, final Class[] methodParameterTypes) { - if (args.length == methodParameterTypes.length - && args[args.length - 1].getClass().equals(methodParameterTypes[methodParameterTypes.length - 1])) { + if (args.length == methodParameterTypes.length && (args[args.length - 1] == null || + args[args.length - 1].getClass().equals(methodParameterTypes[methodParameterTypes.length - 1]))) { // The args array is already in the canonical form for the method. return args; } @@ -679,20 +688,28 @@ public static Method getMatchingAccessibleMethod(final Class cls, } catch (final NoSuchMethodException e) { // NOPMD - Swallow the exception } // search through all methods - Method bestMatch = null; final Method[] methods = cls.getMethods(); + final List matchingMethods = new ArrayList<>(); for (final Method method : methods) { // compare name and parameters 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.compareMethodFit( - accessibleMethod, - bestMatch, - parameterTypes) < 0)) { - bestMatch = accessibleMethod; - } + matchingMethods.add (method); + } + } + + // Sort methods by signature to force deterministic result + Collections.sort (matchingMethods, METHOD_BY_SIGNATURE); + + Method bestMatch = null; + for (final Method method : matchingMethods) { + // get accessible version of method + final Method accessibleMethod = getAccessibleMethod(method); + if (accessibleMethod != null && (bestMatch == null || MemberUtils.compareMethodFit( + accessibleMethod, + bestMatch, + parameterTypes) < 0)) { + bestMatch = accessibleMethod; } } if (bestMatch != null) { @@ -703,10 +720,12 @@ public static Method getMatchingAccessibleMethod(final Class cls, final Class[] methodParameterTypes = bestMatch.getParameterTypes(); final Class methodParameterComponentType = methodParameterTypes[methodParameterTypes.length - 1].getComponentType(); final String methodParameterComponentTypeName = ClassUtils.primitiveToWrapper(methodParameterComponentType).getName(); - final String parameterTypeName = parameterTypes[parameterTypes.length - 1].getName(); - final String parameterTypeSuperClassName = parameterTypes[parameterTypes.length - 1].getSuperclass().getName(); - if (!methodParameterComponentTypeName.equals(parameterTypeName) + final Class lastParameterType = parameterTypes[parameterTypes.length - 1]; + final String parameterTypeName = (lastParameterType==null) ? null : lastParameterType.getName(); + final String parameterTypeSuperClassName = (lastParameterType==null) ? null : lastParameterType.getSuperclass().getName(); + + if (parameterTypeName!= null && parameterTypeSuperClassName != null && !methodParameterComponentTypeName.equals(parameterTypeName) && !methodParameterComponentTypeName.equals(parameterTypeSuperClassName)) { return null; } 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 9df5a14f3..168bc2ae4 100644 --- a/src/test/java/org/apache/commons/lang3/reflect/MethodUtilsTest.java +++ b/src/test/java/org/apache/commons/lang3/reflect/MethodUtilsTest.java @@ -446,6 +446,22 @@ public void testInvokeMethod() throws Exception { MethodUtils.invokeMethod(testBean, "varOverloadEcho", 17, 23, 42)); } + @Test + public void testInvokeMethod_VarArgsWithNullValues() throws Exception { + assertEquals("String...", MethodUtils.invokeMethod(testBean, "varOverload", + "a", null, "c")); + assertEquals("String...", MethodUtils.invokeMethod(testBean, "varOverload", + "a", "b", null)); + } + + @Test + public void testInvokeMethod_VarArgsNotUniqueResolvable() throws Exception { + assertEquals("Boolean...", MethodUtils.invokeMethod(testBean, "varOverload", + new Object[] {null})); + assertEquals("Object...", MethodUtils.invokeMethod(testBean, "varOverload", + (Object[]) null)); + } + @Test public void testInvokeExactMethod() throws Exception { assertEquals("foo()", MethodUtils.invokeExactMethod(testBean, "foo",