提交 2f733bed 编写于 作者: A Andy Clement

SFW-8224: distance can be used when computing method matches in ReflectiveMethodResolver

上级 5d8de5c4
...@@ -102,6 +102,49 @@ public class ReflectionHelper { ...@@ -102,6 +102,49 @@ public class ReflectionHelper {
} }
} }
} }
/**
* Based on {@link MethodInvoker.getTypeDifferenceWeight} but operates on TypeDescriptors.
*/
public static int getTypeDifferenceWeight(List<TypeDescriptor> paramTypes, List<TypeDescriptor> argTypes) {
int result = 0;
for (int i = 0,max=paramTypes.size(); i < max; i++) {
TypeDescriptor argType = argTypes.get(i);
TypeDescriptor paramType = paramTypes.get(i);
if (argType==TypeDescriptor.NULL) {
if (paramType.isPrimitive()) {
return Integer.MAX_VALUE;
}
}
if (!ClassUtils.isAssignable(paramType.getClass(), argType.getClass())) {
return Integer.MAX_VALUE;
}
if (argType != TypeDescriptor.NULL) {
Class paramTypeClazz = paramType.getType();
if (paramTypeClazz.isPrimitive()) {
paramTypeClazz = Object.class;
}
Class superClass = argType.getClass().getSuperclass();
while (superClass != null) {
if (paramType.equals(superClass)) {
result = result + 2;
superClass = null;
}
else if (ClassUtils.isAssignable(paramTypeClazz, superClass)) {
result = result + 2;
superClass = superClass.getSuperclass();
}
else {
superClass = null;
}
}
if (paramTypeClazz.isInterface()) {
result = result + 1;
}
}
}
return result;
}
/** /**
* Compare argument arrays and return information about whether they match. A supplied type converter and * Compare argument arrays and return information about whether they match. A supplied type converter and
......
...@@ -49,8 +49,29 @@ public class ReflectiveMethodResolver implements MethodResolver { ...@@ -49,8 +49,29 @@ public class ReflectiveMethodResolver implements MethodResolver {
private static Method[] NO_METHODS = new Method[0]; private static Method[] NO_METHODS = new Method[0];
private Map<Class<?>, MethodFilter> filters = null; private Map<Class<?>, MethodFilter> filters = null;
// Using distance will ensure a more accurate match is discovered,
// more closely following the Java rules.
private boolean useDistance = false;
public ReflectiveMethodResolver() {
}
/**
* This constructors allows the ReflectiveMethodResolver to be configured such that it will
* use a distance computation to check which is the better of two close matches (when there
* are multiple matches). Using the distance computation is intended to ensure matches
* are more closely representative of what a Java compiler would do when taking into
* account boxing/unboxing and whether the method candidates are declared to handle a
* supertype of the type (of the argument) being passed in.
* @param useDistance true if distance computation should be used when calculating matches
*/
public ReflectiveMethodResolver(boolean useDistance) {
this.useDistance = useDistance;
}
/** /**
* Locate a method on a type. There are three kinds of match that might occur: * Locate a method on a type. There are three kinds of match that might occur:
* <ol> * <ol>
...@@ -93,6 +114,7 @@ public class ReflectiveMethodResolver implements MethodResolver { ...@@ -93,6 +114,7 @@ public class ReflectiveMethodResolver implements MethodResolver {
}); });
Method closeMatch = null; Method closeMatch = null;
int closeMatchDistance = Integer.MAX_VALUE;
int[] argsToConvert = null; int[] argsToConvert = null;
Method matchRequiringConversion = null; Method matchRequiringConversion = null;
boolean multipleOptions = false; boolean multipleOptions = false;
...@@ -121,7 +143,16 @@ public class ReflectiveMethodResolver implements MethodResolver { ...@@ -121,7 +143,16 @@ public class ReflectiveMethodResolver implements MethodResolver {
return new ReflectiveMethodExecutor(method, null); return new ReflectiveMethodExecutor(method, null);
} }
else if (matchInfo.kind == ReflectionHelper.ArgsMatchKind.CLOSE) { else if (matchInfo.kind == ReflectionHelper.ArgsMatchKind.CLOSE) {
closeMatch = method; if (!useDistance) {
closeMatch = method;
} else {
int matchDistance = ReflectionHelper.getTypeDifferenceWeight(paramDescriptors, argumentTypes);
if (matchDistance<closeMatchDistance) {
// this is a better match
closeMatchDistance = matchDistance;
closeMatch = method;
}
}
} }
else if (matchInfo.kind == ReflectionHelper.ArgsMatchKind.REQUIRES_CONVERSION) { else if (matchInfo.kind == ReflectionHelper.ArgsMatchKind.REQUIRES_CONVERSION) {
if (matchRequiringConversion != null) { if (matchRequiringConversion != null) {
......
...@@ -813,6 +813,96 @@ public class SpringEL300Tests extends ExpressionTestCase { ...@@ -813,6 +813,96 @@ public class SpringEL300Tests extends ExpressionTestCase {
assertEquals("[D(aaa), D(bbb), D(ccc)]",value3.toString()); assertEquals("[D(aaa), D(bbb), D(ccc)]",value3.toString());
} }
/**
* Test whether {@link ReflectiveMethodResolver} follows Java Method Invocation Conversion order. And more precisely
* that widening reference conversion is 'higher' than a unboxing conversion.
*/
@Test
public void testConversionPriority_8224() throws Exception {
@SuppressWarnings("unused")
class ConversionPriority1 {
public int getX(Number i) {
return 20;
}
public int getX(int i) {
return 10;
}
}
@SuppressWarnings("unused")
class ConversionPriority2 {
public int getX(int i) {
return 10;
}
public int getX(Number i) {
return 20;
}
}
final Integer INTEGER = Integer.valueOf(7);
EvaluationContext emptyEvalContext = new StandardEvaluationContext();
List<TypeDescriptor> args = new ArrayList<TypeDescriptor>();
args.add(TypeDescriptor.forObject(new Integer(42)));
ConversionPriority1 target = new ConversionPriority1();
MethodExecutor me = new ReflectiveMethodResolver(true).resolve(emptyEvalContext, target, "getX", args);
// MethodInvoker chooses getX(int i) when passing Integer
final int actual = (Integer) me.execute(emptyEvalContext, target, new Integer(42)).getValue();
// Compiler chooses getX(Number i) when passing Integer
final int compiler = target.getX(INTEGER);
// Fails!
assertEquals(compiler, actual);
ConversionPriority2 target2 = new ConversionPriority2();
MethodExecutor me2 = new ReflectiveMethodResolver(true).resolve(emptyEvalContext, target2, "getX", args);
// MethodInvoker chooses getX(int i) when passing Integer
int actual2 = (Integer) me2.execute(emptyEvalContext, target2, new Integer(42)).getValue();
// Compiler chooses getX(Number i) when passing Integer
int compiler2 = target2.getX(INTEGER);
// Fails!
assertEquals(compiler2, actual2);
}
/**
* Test whether {@link ReflectiveMethodResolver} handles Widening Primitive Conversion. That's passing an 'int' to a
* method accepting 'long' is ok.
*/
@Test
public void testWideningPrimitiveConversion_8224() throws Exception {
class WideningPrimitiveConversion {
public int getX(long i) {
return 10;
}
}
final Integer INTEGER_VALUE = Integer.valueOf(7);
WideningPrimitiveConversion target = new WideningPrimitiveConversion();
EvaluationContext emptyEvalContext = new StandardEvaluationContext();
List<TypeDescriptor> args = new ArrayList<TypeDescriptor>();
args.add(TypeDescriptor.forObject(INTEGER_VALUE));
MethodExecutor me = new ReflectiveMethodResolver(true).resolve(emptyEvalContext, target, "getX", args);
final int actual = (Integer) me.execute(emptyEvalContext, target, INTEGER_VALUE).getValue();
final int compiler = target.getX(INTEGER_VALUE);
assertEquals(compiler, actual);
}
@Test @Test
public void varargsAndPrimitives_SPR8174() throws Exception { public void varargsAndPrimitives_SPR8174() throws Exception {
EvaluationContext emptyEvalContext = new StandardEvaluationContext(); EvaluationContext emptyEvalContext = new StandardEvaluationContext();
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册