LANG-1433: MethodUtils will throw a NPE if invokeMethod() is called for a var-args method (#407)
* LANG-1433: MethodUtils will throw a NPE if invokeMethod() is called for a var-args method with null parameter value * LANG-1433: Result of invokeMethod() is not deterministic for overloaded methods that can not be uniquly resolved from parameter types * LANG-1433: Fixed checkstyle errors
This commit is contained in:
parent
d3112e4306
commit
553a0474ea
|
@ -162,7 +162,7 @@ private static float getTotalTransformationCost(final Class<?>[] srcArgs, final
|
||||||
// When isVarArgs is true, srcArgs and dstArgs may differ in length.
|
// When isVarArgs is true, srcArgs and dstArgs may differ in length.
|
||||||
// There are two special cases to consider:
|
// There are two special cases to consider:
|
||||||
final boolean noVarArgsPassed = srcArgs.length < destArgs.length;
|
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 float varArgsCost = 0.001f;
|
||||||
final Class<?> destClass = destArgs[destArgs.length-1].getComponentType();
|
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
|
* @return The cost of promoting the primitive
|
||||||
*/
|
*/
|
||||||
private static float getPrimitivePromotionCost(final Class<?> srcClass, final Class<?> destClass) {
|
private static float getPrimitivePromotionCost(final Class<?> srcClass, final Class<?> destClass) {
|
||||||
|
if (srcClass == null) {
|
||||||
|
return 1.5f;
|
||||||
|
}
|
||||||
float cost = 0.0f;
|
float cost = 0.0f;
|
||||||
Class<?> cls = srcClass;
|
Class<?> cls = srcClass;
|
||||||
if (!cls.isPrimitive()) {
|
if (!cls.isPrimitive()) {
|
||||||
|
|
|
@ -25,6 +25,8 @@
|
||||||
import java.lang.reflect.TypeVariable;
|
import java.lang.reflect.TypeVariable;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -59,6 +61,13 @@
|
||||||
*/
|
*/
|
||||||
public class MethodUtils {
|
public class MethodUtils {
|
||||||
|
|
||||||
|
private static final Comparator<Method> METHOD_BY_SIGNATURE = new Comparator<Method>() {
|
||||||
|
@Override
|
||||||
|
public int compare(final Method m1, final Method m2) {
|
||||||
|
return m1.toString ().compareTo (m2.toString ());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>{@link MethodUtils} instances should NOT be constructed in standard programming.
|
* <p>{@link MethodUtils} instances should NOT be constructed in standard programming.
|
||||||
* Instead, the class should be used as
|
* Instead, the class should be used as
|
||||||
|
@ -462,8 +471,8 @@ private static Object[] toVarArgs(final Method method, Object[] args) {
|
||||||
* @since 3.5
|
* @since 3.5
|
||||||
*/
|
*/
|
||||||
static Object[] getVarArgs(final Object[] args, final Class<?>[] methodParameterTypes) {
|
static Object[] getVarArgs(final Object[] args, final Class<?>[] methodParameterTypes) {
|
||||||
if (args.length == methodParameterTypes.length
|
if (args.length == methodParameterTypes.length && (args[args.length - 1] == null ||
|
||||||
&& args[args.length - 1].getClass().equals(methodParameterTypes[methodParameterTypes.length - 1])) {
|
args[args.length - 1].getClass().equals(methodParameterTypes[methodParameterTypes.length - 1]))) {
|
||||||
// The args array is already in the canonical form for the method.
|
// The args array is already in the canonical form for the method.
|
||||||
return args;
|
return args;
|
||||||
}
|
}
|
||||||
|
@ -679,12 +688,21 @@ public static Method getMatchingAccessibleMethod(final Class<?> cls,
|
||||||
} catch (final NoSuchMethodException e) { // NOPMD - Swallow the exception
|
} catch (final NoSuchMethodException e) { // NOPMD - Swallow the exception
|
||||||
}
|
}
|
||||||
// search through all methods
|
// search through all methods
|
||||||
Method bestMatch = null;
|
|
||||||
final Method[] methods = cls.getMethods();
|
final Method[] methods = cls.getMethods();
|
||||||
|
final List<Method> matchingMethods = new ArrayList<>();
|
||||||
for (final Method method : methods) {
|
for (final Method method : methods) {
|
||||||
// compare name and parameters
|
// compare name and parameters
|
||||||
if (method.getName().equals(methodName) &&
|
if (method.getName().equals(methodName) &&
|
||||||
MemberUtils.isMatchingMethod(method, parameterTypes)) {
|
MemberUtils.isMatchingMethod(method, parameterTypes)) {
|
||||||
|
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
|
// get accessible version of method
|
||||||
final Method accessibleMethod = getAccessibleMethod(method);
|
final Method accessibleMethod = getAccessibleMethod(method);
|
||||||
if (accessibleMethod != null && (bestMatch == null || MemberUtils.compareMethodFit(
|
if (accessibleMethod != null && (bestMatch == null || MemberUtils.compareMethodFit(
|
||||||
|
@ -694,7 +712,6 @@ public static Method getMatchingAccessibleMethod(final Class<?> cls,
|
||||||
bestMatch = accessibleMethod;
|
bestMatch = accessibleMethod;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (bestMatch != null) {
|
if (bestMatch != null) {
|
||||||
MemberUtils.setAccessibleWorkaround(bestMatch);
|
MemberUtils.setAccessibleWorkaround(bestMatch);
|
||||||
}
|
}
|
||||||
|
@ -703,10 +720,12 @@ public static Method getMatchingAccessibleMethod(final Class<?> cls,
|
||||||
final Class<?>[] methodParameterTypes = bestMatch.getParameterTypes();
|
final Class<?>[] methodParameterTypes = bestMatch.getParameterTypes();
|
||||||
final Class<?> methodParameterComponentType = methodParameterTypes[methodParameterTypes.length - 1].getComponentType();
|
final Class<?> methodParameterComponentType = methodParameterTypes[methodParameterTypes.length - 1].getComponentType();
|
||||||
final String methodParameterComponentTypeName = ClassUtils.primitiveToWrapper(methodParameterComponentType).getName();
|
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)) {
|
&& !methodParameterComponentTypeName.equals(parameterTypeSuperClassName)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -446,6 +446,22 @@ public void testInvokeMethod() throws Exception {
|
||||||
MethodUtils.invokeMethod(testBean, "varOverloadEcho", 17, 23, 42));
|
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
|
@Test
|
||||||
public void testInvokeExactMethod() throws Exception {
|
public void testInvokeExactMethod() throws Exception {
|
||||||
assertEquals("foo()", MethodUtils.invokeExactMethod(testBean, "foo",
|
assertEquals("foo()", MethodUtils.invokeExactMethod(testBean, "foo",
|
||||||
|
|
Loading…
Reference in New Issue