提交 a40e4247 编写于 作者: A Andy Clement

Fix SpEL handling of function reference

These changes provide more robust handling of function
reference compilation in SpEL expressions. Prior to
this change the isCompilable check was not performing
enough visibility checks on the proposed target
function, causing bytecode to be generated that
would lead to an IllegalAccessError.
The changes also bring the argument handling for
function invocation completely inline with that used
for method invocation allowing some code to be deleted.

Many new tests are also included for function
reference compilation.

Issue: SPR-12359
上级 01aa64c5
...@@ -53,6 +53,8 @@ public class FunctionReference extends SpelNodeImpl { ...@@ -53,6 +53,8 @@ public class FunctionReference extends SpelNodeImpl {
// Captures the most recently used method for the function invocation *if* the method // Captures the most recently used method for the function invocation *if* the method
// can safely be used for compilation (i.e. no argument conversion is going on) // can safely be used for compilation (i.e. no argument conversion is going on)
private Method method; private Method method;
private boolean argumentConversionOccurred;
public FunctionReference(String functionName, int pos, SpelNodeImpl... arguments) { public FunctionReference(String functionName, int pos, SpelNodeImpl... arguments) {
...@@ -104,7 +106,7 @@ public class FunctionReference extends SpelNodeImpl { ...@@ -104,7 +106,7 @@ public class FunctionReference extends SpelNodeImpl {
method.getDeclaringClass().getName() + "." + method.getName(), this.name); method.getDeclaringClass().getName() + "." + method.getName(), this.name);
} }
boolean argumentConversionOccurred = false; argumentConversionOccurred = false;
// Convert arguments if necessary and remap them for varargs if required // Convert arguments if necessary and remap them for varargs if required
if (functionArgs != null) { if (functionArgs != null) {
TypeConverter converter = state.getEvaluationContext().getTypeConverter(); TypeConverter converter = state.getEvaluationContext().getTypeConverter();
...@@ -158,8 +160,21 @@ public class FunctionReference extends SpelNodeImpl { ...@@ -158,8 +160,21 @@ public class FunctionReference extends SpelNodeImpl {
@Override @Override
public boolean isCompilable() { public boolean isCompilable() {
// Don't yet support non-static method compilation. if (this.method == null || argumentConversionOccurred) {
return (this.method != null && Modifier.isStatic(this.method.getModifiers())); return false;
}
int methodModifiers = this.method.getModifiers();
if (!Modifier.isStatic(methodModifiers) ||
!Modifier.isPublic(methodModifiers) ||
!Modifier.isPublic(method.getDeclaringClass().getModifiers())) {
return false;
}
for (SpelNodeImpl child : this.children) {
if (!child.isCompilable()) {
return false;
}
}
return true;
} }
@Override @Override
......
...@@ -25,7 +25,6 @@ import org.springframework.core.convert.TypeDescriptor; ...@@ -25,7 +25,6 @@ import org.springframework.core.convert.TypeDescriptor;
import org.springframework.expression.EvaluationException; import org.springframework.expression.EvaluationException;
import org.springframework.expression.TypeConverter; import org.springframework.expression.TypeConverter;
import org.springframework.expression.spel.SpelEvaluationException; import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.expression.spel.SpelMessage;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.ClassUtils; import org.springframework.util.ClassUtils;
import org.springframework.util.MethodInvoker; import org.springframework.util.MethodInvoker;
...@@ -221,13 +220,33 @@ public class ReflectionHelper { ...@@ -221,13 +220,33 @@ public class ReflectionHelper {
return (match != null ? new ArgumentsMatchInfo(match) : null); return (match != null ? new ArgumentsMatchInfo(match) : null);
} }
// TODO could do with more refactoring around argument handling and varargs
/**
* Convert a supplied set of arguments into the requested types. If the parameterTypes are related to
* a varargs method then the final entry in the parameterTypes array is going to be an array itself whose
* component type should be used as the conversion target for extraneous arguments. (For example, if the
* parameterTypes are {Integer, String[]} and the input arguments are {Integer, boolean, float} then both
* the boolean and float must be converted to strings). This method does *not* repackage the arguments
* into a form suitable for the varargs invocation - a subsequent call to setupArgumentsForVarargsInvocation handles that.
* @param converter the converter to use for type conversions
* @param arguments the arguments to convert to the requested parameter types
* @param method the target Method
* @return true if some kind of conversion occurred on the argument
* @throws SpelEvaluationException if there is a problem with conversion
*/
public static boolean convertAllArguments(TypeConverter converter, Object[] arguments, Method method) throws SpelEvaluationException {
Integer varargsPosition = method.isVarArgs() ? method.getParameterTypes().length-1:null;
return convertArguments(converter, arguments, method, varargsPosition);
}
/** /**
* Takes an input set of argument values and converts them to the types specified as the * Takes an input set of argument values and converts them to the types specified as the
* required parameter types. The arguments are converted 'in-place' in the input array. * required parameter types. The arguments are converted 'in-place' in the input array.
* @param converter the type converter to use for attempting conversions * @param converter the type converter to use for attempting conversions
* @param arguments the actual arguments that need conversion * @param arguments the actual arguments that need conversion
* @param methodOrCtor the target Method or Constructor * @param methodOrCtor the target Method or Constructor
* @param varargsPosition the known position of the varargs argument, if any * @param varargsPosition the known position of the varargs argument, if any (null if not varargs)
* @return true if some kind of conversion occurred on an argument * @return true if some kind of conversion occurred on an argument
* @throws EvaluationException if a problem occurs during conversion * @throws EvaluationException if a problem occurs during conversion
*/ */
...@@ -243,6 +262,7 @@ public class ReflectionHelper { ...@@ -243,6 +262,7 @@ public class ReflectionHelper {
} }
} }
else { else {
// Convert everything up to the varargs position
for (int i = 0; i < varargsPosition; i++) { for (int i = 0; i < varargsPosition; i++) {
TypeDescriptor targetType = new TypeDescriptor(MethodParameter.forMethodOrConstructor(methodOrCtor, i)); TypeDescriptor targetType = new TypeDescriptor(MethodParameter.forMethodOrConstructor(methodOrCtor, i));
Object argument = arguments[i]; Object argument = arguments[i];
...@@ -251,15 +271,23 @@ public class ReflectionHelper { ...@@ -251,15 +271,23 @@ public class ReflectionHelper {
} }
MethodParameter methodParam = MethodParameter.forMethodOrConstructor(methodOrCtor, varargsPosition); MethodParameter methodParam = MethodParameter.forMethodOrConstructor(methodOrCtor, varargsPosition);
if (varargsPosition == arguments.length - 1) { if (varargsPosition == arguments.length - 1) {
// If the target is varargs and there is just one more argument
// then convert it here
TypeDescriptor targetType = new TypeDescriptor(methodParam); TypeDescriptor targetType = new TypeDescriptor(methodParam);
Object argument = arguments[varargsPosition]; Object argument = arguments[varargsPosition];
TypeDescriptor sourceType = TypeDescriptor.forObject(argument); TypeDescriptor sourceType = TypeDescriptor.forObject(argument);
arguments[varargsPosition] = converter.convertValue(argument, sourceType, targetType); arguments[varargsPosition] = converter.convertValue(argument, sourceType, targetType);
if (!looksLikeSimpleArrayPackaging(sourceType, targetType)) { // Three outcomes of that previous line:
conversionOccurred |= (argument != arguments[varargsPosition]); // 1) the input argument was already compatible (ie. array of valid type) and nothing was done
// 2) the input argument was correct type but not in an array so it was made into an array
// 3) the input argument was the wrong type and got converted and put into an array
if (argument != arguments[varargsPosition] &&
!isFirstEntryInArray(argument, arguments[varargsPosition])) {
conversionOccurred = true; // case 3
} }
} }
else { else {
// Convert remaining arguments to the varargs element type
TypeDescriptor targetType = new TypeDescriptor(methodParam).getElementTypeDescriptor(); TypeDescriptor targetType = new TypeDescriptor(methodParam).getElementTypeDescriptor();
for (int i = varargsPosition; i < arguments.length; i++) { for (int i = varargsPosition; i < arguments.length; i++) {
Object argument = arguments[i]; Object argument = arguments[i];
...@@ -272,131 +300,57 @@ public class ReflectionHelper { ...@@ -272,131 +300,57 @@ public class ReflectionHelper {
} }
/** /**
* Check if the target type simply represents the array (possibly boxed/unboxed) form of sourceType. * Check if the supplied value is the first entry in the array represented by the possibleArray value.
* @param sourceType the type of the original argument * @param value the value to check for in the array
* @param actualType the type of the converted argument * @param possibleArray an array object that may have the supplied value as the first element
* @return * @return true if the supplied value is the first entry in the array
*/ */
private static boolean looksLikeSimpleArrayPackaging(TypeDescriptor sourceType, TypeDescriptor targetType) { private static boolean isFirstEntryInArray(Object value, Object possibleArray) {
TypeDescriptor td = targetType.getElementTypeDescriptor(); if (possibleArray == null) {
if (td != null) { return false;
if (td.equals(sourceType)) {
return true;
}
else { // check for boxing
if (td.isPrimitive() || sourceType.isPrimitive()) {
Class<?> targetElementClass = td.getType();
Class<?> sourceElementClass = sourceType.getType();
if (targetElementClass.isPrimitive()) {
if (targetElementClass == Boolean.TYPE) {
return sourceElementClass == Boolean.class;
}
else if (targetElementClass == Double.TYPE) {
return sourceElementClass == Double.class;
}
else if (targetElementClass == Float.TYPE) {
return sourceElementClass == Float.class;
}
else if (targetElementClass == Integer.TYPE) {
return sourceElementClass == Integer.class;
}
else if (targetElementClass == Long.TYPE) {
return sourceElementClass == Long.class;
}
else if (targetElementClass == Short.TYPE) {
return sourceElementClass == Short.class;
}
else if (targetElementClass == Character.TYPE) {
return sourceElementClass == Character.class;
}
else if (targetElementClass == Byte.TYPE) {
return sourceElementClass == Byte.class;
}
}
else if (sourceElementClass.isPrimitive()) {
if (sourceElementClass == Boolean.TYPE) {
return targetElementClass == Boolean.class;
}
else if (sourceElementClass == Double.TYPE) {
return targetElementClass == Double.class;
}
else if (sourceElementClass == Float.TYPE) {
return targetElementClass == Float.class;
}
else if (sourceElementClass == Integer.TYPE) {
return targetElementClass == Integer.class;
}
else if (sourceElementClass == Long.TYPE) {
return targetElementClass == Long.class;
}
else if (sourceElementClass == Character.TYPE) {
return targetElementClass == Character.class;
}
else if (sourceElementClass == Short.TYPE) {
return targetElementClass == Short.class;
}
else if (sourceElementClass == Byte.TYPE) {
return targetElementClass == Byte.class;
}
}
}
}
} }
return false; Class<?> type = possibleArray.getClass();
} if (type.isArray()) {
Class<?> componentType = type.getComponentType();
/** if (componentType.isPrimitive()) {
* Convert a supplied set of arguments into the requested types. If the parameterTypes are related to if (componentType == Boolean.TYPE) {
* a varargs method then the final entry in the parameterTypes array is going to be an array itself whose return value instanceof Boolean &&
* component type should be used as the conversion target for extraneous arguments. (For example, if the ((boolean[])possibleArray)[0] == (Boolean)value;
* parameterTypes are {Integer, String[]} and the input arguments are {Integer, boolean, float} then both }
* the boolean and float must be converted to strings). This method does not repackage the arguments else if (componentType == Double.TYPE) {
* into a form suitable for the varargs invocation return value instanceof Double &&
* @param converter the converter to use for type conversions ((double[])possibleArray)[0] == (Double)value;
* @param arguments the arguments to convert to the requested parameter types }
* @param method the target Method else if (componentType == Float.TYPE) {
* @return true if some kind of conversion occurred on the argument return value instanceof Float &&
* @throws SpelEvaluationException if there is a problem with conversion ((float[])possibleArray)[0] == (Float)value;
*/ }
public static boolean convertAllArguments(TypeConverter converter, Object[] arguments, Method method) throws SpelEvaluationException { else if (componentType == Integer.TYPE) {
Integer varargsPosition = null; return value instanceof Integer &&
boolean conversionOccurred = false; ((int[])possibleArray)[0] == (Integer)value;
if (method.isVarArgs()) { }
Class<?>[] paramTypes = method.getParameterTypes(); else if (componentType == Long.TYPE) {
varargsPosition = paramTypes.length - 1; return value instanceof Long &&
} ((long[])possibleArray)[0] == (Long)value;
for (int argPos = 0; argPos < arguments.length; argPos++) { }
TypeDescriptor targetType; else if (componentType == Short.TYPE) {
if (varargsPosition != null && argPos >= varargsPosition) { return value instanceof Short &&
MethodParameter methodParam = new MethodParameter(method, varargsPosition); ((short[])possibleArray)[0] == (Short)value;
targetType = TypeDescriptor.nested(methodParam, 1); }
else if (componentType == Character.TYPE) {
return value instanceof Character &&
((char[])possibleArray)[0] == (Character)value;
}
else if (componentType == Byte.TYPE) {
return value instanceof Byte &&
((byte[])possibleArray)[0] == (Byte)value;
}
} }
else { else {
targetType = new TypeDescriptor(new MethodParameter(method, argPos)); return ((Object[])possibleArray)[0] == value;
}
try {
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);
}
arguments[argPos] = converter.convertValue(argument, TypeDescriptor.forObject(argument), targetType);
conversionOccurred |= (argument != arguments[argPos]);
}
}
catch (EvaluationException ex) {
// allows for another type converter throwing a different kind of EvaluationException
if (ex instanceof SpelEvaluationException) {
throw (SpelEvaluationException)ex;
}
else {
throw new SpelEvaluationException(ex,
SpelMessage.TYPE_CONVERSION_ERROR,arguments[argPos].getClass().getName(), targetType);
}
} }
} }
return conversionOccurred; return false;
} }
/** /**
...@@ -412,9 +366,11 @@ public class ReflectionHelper { ...@@ -412,9 +366,11 @@ public class ReflectionHelper {
// Check if array already built for final argument // Check if array already built for final argument
int parameterCount = requiredParameterTypes.length; int parameterCount = requiredParameterTypes.length;
int argumentCount = args.length; int argumentCount = args.length;
// Check if repackaging is needed: if (parameterCount == args.length) {
if (parameterCount != args.length ||
}
else if (parameterCount != args.length ||
requiredParameterTypes[parameterCount - 1] != requiredParameterTypes[parameterCount - 1] !=
(args[argumentCount - 1] != null ? args[argumentCount - 1].getClass() : null)) { (args[argumentCount - 1] != null ? args[argumentCount - 1].getClass() : null)) {
......
...@@ -706,6 +706,49 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests { ...@@ -706,6 +706,49 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests {
return buf.toString(); return buf.toString();
} }
@Test
public void compiledExpressionShouldWorkWhenUsingCustomFunctionWithVarargs() throws Exception {
StandardEvaluationContext context = null;
// Here the target method takes Object... and we are passing a string
expression = parser.parseExpression("#doFormat('hey %s', 'there')");
context = new StandardEvaluationContext();
context.registerFunction("doFormat",
DelegatingStringFormat.class.getDeclaredMethod("format", String.class,
Object[].class));
((SpelExpression) expression).setEvaluationContext(context);
assertEquals("hey there", expression.getValue(String.class));
assertTrue(((SpelNodeImpl) ((SpelExpression) expression).getAST()).isCompilable());
assertCanCompile(expression);
assertEquals("hey there", expression.getValue(String.class));
expression = parser.parseExpression("#doFormat([0], 'there')");
context = new StandardEvaluationContext(new Object[] { "hey %s" });
context.registerFunction("doFormat",
DelegatingStringFormat.class.getDeclaredMethod("format", String.class,
Object[].class));
((SpelExpression) expression).setEvaluationContext(context);
assertEquals("hey there", expression.getValue(String.class));
assertTrue(((SpelNodeImpl) ((SpelExpression) expression).getAST()).isCompilable());
assertCanCompile(expression);
assertEquals("hey there", expression.getValue(String.class));
expression = parser.parseExpression("#doFormat([0], #arg)");
context = new StandardEvaluationContext(new Object[] { "hey %s" });
context.registerFunction("doFormat",
DelegatingStringFormat.class.getDeclaredMethod("format", String.class,
Object[].class));
context.setVariable("arg", "there");
((SpelExpression) expression).setEvaluationContext(context);
assertEquals("hey there", expression.getValue(String.class));
assertTrue(((SpelNodeImpl) ((SpelExpression) expression).getAST()).isCompilable());
assertCanCompile(expression);
assertEquals("hey there", expression.getValue(String.class));
}
@Test @Test
public void functionReference() throws Exception { public void functionReference() throws Exception {
EvaluationContext ctx = new StandardEvaluationContext(); EvaluationContext ctx = new StandardEvaluationContext();
...@@ -738,6 +781,240 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests { ...@@ -738,6 +781,240 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests {
assertCanCompile(expression); assertCanCompile(expression);
assertEquals("4.0",expression.getValue(ctx).toString()); assertEquals("4.0",expression.getValue(ctx).toString());
} }
// Confirms visibility of what is being called.
@Test
public void functionReferenceVisibility_SPR12359() throws Exception {
StandardEvaluationContext context = new StandardEvaluationContext(new Object[] { "1" });
context.registerFunction("doCompare", SomeCompareMethod.class.getDeclaredMethod(
"compare", Object.class, Object.class));
context.setVariable("arg", "2");
// type nor method are public
expression = parser.parseExpression("#doCompare([0],#arg)");
assertEquals("-1",expression.getValue(context, Integer.class).toString());
assertCantCompile(expression);
// type not public but method is
context = new StandardEvaluationContext(new Object[] { "1" });
context.registerFunction("doCompare", SomeCompareMethod.class.getDeclaredMethod(
"compare2", Object.class, Object.class));
context.setVariable("arg", "2");
expression = parser.parseExpression("#doCompare([0],#arg)");
assertEquals("-1",expression.getValue(context, Integer.class).toString());
assertCantCompile(expression);
}
@Test
public void functionReferenceNonCompilableArguments_SPR12359() throws Exception {
StandardEvaluationContext context = new StandardEvaluationContext(new Object[] { "1" });
context.registerFunction("negate", SomeCompareMethod2.class.getDeclaredMethod(
"negate", Integer.TYPE));
context.setVariable("arg", "2");
int[] ints = new int[]{1,2,3};
context.setVariable("ints",ints);
expression = parser.parseExpression("#negate(#ints.?[#this<2][0])");
assertEquals("-1",expression.getValue(context, Integer.class).toString());
// Selection isn't compilable.
assertFalse(((SpelNodeImpl)((SpelExpression)expression).getAST()).isCompilable());
}
@Test
public void functionReferenceVarargs_SPR12359() throws Exception {
StandardEvaluationContext context = new StandardEvaluationContext();
context.registerFunction("append",
SomeCompareMethod2.class.getDeclaredMethod("append", String[].class));
context.registerFunction("append2",
SomeCompareMethod2.class.getDeclaredMethod("append2", Object[].class));
context.registerFunction("append3",
SomeCompareMethod2.class.getDeclaredMethod("append3", String[].class));
context.registerFunction("append4",
SomeCompareMethod2.class.getDeclaredMethod("append4", String.class, String[].class));
context.registerFunction("appendChar",
SomeCompareMethod2.class.getDeclaredMethod("appendChar", char[].class));
context.registerFunction("sum",
SomeCompareMethod2.class.getDeclaredMethod("sum", int[].class));
context.registerFunction("sumDouble",
SomeCompareMethod2.class.getDeclaredMethod("sumDouble", double[].class));
context.registerFunction("sumFloat",
SomeCompareMethod2.class.getDeclaredMethod("sumFloat", float[].class));
context.setVariable("stringArray", new String[]{"x","y","z"});
context.setVariable("intArray", new int[]{5,6,9});
context.setVariable("doubleArray", new double[]{5.0d,6.0d,9.0d});
context.setVariable("floatArray", new float[]{5.0f,6.0f,9.0f});
expression = parser.parseExpression("#append('a','b','c')");
assertEquals("abc",expression.getValue(context).toString());
assertTrue(((SpelNodeImpl)((SpelExpression)expression).getAST()).isCompilable());
assertCanCompile(expression);
assertEquals("abc",expression.getValue(context).toString());
expression = parser.parseExpression("#append('a')");
assertEquals("a",expression.getValue(context).toString());
assertTrue(((SpelNodeImpl)((SpelExpression)expression).getAST()).isCompilable());
assertCanCompile(expression);
assertEquals("a",expression.getValue(context).toString());
expression = parser.parseExpression("#append()");
assertEquals("",expression.getValue(context).toString());
assertTrue(((SpelNodeImpl)((SpelExpression)expression).getAST()).isCompilable());
assertCanCompile(expression);
assertEquals("",expression.getValue(context).toString());
expression = parser.parseExpression("#append(#stringArray)");
assertEquals("xyz",expression.getValue(context).toString());
assertTrue(((SpelNodeImpl)((SpelExpression)expression).getAST()).isCompilable());
assertCanCompile(expression);
assertEquals("xyz",expression.getValue(context).toString());
// This is a methodreference invocation, to compare with functionreference
expression = parser.parseExpression("append(#stringArray)");
assertEquals("xyz",expression.getValue(context,new SomeCompareMethod2()).toString());
assertTrue(((SpelNodeImpl)((SpelExpression)expression).getAST()).isCompilable());
assertCanCompile(expression);
assertEquals("xyz",expression.getValue(context,new SomeCompareMethod2()).toString());
expression = parser.parseExpression("#append2('a','b','c')");
assertEquals("abc",expression.getValue(context).toString());
assertTrue(((SpelNodeImpl)((SpelExpression)expression).getAST()).isCompilable());
assertCanCompile(expression);
assertEquals("abc",expression.getValue(context).toString());
expression = parser.parseExpression("append2('a','b')");
assertEquals("ab",expression.getValue(context, new SomeCompareMethod2()).toString());
assertTrue(((SpelNodeImpl)((SpelExpression)expression).getAST()).isCompilable());
assertCanCompile(expression);
assertEquals("ab",expression.getValue(context, new SomeCompareMethod2()).toString());
expression = parser.parseExpression("#append2('a','b')");
assertEquals("ab",expression.getValue(context).toString());
assertTrue(((SpelNodeImpl)((SpelExpression)expression).getAST()).isCompilable());
assertCanCompile(expression);
assertEquals("ab",expression.getValue(context).toString());
expression = parser.parseExpression("#append2()");
assertEquals("",expression.getValue(context).toString());
assertTrue(((SpelNodeImpl)((SpelExpression)expression).getAST()).isCompilable());
assertCanCompile(expression);
assertEquals("",expression.getValue(context).toString());
expression = parser.parseExpression("#append3(#stringArray)");
assertEquals("xyz",expression.getValue(context, new SomeCompareMethod2()).toString());
assertTrue(((SpelNodeImpl)((SpelExpression)expression).getAST()).isCompilable());
assertCanCompile(expression);
assertEquals("xyz",expression.getValue(context, new SomeCompareMethod2()).toString());
// TODO fails due to conversionservice handling of String[] to Object...
// expression = parser.parseExpression("#append2(#stringArray)");
// assertEquals("xyz",expression.getValue(context).toString());
// assertTrue(((SpelNodeImpl)((SpelExpression)expression).getAST()).isCompilable());
// assertCanCompile(expression);
// assertEquals("xyz",expression.getValue(context).toString());
expression = parser.parseExpression("#sum(1,2,3)");
assertEquals(6,expression.getValue(context));
assertTrue(((SpelNodeImpl)((SpelExpression)expression).getAST()).isCompilable());
assertCanCompile(expression);
assertEquals(6,expression.getValue(context));
expression = parser.parseExpression("#sum(2)");
assertEquals(2,expression.getValue(context));
assertTrue(((SpelNodeImpl)((SpelExpression)expression).getAST()).isCompilable());
assertCanCompile(expression);
assertEquals(2,expression.getValue(context));
expression = parser.parseExpression("#sum()");
assertEquals(0,expression.getValue(context));
assertTrue(((SpelNodeImpl)((SpelExpression)expression).getAST()).isCompilable());
assertCanCompile(expression);
assertEquals(0,expression.getValue(context));
expression = parser.parseExpression("#sum(#intArray)");
assertEquals(20,expression.getValue(context));
assertTrue(((SpelNodeImpl)((SpelExpression)expression).getAST()).isCompilable());
assertCanCompile(expression);
assertEquals(20,expression.getValue(context));
expression = parser.parseExpression("#sumDouble(1.0d,2.0d,3.0d)");
assertEquals(6,expression.getValue(context));
assertTrue(((SpelNodeImpl)((SpelExpression)expression).getAST()).isCompilable());
assertCanCompile(expression);
assertEquals(6,expression.getValue(context));
expression = parser.parseExpression("#sumDouble(2.0d)");
assertEquals(2,expression.getValue(context));
assertTrue(((SpelNodeImpl)((SpelExpression)expression).getAST()).isCompilable());
assertCanCompile(expression);
assertEquals(2,expression.getValue(context));
expression = parser.parseExpression("#sumDouble()");
assertEquals(0,expression.getValue(context));
assertTrue(((SpelNodeImpl)((SpelExpression)expression).getAST()).isCompilable());
assertCanCompile(expression);
assertEquals(0,expression.getValue(context));
expression = parser.parseExpression("#sumDouble(#doubleArray)");
assertEquals(20,expression.getValue(context));
assertTrue(((SpelNodeImpl)((SpelExpression)expression).getAST()).isCompilable());
assertCanCompile(expression);
assertEquals(20,expression.getValue(context));
expression = parser.parseExpression("#sumFloat(1.0f,2.0f,3.0f)");
assertEquals(6,expression.getValue(context));
assertTrue(((SpelNodeImpl)((SpelExpression)expression).getAST()).isCompilable());
assertCanCompile(expression);
assertEquals(6,expression.getValue(context));
expression = parser.parseExpression("#sumFloat(2.0f)");
assertEquals(2,expression.getValue(context));
assertTrue(((SpelNodeImpl)((SpelExpression)expression).getAST()).isCompilable());
assertCanCompile(expression);
assertEquals(2,expression.getValue(context));
expression = parser.parseExpression("#sumFloat()");
assertEquals(0,expression.getValue(context));
assertTrue(((SpelNodeImpl)((SpelExpression)expression).getAST()).isCompilable());
assertCanCompile(expression);
assertEquals(0,expression.getValue(context));
expression = parser.parseExpression("#sumFloat(#floatArray)");
assertEquals(20,expression.getValue(context));
assertTrue(((SpelNodeImpl)((SpelExpression)expression).getAST()).isCompilable());
assertCanCompile(expression);
assertEquals(20,expression.getValue(context));
expression = parser.parseExpression("#appendChar('abc'.charAt(0),'abc'.charAt(1))");
assertEquals("ab",expression.getValue(context));
assertTrue(((SpelNodeImpl)((SpelExpression)expression).getAST()).isCompilable());
assertCanCompile(expression);
assertEquals("ab",expression.getValue(context));
expression = parser.parseExpression("#append4('a','b','c')");
assertEquals("a::bc",expression.getValue(context).toString());
assertTrue(((SpelNodeImpl)((SpelExpression)expression).getAST()).isCompilable());
assertCanCompile(expression);
assertEquals("a::bc",expression.getValue(context).toString());
expression = parser.parseExpression("#append4('a','b')");
assertEquals("a::b",expression.getValue(context).toString());
assertTrue(((SpelNodeImpl)((SpelExpression)expression).getAST()).isCompilable());
assertCanCompile(expression);
assertEquals("a::b",expression.getValue(context).toString());
expression = parser.parseExpression("#append4('a')");
assertEquals("a::",expression.getValue(context).toString());
assertTrue(((SpelNodeImpl)((SpelExpression)expression).getAST()).isCompilable());
assertCanCompile(expression);
assertEquals("a::",expression.getValue(context).toString());
expression = parser.parseExpression("#append4('a',#stringArray)");
assertEquals("a::xyz",expression.getValue(context).toString());
assertTrue(((SpelNodeImpl)((SpelExpression)expression).getAST()).isCompilable());
assertCanCompile(expression);
assertEquals("a::xyz",expression.getValue(context).toString());
}
@Test @Test
public void functionReferenceVarargs() throws Exception { public void functionReferenceVarargs() throws Exception {
...@@ -1981,6 +2258,27 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests { ...@@ -1981,6 +2258,27 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests {
expression.getValue(tc); expression.getValue(tc);
assertEquals("aaabbbccc",tc.s); assertEquals("aaabbbccc",tc.s);
tc.reset(); tc.reset();
expression = parser.parseExpression("sixteen('aaa','bbb','ccc')");
assertCantCompile(expression);
expression.getValue(tc);
assertEquals("aaabbbccc",tc.s);
assertCanCompile(expression);
tc.reset();
expression.getValue(tc);
assertEquals("aaabbbccc",tc.s);
tc.reset();
// TODO Fails related to conversion service converting a String[] to satisfy Object...
// expression = parser.parseExpression("sixteen(stringArray)");
// assertCantCompile(expression);
// expression.getValue(tc);
// assertEquals("aaabbbccc",tc.s);
// assertCanCompile(expression);
// tc.reset();
// expression.getValue(tc);
// assertEquals("aaabbbccc",tc.s);
// tc.reset();
// varargs int // varargs int
expression = parser.parseExpression("twelve(1,2,3)"); expression = parser.parseExpression("twelve(1,2,3)");
...@@ -3719,6 +4017,19 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests { ...@@ -3719,6 +4017,19 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests {
} }
} }
} }
public void sixteen(Object... vargs) {
if (vargs==null) {
s = "";
}
else {
s = "";
for (Object varg: vargs) {
s+=varg;
}
}
}
} }
public static class TestClass6 { public static class TestClass6 {
...@@ -3867,5 +4178,97 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests { ...@@ -3867,5 +4178,97 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests {
return "wibble"; return "wibble";
} }
} }
// Here the declaring class is not public
static class SomeCompareMethod {
// method not public
static int compare(Object o1, Object o2) {
return -1;
}
// public
public static int compare2(Object o1, Object o2) {
return -1;
}
}
public static class SomeCompareMethod2 {
public static int negate(int i1) {
return -i1;
}
public static String append(String... strings) {
StringBuilder b = new StringBuilder();
for (String string: strings) {
b.append(string);
}
return b.toString();
}
public static String append2(Object... objects) {
StringBuilder b = new StringBuilder();
for (Object object: objects) {
b.append(object.toString());
}
return b.toString();
}
public static String append3(String[] strings) {
StringBuilder b = new StringBuilder();
for (String string: strings) {
b.append(string);
}
return b.toString();
}
public static String append4(String s, String... strings) {
StringBuilder b = new StringBuilder();
b.append(s).append("::");
for (String string: strings) {
b.append(string);
}
return b.toString();
}
public static String appendChar(char... values) {
StringBuilder b = new StringBuilder();
for (char ch: values) {
b.append(ch);
}
return b.toString();
}
public static int sum(int... ints) {
int total = 0;
for (int i: ints) {
total+=i;
}
return total;
}
public static int sumDouble(double... values) {
int total = 0;
for (double i: values) {
total+=i;
}
return total;
}
public static int sumFloat(float... values) {
int total = 0;
for (float i: values) {
total+=i;
}
return total;
}
}
public static class DelegatingStringFormat {
public static String format(String s, Object... args) {
return String.format(s, args);
}
}
} }
...@@ -30,8 +30,6 @@ import org.springframework.expression.ParseException; ...@@ -30,8 +30,6 @@ import org.springframework.expression.ParseException;
import org.springframework.expression.PropertyAccessor; import org.springframework.expression.PropertyAccessor;
import org.springframework.expression.TypedValue; import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.AbstractExpressionTests; import org.springframework.expression.spel.AbstractExpressionTests;
import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.expression.spel.SpelMessage;
import org.springframework.expression.spel.SpelUtilities; import org.springframework.expression.spel.SpelUtilities;
import org.springframework.expression.spel.ast.FormatHelper; import org.springframework.expression.spel.ast.FormatHelper;
import org.springframework.expression.spel.standard.SpelExpression; import org.springframework.expression.spel.standard.SpelExpression;
...@@ -264,16 +262,6 @@ public class ReflectionHelperTests extends AbstractExpressionTests { ...@@ -264,16 +262,6 @@ public class ReflectionHelperTests extends AbstractExpressionTests {
ReflectionHelper.convertAllArguments(tc, args, twoArg); ReflectionHelper.convertAllArguments(tc, args, twoArg);
checkArguments(args,"3"); checkArguments(args,"3");
// missing converter
args = new Object[] {3, false, 3.0f};
try {
ReflectionHelper.convertAllArguments(null, args, twoArg);
fail("Should have failed because no converter supplied");
}
catch (SpelEvaluationException se) {
assertEquals(SpelMessage.TYPE_CONVERSION_ERROR,se.getMessageCode());
}
// null value // null value
args = new Object[] {3, null, 3.0f}; args = new Object[] {3, null, 3.0f};
ReflectionHelper.convertAllArguments(tc, args, twoArg); ReflectionHelper.convertAllArguments(tc, args, twoArg);
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册