diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/CodeFlow.java b/spring-expression/src/main/java/org/springframework/expression/spel/CodeFlow.java index 529bf0587c..acdef4d84f 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/CodeFlow.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/CodeFlow.java @@ -19,15 +19,19 @@ package org.springframework.expression.spel; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.util.ArrayList; +import java.util.List; import java.util.Stack; +import org.springframework.asm.ClassWriter; import org.springframework.asm.MethodVisitor; import org.springframework.asm.Opcodes; +import org.springframework.expression.spel.ast.SpelNodeImpl; import org.springframework.util.Assert; /** - * Records intermediate compilation state as the bytecode is generated for a parsed - * expression. Also contains bytecode generation helper functions. + * Manages the class being generated by the compilation process. It records + * intermediate compilation state as the bytecode is generated. It also includes + * various bytecode generation helper functions. * * @author Andy Clement * @since 4.1 @@ -42,13 +46,51 @@ public class CodeFlow implements Opcodes { */ private final Stack> compilationScopes; + /** + * The current class being generated + */ + private ClassWriter cw; - public CodeFlow() { + /** + * As SpEL ast nodes are called to generate code for the main evaluation method + * they can register to add a field to this class. Any registered FieldAdders + * will be called after the main evaluation function has finished being generated. + */ + private List fieldAdders = null; + + /** + * As SpEL ast nodes are called to generate code for the main evaluation method + * they can register to add code to a static initializer in the class. Any + * registered ClinitAdders will be called after the main evaluation function + * has finished being generated. + */ + private List clinitAdders = null; + + /** + * Name of the class being generated. Typically used when generating code + * that accesses freshly generated fields on the generated type. + */ + private String clazzName; + + /** + * When code generation requires holding a value in a class level field, this + * is used to track the next available field id (used as a name suffix). + */ + private int nextFieldId = 1; + + /** + * When code generation requires an intermediate variable within a method, + * this method records the next available variable (variable 0 is 'this'). + */ + private int nextFreeVariableId = 1; + + public CodeFlow(String clazzName, ClassWriter cw) { this.compilationScopes = new Stack>(); this.compilationScopes.add(new ArrayList()); + this.cw = cw; + this.clazzName = clazzName; } - /** * Push the byte code to load the target (i.e. what was passed as the first argument * to CompiledExpression.getValue(target, context)) @@ -106,7 +148,6 @@ public class CodeFlow implements Opcodes { } } - /** * Insert any necessary cast and value call to convert from a boxed type to a * primitive value @@ -612,5 +653,256 @@ public class CodeFlow implements Opcodes { } return descriptors; } + + /** + * Called after the main expression evaluation method has been generated, this + * method will callback any registered FieldAdders or ClinitAdders to add any + * extra information to the class representing the compiled expression. + */ + public void finish() { + if (fieldAdders != null) { + for (FieldAdder fieldAdder: fieldAdders) { + fieldAdder.generateField(cw,this); + } + } + if (clinitAdders != null) { + MethodVisitor mv = cw.visitMethod(ACC_PUBLIC | ACC_STATIC, "", "()V", null, null); + mv.visitCode(); + nextFreeVariableId = 0; // To 0 because there is no 'this' in a clinit + for (ClinitAdder clinitAdder: clinitAdders) { + clinitAdder.generateCode(mv, this); + } + mv.visitInsn(RETURN); + mv.visitMaxs(0,0); // not supplied due to COMPUTE_MAXS + mv.visitEnd(); + } + } + /** + * Register a FieldAdder which will add a new field to the generated + * class to support the code produced by an ast nodes primary + * generateCode() method. + */ + public void registerNewField(FieldAdder fieldAdder) { + if (fieldAdders == null) { + fieldAdders = new ArrayList(); + } + fieldAdders.add(fieldAdder); + } + + /** + * Register a ClinitAdder which will add code to the static + * initializer in the generated class to support the code + * produced by an ast nodes primary generateCode() method. + */ + public void registerNewClinit(ClinitAdder clinitAdder) { + if (clinitAdders == null) { + clinitAdders = new ArrayList(); + } + clinitAdders.add(clinitAdder); + } + + public int nextFieldId() { + return nextFieldId++; + } + + public int nextFreeVariableId() { + return nextFreeVariableId++; + } + + public String getClassname() { + return clazzName; + } + + public interface FieldAdder { + public void generateField(ClassWriter cw, CodeFlow codeflow); + } + + public interface ClinitAdder { + public void generateCode(MethodVisitor mv, CodeFlow codeflow); + } + + /** + * Create the optimal instruction for loading a number on the stack. + * @param mv where to insert the bytecode + * @param value the value to be loaded + */ + public static void insertOptimalLoad(MethodVisitor mv, int value) { + if (value < 6) { + mv.visitInsn(ICONST_0+value); + } + else if (value < Byte.MAX_VALUE) { + mv.visitIntInsn(BIPUSH, value); + } + else if (value < Short.MAX_VALUE) { + mv.visitIntInsn(SIPUSH, value); + } + else { + mv.visitLdcInsn(value); + } + } + + /** + * Produce appropriate bytecode to store a stack item in an array. The + * instruction to use varies depending on whether the type + * is a primitive or reference type. + * @param mv where to insert the bytecode + * @param arrayElementType the type of the array elements + */ + public static void insertArrayStore(MethodVisitor mv, String arrayElementType) { + if (arrayElementType.length()==1) { + switch (arrayElementType.charAt(0)) { + case 'I': mv.visitInsn(IASTORE); break; + case 'J': mv.visitInsn(LASTORE); break; + case 'F': mv.visitInsn(FASTORE); break; + case 'D': mv.visitInsn(DASTORE); break; + case 'B': mv.visitInsn(BASTORE); break; + case 'C': mv.visitInsn(CASTORE); break; + case 'S': mv.visitInsn(SASTORE); break; + case 'Z': mv.visitInsn(BASTORE); break; + default: + throw new IllegalArgumentException("Unexpected arraytype "+arrayElementType.charAt(0)); + } + } + else { + mv.visitInsn(AASTORE); + } + } + + /** + * Determine the appropriate T tag to use for the NEWARRAY bytecode. + * @param arraytype the array primitive component type + * @return the T tag to use for NEWARRAY + */ + public static int arrayCodeFor(String arraytype) { + switch (arraytype.charAt(0)) { + case 'I': return T_INT; + case 'J': return T_LONG; + case 'F': return T_FLOAT; + case 'D': return T_DOUBLE; + case 'B': return T_BYTE; + case 'C': return T_CHAR; + case 'S': return T_SHORT; + case 'Z': return T_BOOLEAN; + default: + throw new IllegalArgumentException("Unexpected arraytype "+arraytype.charAt(0)); + } + } + + /** + * @return true if the supplied array type has a core component reference type + */ + public static boolean isReferenceTypeArray(String arraytype) { + int length = arraytype.length(); + for (int i=0;i getConstantValue() { return (List) this.constant.getValue(); } + + @Override + public boolean isCompilable() { + return isConstant(); + } + + @Override + public void generateCode(MethodVisitor mv, CodeFlow codeflow) { + final String constantFieldName = "inlineList$"+codeflow.nextFieldId(); + final String clazzname = codeflow.getClassname(); + + codeflow.registerNewField(new CodeFlow.FieldAdder() { + public void generateField(ClassWriter cw, CodeFlow codeflow) { + cw.visitField(ACC_PRIVATE|ACC_STATIC|ACC_FINAL, constantFieldName, "Ljava/util/List;", null, null); + } + }); + + codeflow.registerNewClinit(new CodeFlow.ClinitAdder() { + public void generateCode(MethodVisitor mv, CodeFlow codeflow) { + generateClinitCode(clazzname,constantFieldName, mv,codeflow,false); + } + }); + + mv.visitFieldInsn(GETSTATIC, clazzname, constantFieldName, "Ljava/util/List;"); + codeflow.pushDescriptor("Ljava/util/List"); + } + + void generateClinitCode(String clazzname, String constantFieldName, MethodVisitor mv, CodeFlow codeflow, boolean nested) { + mv.visitTypeInsn(NEW, "java/util/ArrayList"); + mv.visitInsn(DUP); + mv.visitMethodInsn(INVOKESPECIAL, "java/util/ArrayList", "", "()V", false); + if (!nested) { + mv.visitFieldInsn(PUTSTATIC, clazzname, constantFieldName, "Ljava/util/List;"); + } + int childcount = getChildCount(); + for (int c=0; c < childcount; c++) { + if (!nested) { + mv.visitFieldInsn(GETSTATIC, clazzname, constantFieldName, "Ljava/util/List;"); + } + else { + mv.visitInsn(DUP); + } + // The children might be further lists if they are not constants. In this + // situation do not call back into generateCode() because it will register another clinit adder. + // Instead, directly build the list here: + if (children[c] instanceof InlineList) { + ((InlineList)children[c]).generateClinitCode(clazzname, constantFieldName, mv, codeflow, true); + } + else { + children[c].generateCode(mv, codeflow); + if (CodeFlow.isPrimitive(codeflow.lastDescriptor())) { + CodeFlow.insertBoxIfNecessary(mv, codeflow.lastDescriptor().charAt(0)); + } + } + mv.visitMethodInsn(INVOKEINTERFACE, "java/util/List", "add", "(Ljava/lang/Object;)Z", true); + mv.visitInsn(POP); + } + } } diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/MethodReference.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/MethodReference.java index c23acbd8b1..d86a71df5e 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/MethodReference.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/MethodReference.java @@ -268,20 +268,18 @@ public class MethodReference extends SpelNodeImpl { } ReflectiveMethodExecutor executor = (ReflectiveMethodExecutor) executorToCheck.get(); - Method method = executor.getMethod(); - if (!Modifier.isPublic(method.getModifiers()) || !Modifier.isPublic(method.getDeclaringClass().getModifiers())) { - return false; - } - if (method.isVarArgs()) { - return false; - } if (executor.didArgumentConversionOccur()) { return false; } + Method method = executor.getMethod(); + Class clazz = method.getDeclaringClass(); + if (!Modifier.isPublic(clazz.getModifiers()) && executor.getPublicDeclaringClass() == null) { + return false; + } return true; } - + @Override public void generateCode(MethodVisitor mv, CodeFlow cf) { CachedMethodExecutor executorToCheck = this.cachedExecutor; @@ -289,7 +287,8 @@ public class MethodReference extends SpelNodeImpl { throw new IllegalStateException("No applicable cached executor found: " + executorToCheck); } - Method method = ((ReflectiveMethodExecutor) executorToCheck.get()).getMethod(); + ReflectiveMethodExecutor methodExecutor = (ReflectiveMethodExecutor)executorToCheck.get(); + Method method = methodExecutor.getMethod(); boolean isStaticMethod = Modifier.isStatic(method.getModifiers()); String descriptor = cf.lastDescriptor(); @@ -298,27 +297,19 @@ public class MethodReference extends SpelNodeImpl { } boolean itf = method.getDeclaringClass().isInterface(); - String methodDeclaringClassSlashedDescriptor = method.getDeclaringClass().getName().replace('.', '/'); + String methodDeclaringClassSlashedDescriptor = null; + if (Modifier.isPublic(method.getDeclaringClass().getModifiers())) { + methodDeclaringClassSlashedDescriptor = method.getDeclaringClass().getName().replace('.', '/'); + } + else { + methodDeclaringClassSlashedDescriptor = methodExecutor.getPublicDeclaringClass().getName().replace('.', '/'); + } if (!isStaticMethod) { - if (descriptor == null || !descriptor.equals(methodDeclaringClassSlashedDescriptor)) { - mv.visitTypeInsn(CHECKCAST, methodDeclaringClassSlashedDescriptor); + if (descriptor == null || !descriptor.substring(1).equals(methodDeclaringClassSlashedDescriptor)) { + CodeFlow.insertCheckCast(mv, "L"+ methodDeclaringClassSlashedDescriptor); } } - String[] paramDescriptors = CodeFlow.toParamDescriptors(method); - for (int i = 0; i < this.children.length;i++) { - SpelNodeImpl child = this.children[i]; - cf.enterCompilationScope(); - child.generateCode(mv, cf); - // Check if need to box it for the method reference? - if (CodeFlow.isPrimitive(cf.lastDescriptor()) && paramDescriptors[i].charAt(0) == 'L') { - CodeFlow.insertBoxIfNecessary(mv, cf.lastDescriptor().charAt(0)); - } - else if (!cf.lastDescriptor().equals(paramDescriptors[i])) { - // This would be unnecessary in the case of subtyping (e.g. method takes Number but Integer passed in) - CodeFlow.insertCheckCast(mv, paramDescriptors[i]); - } - cf.exitCompilationScope(); - } + CodeFlow.generateCodeForArguments(mv, cf, method, children); mv.visitMethodInsn(isStaticMethod ? INVOKESTATIC : INVOKEVIRTUAL, methodDeclaringClassSlashedDescriptor, method.getName(), CodeFlow.createSignatureDescriptor(method), itf); cf.pushDescriptor(this.exitTypeDescriptor); diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpPlus.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpPlus.java index 5e5cc65052..9006d5c555 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpPlus.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/OpPlus.java @@ -130,6 +130,7 @@ public class OpPlus extends Operator { } if (leftOperand instanceof String && rightOperand instanceof String) { + this.exitTypeDescriptor = "Ljava/lang/String"; return new TypedValue((String) leftOperand + rightOperand); } @@ -192,37 +193,65 @@ public class OpPlus extends Operator { return (this.exitTypeDescriptor != null); } + /** + * Walk through a possible tree of nodes that combine strings and append + * them all to the same (on stack) StringBuilder. + */ + private void walk(MethodVisitor mv, CodeFlow cf, SpelNodeImpl operand) { + if (operand instanceof OpPlus) { + OpPlus plus = (OpPlus)operand; + walk(mv,cf,plus.getLeftOperand()); + walk(mv,cf,plus.getRightOperand()); + } + else { + cf.enterCompilationScope(); + operand.generateCode(mv,cf); + cf.exitCompilationScope(); + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false); + } + } + @Override public void generateCode(MethodVisitor mv, CodeFlow cf) { - getLeftOperand().generateCode(mv, cf); - String leftDesc = getLeftOperand().exitTypeDescriptor; - if (!CodeFlow.isPrimitive(leftDesc)) { - CodeFlow.insertUnboxInsns(mv, this.exitTypeDescriptor.charAt(0), leftDesc); + if (this.exitTypeDescriptor == "Ljava/lang/String") { + mv.visitTypeInsn(NEW, "java/lang/StringBuilder"); + mv.visitInsn(DUP); + mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "", "()V", false); + walk(mv,cf,getLeftOperand()); + walk(mv,cf,getRightOperand()); + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false); } - if (this.children.length > 1) { - cf.enterCompilationScope(); - getRightOperand().generateCode(mv, cf); - String rightDesc = getRightOperand().exitTypeDescriptor; - cf.exitCompilationScope(); - if (!CodeFlow.isPrimitive(rightDesc)) { - CodeFlow.insertUnboxInsns(mv, this.exitTypeDescriptor.charAt(0), rightDesc); + else { + getLeftOperand().generateCode(mv, cf); + String leftDesc = getLeftOperand().exitTypeDescriptor; + if (!CodeFlow.isPrimitive(leftDesc)) { + CodeFlow.insertUnboxInsns(mv, this.exitTypeDescriptor.charAt(0), leftDesc); } - switch (this.exitTypeDescriptor.charAt(0)) { - case 'I': - mv.visitInsn(IADD); - break; - case 'J': - mv.visitInsn(LADD); - break; - case 'F': - mv.visitInsn(FADD); - break; - case 'D': - mv.visitInsn(DADD); - break; - default: - throw new IllegalStateException( - "Unrecognized exit type descriptor: '" + this.exitTypeDescriptor + "'"); + if (this.children.length > 1) { + cf.enterCompilationScope(); + getRightOperand().generateCode(mv, cf); + String rightDesc = getRightOperand().exitTypeDescriptor; + cf.exitCompilationScope(); + if (!CodeFlow.isPrimitive(rightDesc)) { + CodeFlow.insertUnboxInsns(mv, this.exitTypeDescriptor.charAt(0), rightDesc); + } + switch (this.exitTypeDescriptor.charAt(0)) { + case 'I': + mv.visitInsn(IADD); + break; + case 'J': + mv.visitInsn(LADD); + break; + case 'F': + mv.visitInsn(FADD); + break; + case 'D': + mv.visitInsn(DADD); + break; + default: + throw new IllegalStateException( + "Unrecognized exit type descriptor: '" + this.exitTypeDescriptor + "'"); + } } } cf.pushDescriptor(this.exitTypeDescriptor); diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/standard/SpelCompiler.java b/spring-expression/src/main/java/org/springframework/expression/spel/standard/SpelCompiler.java index ac400689fb..2e9ed08172 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/standard/SpelCompiler.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/standard/SpelCompiler.java @@ -153,7 +153,7 @@ public class SpelCompiler implements Opcodes { new String[ ]{"org/springframework/expression/EvaluationException"}); mv.visitCode(); - CodeFlow cf = new CodeFlow(); + CodeFlow cf = new CodeFlow(clazzName, cw); // Ask the expression AST to generate the body of the method try { @@ -176,6 +176,9 @@ public class SpelCompiler implements Opcodes { mv.visitMaxs(0, 0); // not supplied due to COMPUTE_MAXS mv.visitEnd(); cw.visitEnd(); + + cf.finish(); + byte[] data = cw.toByteArray(); // TODO need to make this conditionally occur based on a debug flag // dump(expressionToCompile.toStringAST(), clazzName, data); @@ -241,6 +244,7 @@ public class SpelCompiler implements Opcodes { tempFile.delete(); File f = new File(tempFile, dir); f.mkdirs(); + // System.out.println("Expression '" + expressionText + "' compiled code dumped to " + dumpLocation); if (logger.isDebugEnabled()) { logger.debug("Expression '" + expressionText + "' compiled code dumped to " + dumpLocation); } diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectionHelper.java b/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectionHelper.java index 263bceed7a..245228e555 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectionHelper.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectionHelper.java @@ -287,7 +287,6 @@ public class ReflectionHelper { if (method.isVarArgs()) { Class[] paramTypes = method.getParameterTypes(); varargsPosition = paramTypes.length - 1; - conversionOccurred = true; } for (int argPos = 0; argPos < arguments.length; argPos++) { TypeDescriptor targetType; diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectiveMethodExecutor.java b/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectiveMethodExecutor.java index f995547937..637385fdb6 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectiveMethodExecutor.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectiveMethodExecutor.java @@ -17,6 +17,7 @@ package org.springframework.expression.spel.support; import java.lang.reflect.Method; +import java.lang.reflect.Modifier; import org.springframework.core.MethodParameter; import org.springframework.core.convert.TypeDescriptor; @@ -34,9 +35,13 @@ import org.springframework.util.ReflectionUtils; public class ReflectiveMethodExecutor implements MethodExecutor { private final Method method; - + private final Integer varargsPosition; + private boolean computedPublicDeclaringClass = false; + + private Class publicDeclaringClass; + private boolean argumentConversionOccurred = false; public ReflectiveMethodExecutor(Method method) { @@ -54,6 +59,41 @@ public class ReflectiveMethodExecutor implements MethodExecutor { return this.method; } + /** + * Find the first public class in the methods declaring class hierarchy that declares this method. + * Sometimes the reflective method discovery logic finds a suitable method that can easily be + * called via reflection but cannot be called from generated code when compiling the expression + * because of visibility restrictions. For example if a non public class overrides toString(), this + * helper method will walk up the type hierarchy to find the first public type that declares the + * method (if there is one!). For toString() it may walk as far as Object. + */ + public Class getPublicDeclaringClass() { + if (!computedPublicDeclaringClass) { + this.publicDeclaringClass = discoverPublicClass(method, method.getDeclaringClass()); + this.computedPublicDeclaringClass = true; + } + return this.publicDeclaringClass; + } + + private Class discoverPublicClass(Method method, Class clazz) { + if (Modifier.isPublic(clazz.getModifiers())) { + try { + clazz.getDeclaredMethod(method.getName(), method.getParameterTypes()); + return clazz; + } catch (NoSuchMethodException nsme) { + + } + } + Class[] intfaces = clazz.getInterfaces(); + for (Class intface: intfaces) { + discoverPublicClass(method, intface); + } + if (clazz.getSuperclass() != null) { + return discoverPublicClass(method, clazz.getSuperclass()); + } + return null; + } + public boolean didArgumentConversionOccur() { return this.argumentConversionOccurred; } 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 c214e999ae..956d9e7911 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 @@ -99,18 +99,18 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests { * CompoundExpression * ConstructorReference * FunctionReference + * InlineList + * OpModulus * * Not yet compiled (some may never need to be): * Assign * BeanReference * Identifier - * InlineList * OpDec * OpBetween * OpMatches * OpPower * OpInc - * OpModulus * Projection * QualifiedId * Selection @@ -270,6 +270,121 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests { assertEquals(3.4d,expression.getValue()); } + + @SuppressWarnings("rawtypes") + @Test + public void inlineList() throws Exception { + expression = parser.parseExpression("'abcde'.substring({1,3,4}[0])"); + Object o = expression.getValue(); + assertEquals("bcde",o); + assertCanCompile(expression); + o = expression.getValue(); + assertEquals("bcde", o); + + expression = parser.parseExpression("{'abc','def'}"); + List l = (List) expression.getValue(); + assertEquals("[abc, def]", l.toString()); + assertCanCompile(expression); + l = (List) expression.getValue(); + assertEquals("[abc, def]", l.toString()); + + expression = parser.parseExpression("{'abc','def'}[0]"); + o = expression.getValue(); + assertEquals("abc",o); + assertCanCompile(expression); + o = expression.getValue(); + assertEquals("abc", o); + + expression = parser.parseExpression("{'abcde','ijklm'}[0].substring({1,3,4}[0])"); + o = expression.getValue(); + assertEquals("bcde",o); + assertCanCompile(expression); + o = expression.getValue(); + assertEquals("bcde", o); + + expression = parser.parseExpression("{'abcde','ijklm'}[0].substring({1,3,4}[0],{1,3,4}[1])"); + o = expression.getValue(); + assertEquals("bc",o); + assertCanCompile(expression); + o = expression.getValue(); + assertEquals("bc", o); + } + + @SuppressWarnings("rawtypes") + @Test + public void nestedInlineLists() throws Exception { + Object o = null; + + expression = parser.parseExpression("{{1,2,3},{4,5,6},{7,8,9}}"); + o = expression.getValue(); + assertEquals("[[1, 2, 3], [4, 5, 6], [7, 8, 9]]",o.toString()); + assertCanCompile(expression); + o = expression.getValue(); + assertEquals("[[1, 2, 3], [4, 5, 6], [7, 8, 9]]",o.toString()); + + expression = parser.parseExpression("{{1,2,3},{4,5,6},{7,8,9}}.toString()"); + o = expression.getValue(); + assertEquals("[[1, 2, 3], [4, 5, 6], [7, 8, 9]]",o); + assertCanCompile(expression); + o = expression.getValue(); + assertEquals("[[1, 2, 3], [4, 5, 6], [7, 8, 9]]",o); + + expression = parser.parseExpression("{{1,2,3},{4,5,6},{7,8,9}}[1][0]"); + o = expression.getValue(); + assertEquals(4,o); + assertCanCompile(expression); + o = expression.getValue(); + assertEquals(4,o); + + expression = parser.parseExpression("{{1,2,3},'abc',{7,8,9}}[1]"); + o = expression.getValue(); + assertEquals("abc",o); + assertCanCompile(expression); + o = expression.getValue(); + assertEquals("abc",o); + + expression = parser.parseExpression("'abcde'.substring({{1,3},1,3,4}[0][1])"); + o = expression.getValue(); + assertEquals("de",o); + assertCanCompile(expression); + o = expression.getValue(); + assertEquals("de", o); + + expression = parser.parseExpression("'abcde'.substring({{1,3},1,3,4}[1])"); + o = expression.getValue(); + assertEquals("bcde",o); + assertCanCompile(expression); + o = expression.getValue(); + assertEquals("bcde", o); + + expression = parser.parseExpression("{'abc',{'def','ghi'}}"); + List l = (List) expression.getValue(); + assertEquals("[abc, [def, ghi]]", l.toString()); + assertCanCompile(expression); + l = (List) expression.getValue(); + assertEquals("[abc, [def, ghi]]", l.toString()); + + expression = parser.parseExpression("{'abcde',{'ijklm','nopqr'}}[0].substring({1,3,4}[0])"); + o = expression.getValue(); + assertEquals("bcde",o); + assertCanCompile(expression); + o = expression.getValue(); + assertEquals("bcde", o); + + expression = parser.parseExpression("{'abcde',{'ijklm','nopqr'}}[1][0].substring({1,3,4}[0])"); + o = expression.getValue(); + assertEquals("jklm",o); + assertCanCompile(expression); + o = expression.getValue(); + assertEquals("jklm", o); + + expression = parser.parseExpression("{'abcde',{'ijklm','nopqr'}}[1][1].substring({1,3,4}[0],{1,3,4}[1])"); + o = expression.getValue(); + assertEquals("op",o); + assertCanCompile(expression); + o = expression.getValue(); + assertEquals("op", o); + } @Test public void intLiteral() throws Exception { @@ -581,6 +696,14 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests { return a+b; } + public static String join(String...strings) { + StringBuilder buf = new StringBuilder(); + for (String string: strings) { + buf.append(string); + } + return buf.toString(); + } + @Test public void functionReference() throws Exception { EvaluationContext ctx = new StandardEvaluationContext(); @@ -605,6 +728,24 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests { assertEquals("foobar",expression.getValue(ctx)); ctx.setVariable("b", "boo"); assertEquals("fooboo",expression.getValue(ctx)); + + m = Math.class.getDeclaredMethod("pow",Double.TYPE,Double.TYPE); + ctx.setVariable("kapow",m); + expression = parser.parseExpression("#kapow(2.0d,2.0d)"); + assertEquals("4.0",expression.getValue(ctx).toString()); + assertCanCompile(expression); + assertEquals("4.0",expression.getValue(ctx).toString()); + } + + @Test + public void functionReferenceVarargs() throws Exception { + EvaluationContext ctx = new StandardEvaluationContext(); + Method m = this.getClass().getDeclaredMethod("join", String[].class); + ctx.setVariable("join", m); + expression = parser.parseExpression("#join('a','b','c')"); + assertEquals("abc",expression.getValue(ctx)); + assertCanCompile(expression); + assertEquals("abc",expression.getValue(ctx)); } @Test @@ -1233,6 +1374,43 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests { assertCanCompile(expression); assertEquals(3L,expression.getValue()); } + + @Test + public void opPlusString() throws Exception { + expression = parse("'hello' + 'world'"); + assertEquals("helloworld",expression.getValue()); + assertCanCompile(expression); + assertEquals("helloworld",expression.getValue()); + + // Method with string return + expression = parse("'hello' + getWorld()"); + assertEquals("helloworld",expression.getValue(new Greeter())); + assertCanCompile(expression); + assertEquals("helloworld",expression.getValue(new Greeter())); + + // Method with string return + expression = parse("getWorld() + 'hello'"); + assertEquals("worldhello",expression.getValue(new Greeter())); + assertCanCompile(expression); + assertEquals("worldhello",expression.getValue(new Greeter())); + + // Three strings, optimal bytecode would only use one StringBuilder + expression = parse("'hello' + getWorld() + ' spring'"); + assertEquals("helloworld spring",expression.getValue(new Greeter())); + assertCanCompile(expression); + assertEquals("helloworld spring",expression.getValue(new Greeter())); + + // Three strings, optimal bytecode would only use one StringBuilder + expression = parse("'hello' + 3 + ' spring'"); + assertEquals("hello3 spring",expression.getValue(new Greeter())); + assertCantCompile(expression); + } + + public static class Greeter { + public String getWorld() { + return "world"; + } + } @Test public void opMinus() throws Exception { @@ -1549,6 +1727,176 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests { assertCantCompile(expression); } + @Test + public void methodReferenceVarargs() throws Exception { + TestClass5 tc = new TestClass5(); + + // varargs string + expression = parser.parseExpression("eleven()"); + assertCantCompile(expression); + expression.getValue(tc); + assertEquals("",tc.s); + assertCanCompile(expression); + tc.reset(); + expression.getValue(tc); + assertEquals("",tc.s); + tc.reset(); + + // varargs string + expression = parser.parseExpression("eleven(stringArray)"); + assertCantCompile(expression); + expression.getValue(tc); + assertEquals("aaabbbccc",tc.s); + assertCanCompile(expression); + tc.reset(); + expression.getValue(tc); + assertEquals("aaabbbccc",tc.s); + tc.reset(); + + // varargs string + expression = parser.parseExpression("eleven('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(); + + // varargs int + expression = parser.parseExpression("twelve(1,2,3)"); + assertCantCompile(expression); + expression.getValue(tc); + assertEquals(6,tc.i); + assertCanCompile(expression); + tc.reset(); + expression.getValue(tc); + assertEquals(6,tc.i); + tc.reset(); + + // one string then varargs string + expression = parser.parseExpression("thirteen('aaa','bbb','ccc')"); + assertCantCompile(expression); + expression.getValue(tc); + assertEquals("aaa::bbbccc",tc.s); + assertCanCompile(expression); + tc.reset(); + expression.getValue(tc); + assertEquals("aaa::bbbccc",tc.s); + tc.reset(); + + // nothing passed to varargs parameter + expression = parser.parseExpression("thirteen('aaa')"); + assertCantCompile(expression); + expression.getValue(tc); + assertEquals("aaa::",tc.s); + assertCanCompile(expression); + tc.reset(); + expression.getValue(tc); + assertEquals("aaa::",tc.s); + tc.reset(); + + // nested arrays + expression = parser.parseExpression("fourteen('aaa',stringArray,stringArray)"); + assertCantCompile(expression); + expression.getValue(tc); + assertEquals("aaa::{aaabbbccc}{aaabbbccc}",tc.s); + assertCanCompile(expression); + tc.reset(); + expression.getValue(tc); + assertEquals("aaa::{aaabbbccc}{aaabbbccc}",tc.s); + tc.reset(); + + // nested primitive array + expression = parser.parseExpression("fifteen('aaa',intArray,intArray)"); + assertCantCompile(expression); + expression.getValue(tc); + assertEquals("aaa::{112233}{112233}",tc.s); + assertCanCompile(expression); + tc.reset(); + expression.getValue(tc); + assertEquals("aaa::{112233}{112233}",tc.s); + tc.reset(); + + // varargs boolean + expression = parser.parseExpression("arrayz(true,true,false)"); + assertCantCompile(expression); + expression.getValue(tc); + assertEquals("truetruefalse",tc.s); + assertCanCompile(expression); + tc.reset(); + expression.getValue(tc); + assertEquals("truetruefalse",tc.s); + tc.reset(); + + // varargs short + expression = parser.parseExpression("arrays(s1,s2,s3)"); + assertCantCompile(expression); + expression.getValue(tc); + assertEquals("123",tc.s); + assertCanCompile(expression); + tc.reset(); + expression.getValue(tc); + assertEquals("123",tc.s); + tc.reset(); + + // varargs double + expression = parser.parseExpression("arrayd(1.0d,2.0d,3.0d)"); + assertCantCompile(expression); + expression.getValue(tc); + assertEquals("1.02.03.0",tc.s); + assertCanCompile(expression); + tc.reset(); + expression.getValue(tc); + assertEquals("1.02.03.0",tc.s); + tc.reset(); + + // varargs long + expression = parser.parseExpression("arrayj(l1,l2,l3)"); + assertCantCompile(expression); + expression.getValue(tc); + assertEquals("123",tc.s); + assertCanCompile(expression); + tc.reset(); + expression.getValue(tc); + assertEquals("123",tc.s); + tc.reset(); + + // varargs char + expression = parser.parseExpression("arrayc(c1,c2,c3)"); + assertCantCompile(expression); + expression.getValue(tc); + assertEquals("abc",tc.s); + assertCanCompile(expression); + tc.reset(); + expression.getValue(tc); + assertEquals("abc",tc.s); + tc.reset(); + + // varargs byte + expression = parser.parseExpression("arrayb(b1,b2,b3)"); + assertCantCompile(expression); + expression.getValue(tc); + assertEquals("656667",tc.s); + assertCanCompile(expression); + tc.reset(); + expression.getValue(tc); + assertEquals("656667",tc.s); + tc.reset(); + + // varargs float + expression = parser.parseExpression("arrayf(f1,f2,f3)"); + assertCantCompile(expression); + expression.getValue(tc); + assertEquals("1.02.03.0",tc.s); + assertCanCompile(expression); + tc.reset(); + expression.getValue(tc); + assertEquals("1.02.03.0",tc.s); + tc.reset(); + } + @Test public void methodReference() throws Exception { TestClass5 tc = new TestClass5(); @@ -1628,7 +1976,7 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests { expression.getValue(tc); assertEquals("bar",TestClass5._s); tc.reset(); - + // non-static method, one parameter of primitive type expression = parser.parseExpression("nine(231)"); assertCantCompile(expression); @@ -1638,7 +1986,7 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests { expression.getValue(tc); assertEquals(231,tc.i); tc.reset(); - + // static method, one parameter of primitive type expression = parser.parseExpression("ten(111)"); assertCantCompile(expression); @@ -1649,28 +1997,6 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests { assertEquals(111,TestClass5._i); tc.reset(); - // non-static method, varargs with reference type - expression = parser.parseExpression("eleven(\"a\",\"b\",\"c\")"); - assertCantCompile(expression); - expression.getValue(tc); - assertCantCompile(expression); // Varargs is not yet supported - - expression = parser.parseExpression("eleven()"); - assertCantCompile(expression); - expression.getValue(tc); - assertCantCompile(expression); // Varargs is not yet supported - - // static method, varargs with primitive type - expression = parser.parseExpression("twelve(1,2,3)"); - assertCantCompile(expression); - expression.getValue(tc); - assertCantCompile(expression); // Varargs is not yet supported - - expression = parser.parseExpression("twelve()"); - assertCantCompile(expression); - expression.getValue(tc); - assertCantCompile(expression); // Varargs is not yet supported - // method that gets type converted parameters // Converting from an int to a string @@ -2022,7 +2348,7 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests { assertEquals("bbb",expression.getValue(strings)); assertCanCompile(expression); assertEquals("bbb",expression.getValue(strings)); - assertEquals("Ljava/lang/String",getAst().getExitDescriptor()); + assertEquals("Ljava/lang/Object",getAst().getExitDescriptor()); List ints = new ArrayList(); ints.add(123); @@ -2032,7 +2358,7 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests { assertEquals(789,expression.getValue(ints)); assertCanCompile(expression); assertEquals(789,expression.getValue(ints)); - assertEquals("Ljava/lang/Integer",getAst().getExitDescriptor()); + assertEquals("Ljava/lang/Object",getAst().getExitDescriptor()); // Maps Map map1 = new HashMap(); @@ -2043,7 +2369,7 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests { assertEquals(111,expression.getValue(map1)); assertCanCompile(expression); assertEquals(111,expression.getValue(map1)); - assertEquals("Ljava/lang/Integer",getAst().getExitDescriptor()); + assertEquals("Ljava/lang/Object",getAst().getExitDescriptor()); // Object TestClass6 tc = new TestClass6(); @@ -2075,7 +2401,13 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests { assertEquals("d e f",stringify(expression.getValue(listOfStringArrays))); assertCanCompile(expression); assertEquals("d e f",stringify(expression.getValue(listOfStringArrays))); - assertEquals("[Ljava/lang/String",getAst().getExitDescriptor()); + assertEquals("Ljava/lang/Object",getAst().getExitDescriptor()); + + expression = parser.parseExpression("[1][0]"); + assertEquals("d",stringify(expression.getValue(listOfStringArrays))); + assertCanCompile(expression); + assertEquals("d",stringify(expression.getValue(listOfStringArrays))); + assertEquals("Ljava/lang/String",getAst().getExitDescriptor()); List listOfIntegerArrays = new ArrayList(); listOfIntegerArrays.add(new Integer[]{1,2,3}); @@ -2084,7 +2416,7 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests { assertEquals("1 2 3",stringify(expression.getValue(listOfIntegerArrays))); assertCanCompile(expression); assertEquals("1 2 3",stringify(expression.getValue(listOfIntegerArrays))); - assertEquals("[Ljava/lang/Integer",getAst().getExitDescriptor()); + assertEquals("Ljava/lang/Object",getAst().getExitDescriptor()); expression = parser.parseExpression("[0][1]"); assertEquals(2,expression.getValue(listOfIntegerArrays)); @@ -2112,7 +2444,7 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests { assertEquals("f",stringify(expression.getValue(stringArrayOfLists))); assertCanCompile(expression); assertEquals("f",stringify(expression.getValue(stringArrayOfLists))); - assertEquals("Ljava/lang/String",getAst().getExitDescriptor()); + assertEquals("Ljava/lang/Object",getAst().getExitDescriptor()); // array of arrays String[][] referenceTypeArrayOfArrays = new String[][]{new String[]{"a","b","c"},new String[]{"d","e","f"}}; @@ -2158,15 +2490,15 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests { expression = parser.parseExpression("[1]"); assertEquals("d e f",stringify(expression.getValue(listOfListOfStrings))); assertCanCompile(expression); - assertEquals("Ljava/util/ArrayList",getAst().getExitDescriptor()); + assertEquals("Ljava/lang/Object",getAst().getExitDescriptor()); assertEquals("d e f",stringify(expression.getValue(listOfListOfStrings))); - assertEquals("Ljava/util/ArrayList",getAst().getExitDescriptor()); + assertEquals("Ljava/lang/Object",getAst().getExitDescriptor()); expression = parser.parseExpression("[1][2]"); assertEquals("f",stringify(expression.getValue(listOfListOfStrings))); assertCanCompile(expression); assertEquals("f",stringify(expression.getValue(listOfListOfStrings))); - assertEquals("Ljava/lang/String",getAst().getExitDescriptor()); + assertEquals("Ljava/lang/Object",getAst().getExitDescriptor()); // Map of lists Map> mapToLists = new HashMap>(); @@ -2178,15 +2510,15 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests { expression = parser.parseExpression("['foo']"); assertEquals("a b c",stringify(expression.getValue(mapToLists))); assertCanCompile(expression); - assertEquals("Ljava/util/ArrayList",getAst().getExitDescriptor()); + assertEquals("Ljava/lang/Object",getAst().getExitDescriptor()); assertEquals("a b c",stringify(expression.getValue(mapToLists))); - assertEquals("Ljava/util/ArrayList",getAst().getExitDescriptor()); + assertEquals("Ljava/lang/Object",getAst().getExitDescriptor()); expression = parser.parseExpression("['foo'][2]"); assertEquals("c",stringify(expression.getValue(mapToLists))); assertCanCompile(expression); assertEquals("c",stringify(expression.getValue(mapToLists))); - assertEquals("Ljava/lang/String",getAst().getExitDescriptor()); + assertEquals("Ljava/lang/Object",getAst().getExitDescriptor()); // Map to array Map mapToIntArray = new HashMap(); @@ -2196,9 +2528,9 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests { expression = parser.parseExpression("['foo']"); assertEquals("1 2 3",stringify(expression.getValue(mapToIntArray))); assertCanCompile(expression); - assertEquals("[I",getAst().getExitDescriptor()); + assertEquals("Ljava/lang/Object",getAst().getExitDescriptor()); assertEquals("1 2 3",stringify(expression.getValue(mapToIntArray))); - assertEquals("[I",getAst().getExitDescriptor()); + assertEquals("Ljava/lang/Object",getAst().getExitDescriptor()); expression = parser.parseExpression("['foo'][1]"); assertEquals(2,expression.getValue(mapToIntArray)); @@ -2237,7 +2569,7 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests { assertEquals("value1",stringify(expression.getValue(mapArray))); assertCanCompile(expression); assertEquals("value1",stringify(expression.getValue(mapArray))); - assertEquals("Ljava/lang/String",getAst().getExitDescriptor()); + assertEquals("Ljava/lang/Object",getAst().getExitDescriptor()); } @Test @@ -2857,6 +3189,29 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests { public static int _i = 0; public static String _s = null; + public static short s1 = (short)1; + public static short s2 = (short)2; + public static short s3 = (short)3; + + public static long l1 = 1L; + public static long l2 = 2L; + public static long l3 = 3L; + + public static float f1 = 1f; + public static float f2 = 2f; + public static float f3 = 3f; + + public static char c1 = 'a'; + public static char c2 = 'b'; + public static char c3 = 'c'; + + public static byte b1 = (byte)65; + public static byte b2 = (byte)66; + public static byte b3 = (byte)67; + + public static String[] stringArray = new String[]{"aaa","bbb","ccc"}; + public static int[] intArray = new int[]{11,22,33}; + public Object obj = null; public String field = null; @@ -2900,7 +3255,7 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests { } } } - + public void twelve(int... vargs) { if (vargs==null) { i = 0; @@ -2912,6 +3267,120 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests { } } } + + public void thirteen(String a, String... vargs) { + if (vargs==null) { + s = a+"::"; + } + else { + s = a+"::"; + for (String varg: vargs) { + s+=varg; + } + } + } + + public void arrayz(boolean... bs) { + s = ""; + if (bs != null) { + s = ""; + for (boolean b: bs) { + s+=Boolean.toString(b); + } + } + } + + public void arrays(short... ss) { + s = ""; + if (ss != null) { + s = ""; + for (short s: ss) { + this.s+=Short.toString(s); + } + } + } + + public void arrayd(double... vargs) { + s = ""; + if (vargs != null) { + s = ""; + for (double v: vargs) { + this.s+=Double.toString(v); + } + } + } + + public void arrayf(float... vargs) { + s = ""; + if (vargs != null) { + s = ""; + for (float v: vargs) { + this.s+=Float.toString(v); + } + } + } + + public void arrayj(long... vargs) { + s = ""; + if (vargs != null) { + s = ""; + for (long v: vargs) { + this.s+=Long.toString(v); + } + } + } + + public void arrayb(byte... vargs) { + s = ""; + if (vargs != null) { + s = ""; + for (Byte v: vargs) { + this.s+=Byte.toString(v); + } + } + } + + public void arrayc(char... vargs) { + s = ""; + if (vargs != null) { + s = ""; + for (char v: vargs) { + this.s+=Character.toString(v); + } + } + } + + public void fourteen(String a, String[]... vargs) { + if (vargs==null) { + s = a+"::"; + } + else { + s = a+"::"; + for (String[] varg: vargs) { + s+="{"; + for (String v: varg) { + s+=v; + } + s+="}"; + } + } + } + + public void fifteen(String a, int[]... vargs) { + if (vargs==null) { + s = a+"::"; + } + else { + s = a+"::"; + for (int[] varg: vargs) { + s+="{"; + for (int v: varg) { + s+=Integer.toString(v); + } + s+="}"; + } + } + } } public static class TestClass6 { diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/SpelCompilationPerformanceTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/SpelCompilationPerformanceTests.java index 8d904a6b83..6b3a821570 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/SpelCompilationPerformanceTests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/SpelCompilationPerformanceTests.java @@ -61,6 +61,144 @@ public class SpelCompilationPerformanceTests extends AbstractExpressionTests { return duration; } } + + @Test + public void inlineLists() throws Exception { + expression = parser.parseExpression("{'abcde','ijklm'}[0].substring({1,3,4}[0],{1,3,4}[1])"); + Object o = expression.getValue(); + assertEquals("bc",o); + System.out.println("Performance check for SpEL expression: '{'abcde','ijklm'}[0].substring({1,3,4}[0],{1,3,4}[1])'"); + long stime = System.currentTimeMillis(); + for (int i=0;i<1000000;i++) { + o = expression.getValue(); + } + System.out.println("One million iterations: "+(System.currentTimeMillis()-stime)+"ms"); + stime = System.currentTimeMillis(); + for (int i=0;i<1000000;i++) { + o = expression.getValue(); + } + System.out.println("One million iterations: "+(System.currentTimeMillis()-stime)+"ms"); + stime = System.currentTimeMillis(); + for (int i=0;i<1000000;i++) { + o = expression.getValue(); + } + System.out.println("One million iterations: "+(System.currentTimeMillis()-stime)+"ms"); + compile(expression); + System.out.println("Now compiled:"); + o = expression.getValue(); + assertEquals("bc", o); + + stime = System.currentTimeMillis(); + for (int i=0;i<1000000;i++) { + o = expression.getValue(); + } + System.out.println("One million iterations: "+(System.currentTimeMillis()-stime)+"ms"); + stime = System.currentTimeMillis(); + for (int i=0;i<1000000;i++) { + o = expression.getValue(); + } + System.out.println("One million iterations: "+(System.currentTimeMillis()-stime)+"ms"); + stime = System.currentTimeMillis(); + for (int i=0;i<1000000;i++) { + o = expression.getValue(); + } + System.out.println("One million iterations: "+(System.currentTimeMillis()-stime)+"ms"); + } + + @Test + public void inlineNestedLists() throws Exception { + expression = parser.parseExpression("{'abcde',{'ijklm','nopqr'}}[1][0].substring({1,3,4}[0],{1,3,4}[1])"); + Object o = expression.getValue(); + assertEquals("jk",o); + System.out.println("Performance check for SpEL expression: '{'abcde','ijklm'}[0].substring({1,3,4}[0],{1,3,4}[1])'"); + long stime = System.currentTimeMillis(); + for (int i=0;i<1000000;i++) { + o = expression.getValue(); + } + System.out.println("One million iterations: "+(System.currentTimeMillis()-stime)+"ms"); + stime = System.currentTimeMillis(); + for (int i=0;i<1000000;i++) { + o = expression.getValue(); + } + System.out.println("One million iterations: "+(System.currentTimeMillis()-stime)+"ms"); + stime = System.currentTimeMillis(); + for (int i=0;i<1000000;i++) { + o = expression.getValue(); + } + System.out.println("One million iterations: "+(System.currentTimeMillis()-stime)+"ms"); + compile(expression); + System.out.println("Now compiled:"); + o = expression.getValue(); + assertEquals("jk", o); + + stime = System.currentTimeMillis(); + for (int i=0;i<1000000;i++) { + o = expression.getValue(); + } + System.out.println("One million iterations: "+(System.currentTimeMillis()-stime)+"ms"); + stime = System.currentTimeMillis(); + for (int i=0;i<1000000;i++) { + o = expression.getValue(); + } + System.out.println("One million iterations: "+(System.currentTimeMillis()-stime)+"ms"); + stime = System.currentTimeMillis(); + for (int i=0;i<1000000;i++) { + o = expression.getValue(); + } + System.out.println("One million iterations: "+(System.currentTimeMillis()-stime)+"ms"); + } + + + @Test + public void stringConcatenation() throws Exception { + expression = parser.parseExpression("'hello' + getWorld() + ' spring'"); + Greeter g = new Greeter(); + Object o = expression.getValue(g); + assertEquals("helloworld spring", o); + + System.out.println("Performance check for SpEL expression: '{'abcde','ijklm'}[0].substring({1,3,4}[0],{1,3,4}[1])'"); + long stime = System.currentTimeMillis(); + for (int i=0;i<1000000;i++) { + o = expression.getValue(g); + } + System.out.println("One million iterations: "+(System.currentTimeMillis()-stime)+"ms"); + stime = System.currentTimeMillis(); + for (int i=0;i<1000000;i++) { + o = expression.getValue(g); + } + System.out.println("One million iterations: "+(System.currentTimeMillis()-stime)+"ms"); + stime = System.currentTimeMillis(); + for (int i=0;i<1000000;i++) { + o = expression.getValue(g); + } + System.out.println("One million iterations: "+(System.currentTimeMillis()-stime)+"ms"); + compile(expression); + System.out.println("Now compiled:"); + o = expression.getValue(g); + assertEquals("helloworld spring", o); + + stime = System.currentTimeMillis(); + for (int i=0;i<1000000;i++) { + o = expression.getValue(g); + } + System.out.println("One million iterations: "+(System.currentTimeMillis()-stime)+"ms"); + stime = System.currentTimeMillis(); + for (int i=0;i<1000000;i++) { + o = expression.getValue(g); + } + System.out.println("One million iterations: "+(System.currentTimeMillis()-stime)+"ms"); + stime = System.currentTimeMillis(); + for (int i=0;i<1000000;i++) { + o = expression.getValue(g); + } + System.out.println("One million iterations: "+(System.currentTimeMillis()-stime)+"ms"); + } + + public static class Greeter { + public String getWorld() { + return "world"; + } + } @Test public void complexExpressionPerformance() throws Exception {