SFW-8224: distance can be used when computing method matches in ReflectiveMethodResolver
This commit is contained in:
parent
5d8de5c449
commit
2f733bedc5
|
|
@ -103,6 +103,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
|
||||||
* conversionAllowed flag allow for matches to take into account that a type may be transformed into a different
|
* conversionAllowed flag allow for matches to take into account that a type may be transformed into a different
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,27 @@ public class ReflectiveMethodResolver implements MethodResolver {
|
||||||
|
|
||||||
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:
|
||||||
|
|
@ -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 {
|
||||||
return new ReflectiveMethodExecutor(method, null);
|
return new ReflectiveMethodExecutor(method, null);
|
||||||
}
|
}
|
||||||
else if (matchInfo.kind == ReflectionHelper.ArgsMatchKind.CLOSE) {
|
else if (matchInfo.kind == ReflectionHelper.ArgsMatchKind.CLOSE) {
|
||||||
|
if (!useDistance) {
|
||||||
closeMatch = method;
|
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 {
|
||||||
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();
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue