Browse Source

Support compilation of varargs invocations in SpEL for array subtypes

This commit merges support for compiling SpEL expressions that contain
varargs invocations where the supplied array is a subtype of the
declared varargs array type.

Closes gh-32804
pull/32829/head
Sam Brannen 2 years ago
parent
commit
061c13d367
  1. 69
      spring-expression/src/main/java/org/springframework/expression/spel/ast/SpelNodeImpl.java
  2. 196
      spring-expression/src/test/java/org/springframework/expression/spel/SpelCompilationCoverageTests.java

69
spring-expression/src/main/java/org/springframework/expression/spel/ast/SpelNodeImpl.java

@ -16,13 +16,13 @@
package org.springframework.expression.spel.ast; package org.springframework.expression.spel.ast;
import java.lang.reflect.Constructor; import java.lang.reflect.Executable;
import java.lang.reflect.Member; import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.util.function.Supplier; import java.util.function.Supplier;
import org.springframework.asm.MethodVisitor; import org.springframework.asm.MethodVisitor;
import org.springframework.asm.Opcodes; import org.springframework.asm.Opcodes;
import org.springframework.asm.Type;
import org.springframework.expression.EvaluationException; import org.springframework.expression.EvaluationException;
import org.springframework.expression.TypedValue; import org.springframework.expression.TypedValue;
import org.springframework.expression.common.ExpressionUtils; import org.springframework.expression.common.ExpressionUtils;
@ -33,7 +33,9 @@ import org.springframework.expression.spel.SpelMessage;
import org.springframework.expression.spel.SpelNode; import org.springframework.expression.spel.SpelNode;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils; import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
/** /**
* The common supertype of all AST nodes in a parsed Spring Expression Language * The common supertype of all AST nodes in a parsed Spring Expression Language
@ -216,20 +218,37 @@ public abstract class SpelNodeImpl implements SpelNode, Opcodes {
* @param cf the current codeflow * @param cf the current codeflow
* @param member the method or constructor for which arguments are being set up * @param member the method or constructor for which arguments are being set up
* @param arguments the expression nodes for the expression supplied argument values * @param arguments the expression nodes for the expression supplied argument values
* @deprecated As of Spring Framework 6.2, in favor of
* {@link #generateCodeForArguments(MethodVisitor, CodeFlow, Executable, SpelNodeImpl[])}
*/ */
@Deprecated(since = "6.2")
protected static void generateCodeForArguments(MethodVisitor mv, CodeFlow cf, Member member, SpelNodeImpl[] arguments) { protected static void generateCodeForArguments(MethodVisitor mv, CodeFlow cf, Member member, SpelNodeImpl[] arguments) {
String[] paramDescriptors = null; if (member instanceof Executable executable) {
boolean isVarargs = false; generateCodeForArguments(mv, cf, executable, arguments);
if (member instanceof Constructor<?> ctor) { }
paramDescriptors = CodeFlow.toDescriptors(ctor.getParameterTypes()); throw new IllegalArgumentException(
isVarargs = ctor.isVarArgs(); "The supplied member must be an instance of java.lang.reflect.Executable: " + member);
} }
else { // Method
Method method = (Method)member; /**
paramDescriptors = CodeFlow.toDescriptors(method.getParameterTypes()); * Generate code that handles building the argument values for the specified
isVarargs = method.isVarArgs(); * {@link Executable} (method or constructor).
} * <p>This method takes into account whether the invoked executable was
if (isVarargs) { * declared to accept varargs, and if it was then the argument values will be
* appropriately packaged into an array.
* @param mv the method visitor where code should be generated
* @param cf the current {@link CodeFlow}
* @param executable the {@link Executable} (method or constructor) for which
* arguments are being set up
* @param arguments the expression nodes for the expression supplied argument
* values
* @since 6.2
*/
protected static void generateCodeForArguments(MethodVisitor mv, CodeFlow cf, Executable executable, SpelNodeImpl[] arguments) {
Class<?>[] parameterTypes = executable.getParameterTypes();
String[] paramDescriptors = CodeFlow.toDescriptors(parameterTypes);
if (executable.isVarArgs()) {
// The final parameter may or may not need packaging into an array, or nothing may // The final parameter may or may not need packaging into an array, or nothing may
// have been passed to satisfy the varargs and so something needs to be built. // have been passed to satisfy the varargs and so something needs to be built.
int p = 0; // Current supplied argument being processed int p = 0; // Current supplied argument being processed
@ -241,13 +260,18 @@ public abstract class SpelNodeImpl implements SpelNode, Opcodes {
} }
SpelNodeImpl lastChild = (childCount == 0 ? null : arguments[childCount - 1]); SpelNodeImpl lastChild = (childCount == 0 ? null : arguments[childCount - 1]);
String arrayType = paramDescriptors[paramDescriptors.length - 1]; ClassLoader classLoader = executable.getDeclaringClass().getClassLoader();
Class<?> lastChildType = (lastChild != null ?
loadClassForExitDescriptor(lastChild.getExitDescriptor(), classLoader) : null);
Class<?> lastParameterType = parameterTypes[parameterTypes.length - 1];
// Determine if the final passed argument is already suitably packaged in array // Determine if the final passed argument is already suitably packaged in array
// form to be passed to the method // form to be passed to the method
if (lastChild != null && arrayType.equals(lastChild.getExitDescriptor())) { if (lastChild != null && lastChildType != null && lastParameterType.isAssignableFrom(lastChildType)) {
cf.generateCodeForArgument(mv, lastChild, paramDescriptors[p]); cf.generateCodeForArgument(mv, lastChild, paramDescriptors[p]);
} }
else { else {
String arrayType = paramDescriptors[paramDescriptors.length - 1];
arrayType = arrayType.substring(1); // trim the leading '[', may leave other '[' arrayType = arrayType.substring(1); // trim the leading '[', may leave other '['
// build array big enough to hold remaining arguments // build array big enough to hold remaining arguments
CodeFlow.insertNewArrayCode(mv, childCount - p, arrayType); CodeFlow.insertNewArrayCode(mv, childCount - p, arrayType);
@ -270,6 +294,19 @@ public abstract class SpelNodeImpl implements SpelNode, Opcodes {
} }
} }
@Nullable
private static Class<?> loadClassForExitDescriptor(@Nullable String exitDescriptor, ClassLoader classLoader) {
if (!StringUtils.hasText(exitDescriptor)) {
return null;
}
String typeDescriptor = exitDescriptor;
if (typeDescriptor.startsWith("[") || typeDescriptor.startsWith("L")) {
typeDescriptor += ";";
}
String className = Type.getType(typeDescriptor).getClassName();
return ClassUtils.resolveClassName(className, classLoader);
}
/** /**
* Ask an argument to generate its bytecode and then follow it up * Ask an argument to generate its bytecode and then follow it up
* with any boxing/unboxing/checkcasting to ensure it matches the expected parameter descriptor. * with any boxing/unboxing/checkcasting to ensure it matches the expected parameter descriptor.

196
spring-expression/src/test/java/org/springframework/expression/spel/SpelCompilationCoverageTests.java

@ -28,8 +28,10 @@ import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.StringTokenizer; import java.util.StringTokenizer;
import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import example.Color; import example.Color;
@ -2267,6 +2269,12 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests {
return a+b; return a+b;
} }
public static String concat2(Object... args) {
return Arrays.stream(args)
.map(Objects::toString)
.collect(Collectors.joining());
}
public static String join(String...strings) { public static String join(String...strings) {
StringBuilder buf = new StringBuilder(); StringBuilder buf = new StringBuilder();
for (String string: strings) { for (String string: strings) {
@ -2277,9 +2285,9 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests {
@Test @Test
void compiledExpressionShouldWorkWhenUsingCustomFunctionWithVarargs() throws Exception { void compiledExpressionShouldWorkWhenUsingCustomFunctionWithVarargs() throws Exception {
StandardEvaluationContext context = null; StandardEvaluationContext context;
// Here the target method takes Object... and we are passing a string // single string argument
expression = parser.parseExpression("#doFormat('hey %s', 'there')"); expression = parser.parseExpression("#doFormat('hey %s', 'there')");
context = new StandardEvaluationContext(); context = new StandardEvaluationContext();
context.registerFunction("doFormat", context.registerFunction("doFormat",
@ -2287,10 +2295,11 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests {
((SpelExpression) expression).setEvaluationContext(context); ((SpelExpression) expression).setEvaluationContext(context);
assertThat(expression.getValue(String.class)).isEqualTo("hey there"); assertThat(expression.getValue(String.class)).isEqualTo("hey there");
assertThat(((SpelNodeImpl) ((SpelExpression) expression).getAST()).isCompilable()).isTrue(); assertThat(((SpelExpression) expression).getAST().isCompilable()).isTrue();
assertCanCompile(expression); assertCanCompile(expression);
assertThat(expression.getValue(String.class)).isEqualTo("hey there"); assertThat(expression.getValue(String.class)).isEqualTo("hey there");
// single string argument from root array access
expression = parser.parseExpression("#doFormat([0], 'there')"); expression = parser.parseExpression("#doFormat([0], 'there')");
context = new StandardEvaluationContext(new Object[] {"hey %s"}); context = new StandardEvaluationContext(new Object[] {"hey %s"});
context.registerFunction("doFormat", context.registerFunction("doFormat",
@ -2298,10 +2307,11 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests {
((SpelExpression) expression).setEvaluationContext(context); ((SpelExpression) expression).setEvaluationContext(context);
assertThat(expression.getValue(String.class)).isEqualTo("hey there"); assertThat(expression.getValue(String.class)).isEqualTo("hey there");
assertThat(((SpelNodeImpl) ((SpelExpression) expression).getAST()).isCompilable()).isTrue(); assertThat(((SpelExpression) expression).getAST().isCompilable()).isTrue();
assertCanCompile(expression); assertCanCompile(expression);
assertThat(expression.getValue(String.class)).isEqualTo("hey there"); assertThat(expression.getValue(String.class)).isEqualTo("hey there");
// single string from variable
expression = parser.parseExpression("#doFormat([0], #arg)"); expression = parser.parseExpression("#doFormat([0], #arg)");
context = new StandardEvaluationContext(new Object[] {"hey %s"}); context = new StandardEvaluationContext(new Object[] {"hey %s"});
context.registerFunction("doFormat", context.registerFunction("doFormat",
@ -2310,7 +2320,19 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests {
((SpelExpression) expression).setEvaluationContext(context); ((SpelExpression) expression).setEvaluationContext(context);
assertThat(expression.getValue(String.class)).isEqualTo("hey there"); assertThat(expression.getValue(String.class)).isEqualTo("hey there");
assertThat(((SpelNodeImpl) ((SpelExpression) expression).getAST()).isCompilable()).isTrue(); assertThat(((SpelExpression) expression).getAST().isCompilable()).isTrue();
assertCanCompile(expression);
assertThat(expression.getValue(String.class)).isEqualTo("hey there");
// string array argument
expression = parser.parseExpression("#doFormat('hey %s', #arg)");
context = new StandardEvaluationContext();
context.registerFunction("doFormat",
DelegatingStringFormat.class.getDeclaredMethod("format", String.class, Object[].class));
context.setVariable("arg", new String[] { "there" });
((SpelExpression) expression).setEvaluationContext(context);
assertThat(expression.getValue(String.class)).isEqualTo("hey there");
assertThat(((SpelExpression) expression).getAST().isCompilable()).isTrue();
assertCanCompile(expression); assertCanCompile(expression);
assertThat(expression.getValue(String.class)).isEqualTo("hey there"); assertThat(expression.getValue(String.class)).isEqualTo("hey there");
} }
@ -2320,6 +2342,10 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests {
EvaluationContext ctx = new StandardEvaluationContext(); EvaluationContext ctx = new StandardEvaluationContext();
Method m = getClass().getDeclaredMethod("concat", String.class, String.class); Method m = getClass().getDeclaredMethod("concat", String.class, String.class);
ctx.setVariable("concat", m); ctx.setVariable("concat", m);
Method m2 = getClass().getDeclaredMethod("concat2", Object[].class);
ctx.setVariable("concat2", m2);
Method m3 = getClass().getDeclaredMethod("join", String[].class);
ctx.setVariable("join", m3);
expression = parser.parseExpression("#concat('a','b')"); expression = parser.parseExpression("#concat('a','b')");
assertThat(expression.getValue(ctx)).isEqualTo("ab"); assertThat(expression.getValue(ctx)).isEqualTo("ab");
@ -2331,6 +2357,20 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests {
assertCanCompile(expression); assertCanCompile(expression);
assertThat(expression.getValue(ctx)).isEqualTo('b'); assertThat(expression.getValue(ctx)).isEqualTo('b');
// varargs
expression = parser.parseExpression("#join(#stringArray)");
ctx.setVariable("stringArray", new String[] { "a", "b", "c" });
assertThat(expression.getValue(ctx)).isEqualTo("abc");
assertCanCompile(expression);
assertThat(expression.getValue(ctx)).isEqualTo("abc");
// varargs with argument component type that is a subtype of the varargs component type.
expression = parser.parseExpression("#concat2(#stringArray)");
ctx.setVariable("stringArray", new String[] { "a", "b", "c" });
assertThat(expression.getValue(ctx)).isEqualTo("abc");
assertCanCompile(expression);
assertThat(expression.getValue(ctx)).isEqualTo("abc");
expression = parser.parseExpression("#concat(#a,#b)"); expression = parser.parseExpression("#concat(#a,#b)");
ctx.setVariable("a", "foo"); ctx.setVariable("a", "foo");
ctx.setVariable("b", "bar"); ctx.setVariable("b", "bar");
@ -2465,173 +2505,172 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests {
expression = parser.parseExpression("#append('a','b','c')"); expression = parser.parseExpression("#append('a','b','c')");
assertThat(expression.getValue(context).toString()).isEqualTo("abc"); assertThat(expression.getValue(context).toString()).isEqualTo("abc");
assertThat(((SpelNodeImpl)((SpelExpression) expression).getAST()).isCompilable()).isTrue(); assertThat(((SpelExpression) expression).getAST().isCompilable()).isTrue();
assertCanCompile(expression); assertCanCompile(expression);
assertThat(expression.getValue(context).toString()).isEqualTo("abc"); assertThat(expression.getValue(context).toString()).isEqualTo("abc");
expression = parser.parseExpression("#append('a')"); expression = parser.parseExpression("#append('a')");
assertThat(expression.getValue(context).toString()).isEqualTo("a"); assertThat(expression.getValue(context).toString()).isEqualTo("a");
assertThat(((SpelNodeImpl)((SpelExpression) expression).getAST()).isCompilable()).isTrue(); assertThat(((SpelExpression) expression).getAST().isCompilable()).isTrue();
assertCanCompile(expression); assertCanCompile(expression);
assertThat(expression.getValue(context).toString()).isEqualTo("a"); assertThat(expression.getValue(context).toString()).isEqualTo("a");
expression = parser.parseExpression("#append()"); expression = parser.parseExpression("#append()");
assertThat(expression.getValue(context).toString()).isEmpty(); assertThat(expression.getValue(context).toString()).isEmpty();
assertThat(((SpelNodeImpl)((SpelExpression) expression).getAST()).isCompilable()).isTrue(); assertThat(((SpelExpression) expression).getAST().isCompilable()).isTrue();
assertCanCompile(expression); assertCanCompile(expression);
assertThat(expression.getValue(context).toString()).isEmpty(); assertThat(expression.getValue(context).toString()).isEmpty();
expression = parser.parseExpression("#append(#stringArray)"); expression = parser.parseExpression("#append(#stringArray)");
assertThat(expression.getValue(context).toString()).isEqualTo("xyz"); assertThat(expression.getValue(context).toString()).isEqualTo("xyz");
assertThat(((SpelNodeImpl)((SpelExpression) expression).getAST()).isCompilable()).isTrue(); assertThat(((SpelExpression) expression).getAST().isCompilable()).isTrue();
assertCanCompile(expression); assertCanCompile(expression);
assertThat(expression.getValue(context).toString()).isEqualTo("xyz"); assertThat(expression.getValue(context).toString()).isEqualTo("xyz");
// This is a methodreference invocation, to compare with functionreference // This is a methodreference invocation, to compare with functionreference
expression = parser.parseExpression("append(#stringArray)"); expression = parser.parseExpression("append(#stringArray)");
assertThat(expression.getValue(context, new SomeCompareMethod2()).toString()).isEqualTo("xyz"); assertThat(expression.getValue(context, new SomeCompareMethod2()).toString()).isEqualTo("xyz");
assertThat(((SpelNodeImpl)((SpelExpression) expression).getAST()).isCompilable()).isTrue(); assertThat(((SpelExpression) expression).getAST().isCompilable()).isTrue();
assertCanCompile(expression); assertCanCompile(expression);
assertThat(expression.getValue(context, new SomeCompareMethod2()).toString()).isEqualTo("xyz"); assertThat(expression.getValue(context, new SomeCompareMethod2()).toString()).isEqualTo("xyz");
expression = parser.parseExpression("#append2('a','b','c')"); expression = parser.parseExpression("#append2('a','b','c')");
assertThat(expression.getValue(context).toString()).isEqualTo("abc"); assertThat(expression.getValue(context).toString()).isEqualTo("abc");
assertThat(((SpelNodeImpl)((SpelExpression) expression).getAST()).isCompilable()).isTrue(); assertThat(((SpelExpression) expression).getAST().isCompilable()).isTrue();
assertCanCompile(expression); assertCanCompile(expression);
assertThat(expression.getValue(context).toString()).isEqualTo("abc"); assertThat(expression.getValue(context).toString()).isEqualTo("abc");
expression = parser.parseExpression("append2('a','b')"); expression = parser.parseExpression("append2('a','b')");
assertThat(expression.getValue(context, new SomeCompareMethod2()).toString()).isEqualTo("ab"); assertThat(expression.getValue(context, new SomeCompareMethod2()).toString()).isEqualTo("ab");
assertThat(((SpelNodeImpl)((SpelExpression) expression).getAST()).isCompilable()).isTrue(); assertThat(((SpelExpression) expression).getAST().isCompilable()).isTrue();
assertCanCompile(expression); assertCanCompile(expression);
assertThat(expression.getValue(context, new SomeCompareMethod2()).toString()).isEqualTo("ab"); assertThat(expression.getValue(context, new SomeCompareMethod2()).toString()).isEqualTo("ab");
expression = parser.parseExpression("#append2('a','b')"); expression = parser.parseExpression("#append2('a','b')");
assertThat(expression.getValue(context).toString()).isEqualTo("ab"); assertThat(expression.getValue(context).toString()).isEqualTo("ab");
assertThat(((SpelNodeImpl)((SpelExpression) expression).getAST()).isCompilable()).isTrue(); assertThat(((SpelExpression) expression).getAST().isCompilable()).isTrue();
assertCanCompile(expression); assertCanCompile(expression);
assertThat(expression.getValue(context).toString()).isEqualTo("ab"); assertThat(expression.getValue(context).toString()).isEqualTo("ab");
expression = parser.parseExpression("#append2()"); expression = parser.parseExpression("#append2()");
assertThat(expression.getValue(context).toString()).isEmpty(); assertThat(expression.getValue(context).toString()).isEmpty();
assertThat(((SpelNodeImpl)((SpelExpression) expression).getAST()).isCompilable()).isTrue(); assertThat(((SpelExpression) expression).getAST().isCompilable()).isTrue();
assertCanCompile(expression); assertCanCompile(expression);
assertThat(expression.getValue(context).toString()).isEmpty(); assertThat(expression.getValue(context).toString()).isEmpty();
expression = parser.parseExpression("#append3(#stringArray)"); expression = parser.parseExpression("#append3(#stringArray)");
assertThat(expression.getValue(context, new SomeCompareMethod2()).toString()).isEqualTo("xyz"); assertThat(expression.getValue(context, new SomeCompareMethod2()).toString()).isEqualTo("xyz");
assertThat(((SpelNodeImpl)((SpelExpression) expression).getAST()).isCompilable()).isTrue(); assertThat(((SpelExpression) expression).getAST().isCompilable()).isTrue();
assertCanCompile(expression); assertCanCompile(expression);
assertThat(expression.getValue(context, new SomeCompareMethod2()).toString()).isEqualTo("xyz"); assertThat(expression.getValue(context, new SomeCompareMethod2()).toString()).isEqualTo("xyz");
// TODO Determine why the String[] is passed as the first element of the Object... varargs array instead of the entire varargs array. expression = parser.parseExpression("#append2(#stringArray)");
// expression = parser.parseExpression("#append2(#stringArray)"); assertThat(expression.getValue(context)).hasToString("xyz");
// assertThat(expression.getValue(context)).hasToString("xyz"); assertThat(((SpelExpression) expression).getAST().isCompilable()).isTrue();
// assertThat(((SpelNodeImpl) ((SpelExpression) expression).getAST()).isCompilable()).isTrue(); assertCanCompile(expression);
// assertCanCompile(expression); assertThat(expression.getValue(context)).hasToString("xyz");
// assertThat(expression.getValue(context)).hasToString("xyz");
expression = parser.parseExpression("#sum(1,2,3)"); expression = parser.parseExpression("#sum(1,2,3)");
assertThat(expression.getValue(context)).isEqualTo(6); assertThat(expression.getValue(context)).isEqualTo(6);
assertThat(((SpelNodeImpl)((SpelExpression) expression).getAST()).isCompilable()).isTrue(); assertThat(((SpelExpression) expression).getAST().isCompilable()).isTrue();
assertCanCompile(expression); assertCanCompile(expression);
assertThat(expression.getValue(context)).isEqualTo(6); assertThat(expression.getValue(context)).isEqualTo(6);
expression = parser.parseExpression("#sum(2)"); expression = parser.parseExpression("#sum(2)");
assertThat(expression.getValue(context)).isEqualTo(2); assertThat(expression.getValue(context)).isEqualTo(2);
assertThat(((SpelNodeImpl)((SpelExpression) expression).getAST()).isCompilable()).isTrue(); assertThat(((SpelExpression) expression).getAST().isCompilable()).isTrue();
assertCanCompile(expression); assertCanCompile(expression);
assertThat(expression.getValue(context)).isEqualTo(2); assertThat(expression.getValue(context)).isEqualTo(2);
expression = parser.parseExpression("#sum()"); expression = parser.parseExpression("#sum()");
assertThat(expression.getValue(context)).isEqualTo(0); assertThat(expression.getValue(context)).isEqualTo(0);
assertThat(((SpelNodeImpl)((SpelExpression) expression).getAST()).isCompilable()).isTrue(); assertThat(((SpelExpression) expression).getAST().isCompilable()).isTrue();
assertCanCompile(expression); assertCanCompile(expression);
assertThat(expression.getValue(context)).isEqualTo(0); assertThat(expression.getValue(context)).isEqualTo(0);
expression = parser.parseExpression("#sum(#intArray)"); expression = parser.parseExpression("#sum(#intArray)");
assertThat(expression.getValue(context)).isEqualTo(20); assertThat(expression.getValue(context)).isEqualTo(20);
assertThat(((SpelNodeImpl)((SpelExpression) expression).getAST()).isCompilable()).isTrue(); assertThat(((SpelExpression) expression).getAST().isCompilable()).isTrue();
assertCanCompile(expression); assertCanCompile(expression);
assertThat(expression.getValue(context)).isEqualTo(20); assertThat(expression.getValue(context)).isEqualTo(20);
expression = parser.parseExpression("#sumDouble(1.0d,2.0d,3.0d)"); expression = parser.parseExpression("#sumDouble(1.0d,2.0d,3.0d)");
assertThat(expression.getValue(context)).isEqualTo(6); assertThat(expression.getValue(context)).isEqualTo(6);
assertThat(((SpelNodeImpl)((SpelExpression) expression).getAST()).isCompilable()).isTrue(); assertThat(((SpelExpression) expression).getAST().isCompilable()).isTrue();
assertCanCompile(expression); assertCanCompile(expression);
assertThat(expression.getValue(context)).isEqualTo(6); assertThat(expression.getValue(context)).isEqualTo(6);
expression = parser.parseExpression("#sumDouble(2.0d)"); expression = parser.parseExpression("#sumDouble(2.0d)");
assertThat(expression.getValue(context)).isEqualTo(2); assertThat(expression.getValue(context)).isEqualTo(2);
assertThat(((SpelNodeImpl)((SpelExpression) expression).getAST()).isCompilable()).isTrue(); assertThat(((SpelExpression) expression).getAST().isCompilable()).isTrue();
assertCanCompile(expression); assertCanCompile(expression);
assertThat(expression.getValue(context)).isEqualTo(2); assertThat(expression.getValue(context)).isEqualTo(2);
expression = parser.parseExpression("#sumDouble()"); expression = parser.parseExpression("#sumDouble()");
assertThat(expression.getValue(context)).isEqualTo(0); assertThat(expression.getValue(context)).isEqualTo(0);
assertThat(((SpelNodeImpl)((SpelExpression) expression).getAST()).isCompilable()).isTrue(); assertThat(((SpelExpression) expression).getAST().isCompilable()).isTrue();
assertCanCompile(expression); assertCanCompile(expression);
assertThat(expression.getValue(context)).isEqualTo(0); assertThat(expression.getValue(context)).isEqualTo(0);
expression = parser.parseExpression("#sumDouble(#doubleArray)"); expression = parser.parseExpression("#sumDouble(#doubleArray)");
assertThat(expression.getValue(context)).isEqualTo(20); assertThat(expression.getValue(context)).isEqualTo(20);
assertThat(((SpelNodeImpl)((SpelExpression) expression).getAST()).isCompilable()).isTrue(); assertThat(((SpelExpression) expression).getAST().isCompilable()).isTrue();
assertCanCompile(expression); assertCanCompile(expression);
assertThat(expression.getValue(context)).isEqualTo(20); assertThat(expression.getValue(context)).isEqualTo(20);
expression = parser.parseExpression("#sumFloat(1.0f,2.0f,3.0f)"); expression = parser.parseExpression("#sumFloat(1.0f,2.0f,3.0f)");
assertThat(expression.getValue(context)).isEqualTo(6); assertThat(expression.getValue(context)).isEqualTo(6);
assertThat(((SpelNodeImpl)((SpelExpression) expression).getAST()).isCompilable()).isTrue(); assertThat(((SpelExpression) expression).getAST().isCompilable()).isTrue();
assertCanCompile(expression); assertCanCompile(expression);
assertThat(expression.getValue(context)).isEqualTo(6); assertThat(expression.getValue(context)).isEqualTo(6);
expression = parser.parseExpression("#sumFloat(2.0f)"); expression = parser.parseExpression("#sumFloat(2.0f)");
assertThat(expression.getValue(context)).isEqualTo(2); assertThat(expression.getValue(context)).isEqualTo(2);
assertThat(((SpelNodeImpl)((SpelExpression) expression).getAST()).isCompilable()).isTrue(); assertThat(((SpelExpression) expression).getAST().isCompilable()).isTrue();
assertCanCompile(expression); assertCanCompile(expression);
assertThat(expression.getValue(context)).isEqualTo(2); assertThat(expression.getValue(context)).isEqualTo(2);
expression = parser.parseExpression("#sumFloat()"); expression = parser.parseExpression("#sumFloat()");
assertThat(expression.getValue(context)).isEqualTo(0); assertThat(expression.getValue(context)).isEqualTo(0);
assertThat(((SpelNodeImpl)((SpelExpression) expression).getAST()).isCompilable()).isTrue(); assertThat(((SpelExpression) expression).getAST().isCompilable()).isTrue();
assertCanCompile(expression); assertCanCompile(expression);
assertThat(expression.getValue(context)).isEqualTo(0); assertThat(expression.getValue(context)).isEqualTo(0);
expression = parser.parseExpression("#sumFloat(#floatArray)"); expression = parser.parseExpression("#sumFloat(#floatArray)");
assertThat(expression.getValue(context)).isEqualTo(20); assertThat(expression.getValue(context)).isEqualTo(20);
assertThat(((SpelNodeImpl)((SpelExpression) expression).getAST()).isCompilable()).isTrue(); assertThat(((SpelExpression) expression).getAST().isCompilable()).isTrue();
assertCanCompile(expression); assertCanCompile(expression);
assertThat(expression.getValue(context)).isEqualTo(20); assertThat(expression.getValue(context)).isEqualTo(20);
expression = parser.parseExpression("#appendChar('abc'.charAt(0),'abc'.charAt(1))"); expression = parser.parseExpression("#appendChar('abc'.charAt(0),'abc'.charAt(1))");
assertThat(expression.getValue(context)).isEqualTo("ab"); assertThat(expression.getValue(context)).isEqualTo("ab");
assertThat(((SpelNodeImpl)((SpelExpression) expression).getAST()).isCompilable()).isTrue(); assertThat(((SpelExpression) expression).getAST().isCompilable()).isTrue();
assertCanCompile(expression); assertCanCompile(expression);
assertThat(expression.getValue(context)).isEqualTo("ab"); assertThat(expression.getValue(context)).isEqualTo("ab");
expression = parser.parseExpression("#append4('a','b','c')"); expression = parser.parseExpression("#append4('a','b','c')");
assertThat(expression.getValue(context).toString()).isEqualTo("a::bc"); assertThat(expression.getValue(context).toString()).isEqualTo("a::bc");
assertThat(((SpelNodeImpl)((SpelExpression) expression).getAST()).isCompilable()).isTrue(); assertThat(((SpelExpression) expression).getAST().isCompilable()).isTrue();
assertCanCompile(expression); assertCanCompile(expression);
assertThat(expression.getValue(context).toString()).isEqualTo("a::bc"); assertThat(expression.getValue(context).toString()).isEqualTo("a::bc");
expression = parser.parseExpression("#append4('a','b')"); expression = parser.parseExpression("#append4('a','b')");
assertThat(expression.getValue(context).toString()).isEqualTo("a::b"); assertThat(expression.getValue(context).toString()).isEqualTo("a::b");
assertThat(((SpelNodeImpl)((SpelExpression) expression).getAST()).isCompilable()).isTrue(); assertThat(((SpelExpression) expression).getAST().isCompilable()).isTrue();
assertCanCompile(expression); assertCanCompile(expression);
assertThat(expression.getValue(context).toString()).isEqualTo("a::b"); assertThat(expression.getValue(context).toString()).isEqualTo("a::b");
expression = parser.parseExpression("#append4('a')"); expression = parser.parseExpression("#append4('a')");
assertThat(expression.getValue(context).toString()).isEqualTo("a::"); assertThat(expression.getValue(context).toString()).isEqualTo("a::");
assertThat(((SpelNodeImpl)((SpelExpression) expression).getAST()).isCompilable()).isTrue(); assertThat(((SpelExpression) expression).getAST().isCompilable()).isTrue();
assertCanCompile(expression); assertCanCompile(expression);
assertThat(expression.getValue(context).toString()).isEqualTo("a::"); assertThat(expression.getValue(context).toString()).isEqualTo("a::");
expression = parser.parseExpression("#append4('a',#stringArray)"); expression = parser.parseExpression("#append4('a',#stringArray)");
assertThat(expression.getValue(context).toString()).isEqualTo("a::xyz"); assertThat(expression.getValue(context).toString()).isEqualTo("a::xyz");
assertThat(((SpelNodeImpl)((SpelExpression) expression).getAST()).isCompilable()).isTrue(); assertThat(((SpelExpression) expression).getAST().isCompilable()).isTrue();
assertCanCompile(expression); assertCanCompile(expression);
assertThat(expression.getValue(context).toString()).isEqualTo("a::xyz"); assertThat(expression.getValue(context).toString()).isEqualTo("a::xyz");
} }
@ -4878,6 +4917,28 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests {
tc8 = (TestClass8) o; tc8 = (TestClass8) o;
assertThat(tc8.i).isEqualTo(42); assertThat(tc8.i).isEqualTo(42);
// varargs
expression = parser.parseExpression("new " + testclass8 + "(#root)");
Object[] objectArray = { "a", "b", "c" };
o = expression.getValue(objectArray);
assertThat(o).isExactlyInstanceOf(TestClass8.class);
assertCanCompile(expression);
o = expression.getValue(objectArray);
assertThat(o).isExactlyInstanceOf(TestClass8.class);
tc8 = (TestClass8) o;
assertThat(tc8.args).containsExactly("a", "b", "c");
// varargs with argument component type that is a subtype of the varargs component type.
expression = parser.parseExpression("new " + testclass8 + "(#root)");
String[] stringArray = { "a", "b", "c" };
o = expression.getValue(stringArray);
assertThat(o).isExactlyInstanceOf(TestClass8.class);
assertCanCompile(expression);
o = expression.getValue(stringArray);
assertThat(o).isExactlyInstanceOf(TestClass8.class);
tc8 = (TestClass8) o;
assertThat(tc8.args).containsExactly("a", "b", "c");
// private class, can't compile it // private class, can't compile it
String testclass9 = "org.springframework.expression.spel.SpelCompilationCoverageTests$TestClass9"; String testclass9 = "org.springframework.expression.spel.SpelCompilationCoverageTests$TestClass9";
expression = parser.parseExpression("new " + testclass9 + "(42)"); expression = parser.parseExpression("new " + testclass9 + "(42)");
@ -4984,6 +5045,7 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests {
assertThat(tc.s).isEqualTo("aaabbbccc"); assertThat(tc.s).isEqualTo("aaabbbccc");
tc.reset(); tc.reset();
// varargs object
expression = parser.parseExpression("sixteen('aaa','bbb','ccc')"); expression = parser.parseExpression("sixteen('aaa','bbb','ccc')");
assertCannotCompile(expression); assertCannotCompile(expression);
expression.getValue(tc); expression.getValue(tc);
@ -4994,27 +5056,38 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests {
assertThat(tc.s).isEqualTo("aaabbbccc"); assertThat(tc.s).isEqualTo("aaabbbccc");
tc.reset(); tc.reset();
// string array from property in varargs object
expression = parser.parseExpression("sixteen(seventeen)"); expression = parser.parseExpression("sixteen(seventeen)");
assertCannotCompile(expression); assertCannotCompile(expression);
expression.getValue(tc); expression.getValue(tc);
assertThat(tc.s).isEqualTo("aaabbbccc"); assertThat(tc.s).isEqualTo("aaabbbccc");
assertCanCompile(expression); assertCanCompile(expression);
tc.reset(); tc.reset();
// see TODO below expression.getValue(tc);
// expression.getValue(tc); assertThat(tc.s).isEqualTo("aaabbbccc");
// assertThat(tc.s).isEqualTo("aaabbbccc"); tc.reset();
// tc.reset();
// string array from variable in varargs object
// TODO Determine why the String[] is passed as the first element of the Object... varargs array instead of the entire varargs array. expression = parser.parseExpression("sixteen(stringArray)");
// expression = parser.parseExpression("sixteen(stringArray)"); assertCannotCompile(expression);
// assertCannotCompile(expression); expression.getValue(tc);
// expression.getValue(tc); assertThat(tc.s).isEqualTo("aaabbbccc");
// assertThat(tc.s).isEqualTo("aaabbbccc"); assertCanCompile(expression);
// assertCanCompile(expression); tc.reset();
// tc.reset(); expression.getValue(tc);
// expression.getValue(tc); assertThat(tc.s).isEqualTo("aaabbbccc");
// assertThat(tc.s).isEqualTo("aaabbbccc"); tc.reset();
// tc.reset();
// string array in varargs object with other parameter
expression = parser.parseExpression("eighteen('AAA', stringArray)");
assertCannotCompile(expression);
expression.getValue(tc);
assertThat(tc.s).isEqualTo("AAA::aaabbbccc");
assertCanCompile(expression);
tc.reset();
expression.getValue(tc);
assertThat(tc.s).isEqualTo("AAA::aaabbbccc");
tc.reset();
// varargs int // varargs int
expression = parser.parseExpression("twelve(1,2,3)"); expression = parser.parseExpression("twelve(1,2,3)");
@ -6863,6 +6936,18 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests {
public String[] seventeen() { public String[] seventeen() {
return new String[] { "aaa", "bbb", "ccc" }; return new String[] { "aaa", "bbb", "ccc" };
} }
public void eighteen(String a, Object... vargs) {
if (vargs == null) {
s = a + "::";
}
else {
s = a + "::";
for (Object varg: vargs) {
s += varg;
}
}
}
} }
@ -6911,6 +6996,7 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests {
public String s; public String s;
public double d; public double d;
public boolean z; public boolean z;
public Object[] args;
public TestClass8(int i, String s, double d, boolean z) { public TestClass8(int i, String s, double d, boolean z) {
this.i = i; this.i = i;
@ -6926,6 +7012,10 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests {
this.i = i; this.i = i;
} }
public TestClass8(Object... args) {
this.args = args;
}
@SuppressWarnings("unused") @SuppressWarnings("unused")
private TestClass8(String a, String b) { private TestClass8(String a, String b) {
this.s = a+b; this.s = a+b;

Loading…
Cancel
Save