From f18debb5a35400afed38bcd258474fb895519b60 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Wed, 15 Jan 2014 15:15:39 +0100 Subject: [PATCH] Fixed getTypeDifferenceWeight algorithm in ReflectionHelper, and deprecated ArgsMatchKind enum (for removal in 4.0) Issue: SPR-11306 --- .../spel/support/ReflectionHelper.java | 179 +++++++----------- .../ReflectiveConstructorResolver.java | 25 +-- .../support/ReflectiveMethodResolver.java | 15 +- 3 files changed, 92 insertions(+), 127 deletions(-) diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectionHelper.java b/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectionHelper.java index 2e6ff92d737..8269eb0b0e2 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectionHelper.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectionHelper.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -42,9 +42,9 @@ import org.springframework.util.MethodInvoker; public class ReflectionHelper { /** - * 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 type by the converter. + * 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 type by the converter. * @param expectedArgTypes the array of types the method/constructor is expecting * @param suppliedArgTypes the array of types that are being supplied at the point of invocation * @param typeConverter a registered type converter @@ -109,25 +109,25 @@ public class ReflectionHelper { */ 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); + for (int i = 0; i < paramTypes.size(); i++) { TypeDescriptor paramType = paramTypes.get(i); + TypeDescriptor argType = argTypes.get(i); if (argType == null) { if (paramType.isPrimitive()) { return Integer.MAX_VALUE; } } - if (!ClassUtils.isAssignable(paramType.getClass(), argType.getClass())) { - return Integer.MAX_VALUE; - } - if (argType != null) { - Class paramTypeClazz = paramType.getType(); + else { + Class paramTypeClazz = paramType.getType(); + if (!ClassUtils.isAssignable(paramTypeClazz, argType.getType())) { + return Integer.MAX_VALUE; + } if (paramTypeClazz.isPrimitive()) { paramTypeClazz = Object.class; } - Class superClass = argType.getClass().getSuperclass(); + Class superClass = argType.getType().getSuperclass(); while (superClass != null) { - if (paramType.equals(superClass)) { + if (paramTypeClazz.equals(superClass)) { result = result + 2; superClass = null; } @@ -199,6 +199,7 @@ public class ReflectionHelper { } } } + // If already confirmed it cannot be a match, then return if (match == null) { return null; @@ -211,25 +212,26 @@ public class ReflectionHelper { // expected argument - that is a match, the caller has already built the array. Proceed with it. } else { - // Now... we have the final argument in the method we are checking as a match and we have 0 or more other - // arguments left to pass to it. - Class varargsParameterType = expectedArgTypes.get(expectedArgTypes.size() - 1).getElementTypeDescriptor().getType(); + // Now... we have the final argument in the method we are checking as a match and we have 0 + // or more other arguments left to pass to it. + Class varargsParamType = expectedArgTypes.get(expectedArgTypes.size() - 1).getElementTypeDescriptor().getType(); // All remaining parameters must be of this type or convertable to this type for (int i = expectedArgTypes.size() - 1; i < suppliedArgTypes.size(); i++) { TypeDescriptor suppliedArg = suppliedArgTypes.get(i); if (suppliedArg == null) { - if (varargsParameterType.isPrimitive()) { + if (varargsParamType.isPrimitive()) { match = null; } - } else { - if (varargsParameterType != suppliedArg.getType()) { - if (ClassUtils.isAssignable(varargsParameterType, suppliedArg.getType())) { + } + else { + if (varargsParamType != suppliedArg.getType()) { + if (ClassUtils.isAssignable(varargsParamType, suppliedArg.getType())) { if (match != ArgsMatchKind.REQUIRES_CONVERSION) { match = ArgsMatchKind.CLOSE; } } - else if (typeConverter.canConvert(suppliedArg, TypeDescriptor.valueOf(varargsParameterType))) { + else if (typeConverter.canConvert(suppliedArg, TypeDescriptor.valueOf(varargsParamType))) { if (argsRequiringConversion == null) { argsRequiringConversion = new ArrayList(); } @@ -274,13 +276,15 @@ public class ReflectionHelper { */ static void convertArguments(TypeConverter converter, Object[] arguments, Object methodOrCtor, int[] argumentsRequiringConversion, Integer varargsPosition) throws EvaluationException { + if (varargsPosition == null) { for (int i = 0; i < arguments.length; i++) { TypeDescriptor targetType = new TypeDescriptor(MethodParameter.forMethodOrConstructor(methodOrCtor, i)); Object argument = arguments[i]; arguments[i] = converter.convertValue(argument, TypeDescriptor.forObject(argument), targetType); } - } else { + } + else { for (int i = 0; i < varargsPosition; i++) { TypeDescriptor targetType = new TypeDescriptor(MethodParameter.forMethodOrConstructor(methodOrCtor, i)); Object argument = arguments[i]; @@ -291,7 +295,8 @@ public class ReflectionHelper { TypeDescriptor targetType = new TypeDescriptor(methodParam); Object argument = arguments[varargsPosition]; arguments[varargsPosition] = converter.convertValue(argument, TypeDescriptor.forObject(argument), targetType); - } else { + } + else { TypeDescriptor targetType = TypeDescriptor.nested(methodParam, 1); for (int i = varargsPosition; i < arguments.length; i++) { Object argument = arguments[i]; @@ -313,28 +318,31 @@ public class ReflectionHelper { * @param method the target Method * @throws SpelEvaluationException if there is a problem with conversion */ - public static void convertAllArguments(TypeConverter converter, Object[] arguments, Method method) throws SpelEvaluationException { + public static void convertAllArguments(TypeConverter converter, Object[] arguments, Method method) + throws SpelEvaluationException { + Integer varargsPosition = null; if (method.isVarArgs()) { - Class[] paramTypes = method.getParameterTypes(); + Class[] paramTypes = method.getParameterTypes(); varargsPosition = paramTypes.length - 1; } - for (int argPosition = 0; argPosition < arguments.length; argPosition++) { + for (int argPos = 0; argPos < arguments.length; argPos++) { TypeDescriptor targetType; - if (varargsPosition != null && argPosition >= varargsPosition) { + if (varargsPosition != null && argPos >= varargsPosition) { MethodParameter methodParam = new MethodParameter(method, varargsPosition); targetType = TypeDescriptor.nested(methodParam, 1); } else { - targetType = new TypeDescriptor(new MethodParameter(method, argPosition)); + targetType = new TypeDescriptor(new MethodParameter(method, argPos)); } try { - Object argument = arguments[argPosition]; + Object argument = arguments[argPos]; if (argument != null && !targetType.getObjectType().isInstance(argument)) { if (converter == null) { - throw new SpelEvaluationException(SpelMessage.TYPE_CONVERSION_ERROR, argument.getClass().getName(), targetType); + throw new SpelEvaluationException( + SpelMessage.TYPE_CONVERSION_ERROR, argument.getClass().getName(), targetType); } - arguments[argPosition] = converter.convertValue(argument, TypeDescriptor.forObject(argument), targetType); + arguments[argPos] = converter.convertValue(argument, TypeDescriptor.forObject(argument), targetType); } } catch (EvaluationException ex) { @@ -343,7 +351,8 @@ public class ReflectionHelper { throw (SpelEvaluationException)ex; } else { - throw new SpelEvaluationException(ex, SpelMessage.TYPE_CONVERSION_ERROR,arguments[argPosition].getClass().getName(), targetType); + throw new SpelEvaluationException(ex, + SpelMessage.TYPE_CONVERSION_ERROR,arguments[argPos].getClass().getName(), targetType); } } } @@ -358,7 +367,7 @@ public class ReflectionHelper { * @param args the arguments to be setup ready for the invocation * @return a repackaged array of arguments where any varargs setup has been done */ - public static Object[] setupArgumentsForVarargsInvocation(Class[] requiredParameterTypes, Object... args) { + public static Object[] setupArgumentsForVarargsInvocation(Class[] requiredParameterTypes, Object... args) { // Check if array already built for final argument int parameterCount = requiredParameterTypes.length; int argumentCount = args.length; @@ -366,99 +375,54 @@ public class ReflectionHelper { // Check if repackaging is needed: if (parameterCount != args.length || requiredParameterTypes[parameterCount - 1] != - (args[argumentCount - 1] == null ? null : args[argumentCount - 1].getClass())) { - int arraySize = 0; // zero size array if nothing to pass as the varargs parameter + (args[argumentCount - 1] != null ? args[argumentCount - 1].getClass() : null)) { + + int arraySize = 0; // zero size array if nothing to pass as the varargs parameter if (argumentCount >= parameterCount) { arraySize = argumentCount - (parameterCount - 1); } // Create an array for the varargs arguments Object[] newArgs = new Object[parameterCount]; - for (int i = 0; i < newArgs.length - 1; i++) { - newArgs[i] = args[i]; - } - // Now sort out the final argument, which is the varargs one. Before entering this - // method the arguments should have been converted to the box form of the required type. - Class componentType = requiredParameterTypes[parameterCount-1].getComponentType(); - if (componentType.isPrimitive()) { - if (componentType==Integer.TYPE) { - int[] repackagedArguments = (int[]) Array.newInstance(componentType, arraySize); - for (int i = 0; i < arraySize; i++) { - repackagedArguments[i] = ((Integer)args[parameterCount + i - 1]).intValue(); - } - newArgs[newArgs.length - 1] = repackagedArguments; - } else if(componentType==Float.TYPE) { - float[] repackagedArguments = (float[]) Array.newInstance(componentType, arraySize); - for (int i = 0; i < arraySize; i++) { - repackagedArguments[i] = ((Float)args[parameterCount + i - 1]).floatValue(); - } - newArgs[newArgs.length - 1] = repackagedArguments; - } else if(componentType==Double.TYPE) { - double[] repackagedArguments = (double[]) Array.newInstance(componentType, arraySize); - for (int i = 0; i < arraySize; i++) { - repackagedArguments[i] = ((Double)args[parameterCount + i - 1]).doubleValue(); - } - newArgs[newArgs.length - 1] = repackagedArguments; - } else if(componentType==Short.TYPE) { - short[] repackagedArguments = (short[]) Array.newInstance(componentType, arraySize); - for (int i = 0; i < arraySize; i++) { - repackagedArguments[i] = ((Short)args[parameterCount + i - 1]).shortValue(); - } - newArgs[newArgs.length - 1] = repackagedArguments; - } else if(componentType==Character.TYPE) { - char[] repackagedArguments = (char[]) Array.newInstance(componentType, arraySize); - for (int i = 0; i < arraySize; i++) { - repackagedArguments[i] = ((Character)args[parameterCount + i - 1]).charValue(); - } - newArgs[newArgs.length - 1] = repackagedArguments; - } else if(componentType==Byte.TYPE) { - byte[] repackagedArguments = (byte[]) Array.newInstance(componentType, arraySize); - for (int i = 0; i < arraySize; i++) { - repackagedArguments[i] = ((Byte)args[parameterCount + i - 1]).byteValue(); - } - newArgs[newArgs.length - 1] = repackagedArguments; - } else if(componentType==Boolean.TYPE) { - boolean[] repackagedArguments = (boolean[]) Array.newInstance(componentType, arraySize); - for (int i = 0; i < arraySize; i++) { - repackagedArguments[i] = ((Boolean)args[parameterCount + i - 1]).booleanValue(); - } - newArgs[newArgs.length - 1] = repackagedArguments; - } else if(componentType==Long.TYPE) { - long[] repackagedArguments = (long[]) Array.newInstance(componentType, arraySize); - for (int i = 0; i < arraySize; i++) { - repackagedArguments[i] = ((Long)args[parameterCount + i - 1]).longValue(); - } - newArgs[newArgs.length - 1] = repackagedArguments; - } - } else { - Object[] repackagedArguments = (Object[]) Array.newInstance(componentType, arraySize); - // Copy all but the varargs arguments - for (int i = 0; i < arraySize; i++) { - repackagedArguments[i] = args[parameterCount + i - 1]; - } - newArgs[newArgs.length - 1] = repackagedArguments; + System.arraycopy(args, 0, newArgs, 0, newArgs.length - 1); + + // Now sort out the final argument, which is the varargs one. Before entering this method, + // the arguments should have been converted to the box form of the required type. + Class componentType = requiredParameterTypes[parameterCount - 1].getComponentType(); + Object repackagedArgs = Array.newInstance(componentType, arraySize); + for (int i = 0; i < arraySize; i++) { + Array.set(repackagedArgs, i, args[parameterCount - 1 + i]); } + newArgs[newArgs.length - 1] = repackagedArgs; return newArgs; } return args; } + /** + * @deprecated as of Spring 3.2.7, since there is no need to ever refer to this directly... + * and {@link #REQUIRES_CONVERSION} is effectively unused, going away in Spring 4.0 + */ + @Deprecated public static enum ArgsMatchKind { - // An exact match is where the parameter types exactly match what the method/constructor being invoked is expecting + + /** An exact match is where the parameter types exactly match what the method/constructor is expecting */ EXACT, - // A close match is where the parameter types either exactly match or are assignment compatible with the method/constructor being invoked + + /** A close match is where the parameter types either exactly match or are assignment-compatible */ CLOSE, - // A conversion match is where the type converter must be used to transform some of the parameter types + + /** A conversion match is where the type converter must be used to transform some of the parameter types */ REQUIRES_CONVERSION } /** - * An instance of ArgumentsMatchInfo describes what kind of match was achieved between two sets of arguments - the set that a - * method/constructor is expecting and the set that are being supplied at the point of invocation. If the kind - * indicates that conversion is required for some of the arguments then the arguments that require conversion are - * listed in the argsRequiringConversion array. + * An instance of ArgumentsMatchInfo describes what kind of match was achieved between two sets of arguments - + * the set that a method/constructor is expecting and the set that are being supplied at the point of invocation. + * If the kind indicates that conversion is required for some of the arguments then the arguments that require + * conversion are listed in the argsRequiringConversion array. */ public static class ArgumentsMatchInfo { @@ -487,11 +451,12 @@ public class ReflectionHelper { return (this.kind == ArgsMatchKind.REQUIRES_CONVERSION); } + @Override public String toString() { StringBuilder sb = new StringBuilder(); - sb.append("ArgumentMatch: ").append(this.kind); + sb.append("ArgumentsMatchInfo: ").append(this.kind); if (this.argsRequiringConversion != null) { - sb.append(" (argsForConversion:"); + sb.append(" (argsRequiringConversion:"); for (int i = 0; i < this.argsRequiringConversion.length;i++) { if (i > 0) { sb.append(","); diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectiveConstructorResolver.java b/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectiveConstructorResolver.java index 4342941af80..8324e64fbf4 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectiveConstructorResolver.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectiveConstructorResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,7 +32,7 @@ import org.springframework.expression.EvaluationException; import org.springframework.expression.TypeConverter; /** - * A constructor resolver that uses reflection to locate the constructor that should be invoked + * A constructor resolver that uses reflection to locate the constructor that should be invoked. * * @author Andy Clement * @author Juergen Hoeller @@ -55,28 +55,28 @@ public class ReflectiveConstructorResolver implements ConstructorResolver { try { TypeConverter typeConverter = context.getTypeConverter(); Class type = context.getTypeLocator().findType(typename); - Constructor[] ctors = type.getConstructors(); + Constructor[] ctors = type.getConstructors(); - Arrays.sort(ctors, new Comparator() { - public int compare(Constructor c1, Constructor c2) { + Arrays.sort(ctors, new Comparator>() { + public int compare(Constructor c1, Constructor c2) { int c1pl = c1.getParameterTypes().length; int c2pl = c2.getParameterTypes().length; return (new Integer(c1pl)).compareTo(c2pl); } }); - Constructor closeMatch = null; + Constructor closeMatch = null; int[] argsToConvert = null; Constructor matchRequiringConversion = null; - for (Constructor ctor : ctors) { - Class[] paramTypes = ctor.getParameterTypes(); + for (Constructor ctor : ctors) { + Class[] paramTypes = ctor.getParameterTypes(); List paramDescriptors = new ArrayList(paramTypes.length); for (int i = 0; i < paramTypes.length; i++) { paramDescriptors.add(new TypeDescriptor(new MethodParameter(ctor, i))); } ReflectionHelper.ArgumentsMatchInfo matchInfo = null; - if (ctor.isVarArgs() && argumentTypes.size() >= (paramTypes.length - 1)) { + if (ctor.isVarArgs() && argumentTypes.size() >= paramTypes.length - 1) { // *sigh* complicated // Basically.. we have to have all parameters match up until the varargs one, then the rest of what is // being provided should be @@ -90,18 +90,19 @@ public class ReflectiveConstructorResolver implements ConstructorResolver { matchInfo = ReflectionHelper.compareArguments(paramDescriptors, argumentTypes, typeConverter); } if (matchInfo != null) { - if (matchInfo.kind == ReflectionHelper.ArgsMatchKind.EXACT) { + if (matchInfo.isExactMatch()) { return new ReflectiveConstructorExecutor(ctor, null); } - else if (matchInfo.kind == ReflectionHelper.ArgsMatchKind.CLOSE) { + else if (matchInfo.isCloseMatch()) { closeMatch = ctor; } - else if (matchInfo.kind == ReflectionHelper.ArgsMatchKind.REQUIRES_CONVERSION) { + else if (matchInfo.isMatchRequiringConversion()) { argsToConvert = matchInfo.argsRequiringConversion; matchRequiringConversion = ctor; } } } + if (closeMatch != null) { return new ReflectiveConstructorExecutor(closeMatch, null); } diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectiveMethodResolver.java b/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectiveMethodResolver.java index fbd0b77bcaf..7ec088fd4d1 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectiveMethodResolver.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectiveMethodResolver.java @@ -53,8 +53,6 @@ import org.springframework.expression.spel.SpelMessage; */ 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, @@ -78,6 +76,7 @@ public class ReflectiveMethodResolver implements MethodResolver { this.useDistance = useDistance; } + /** * Locate a method on a type. There are three kinds of match that might occur: *
    @@ -140,27 +139,27 @@ public class ReflectiveMethodResolver implements MethodResolver { matchInfo = ReflectionHelper.compareArgumentsVarargs(paramDescriptors, argumentTypes, typeConverter); } else if (paramTypes.length == argumentTypes.size()) { - // name and parameter number match, check the arguments + // Name and parameter number match, check the arguments matchInfo = ReflectionHelper.compareArguments(paramDescriptors, argumentTypes, typeConverter); } if (matchInfo != null) { - if (matchInfo.kind == ReflectionHelper.ArgsMatchKind.EXACT) { + if (matchInfo.isExactMatch()) { return new ReflectiveMethodExecutor(method, null); } - else if (matchInfo.kind == ReflectionHelper.ArgsMatchKind.CLOSE) { + else if (matchInfo.isCloseMatch()) { if (!this.useDistance) { closeMatch = method; } else { int matchDistance = ReflectionHelper.getTypeDifferenceWeight(paramDescriptors, argumentTypes); - if (matchDistance