From 2f733bedc5ab3d46f3fc5d599e06a82724079ef9 Mon Sep 17 00:00:00 2001 From: Andy Clement Date: Wed, 11 May 2011 21:44:24 +0000 Subject: [PATCH] SFW-8224: distance can be used when computing method matches in ReflectiveMethodResolver --- .../spel/support/ReflectionHelper.java | 43 +++++++++ .../support/ReflectiveMethodResolver.java | 33 ++++++- .../expression/spel/SpringEL300Tests.java | 90 +++++++++++++++++++ 3 files changed, 165 insertions(+), 1 deletion(-) diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/ReflectionHelper.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/ReflectionHelper.java index e6c561392ab..d5771678dda 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/ReflectionHelper.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/ReflectionHelper.java @@ -102,6 +102,49 @@ public class ReflectionHelper { } } } + + /** + * Based on {@link MethodInvoker.getTypeDifferenceWeight} but operates on TypeDescriptors. + */ + public static int getTypeDifferenceWeight(List paramTypes, List 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 diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/ReflectiveMethodResolver.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/ReflectiveMethodResolver.java index 86bc4b3a6dd..c606e854fa6 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/ReflectiveMethodResolver.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/ReflectiveMethodResolver.java @@ -49,8 +49,29 @@ public class ReflectiveMethodResolver implements MethodResolver { private static Method[] NO_METHODS = new Method[0]; private Map, 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: *
    @@ -93,6 +114,7 @@ public class ReflectiveMethodResolver implements MethodResolver { }); Method closeMatch = null; + int closeMatchDistance = Integer.MAX_VALUE; int[] argsToConvert = null; Method matchRequiringConversion = null; boolean multipleOptions = false; @@ -121,7 +143,16 @@ public class ReflectiveMethodResolver implements MethodResolver { return new ReflectiveMethodExecutor(method, null); } else if (matchInfo.kind == ReflectionHelper.ArgsMatchKind.CLOSE) { - closeMatch = method; + if (!useDistance) { + closeMatch = method; + } else { + int matchDistance = ReflectionHelper.getTypeDifferenceWeight(paramDescriptors, argumentTypes); + if (matchDistance args = new ArrayList(); + 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 args = new ArrayList(); + 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 public void varargsAndPrimitives_SPR8174() throws Exception { EvaluationContext emptyEvalContext = new StandardEvaluationContext();