diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/SpelNodeImpl.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/SpelNodeImpl.java index 06499fa14c4..bc71a1c243c 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/SpelNodeImpl.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/SpelNodeImpl.java @@ -16,13 +16,13 @@ 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.Method; import java.util.function.Supplier; import org.springframework.asm.MethodVisitor; import org.springframework.asm.Opcodes; +import org.springframework.asm.Type; import org.springframework.expression.EvaluationException; import org.springframework.expression.TypedValue; 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.lang.Nullable; import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; /** * 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 member the method or constructor for which arguments are being set up * @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) { - String[] paramDescriptors = null; - boolean isVarargs = false; - if (member instanceof Constructor> ctor) { - paramDescriptors = CodeFlow.toDescriptors(ctor.getParameterTypes()); - isVarargs = ctor.isVarArgs(); + if (member instanceof Executable executable) { + generateCodeForArguments(mv, cf, executable, arguments); } - else { // Method - Method method = (Method)member; - paramDescriptors = CodeFlow.toDescriptors(method.getParameterTypes()); - isVarargs = method.isVarArgs(); - } - if (isVarargs) { + throw new IllegalArgumentException( + "The supplied member must be an instance of java.lang.reflect.Executable: " + member); + } + + /** + * Generate code that handles building the argument values for the specified + * {@link Executable} (method or constructor). + *
This method takes into account whether the invoked executable was + * 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 // have been passed to satisfy the varargs and so something needs to be built. 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]); - 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 // 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]); } else { + String arrayType = paramDescriptors[paramDescriptors.length - 1]; arrayType = arrayType.substring(1); // trim the leading '[', may leave other '[' // build array big enough to hold remaining arguments 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 * with any boxing/unboxing/checkcasting to ensure it matches the expected parameter descriptor. diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/SpelCompilationCoverageTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/SpelCompilationCoverageTests.java index 32dd5eac5f9..688d517334a 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/SpelCompilationCoverageTests.java +++ b/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.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.StringTokenizer; +import java.util.stream.Collectors; import java.util.stream.Stream; import example.Color; @@ -2267,6 +2269,12 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests { 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) { StringBuilder buf = new StringBuilder(); for (String string: strings) { @@ -2277,9 +2285,9 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests { @Test 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')"); context = new StandardEvaluationContext(); context.registerFunction("doFormat", @@ -2287,10 +2295,11 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests { ((SpelExpression) expression).setEvaluationContext(context); 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"); + // single string argument from root array access expression = parser.parseExpression("#doFormat([0], 'there')"); context = new StandardEvaluationContext(new Object[] {"hey %s"}); context.registerFunction("doFormat", @@ -2298,10 +2307,11 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests { ((SpelExpression) expression).setEvaluationContext(context); 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"); + // single string from variable expression = parser.parseExpression("#doFormat([0], #arg)"); context = new StandardEvaluationContext(new Object[] {"hey %s"}); context.registerFunction("doFormat", @@ -2310,7 +2320,19 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests { ((SpelExpression) expression).setEvaluationContext(context); 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); assertThat(expression.getValue(String.class)).isEqualTo("hey there"); } @@ -2319,7 +2341,11 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests { void functionReference() throws Exception { EvaluationContext ctx = new StandardEvaluationContext(); 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')"); assertThat(expression.getValue(ctx)).isEqualTo("ab"); @@ -2331,6 +2357,20 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests { assertCanCompile(expression); 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)"); ctx.setVariable("a", "foo"); ctx.setVariable("b", "bar"); @@ -2465,173 +2505,172 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests { expression = parser.parseExpression("#append('a','b','c')"); assertThat(expression.getValue(context).toString()).isEqualTo("abc"); - assertThat(((SpelNodeImpl)((SpelExpression) expression).getAST()).isCompilable()).isTrue(); + assertThat(((SpelExpression) expression).getAST().isCompilable()).isTrue(); assertCanCompile(expression); assertThat(expression.getValue(context).toString()).isEqualTo("abc"); expression = parser.parseExpression("#append('a')"); assertThat(expression.getValue(context).toString()).isEqualTo("a"); - assertThat(((SpelNodeImpl)((SpelExpression) expression).getAST()).isCompilable()).isTrue(); + assertThat(((SpelExpression) expression).getAST().isCompilable()).isTrue(); assertCanCompile(expression); assertThat(expression.getValue(context).toString()).isEqualTo("a"); expression = parser.parseExpression("#append()"); assertThat(expression.getValue(context).toString()).isEmpty(); - assertThat(((SpelNodeImpl)((SpelExpression) expression).getAST()).isCompilable()).isTrue(); + assertThat(((SpelExpression) expression).getAST().isCompilable()).isTrue(); assertCanCompile(expression); assertThat(expression.getValue(context).toString()).isEmpty(); expression = parser.parseExpression("#append(#stringArray)"); assertThat(expression.getValue(context).toString()).isEqualTo("xyz"); - assertThat(((SpelNodeImpl)((SpelExpression) expression).getAST()).isCompilable()).isTrue(); + assertThat(((SpelExpression) expression).getAST().isCompilable()).isTrue(); assertCanCompile(expression); assertThat(expression.getValue(context).toString()).isEqualTo("xyz"); // This is a methodreference invocation, to compare with functionreference expression = parser.parseExpression("append(#stringArray)"); assertThat(expression.getValue(context, new SomeCompareMethod2()).toString()).isEqualTo("xyz"); - assertThat(((SpelNodeImpl)((SpelExpression) expression).getAST()).isCompilable()).isTrue(); + assertThat(((SpelExpression) expression).getAST().isCompilable()).isTrue(); assertCanCompile(expression); assertThat(expression.getValue(context, new SomeCompareMethod2()).toString()).isEqualTo("xyz"); expression = parser.parseExpression("#append2('a','b','c')"); assertThat(expression.getValue(context).toString()).isEqualTo("abc"); - assertThat(((SpelNodeImpl)((SpelExpression) expression).getAST()).isCompilable()).isTrue(); + assertThat(((SpelExpression) expression).getAST().isCompilable()).isTrue(); assertCanCompile(expression); assertThat(expression.getValue(context).toString()).isEqualTo("abc"); expression = parser.parseExpression("append2('a','b')"); assertThat(expression.getValue(context, new SomeCompareMethod2()).toString()).isEqualTo("ab"); - assertThat(((SpelNodeImpl)((SpelExpression) expression).getAST()).isCompilable()).isTrue(); + assertThat(((SpelExpression) expression).getAST().isCompilable()).isTrue(); assertCanCompile(expression); assertThat(expression.getValue(context, new SomeCompareMethod2()).toString()).isEqualTo("ab"); expression = parser.parseExpression("#append2('a','b')"); assertThat(expression.getValue(context).toString()).isEqualTo("ab"); - assertThat(((SpelNodeImpl)((SpelExpression) expression).getAST()).isCompilable()).isTrue(); + assertThat(((SpelExpression) expression).getAST().isCompilable()).isTrue(); assertCanCompile(expression); assertThat(expression.getValue(context).toString()).isEqualTo("ab"); expression = parser.parseExpression("#append2()"); assertThat(expression.getValue(context).toString()).isEmpty(); - assertThat(((SpelNodeImpl)((SpelExpression) expression).getAST()).isCompilable()).isTrue(); + assertThat(((SpelExpression) expression).getAST().isCompilable()).isTrue(); assertCanCompile(expression); assertThat(expression.getValue(context).toString()).isEmpty(); expression = parser.parseExpression("#append3(#stringArray)"); assertThat(expression.getValue(context, new SomeCompareMethod2()).toString()).isEqualTo("xyz"); - assertThat(((SpelNodeImpl)((SpelExpression) expression).getAST()).isCompilable()).isTrue(); + assertThat(((SpelExpression) expression).getAST().isCompilable()).isTrue(); assertCanCompile(expression); 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)"); - // assertThat(expression.getValue(context)).hasToString("xyz"); - // assertThat(((SpelNodeImpl) ((SpelExpression) expression).getAST()).isCompilable()).isTrue(); - // assertCanCompile(expression); - // assertThat(expression.getValue(context)).hasToString("xyz"); + expression = parser.parseExpression("#append2(#stringArray)"); + assertThat(expression.getValue(context)).hasToString("xyz"); + assertThat(((SpelExpression) expression).getAST().isCompilable()).isTrue(); + assertCanCompile(expression); + assertThat(expression.getValue(context)).hasToString("xyz"); expression = parser.parseExpression("#sum(1,2,3)"); assertThat(expression.getValue(context)).isEqualTo(6); - assertThat(((SpelNodeImpl)((SpelExpression) expression).getAST()).isCompilable()).isTrue(); + assertThat(((SpelExpression) expression).getAST().isCompilable()).isTrue(); assertCanCompile(expression); assertThat(expression.getValue(context)).isEqualTo(6); expression = parser.parseExpression("#sum(2)"); assertThat(expression.getValue(context)).isEqualTo(2); - assertThat(((SpelNodeImpl)((SpelExpression) expression).getAST()).isCompilable()).isTrue(); + assertThat(((SpelExpression) expression).getAST().isCompilable()).isTrue(); assertCanCompile(expression); assertThat(expression.getValue(context)).isEqualTo(2); expression = parser.parseExpression("#sum()"); assertThat(expression.getValue(context)).isEqualTo(0); - assertThat(((SpelNodeImpl)((SpelExpression) expression).getAST()).isCompilable()).isTrue(); + assertThat(((SpelExpression) expression).getAST().isCompilable()).isTrue(); assertCanCompile(expression); assertThat(expression.getValue(context)).isEqualTo(0); expression = parser.parseExpression("#sum(#intArray)"); assertThat(expression.getValue(context)).isEqualTo(20); - assertThat(((SpelNodeImpl)((SpelExpression) expression).getAST()).isCompilable()).isTrue(); + assertThat(((SpelExpression) expression).getAST().isCompilable()).isTrue(); assertCanCompile(expression); assertThat(expression.getValue(context)).isEqualTo(20); expression = parser.parseExpression("#sumDouble(1.0d,2.0d,3.0d)"); assertThat(expression.getValue(context)).isEqualTo(6); - assertThat(((SpelNodeImpl)((SpelExpression) expression).getAST()).isCompilable()).isTrue(); + assertThat(((SpelExpression) expression).getAST().isCompilable()).isTrue(); assertCanCompile(expression); assertThat(expression.getValue(context)).isEqualTo(6); expression = parser.parseExpression("#sumDouble(2.0d)"); assertThat(expression.getValue(context)).isEqualTo(2); - assertThat(((SpelNodeImpl)((SpelExpression) expression).getAST()).isCompilable()).isTrue(); + assertThat(((SpelExpression) expression).getAST().isCompilable()).isTrue(); assertCanCompile(expression); assertThat(expression.getValue(context)).isEqualTo(2); expression = parser.parseExpression("#sumDouble()"); assertThat(expression.getValue(context)).isEqualTo(0); - assertThat(((SpelNodeImpl)((SpelExpression) expression).getAST()).isCompilable()).isTrue(); + assertThat(((SpelExpression) expression).getAST().isCompilable()).isTrue(); assertCanCompile(expression); assertThat(expression.getValue(context)).isEqualTo(0); expression = parser.parseExpression("#sumDouble(#doubleArray)"); assertThat(expression.getValue(context)).isEqualTo(20); - assertThat(((SpelNodeImpl)((SpelExpression) expression).getAST()).isCompilable()).isTrue(); + assertThat(((SpelExpression) expression).getAST().isCompilable()).isTrue(); assertCanCompile(expression); assertThat(expression.getValue(context)).isEqualTo(20); expression = parser.parseExpression("#sumFloat(1.0f,2.0f,3.0f)"); assertThat(expression.getValue(context)).isEqualTo(6); - assertThat(((SpelNodeImpl)((SpelExpression) expression).getAST()).isCompilable()).isTrue(); + assertThat(((SpelExpression) expression).getAST().isCompilable()).isTrue(); assertCanCompile(expression); assertThat(expression.getValue(context)).isEqualTo(6); expression = parser.parseExpression("#sumFloat(2.0f)"); assertThat(expression.getValue(context)).isEqualTo(2); - assertThat(((SpelNodeImpl)((SpelExpression) expression).getAST()).isCompilable()).isTrue(); + assertThat(((SpelExpression) expression).getAST().isCompilable()).isTrue(); assertCanCompile(expression); assertThat(expression.getValue(context)).isEqualTo(2); expression = parser.parseExpression("#sumFloat()"); assertThat(expression.getValue(context)).isEqualTo(0); - assertThat(((SpelNodeImpl)((SpelExpression) expression).getAST()).isCompilable()).isTrue(); + assertThat(((SpelExpression) expression).getAST().isCompilable()).isTrue(); assertCanCompile(expression); assertThat(expression.getValue(context)).isEqualTo(0); expression = parser.parseExpression("#sumFloat(#floatArray)"); assertThat(expression.getValue(context)).isEqualTo(20); - assertThat(((SpelNodeImpl)((SpelExpression) expression).getAST()).isCompilable()).isTrue(); + assertThat(((SpelExpression) expression).getAST().isCompilable()).isTrue(); assertCanCompile(expression); assertThat(expression.getValue(context)).isEqualTo(20); expression = parser.parseExpression("#appendChar('abc'.charAt(0),'abc'.charAt(1))"); assertThat(expression.getValue(context)).isEqualTo("ab"); - assertThat(((SpelNodeImpl)((SpelExpression) expression).getAST()).isCompilable()).isTrue(); + assertThat(((SpelExpression) expression).getAST().isCompilable()).isTrue(); assertCanCompile(expression); assertThat(expression.getValue(context)).isEqualTo("ab"); expression = parser.parseExpression("#append4('a','b','c')"); assertThat(expression.getValue(context).toString()).isEqualTo("a::bc"); - assertThat(((SpelNodeImpl)((SpelExpression) expression).getAST()).isCompilable()).isTrue(); + assertThat(((SpelExpression) expression).getAST().isCompilable()).isTrue(); assertCanCompile(expression); assertThat(expression.getValue(context).toString()).isEqualTo("a::bc"); expression = parser.parseExpression("#append4('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); assertThat(expression.getValue(context).toString()).isEqualTo("a::b"); expression = parser.parseExpression("#append4('a')"); assertThat(expression.getValue(context).toString()).isEqualTo("a::"); - assertThat(((SpelNodeImpl)((SpelExpression) expression).getAST()).isCompilable()).isTrue(); + assertThat(((SpelExpression) expression).getAST().isCompilable()).isTrue(); assertCanCompile(expression); assertThat(expression.getValue(context).toString()).isEqualTo("a::"); expression = parser.parseExpression("#append4('a',#stringArray)"); assertThat(expression.getValue(context).toString()).isEqualTo("a::xyz"); - assertThat(((SpelNodeImpl)((SpelExpression) expression).getAST()).isCompilable()).isTrue(); + assertThat(((SpelExpression) expression).getAST().isCompilable()).isTrue(); assertCanCompile(expression); assertThat(expression.getValue(context).toString()).isEqualTo("a::xyz"); } @@ -4878,6 +4917,28 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests { tc8 = (TestClass8) o; 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 String testclass9 = "org.springframework.expression.spel.SpelCompilationCoverageTests$TestClass9"; expression = parser.parseExpression("new " + testclass9 + "(42)"); @@ -4984,6 +5045,7 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests { assertThat(tc.s).isEqualTo("aaabbbccc"); tc.reset(); + // varargs object expression = parser.parseExpression("sixteen('aaa','bbb','ccc')"); assertCannotCompile(expression); expression.getValue(tc); @@ -4994,27 +5056,38 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests { assertThat(tc.s).isEqualTo("aaabbbccc"); tc.reset(); + // string array from property in varargs object expression = parser.parseExpression("sixteen(seventeen)"); assertCannotCompile(expression); expression.getValue(tc); assertThat(tc.s).isEqualTo("aaabbbccc"); assertCanCompile(expression); tc.reset(); - // see TODO below - // expression.getValue(tc); - // assertThat(tc.s).isEqualTo("aaabbbccc"); - // tc.reset(); - - // 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)"); - // assertCannotCompile(expression); - // expression.getValue(tc); - // assertThat(tc.s).isEqualTo("aaabbbccc"); - // assertCanCompile(expression); - // tc.reset(); - // expression.getValue(tc); - // assertThat(tc.s).isEqualTo("aaabbbccc"); - // tc.reset(); + expression.getValue(tc); + assertThat(tc.s).isEqualTo("aaabbbccc"); + tc.reset(); + + // string array from variable in varargs object + expression = parser.parseExpression("sixteen(stringArray)"); + assertCannotCompile(expression); + expression.getValue(tc); + assertThat(tc.s).isEqualTo("aaabbbccc"); + assertCanCompile(expression); + tc.reset(); + expression.getValue(tc); + assertThat(tc.s).isEqualTo("aaabbbccc"); + 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 expression = parser.parseExpression("twelve(1,2,3)"); @@ -6863,6 +6936,18 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests { public String[] seventeen() { 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 double d; public boolean z; + public Object[] args; public TestClass8(int i, String s, double d, boolean z) { this.i = i; @@ -6926,6 +7012,10 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests { this.i = i; } + public TestClass8(Object... args) { + this.args = args; + } + @SuppressWarnings("unused") private TestClass8(String a, String b) { this.s = a+b;