Browse Source

Add a compiler for SpEL

With these changes an optional compiler is added for SpEL
expressions. The compiler is off by default but can be enabled
via the SpEL parser configuration object or system property
(when SpEL is embedded and parser configuration is not possible).
Not all expressions are currently handled but the common
cases are and it is an extensible compilation framework.

Issue: SPR-10943
pull/554/head
Andy Clement 12 years ago
parent
commit
2eeb2e9235
  1. 53
      spring-expression/src/main/java/org/springframework/expression/CompilablePropertyAccessor.java
  2. 38
      spring-expression/src/main/java/org/springframework/expression/spel/CompiledExpression.java
  3. 45
      spring-expression/src/main/java/org/springframework/expression/spel/SpelCompilerMode.java
  4. 5
      spring-expression/src/main/java/org/springframework/expression/spel/SpelMessage.java
  5. 33
      spring-expression/src/main/java/org/springframework/expression/spel/SpelParserConfiguration.java
  6. 21
      spring-expression/src/main/java/org/springframework/expression/spel/ast/BooleanLiteral.java
  7. 37
      spring-expression/src/main/java/org/springframework/expression/spel/ast/CompoundExpression.java
  8. 59
      spring-expression/src/main/java/org/springframework/expression/spel/ast/ConstructorReference.java
  9. 73
      spring-expression/src/main/java/org/springframework/expression/spel/ast/Elvis.java
  10. 17
      spring-expression/src/main/java/org/springframework/expression/spel/ast/FloatLiteral.java
  11. 45
      spring-expression/src/main/java/org/springframework/expression/spel/ast/FunctionReference.java
  12. 160
      spring-expression/src/main/java/org/springframework/expression/spel/ast/Indexer.java
  13. 27
      spring-expression/src/main/java/org/springframework/expression/spel/ast/IntLiteral.java
  14. 17
      spring-expression/src/main/java/org/springframework/expression/spel/ast/LongLiteral.java
  15. 97
      spring-expression/src/main/java/org/springframework/expression/spel/ast/MethodReference.java
  16. 16
      spring-expression/src/main/java/org/springframework/expression/spel/ast/NullLiteral.java
  17. 39
      spring-expression/src/main/java/org/springframework/expression/spel/ast/OpAnd.java
  18. 74
      spring-expression/src/main/java/org/springframework/expression/spel/ast/OpDivide.java
  19. 99
      spring-expression/src/main/java/org/springframework/expression/spel/ast/OpEQ.java
  20. 16
      spring-expression/src/main/java/org/springframework/expression/spel/ast/OpGE.java
  21. 13
      spring-expression/src/main/java/org/springframework/expression/spel/ast/OpGT.java
  22. 16
      spring-expression/src/main/java/org/springframework/expression/spel/ast/OpLE.java
  23. 14
      spring-expression/src/main/java/org/springframework/expression/spel/ast/OpLT.java
  24. 87
      spring-expression/src/main/java/org/springframework/expression/spel/ast/OpMinus.java
  25. 67
      spring-expression/src/main/java/org/springframework/expression/spel/ast/OpMultiply.java
  26. 79
      spring-expression/src/main/java/org/springframework/expression/spel/ast/OpNE.java
  27. 39
      spring-expression/src/main/java/org/springframework/expression/spel/ast/OpOr.java
  28. 69
      spring-expression/src/main/java/org/springframework/expression/spel/ast/OpPlus.java
  29. 72
      spring-expression/src/main/java/org/springframework/expression/spel/ast/Operator.java
  30. 32
      spring-expression/src/main/java/org/springframework/expression/spel/ast/OperatorInstanceof.java
  31. 27
      spring-expression/src/main/java/org/springframework/expression/spel/ast/OperatorNot.java
  32. 40
      spring-expression/src/main/java/org/springframework/expression/spel/ast/PropertyOrFieldReference.java
  33. 17
      spring-expression/src/main/java/org/springframework/expression/spel/ast/RealLiteral.java
  34. 42
      spring-expression/src/main/java/org/springframework/expression/spel/ast/SpelNodeImpl.java
  35. 15
      spring-expression/src/main/java/org/springframework/expression/spel/ast/StringLiteral.java
  36. 75
      spring-expression/src/main/java/org/springframework/expression/spel/ast/Ternary.java
  37. 45
      spring-expression/src/main/java/org/springframework/expression/spel/ast/TypeReference.java
  38. 27
      spring-expression/src/main/java/org/springframework/expression/spel/ast/VariableReference.java
  39. 603
      spring-expression/src/main/java/org/springframework/expression/spel/standard/CodeFlow.java
  40. 277
      spring-expression/src/main/java/org/springframework/expression/spel/standard/SpelCompiler.java
  41. 254
      spring-expression/src/main/java/org/springframework/expression/spel/standard/SpelExpression.java
  42. 19
      spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectionHelper.java
  43. 8
      spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectiveConstructorExecutor.java
  44. 15
      spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectiveMethodExecutor.java
  45. 57
      spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectivePropertyAccessor.java
  46. 2603
      spring-expression/src/test/java/org/springframework/expression/spel/SpelCompilationCoverageTests.java
  47. 471
      spring-expression/src/test/java/org/springframework/expression/spel/SpelCompilationPerformanceTests.java

53
spring-expression/src/main/java/org/springframework/expression/CompilablePropertyAccessor.java

@ -0,0 +1,53 @@ @@ -0,0 +1,53 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.expression;
import org.springframework.asm.MethodVisitor;
import org.springframework.asm.Opcodes;
import org.springframework.expression.spel.ast.PropertyOrFieldReference;
import org.springframework.expression.spel.standard.CodeFlow;
/**
* A compilable property accessor is able to generate bytecode that represents
* the access operation, facilitating compilation to bytecode of expressions
* that use the accessor.
*
* @author Andy Clement
* @since 4.1
*/
public interface CompilablePropertyAccessor extends PropertyAccessor, Opcodes {
/**
* @return true if this property accessor is currently suitable for compilation.
*/
boolean isCompilable();
/**
* Generate the bytecode the performs the access operation into the specified MethodVisitor using
* context information from the codeflow where necessary.
* @param propertyReference the property reference for which code is being generated
* @param mv the Asm method visitor into which code should be generated
* @param codeflow the current state of the expression compiler
*/
void generateCode(PropertyOrFieldReference propertyReference, MethodVisitor mv, CodeFlow codeflow);
/**
* @return the type of the accessed property - may only be known once an access has occurred.
*/
Class<?> getPropertyType();
}

38
spring-expression/src/main/java/org/springframework/expression/spel/CompiledExpression.java

@ -0,0 +1,38 @@ @@ -0,0 +1,38 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.expression.spel;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.EvaluationException;
/**
* Base superclass for compiled expressions. Each generated compiled expression class will
* extend this class and implement one of the getValue() methods. It is not intended
* to subclassed by user code.
*
* @author Andy Clement
* @since 4.1
*/
public abstract class CompiledExpression {
/**
* Subclasses of CompiledExpression generated by SpelCompiler will provide an implementation of
* this method.
*/
public abstract Object getValue(Object target, EvaluationContext context) throws EvaluationException;
}

45
spring-expression/src/main/java/org/springframework/expression/spel/SpelCompilerMode.java

@ -0,0 +1,45 @@ @@ -0,0 +1,45 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.expression.spel;
/**
* Captures the possible configuration settings for a compiler that can be
* used when evaluating expressions.
*
* @author Andy Clement
* @since 4.1
*/
public enum SpelCompilerMode {
/**
* The compiler is switched off, this is the default.
*/
off,
/**
* In immediate mode, expressions are compiled as soon as possible (usually after 1 interpreted run).
* If a compiled expression fails it will throw an exception to the caller.
*/
immediate,
/**
* In mixed mode, expression evaluate silently switches between interpreted and compiled over time.
* After a number of runs the expression gets compiled. If it later fails (possibly due to inferred
* type information changing) then that will be caught internally and the system switches back to
* interpreted mode. It may subsequently compile it again later.
*/
mixed
}

5
spring-expression/src/main/java/org/springframework/expression/spel/SpelMessage.java

@ -249,7 +249,10 @@ public enum SpelMessage { @@ -249,7 +249,10 @@ public enum SpelMessage {
"Problem parsing left operand"),
MISSING_SELECTION_EXPRESSION(Kind.ERROR, 1071,
"A required selection expression has not been specified");
"A required selection expression has not been specified"),
EXCEPTION_RUNNING_COMPILED_EXPRESSION(Kind.ERROR,1072,
"An exception occurred whilst evaluating a compiled expression");
private final Kind kind;

33
spring-expression/src/main/java/org/springframework/expression/spel/SpelParserConfiguration.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,11 +16,15 @@ @@ -16,11 +16,15 @@
package org.springframework.expression.spel;
import org.springframework.core.SpringProperties;
/**
* Configuration object for the SpEL expression parser.
*
* @author Juergen Hoeller
* @author Phillip Webb
* @author Andy Clement
* @since 3.0
* @see org.springframework.expression.spel.standard.SpelExpressionParser#SpelExpressionParser(SpelParserConfiguration)
*/
@ -29,10 +33,21 @@ public class SpelParserConfiguration { @@ -29,10 +33,21 @@ public class SpelParserConfiguration {
private final boolean autoGrowNullReferences;
private final boolean autoGrowCollections;
private static SpelCompilerMode defaultCompilerMode = SpelCompilerMode.off;
private SpelCompilerMode compilerMode;
private final int maximumAutoGrowSize;
static {
String compilerMode = SpringProperties.getProperty("spring.expression.compiler.mode");
if (compilerMode != null) {
defaultCompilerMode = SpelCompilerMode.valueOf(compilerMode.toLowerCase());
// System.out.println("SpelCompiler: switched to "+defaultCompilerMode+" mode");
}
}
/**
* Create a new {@link SpelParserConfiguration} instance.
* @param autoGrowNullReferences if null references should automatically grow
@ -53,8 +68,22 @@ public class SpelParserConfiguration { @@ -53,8 +68,22 @@ public class SpelParserConfiguration {
this.autoGrowNullReferences = autoGrowNullReferences;
this.autoGrowCollections = autoGrowCollections;
this.maximumAutoGrowSize = maximumAutoGrowSize;
this.compilerMode = defaultCompilerMode;
}
/**
* @param compilerMode the compiler mode that parsers using this configuration object should use
*/
public void setCompilerMode(SpelCompilerMode compilerMode) {
this.compilerMode = compilerMode;
}
/**
* @return the configuration mode for parsers using this configuration object
*/
public SpelCompilerMode getCompilerMode() {
return this.compilerMode;
}
/**
* @return {@code true} if {@code null} references should be automatically grown

21
spring-expression/src/main/java/org/springframework/expression/spel/ast/BooleanLiteral.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -15,6 +15,8 @@ @@ -15,6 +15,8 @@
*/
package org.springframework.expression.spel.ast;
import org.springframework.asm.MethodVisitor;
import org.springframework.expression.spel.standard.CodeFlow;
import org.springframework.expression.spel.support.BooleanTypedValue;
/**
@ -31,6 +33,7 @@ public class BooleanLiteral extends Literal { @@ -31,6 +33,7 @@ public class BooleanLiteral extends Literal {
public BooleanLiteral(String payload, int pos, boolean value) {
super(payload, pos);
this.value = BooleanTypedValue.forValue(value);
this.exitTypeDescriptor = "Z";
}
@ -38,5 +41,21 @@ public class BooleanLiteral extends Literal { @@ -38,5 +41,21 @@ public class BooleanLiteral extends Literal {
public BooleanTypedValue getLiteralValue() {
return this.value;
}
@Override
public boolean isCompilable() {
return true;
}
@Override
public void generateCode(MethodVisitor mv, CodeFlow codeflow) {
if (this.value == BooleanTypedValue.TRUE) {
mv.visitLdcInsn(1);
}
else {
mv.visitLdcInsn(0);
}
codeflow.pushDescriptor(getExitDescriptor());
}
}

37
spring-expression/src/main/java/org/springframework/expression/spel/ast/CompoundExpression.java

@ -16,10 +16,12 @@ @@ -16,10 +16,12 @@
package org.springframework.expression.spel.ast;
import org.springframework.asm.MethodVisitor;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.expression.spel.standard.CodeFlow;
/**
* Represents a DOT separated expression sequence, such as 'property1.property2.methodOne()'
@ -81,7 +83,15 @@ public class CompoundExpression extends SpelNodeImpl { @@ -81,7 +83,15 @@ public class CompoundExpression extends SpelNodeImpl {
*/
@Override
public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
return getValueRef(state).getValue();
ValueRef ref = getValueRef(state);
TypedValue result = ref.getValue();
this.exitTypeDescriptor = this.children[this.children.length-1].getExitDescriptor();
return result;
}
@Override
public String getExitDescriptor() {
return this.exitTypeDescriptor;
}
@Override
@ -105,5 +115,30 @@ public class CompoundExpression extends SpelNodeImpl { @@ -105,5 +115,30 @@ public class CompoundExpression extends SpelNodeImpl {
}
return sb.toString();
}
@Override
public boolean isCompilable() {
for (SpelNodeImpl child: children) {
if (!child.isCompilable()) {
return false;
}
}
return true;
}
@Override
public void generateCode(MethodVisitor mv,CodeFlow codeflow) {
// TODO could optimize T(SomeType).staticMethod - no need to generate the T() part
for (int i=0;i<children.length;i++) {
SpelNodeImpl child = children[i];
if (child instanceof TypeReference &&
(i+1) < children.length &&
children[i+1] instanceof MethodReference) {
continue;
}
child.generateCode(mv, codeflow);
}
codeflow.pushDescriptor(this.getExitDescriptor());
}
}

59
spring-expression/src/main/java/org/springframework/expression/spel/ast/ConstructorReference.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -17,10 +17,13 @@ @@ -17,10 +17,13 @@
package org.springframework.expression.spel.ast;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import org.springframework.asm.MethodVisitor;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.expression.AccessException;
import org.springframework.expression.ConstructorExecutor;
@ -34,6 +37,8 @@ import org.springframework.expression.spel.ExpressionState; @@ -34,6 +37,8 @@ import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.expression.spel.SpelMessage;
import org.springframework.expression.spel.SpelNode;
import org.springframework.expression.spel.standard.CodeFlow;
import org.springframework.expression.spel.support.ReflectiveConstructorExecutor;
/**
* Represents the invocation of a constructor. Either a constructor on a regular type or
@ -151,6 +156,10 @@ public class ConstructorReference extends SpelNodeImpl { @@ -151,6 +156,10 @@ public class ConstructorReference extends SpelNodeImpl {
executorToUse = findExecutorForConstructor(typename, argumentTypes, state);
try {
this.cachedExecutor = executorToUse;
if (this.cachedExecutor instanceof ReflectiveConstructorExecutor) {
this.exitTypeDescriptor = CodeFlow.toDescriptor(((ReflectiveConstructorExecutor)this.cachedExecutor).getConstructor().getDeclaringClass());
}
return executorToUse.execute(state.getEvaluationContext(), arguments);
}
catch (AccessException ae) {
@ -403,5 +412,53 @@ public class ConstructorReference extends SpelNodeImpl { @@ -403,5 +412,53 @@ public class ConstructorReference extends SpelNodeImpl {
private boolean hasInitializer() {
return getChildCount() > 1;
}
@Override
public boolean isCompilable() {
if (!(this.cachedExecutor instanceof ReflectiveConstructorExecutor) ||
this.exitTypeDescriptor == null) {
return false;
}
if (getChildCount() > 1) {
for (int c = 1, max = getChildCount();c < max; c++) {
if (!children[c].isCompilable()) {
return false;
}
}
}
ReflectiveConstructorExecutor executor = (ReflectiveConstructorExecutor)this.cachedExecutor;
Constructor<?> constructor = executor.getConstructor();
if (!Modifier.isPublic(constructor.getModifiers()) ||
!Modifier.isPublic(constructor.getDeclaringClass().getModifiers())) {
return false;
}
if (constructor.isVarArgs()) {
return false;
}
return true;
}
@Override
public void generateCode(MethodVisitor mv, CodeFlow codeflow) {
ReflectiveConstructorExecutor executor = ((ReflectiveConstructorExecutor) this.cachedExecutor);
Constructor<?> constructor = executor.getConstructor();
String classSlashedDescriptor = constructor.getDeclaringClass().getName().replace('.','/');
String[] paramDescriptors = CodeFlow.toParamDescriptors(constructor);
mv.visitTypeInsn(NEW,classSlashedDescriptor);
mv.visitInsn(DUP);
for (int c = 1; c < children.length; c++) { // children[0] is the type of the constructor
SpelNodeImpl child = children[c];
codeflow.enterCompilationScope();
child.generateCode(mv, codeflow);
// Check if need to box it for the method reference?
if (CodeFlow.isPrimitive(codeflow.lastDescriptor()) && (paramDescriptors[c-1].charAt(0)=='L')) {
CodeFlow.insertBoxIfNecessary(mv, codeflow.lastDescriptor().charAt(0));
}
codeflow.exitCompilationScope();
}
mv.visitMethodInsn(INVOKESPECIAL,classSlashedDescriptor,"<init>",CodeFlow.createSignatureDescriptor(constructor),false);
codeflow.pushDescriptor(exitTypeDescriptor);
}
}

73
spring-expression/src/main/java/org/springframework/expression/spel/ast/Elvis.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,9 +16,12 @@ @@ -16,9 +16,12 @@
package org.springframework.expression.spel.ast;
import org.springframework.asm.Label;
import org.springframework.asm.MethodVisitor;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.standard.CodeFlow;
/**
* Represents the elvis operator ?:. For an expression "a?:b" if a is not null, the value
@ -49,7 +52,18 @@ public class Elvis extends SpelNodeImpl { @@ -49,7 +52,18 @@ public class Elvis extends SpelNodeImpl {
return value;
}
else {
return this.children[1].getValueInternal(state);
TypedValue result = this.children[1].getValueInternal(state);
if (exitTypeDescriptor == null) {
String testDescriptor = this.children[0].exitTypeDescriptor;
String ifNullDescriptor = this.children[1].exitTypeDescriptor;
if (testDescriptor.equals(ifNullDescriptor)) {
this.exitTypeDescriptor = testDescriptor;
}
else {
this.exitTypeDescriptor = "Ljava/lang/Object";
}
}
return result;
}
}
@ -59,4 +73,59 @@ public class Elvis extends SpelNodeImpl { @@ -59,4 +73,59 @@ public class Elvis extends SpelNodeImpl {
getChild(1).toStringAST()).toString();
}
private void computeExitTypeDescriptor() {
if (exitTypeDescriptor == null &&
this.children[0].getExitDescriptor()!=null &&
this.children[1].getExitDescriptor()!=null) {
String conditionDescriptor = this.children[0].exitTypeDescriptor;
String ifNullValueDescriptor = this.children[1].exitTypeDescriptor;
if (conditionDescriptor.equals(ifNullValueDescriptor)) {
this.exitTypeDescriptor = conditionDescriptor;
}
else if (conditionDescriptor.equals("Ljava/lang/Object") && !CodeFlow.isPrimitive(ifNullValueDescriptor)) {
this.exitTypeDescriptor = ifNullValueDescriptor;
}
else if (ifNullValueDescriptor.equals("Ljava/lang/Object") && !CodeFlow.isPrimitive(conditionDescriptor)) {
this.exitTypeDescriptor = conditionDescriptor;
}
else {
// Use the easiest to compute common super type
this.exitTypeDescriptor = "Ljava/lang/Object";
}
}
}
@Override
public boolean isCompilable() {
SpelNodeImpl condition = this.children[0];
SpelNodeImpl ifNullValue = this.children[1];
if (!(condition.isCompilable() && ifNullValue.isCompilable())) {
return false;
}
return
condition.getExitDescriptor()!=null &&
ifNullValue.getExitDescriptor()!=null;
}
@Override
public void generateCode(MethodVisitor mv, CodeFlow codeflow) {
// exit type descriptor can be null if both components are literal expressions
computeExitTypeDescriptor();
this.children[0].generateCode(mv, codeflow);
Label elseTarget = new Label();
Label endOfIf = new Label();
mv.visitInsn(DUP);
mv.visitJumpInsn(IFNULL, elseTarget);
mv.visitJumpInsn(GOTO, endOfIf);
mv.visitLabel(elseTarget);
mv.visitInsn(POP);
this.children[1].generateCode(mv, codeflow);
if (!CodeFlow.isPrimitive(getExitDescriptor())) {
CodeFlow.insertBoxIfNecessary(mv, codeflow.lastDescriptor().charAt(0));
}
mv.visitLabel(endOfIf);
codeflow.pushDescriptor(getExitDescriptor());
}
}

17
spring-expression/src/main/java/org/springframework/expression/spel/ast/FloatLiteral.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,12 +16,15 @@ @@ -16,12 +16,15 @@
package org.springframework.expression.spel.ast;
import org.springframework.asm.MethodVisitor;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.standard.CodeFlow;
/**
* Expression language AST node that represents a float literal.
*
* @author Satyapal Reddy
* @author Andy Clement
* @since 3.2
*/
public class FloatLiteral extends Literal {
@ -31,6 +34,7 @@ public class FloatLiteral extends Literal { @@ -31,6 +34,7 @@ public class FloatLiteral extends Literal {
FloatLiteral(String payload, int pos, float value) {
super(payload, pos);
this.value = new TypedValue(value);
this.exitTypeDescriptor = "F";
}
@ -38,4 +42,15 @@ public class FloatLiteral extends Literal { @@ -38,4 +42,15 @@ public class FloatLiteral extends Literal {
public TypedValue getLiteralValue() {
return this.value;
}
@Override
public boolean isCompilable() {
return true;
}
@Override
public void generateCode(MethodVisitor mv, CodeFlow codeflow) {
mv.visitLdcInsn(this.value.getValue());
codeflow.pushDescriptor(getExitDescriptor());
}
}

45
spring-expression/src/main/java/org/springframework/expression/spel/ast/FunctionReference.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -19,6 +19,7 @@ package org.springframework.expression.spel.ast; @@ -19,6 +19,7 @@ package org.springframework.expression.spel.ast;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import org.springframework.asm.MethodVisitor;
import org.springframework.core.MethodParameter;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.expression.EvaluationException;
@ -27,6 +28,7 @@ import org.springframework.expression.TypedValue; @@ -27,6 +28,7 @@ import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.expression.spel.SpelMessage;
import org.springframework.expression.spel.standard.CodeFlow;
import org.springframework.expression.spel.support.ReflectionHelper;
import org.springframework.util.ReflectionUtils;
@ -48,6 +50,10 @@ public class FunctionReference extends SpelNodeImpl { @@ -48,6 +50,10 @@ public class FunctionReference extends SpelNodeImpl {
private final String name;
// Captures the most recently used method for the function invocation *if*
// the method can safely be used for compilation (i.e. no argument conversion is
// going on)
private Method method;
public FunctionReference(String functionName, int pos, SpelNodeImpl... arguments) {
super(pos,arguments);
@ -84,6 +90,7 @@ public class FunctionReference extends SpelNodeImpl { @@ -84,6 +90,7 @@ public class FunctionReference extends SpelNodeImpl {
* @throws EvaluationException if there is any problem invoking the method
*/
private TypedValue executeFunctionJLRMethod(ExpressionState state, Method method) throws EvaluationException {
this.method = null;
Object[] functionArgs = getArguments(state);
if (!method.isVarArgs() && method.getParameterTypes().length != functionArgs.length) {
@ -96,11 +103,11 @@ public class FunctionReference extends SpelNodeImpl { @@ -96,11 +103,11 @@ public class FunctionReference extends SpelNodeImpl {
SpelMessage.FUNCTION_MUST_BE_STATIC,
method.getDeclaringClass().getName() + "." + method.getName(), this.name);
}
boolean argumentConversionOccurred = false;
// Convert arguments if necessary and remap them for varargs if required
if (functionArgs != null) {
TypeConverter converter = state.getEvaluationContext().getTypeConverter();
ReflectionHelper.convertAllArguments(converter, functionArgs, method);
argumentConversionOccurred |= ReflectionHelper.convertAllArguments(converter, functionArgs, method);
}
if (method.isVarArgs()) {
functionArgs = ReflectionHelper.setupArgumentsForVarargsInvocation(
@ -110,6 +117,10 @@ public class FunctionReference extends SpelNodeImpl { @@ -110,6 +117,10 @@ public class FunctionReference extends SpelNodeImpl {
try {
ReflectionUtils.makeAccessible(method);
Object result = method.invoke(method.getClass(), functionArgs);
if (!argumentConversionOccurred) {
this.method = method;
this.exitTypeDescriptor = CodeFlow.toDescriptor(method.getReturnType());
}
return new TypedValue(result, new TypeDescriptor(new MethodParameter(method,-1)).narrow(result));
}
catch (Exception ex) {
@ -146,5 +157,33 @@ public class FunctionReference extends SpelNodeImpl { @@ -146,5 +157,33 @@ public class FunctionReference extends SpelNodeImpl {
}
return arguments;
}
@Override
public boolean isCompilable() {
// Don't yet support non-static method compilation.
return method!=null && Modifier.isStatic(method.getModifiers());
}
@Override
public void generateCode(MethodVisitor mv,CodeFlow codeflow) {
String methodDeclaringClassSlashedDescriptor = method.getDeclaringClass().getName().replace('.','/');
String[] paramDescriptors = CodeFlow.toParamDescriptors(method);
for (int c = 0; c < children.length; c++) {
SpelNodeImpl child = children[c];
codeflow.enterCompilationScope();
child.generateCode(mv, codeflow);
// Check if need to box it for the method reference?
if (CodeFlow.isPrimitive(codeflow.lastDescriptor()) && (paramDescriptors[c].charAt(0)=='L')) {
CodeFlow.insertBoxIfNecessary(mv, codeflow.lastDescriptor().charAt(0));
}
else if (!codeflow.lastDescriptor().equals(paramDescriptors[c])) {
// This would be unnecessary in the case of subtyping (e.g. method takes a Number but passed in is an Integer)
CodeFlow.insertCheckCast(mv, paramDescriptors[c]);
}
codeflow.exitCompilationScope();
}
mv.visitMethodInsn(INVOKESTATIC,methodDeclaringClassSlashedDescriptor,method.getName(),CodeFlow.createSignatureDescriptor(method),false);
codeflow.pushDescriptor(exitTypeDescriptor);
}
}

160
spring-expression/src/main/java/org/springframework/expression/spel/ast/Indexer.java

@ -16,10 +16,15 @@ @@ -16,10 +16,15 @@
package org.springframework.expression.spel.ast;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import org.springframework.asm.MethodVisitor;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.expression.AccessException;
import org.springframework.expression.EvaluationContext;
@ -30,6 +35,7 @@ import org.springframework.expression.TypedValue; @@ -30,6 +35,7 @@ import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.expression.spel.SpelMessage;
import org.springframework.expression.spel.standard.CodeFlow;
import org.springframework.expression.spel.support.ReflectivePropertyAccessor;
/**
@ -66,6 +72,10 @@ public class Indexer extends SpelNodeImpl { @@ -66,6 +72,10 @@ public class Indexer extends SpelNodeImpl {
private PropertyAccessor cachedWriteAccessor;
private static enum IndexedType { map, array, list, string, object; }
private IndexedType indexedType;
public Indexer(int pos, SpelNodeImpl expr) {
super(pos, expr);
@ -122,6 +132,7 @@ public class Indexer extends SpelNodeImpl { @@ -122,6 +132,7 @@ public class Indexer extends SpelNodeImpl {
if (targetObjectTypeDescriptor.getMapKeyTypeDescriptor() != null) {
key = state.convertValue(key, targetObjectTypeDescriptor.getMapKeyTypeDescriptor());
}
this.indexedType = IndexedType.map;
return new MapIndexingValueRef(state.getTypeConverter(), (Map<?, ?>) targetObject, key,
targetObjectTypeDescriptor);
}
@ -135,14 +146,19 @@ public class Indexer extends SpelNodeImpl { @@ -135,14 +146,19 @@ public class Indexer extends SpelNodeImpl {
if (targetObject.getClass().isArray() || targetObject instanceof Collection || targetObject instanceof String) {
int idx = (Integer) state.convertValue(index, TypeDescriptor.valueOf(Integer.class));
if (targetObject.getClass().isArray()) {
this.indexedType = IndexedType.array;
return new ArrayIndexingValueRef(state.getTypeConverter(), targetObject, idx, targetObjectTypeDescriptor);
}
else if (targetObject instanceof Collection) {
if (targetObject instanceof List) {
this.indexedType = IndexedType.list;
}
return new CollectionIndexingValueRef((Collection<?>) targetObject, idx, targetObjectTypeDescriptor,
state.getTypeConverter(), state.getConfiguration().isAutoGrowCollections(),
state.getConfiguration().getMaximumAutoGrowSize());
}
else if (targetObject instanceof String) {
this.indexedType = IndexedType.string;
return new StringIndexingLValue((String) targetObject, idx, targetObjectTypeDescriptor);
}
}
@ -150,6 +166,7 @@ public class Indexer extends SpelNodeImpl { @@ -150,6 +166,7 @@ public class Indexer extends SpelNodeImpl {
// Try and treat the index value as a property of the context object
// TODO could call the conversion service to convert the value to a String
if (indexValue.getTypeDescriptor().getType() == String.class) {
this.indexedType = IndexedType.object;
return new PropertyIndexingValueRef(targetObject, (String) indexValue.getValue(),
state.getEvaluationContext(), targetObjectTypeDescriptor);
}
@ -157,6 +174,121 @@ public class Indexer extends SpelNodeImpl { @@ -157,6 +174,121 @@ public class Indexer extends SpelNodeImpl {
throw new SpelEvaluationException(getStartPosition(), SpelMessage.INDEXING_NOT_SUPPORTED_FOR_TYPE,
targetObjectTypeDescriptor.toString());
}
@Override
public boolean isCompilable() {
if (this.indexedType == IndexedType.array) {
return exitTypeDescriptor != null;
}
else if (this.indexedType == IndexedType.list || this.indexedType == IndexedType.map) {
return true;
}
else if (this.indexedType == IndexedType.object) {
// If the string name is changing the accessor is clearly going to change (so compilation is not possible)
if (this.cachedReadAccessor != null &&
(this.cachedReadAccessor instanceof ReflectivePropertyAccessor.OptimalPropertyAccessor) &&
(getChild(0) instanceof StringLiteral)) {
return true;
};
}
return false;
}
@Override
public void generateCode(MethodVisitor mv, CodeFlow codeflow) {
String s = codeflow.lastDescriptor();
if (s == null) {
// stack is empty, should use context object
codeflow.loadTarget(mv);
}
if (this.indexedType == IndexedType.array) {
if (exitTypeDescriptor == "I") {
mv.visitTypeInsn(CHECKCAST,"[I");
SpelNodeImpl index = this.children[0];
index.generateCode(mv, codeflow);
mv.visitInsn(IALOAD);
}
else if (exitTypeDescriptor == "D") {
mv.visitTypeInsn(CHECKCAST,"[D");
SpelNodeImpl index = this.children[0];
index.generateCode(mv, codeflow);
mv.visitInsn(DALOAD);
}
else if (exitTypeDescriptor == "J") {
mv.visitTypeInsn(CHECKCAST,"[J");
SpelNodeImpl index = this.children[0];
index.generateCode(mv, codeflow);
mv.visitInsn(LALOAD);
}
else if (exitTypeDescriptor == "F") {
mv.visitTypeInsn(CHECKCAST,"[F");
SpelNodeImpl index = this.children[0];
index.generateCode(mv, codeflow);
mv.visitInsn(FALOAD);
}
else if (exitTypeDescriptor == "S") {
mv.visitTypeInsn(CHECKCAST,"[S");
SpelNodeImpl index = this.children[0];
index.generateCode(mv, codeflow);
mv.visitInsn(SALOAD);
}
else if (exitTypeDescriptor == "B") {
mv.visitTypeInsn(CHECKCAST,"[B");
SpelNodeImpl index = this.children[0];
index.generateCode(mv, codeflow);
mv.visitInsn(BALOAD);
}
else if (exitTypeDescriptor == "C") {
mv.visitTypeInsn(CHECKCAST,"[C");
SpelNodeImpl index = this.children[0];
index.generateCode(mv, codeflow);
mv.visitInsn(CALOAD);
}
else {
mv.visitTypeInsn(CHECKCAST,"["+exitTypeDescriptor+(CodeFlow.isPrimitiveArray(exitTypeDescriptor)?"":";"));//depthPlusOne(exitTypeDescriptor)+"Ljava/lang/Object;");
SpelNodeImpl index = this.children[0];
index.generateCode(mv, codeflow);
mv.visitInsn(AALOAD);
}
}
else if (this.indexedType == IndexedType.list) {
mv.visitTypeInsn(CHECKCAST,"java/util/List");
this.children[0].generateCode(mv, codeflow);
mv.visitMethodInsn(INVOKEINTERFACE,"java/util/List","get","(I)Ljava/lang/Object;", true);
CodeFlow.insertCheckCast(mv,exitTypeDescriptor);
}
else if (this.indexedType == IndexedType.map) {
mv.visitTypeInsn(CHECKCAST,"java/util/Map");
this.children[0].generateCode(mv, codeflow);
mv.visitMethodInsn(INVOKEINTERFACE,"java/util/Map","get","(Ljava/lang/Object;)Ljava/lang/Object;", true);
CodeFlow.insertCheckCast(mv,exitTypeDescriptor);
}
else if (this.indexedType == IndexedType.object) {
ReflectivePropertyAccessor.OptimalPropertyAccessor accessor =
(ReflectivePropertyAccessor.OptimalPropertyAccessor)this.cachedReadAccessor;
Member member = accessor.member;
boolean isStatic = Modifier.isStatic(member.getModifiers());
String descriptor = codeflow.lastDescriptor();
String memberDeclaringClassSlashedDescriptor = member.getDeclaringClass().getName().replace('.','/');
if (!isStatic) {
if (descriptor == null) {
codeflow.loadTarget(mv);
}
if (descriptor == null || !memberDeclaringClassSlashedDescriptor.equals(descriptor.substring(1))) {
mv.visitTypeInsn(CHECKCAST, memberDeclaringClassSlashedDescriptor);
}
}
if (member instanceof Field) {
mv.visitFieldInsn(isStatic?GETSTATIC:GETFIELD,memberDeclaringClassSlashedDescriptor,member.getName(),CodeFlow.toJVMDescriptor(((Field) member).getType()));
} else {
mv.visitMethodInsn(isStatic?INVOKESTATIC:INVOKEVIRTUAL, memberDeclaringClassSlashedDescriptor, member.getName(),CodeFlow.createSignatureDescriptor((Method)member),false);
}
}
codeflow.pushDescriptor(exitTypeDescriptor);
}
@Override
public String toStringAST() {
@ -236,47 +368,57 @@ public class Indexer extends SpelNodeImpl { @@ -236,47 +368,57 @@ public class Indexer extends SpelNodeImpl {
if (arrayComponentType == Integer.TYPE) {
int[] array = (int[]) ctx;
checkAccess(array.length, idx);
this.exitTypeDescriptor = "I";
return array[idx];
}
else if (arrayComponentType == Boolean.TYPE) {
boolean[] array = (boolean[]) ctx;
checkAccess(array.length, idx);
this.exitTypeDescriptor = "Z";
return array[idx];
}
else if (arrayComponentType == Character.TYPE) {
char[] array = (char[]) ctx;
checkAccess(array.length, idx);
this.exitTypeDescriptor = "C";
return array[idx];
}
else if (arrayComponentType == Long.TYPE) {
long[] array = (long[]) ctx;
checkAccess(array.length, idx);
this.exitTypeDescriptor = "J";
return array[idx];
}
else if (arrayComponentType == Short.TYPE) {
short[] array = (short[]) ctx;
checkAccess(array.length, idx);
this.exitTypeDescriptor = "S";
return array[idx];
}
else if (arrayComponentType == Double.TYPE) {
double[] array = (double[]) ctx;
checkAccess(array.length, idx);
this.exitTypeDescriptor = "D";
return array[idx];
}
else if (arrayComponentType == Float.TYPE) {
float[] array = (float[]) ctx;
checkAccess(array.length, idx);
this.exitTypeDescriptor = "F";
return array[idx];
}
else if (arrayComponentType == Byte.TYPE) {
byte[] array = (byte[]) ctx;
checkAccess(array.length, idx);
this.exitTypeDescriptor = "B";
return array[idx];
}
else {
Object[] array = (Object[]) ctx;
checkAccess(array.length, idx);
return array[idx];
Object retValue = array[idx];
this.exitTypeDescriptor = CodeFlow.toDescriptor(arrayComponentType);
return retValue;
}
}
@ -327,7 +469,7 @@ public class Indexer extends SpelNodeImpl { @@ -327,7 +469,7 @@ public class Indexer extends SpelNodeImpl {
@SuppressWarnings({"rawtypes", "unchecked"})
private static class MapIndexingValueRef implements ValueRef {
private class MapIndexingValueRef implements ValueRef {
private final TypeConverter typeConverter;
@ -347,6 +489,7 @@ public class Indexer extends SpelNodeImpl { @@ -347,6 +489,7 @@ public class Indexer extends SpelNodeImpl {
@Override
public TypedValue getValue() {
Object value = this.map.get(this.key);
exitTypeDescriptor = CodeFlow.toDescriptorFromObject(value);
return new TypedValue(value,
this.mapEntryTypeDescriptor.getMapValueTypeDescriptor(value));
}
@ -408,7 +551,17 @@ public class Indexer extends SpelNodeImpl { @@ -408,7 +551,17 @@ public class Indexer extends SpelNodeImpl {
Indexer.this.cachedReadAccessor = accessor;
Indexer.this.cachedReadName = this.name;
Indexer.this.cachedReadTargetType = targetObjectRuntimeClass;
return accessor.read(this.evaluationContext, this.targetObject, this.name);
if (accessor instanceof ReflectivePropertyAccessor.OptimalPropertyAccessor) {
ReflectivePropertyAccessor.OptimalPropertyAccessor optimalAccessor = (ReflectivePropertyAccessor.OptimalPropertyAccessor)accessor;
Member member = optimalAccessor.member;
if (member instanceof Field) {
Indexer.this.exitTypeDescriptor = CodeFlow.toDescriptor(((Field)member).getType());
} else {
Indexer.this.exitTypeDescriptor = CodeFlow.toDescriptor(((Method)member).getReturnType());
}
}
TypedValue value = accessor.read(this.evaluationContext, this.targetObject, this.name);
return value;
}
}
}
@ -490,6 +643,7 @@ public class Indexer extends SpelNodeImpl { @@ -490,6 +643,7 @@ public class Indexer extends SpelNodeImpl {
growCollectionIfNecessary();
if (this.collection instanceof List) {
Object o = ((List) this.collection).get(this.index);
exitTypeDescriptor = CodeFlow.toDescriptorFromObject(o);
return new TypedValue(o, this.collectionEntryDescriptor.elementTypeDescriptor(o));
}
int pos = 0;

27
spring-expression/src/main/java/org/springframework/expression/spel/ast/IntLiteral.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -15,7 +15,9 @@ @@ -15,7 +15,9 @@
*/
package org.springframework.expression.spel.ast;
import org.springframework.asm.MethodVisitor;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.standard.CodeFlow;
/**
* Expression language AST node that represents an integer literal.
@ -27,10 +29,10 @@ public class IntLiteral extends Literal { @@ -27,10 +29,10 @@ public class IntLiteral extends Literal {
private final TypedValue value;
IntLiteral(String payload, int pos, int value) {
super(payload, pos);
this.value = new TypedValue(value);
this.exitTypeDescriptor = "I";
}
@ -39,4 +41,25 @@ public class IntLiteral extends Literal { @@ -39,4 +41,25 @@ public class IntLiteral extends Literal {
return this.value;
}
@Override
public boolean isCompilable() {
return true;
}
@Override
public void generateCode(MethodVisitor mv, CodeFlow codeflow) {
int intValue = ((Integer)this.value.getValue()).intValue();
if (intValue==-1) {
// Not sure we can get here because -1 is OpMinus
mv.visitInsn(ICONST_M1);
}
else if (intValue >= 0 && intValue < 6) {
mv.visitInsn(ICONST_0+intValue);
}
else {
mv.visitLdcInsn(intValue);
}
codeflow.pushDescriptor(getExitDescriptor());
}
}

17
spring-expression/src/main/java/org/springframework/expression/spel/ast/LongLiteral.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,7 +16,9 @@ @@ -16,7 +16,9 @@
package org.springframework.expression.spel.ast;
import org.springframework.asm.MethodVisitor;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.standard.CodeFlow;
/**
* Expression language AST node that represents a long integer literal.
@ -32,12 +34,23 @@ public class LongLiteral extends Literal { @@ -32,12 +34,23 @@ public class LongLiteral extends Literal {
LongLiteral(String payload, int pos, long value) {
super(payload, pos);
this.value = new TypedValue(value);
this.exitTypeDescriptor = "J";
}
@Override
public TypedValue getLiteralValue() {
return this.value;
}
@Override
public boolean isCompilable() {
return true;
}
@Override
public void generateCode(MethodVisitor mv, CodeFlow codeflow) {
mv.visitLdcInsn(this.value.getValue());
codeflow.pushDescriptor(getExitDescriptor());
}
}

97
spring-expression/src/main/java/org/springframework/expression/spel/ast/MethodReference.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -17,10 +17,13 @@ @@ -17,10 +17,13 @@
package org.springframework.expression.spel.ast;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.springframework.asm.MethodVisitor;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.expression.AccessException;
import org.springframework.expression.EvaluationContext;
@ -32,6 +35,8 @@ import org.springframework.expression.TypedValue; @@ -32,6 +35,8 @@ import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.expression.spel.SpelMessage;
import org.springframework.expression.spel.standard.CodeFlow;
import org.springframework.expression.spel.support.ReflectiveMethodExecutor;
import org.springframework.expression.spel.support.ReflectiveMethodResolver;
/**
@ -77,7 +82,18 @@ public class MethodReference extends SpelNodeImpl { @@ -77,7 +82,18 @@ public class MethodReference extends SpelNodeImpl {
Object value = state.getActiveContextObject().getValue();
TypeDescriptor targetType = state.getActiveContextObject().getTypeDescriptor();
Object[] arguments = getArguments(state);
return getValueInternal(evaluationContext, value, targetType, arguments);
TypedValue result = getValueInternal(evaluationContext, value, targetType, arguments);
if (cachedExecutor.get() instanceof ReflectiveMethodExecutor) {
ReflectiveMethodExecutor executor = (ReflectiveMethodExecutor) cachedExecutor.get();
Method method = executor.getMethod();
exitTypeDescriptor = CodeFlow.toDescriptor(method.getReturnType());
}
return result;
}
@Override
public String getExitDescriptor() {
return exitTypeDescriptor;
}
private TypedValue getValueInternal(EvaluationContext evaluationContext,
@ -161,9 +177,7 @@ public class MethodReference extends SpelNodeImpl { @@ -161,9 +177,7 @@ public class MethodReference extends SpelNodeImpl {
return Collections.unmodifiableList(descriptors);
}
private MethodExecutor getCachedExecutor(EvaluationContext evaluationContext, Object value,
TypeDescriptor target, List<TypeDescriptor> argumentTypes) {
private MethodExecutor getCachedExecutor(EvaluationContext evaluationContext, Object value, TypeDescriptor target, List<TypeDescriptor> argumentTypes) {
List<MethodResolver> methodResolvers = evaluationContext.getMethodResolvers();
if (methodResolvers == null || methodResolvers.size() != 1 ||
!(methodResolvers.get(0) instanceof ReflectiveMethodResolver)) {
@ -255,7 +269,13 @@ public class MethodReference extends SpelNodeImpl { @@ -255,7 +269,13 @@ public class MethodReference extends SpelNodeImpl {
@Override
public TypedValue getValue() {
return getValueInternal(this.evaluationContext, this.value, this.targetType, this.arguments);
TypedValue result = MethodReference.this.getValueInternal(this.evaluationContext, this.value, this.targetType, this.arguments);
if (MethodReference.this.cachedExecutor.get() instanceof ReflectiveMethodExecutor) {
ReflectiveMethodExecutor executor = (ReflectiveMethodExecutor) MethodReference.this.cachedExecutor.get();
Method method = executor.getMethod();
MethodReference.this.exitTypeDescriptor = CodeFlow.toDescriptor(method.getReturnType());
}
return result;
}
@Override
@ -298,4 +318,69 @@ public class MethodReference extends SpelNodeImpl { @@ -298,4 +318,69 @@ public class MethodReference extends SpelNodeImpl {
}
}
/**
* A method reference is compilable if it has been resolved to a reflectively accessible method
* and the child nodes (arguments to the method) are also compilable.
*/
@Override
public boolean isCompilable() {
if (this.cachedExecutor == null || !(this.cachedExecutor.get() instanceof ReflectiveMethodExecutor)) {
return false;
}
for (SpelNodeImpl child: children) {
if (!child.isCompilable()) {
return false;
}
}
ReflectiveMethodExecutor executor = (ReflectiveMethodExecutor) this.cachedExecutor.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;
}
return true;
}
@Override
public void generateCode(MethodVisitor mv,CodeFlow codeflow) {
ReflectiveMethodExecutor executor = (ReflectiveMethodExecutor) this.cachedExecutor.get();
Method method = executor.getMethod();
boolean isStaticMethod = Modifier.isStatic(method.getModifiers());
String descriptor = codeflow.lastDescriptor();
if (descriptor == null && !isStaticMethod) {
codeflow.loadTarget(mv);
}
boolean itf = method.getDeclaringClass().isInterface();
String methodDeclaringClassSlashedDescriptor = method.getDeclaringClass().getName().replace('.','/');
if (!isStaticMethod) {
if (descriptor == null || !descriptor.equals(method.getDeclaringClass())) {
mv.visitTypeInsn(CHECKCAST, method.getDeclaringClass().getName().replace('.','/'));
}
}
String[] paramDescriptors = CodeFlow.toParamDescriptors(method);
for (int c=0;c<children.length;c++) {
SpelNodeImpl child = children[c];
codeflow.enterCompilationScope();
child.generateCode(mv, codeflow);
// Check if need to box it for the method reference?
if (CodeFlow.isPrimitive(codeflow.lastDescriptor()) && (paramDescriptors[c].charAt(0)=='L')) {
CodeFlow.insertBoxIfNecessary(mv, codeflow.lastDescriptor().charAt(0));
}
else if (!codeflow.lastDescriptor().equals(paramDescriptors[c])) {
// This would be unnecessary in the case of subtyping (e.g. method takes a Number but passed in is an Integer)
CodeFlow.insertCheckCast(mv, paramDescriptors[c]);
}
codeflow.exitCompilationScope();
}
mv.visitMethodInsn(isStaticMethod?INVOKESTATIC:INVOKEVIRTUAL,methodDeclaringClassSlashedDescriptor,method.getName(),CodeFlow.createSignatureDescriptor(method), itf);
codeflow.pushDescriptor(exitTypeDescriptor);
}
}

16
spring-expression/src/main/java/org/springframework/expression/spel/ast/NullLiteral.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,7 +16,9 @@ @@ -16,7 +16,9 @@
package org.springframework.expression.spel.ast;
import org.springframework.asm.MethodVisitor;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.standard.CodeFlow;
/**
* Expression language AST node that represents null.
@ -28,6 +30,7 @@ public class NullLiteral extends Literal { @@ -28,6 +30,7 @@ public class NullLiteral extends Literal {
public NullLiteral(int pos) {
super(null,pos);
this.exitTypeDescriptor = "Ljava/lang/Object";
}
@ -41,4 +44,15 @@ public class NullLiteral extends Literal { @@ -41,4 +44,15 @@ public class NullLiteral extends Literal {
return "null";
}
@Override
public boolean isCompilable() {
return true;
}
@Override
public void generateCode(MethodVisitor mv, CodeFlow codeflow) {
mv.visitInsn(ACONST_NULL);
codeflow.pushDescriptor(getExitDescriptor());
}
}

39
spring-expression/src/main/java/org/springframework/expression/spel/ast/OpAnd.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,11 +16,14 @@ @@ -16,11 +16,14 @@
package org.springframework.expression.spel.ast;
import org.springframework.asm.Label;
import org.springframework.asm.MethodVisitor;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.expression.spel.SpelMessage;
import org.springframework.expression.spel.standard.CodeFlow;
import org.springframework.expression.spel.support.BooleanTypedValue;
/**
@ -35,6 +38,7 @@ public class OpAnd extends Operator { @@ -35,6 +38,7 @@ public class OpAnd extends Operator {
public OpAnd(int pos, SpelNodeImpl... operands) {
super("and", pos, operands);
this.exitTypeDescriptor = "Z";
}
@ -65,4 +69,37 @@ public class OpAnd extends Operator { @@ -65,4 +69,37 @@ public class OpAnd extends Operator {
}
}
@Override
public boolean isCompilable() {
SpelNodeImpl left = getLeftOperand();
SpelNodeImpl right= getRightOperand();
if (!left.isCompilable() || !right.isCompilable()) {
return false;
}
return
CodeFlow.isBooleanCompatible(left.getExitDescriptor()) &&
CodeFlow.isBooleanCompatible(right.getExitDescriptor());
}
@Override
public void generateCode(MethodVisitor mv, CodeFlow codeflow) {
// pseudo: if (!leftOperandValue) { result=false; } else { result=rightOperandValue; }
Label elseTarget = new Label();
Label endOfIf = new Label();
codeflow.enterCompilationScope();
getLeftOperand().generateCode(mv, codeflow);
codeflow.unboxBooleanIfNecessary(mv);
codeflow.exitCompilationScope();
mv.visitJumpInsn(IFNE, elseTarget);
mv.visitLdcInsn(0); // FALSE
mv.visitJumpInsn(GOTO,endOfIf);
mv.visitLabel(elseTarget);
codeflow.enterCompilationScope();
getRightOperand().generateCode(mv, codeflow);
codeflow.unboxBooleanIfNecessary(mv);
codeflow.exitCompilationScope();
mv.visitLabel(endOfIf);
codeflow.pushDescriptor(this.exitTypeDescriptor);
}
}

74
spring-expression/src/main/java/org/springframework/expression/spel/ast/OpDivide.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,6 +16,8 @@ @@ -16,6 +16,8 @@
package org.springframework.expression.spel.ast;
import org.springframework.asm.MethodVisitor;
import java.math.BigDecimal;
import java.math.RoundingMode;
@ -23,6 +25,7 @@ import org.springframework.expression.EvaluationException; @@ -23,6 +25,7 @@ import org.springframework.expression.EvaluationException;
import org.springframework.expression.Operation;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.standard.CodeFlow;
import org.springframework.util.NumberUtils;
/**
@ -58,20 +61,79 @@ public class OpDivide extends Operator { @@ -58,20 +61,79 @@ public class OpDivide extends Operator {
}
if (leftNumber instanceof Double || rightNumber instanceof Double) {
if (leftNumber instanceof Double && rightNumber instanceof Double) {
this.exitTypeDescriptor = "D";
}
return new TypedValue(leftNumber.doubleValue() / rightNumber.doubleValue());
}
if (leftNumber instanceof Float || rightNumber instanceof Float) {
else if (leftNumber instanceof Float || rightNumber instanceof Float) {
if (leftNumber instanceof Float && rightNumber instanceof Float) {
this.exitTypeDescriptor = "F";
}
return new TypedValue(leftNumber.floatValue() / rightNumber.floatValue());
}
if (leftNumber instanceof Long || rightNumber instanceof Long) {
else if (leftNumber instanceof Long || rightNumber instanceof Long) {
if (leftNumber instanceof Long && rightNumber instanceof Long) {
this.exitTypeDescriptor = "J";
}
return new TypedValue(leftNumber.longValue() / rightNumber.longValue());
}
// TODO what about non-int result of the division?
return new TypedValue(leftNumber.intValue() / rightNumber.intValue());
else {
if (leftNumber instanceof Integer && rightNumber instanceof Integer) {
this.exitTypeDescriptor = "I";
}
// TODO what about non-int result of the division?
return new TypedValue(leftNumber.intValue() / rightNumber.intValue());
}
}
return state.operate(Operation.DIVIDE, leftOperand, rightOperand);
}
@Override
public boolean isCompilable() {
if (!getLeftOperand().isCompilable()) {
return false;
}
if (this.children.length>1) {
if (!getRightOperand().isCompilable()) {
return false;
}
}
return this.exitTypeDescriptor!=null;
}
@Override
public void generateCode(MethodVisitor mv, CodeFlow codeflow) {
getLeftOperand().generateCode(mv, codeflow);
String leftdesc = getLeftOperand().getExitDescriptor();
if (!CodeFlow.isPrimitive(leftdesc)) {
CodeFlow.insertUnboxInsns(mv, this.exitTypeDescriptor.charAt(0), false);
}
if (this.children.length > 1) {
getRightOperand().generateCode(mv, codeflow);
String rightdesc = getRightOperand().getExitDescriptor();
if (!CodeFlow.isPrimitive(rightdesc)) {
CodeFlow.insertUnboxInsns(mv, this.exitTypeDescriptor.charAt(0), false);
}
switch (this.exitTypeDescriptor.charAt(0)) {
case 'I':
mv.visitInsn(IDIV);
break;
case 'J':
mv.visitInsn(LDIV);
break;
case 'F':
mv.visitInsn(FDIV);
break;
case 'D':
mv.visitInsn(DDIV);
break;
default:
throw new IllegalStateException("Unrecognized exit descriptor: '"+this.exitTypeDescriptor+"'");
}
}
codeflow.pushDescriptor(this.exitTypeDescriptor);
}
}

99
spring-expression/src/main/java/org/springframework/expression/spel/ast/OpEQ.java

@ -16,8 +16,11 @@ @@ -16,8 +16,11 @@
package org.springframework.expression.spel.ast;
import org.springframework.asm.Label;
import org.springframework.asm.MethodVisitor;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.standard.CodeFlow;
import org.springframework.expression.spel.support.BooleanTypedValue;
/**
@ -30,6 +33,7 @@ public class OpEQ extends Operator { @@ -30,6 +33,7 @@ public class OpEQ extends Operator {
public OpEQ(int pos, SpelNodeImpl... operands) {
super("==", pos, operands);
this.exitTypeDescriptor = "Z";
}
@Override
@ -38,5 +42,100 @@ public class OpEQ extends Operator { @@ -38,5 +42,100 @@ public class OpEQ extends Operator {
Object right = getRightOperand().getValueInternal(state).getValue();
return BooleanTypedValue.forValue(equalityCheck(state, left, right));
}
// This check is different to the one in the other numeric operators (OpLt/etc)
// because it allows for simple object comparison
@Override
public boolean isCompilable() {
SpelNodeImpl left = getLeftOperand();
SpelNodeImpl right= getRightOperand();
if (!left.isCompilable() || !right.isCompilable()) {
return false;
}
String leftdesc = left.getExitDescriptor();
String rightdesc = right.getExitDescriptor();
if ((CodeFlow.isPrimitiveOrUnboxableSupportedNumberOrBoolean(leftdesc) ||
CodeFlow.isPrimitiveOrUnboxableSupportedNumber(rightdesc))) {
if (!CodeFlow.areBoxingCompatible(leftdesc, rightdesc)) {
return false;
}
}
return true;
}
@Override
public void generateCode(MethodVisitor mv, CodeFlow codeflow) {
String leftDesc = getLeftOperand().getExitDescriptor();
String rightDesc = getRightOperand().getExitDescriptor();
Label elseTarget = new Label();
Label endOfIf = new Label();
boolean leftPrim = CodeFlow.isPrimitive(leftDesc);
boolean rightPrim = CodeFlow.isPrimitive(rightDesc);
if ((CodeFlow.isPrimitiveOrUnboxableSupportedNumberOrBoolean(leftDesc) ||
CodeFlow.isPrimitiveOrUnboxableSupportedNumberOrBoolean(rightDesc)) &&
CodeFlow.areBoxingCompatible(leftDesc,rightDesc)) {
char targetType = CodeFlow.toPrimitiveTargetDesc(leftDesc);
getLeftOperand().generateCode(mv, codeflow);
if (!leftPrim) {
CodeFlow.insertUnboxInsns(mv, targetType, false);
}
getRightOperand().generateCode(mv, codeflow);
if (!rightPrim) {
CodeFlow.insertUnboxInsns(mv, targetType, false);
}
// assert: SpelCompiler.boxingCompatible(leftDesc, rightDesc)
if (targetType=='D') {
mv.visitInsn(DCMPL);
mv.visitJumpInsn(IFNE, elseTarget);
}
else if (targetType=='F') {
mv.visitInsn(FCMPL);
mv.visitJumpInsn(IFNE, elseTarget);
}
else if (targetType=='J') {
mv.visitInsn(LCMP);
mv.visitJumpInsn(IFNE, elseTarget);
}
else if (targetType=='I' || targetType=='Z') {
mv.visitJumpInsn(IF_ICMPNE, elseTarget);
}
else {
throw new IllegalStateException("Unexpected descriptor "+leftDesc);
}
} else {
getLeftOperand().generateCode(mv, codeflow);
getRightOperand().generateCode(mv, codeflow);
Label leftNotNull = new Label();
mv.visitInsn(DUP_X1); // Dup right on the top of the stack
mv.visitJumpInsn(IFNONNULL,leftNotNull);
// Right is null!
mv.visitInsn(SWAP);
mv.visitInsn(POP); // remove it
Label rightNotNull = new Label();
mv.visitJumpInsn(IFNONNULL, rightNotNull);
// Left is null too
mv.visitInsn(ICONST_1);
mv.visitJumpInsn(GOTO, endOfIf);
mv.visitLabel(rightNotNull);
mv.visitInsn(ICONST_0);
mv.visitJumpInsn(GOTO,endOfIf);
mv.visitLabel(leftNotNull);
mv.visitMethodInsn(INVOKEVIRTUAL,"java/lang/Object","equals","(Ljava/lang/Object;)Z",false);
mv.visitLabel(endOfIf);
codeflow.pushDescriptor("Z");
return;
}
mv.visitInsn(ICONST_1);
mv.visitJumpInsn(GOTO,endOfIf);
mv.visitLabel(elseTarget);
mv.visitInsn(ICONST_0);
mv.visitLabel(endOfIf);
codeflow.pushDescriptor("Z");
}
}

16
spring-expression/src/main/java/org/springframework/expression/spel/ast/OpGE.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -15,10 +15,11 @@ @@ -15,10 +15,11 @@
*/
package org.springframework.expression.spel.ast;
import org.springframework.asm.MethodVisitor;
import java.math.BigDecimal;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.standard.CodeFlow;
import org.springframework.expression.spel.support.BooleanTypedValue;
import org.springframework.util.NumberUtils;
@ -34,6 +35,7 @@ public class OpGE extends Operator { @@ -34,6 +35,7 @@ public class OpGE extends Operator {
public OpGE(int pos, SpelNodeImpl... operands) {
super(">=", pos, operands);
this.exitTypeDescriptor="Z";
}
@ -66,5 +68,15 @@ public class OpGE extends Operator { @@ -66,5 +68,15 @@ public class OpGE extends Operator {
}
return BooleanTypedValue.forValue(state.getTypeComparator().compare(left, right) >= 0);
}
@Override
public boolean isCompilable() {
return isCompilableOperatorUsingNumerics();
}
@Override
public void generateCode(MethodVisitor mv, CodeFlow codeflow) {
generateComparisonCode(mv, codeflow, IFLT, IF_ICMPLT);
}
}

13
spring-expression/src/main/java/org/springframework/expression/spel/ast/OpGT.java

@ -16,10 +16,11 @@ @@ -16,10 +16,11 @@
package org.springframework.expression.spel.ast;
import org.springframework.asm.MethodVisitor;
import java.math.BigDecimal;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.standard.CodeFlow;
import org.springframework.expression.spel.support.BooleanTypedValue;
import org.springframework.util.NumberUtils;
@ -35,6 +36,7 @@ public class OpGT extends Operator { @@ -35,6 +36,7 @@ public class OpGT extends Operator {
public OpGT(int pos, SpelNodeImpl... operands) {
super(">", pos, operands);
this.exitTypeDescriptor = "Z";
}
@Override
@ -75,4 +77,13 @@ public class OpGT extends Operator { @@ -75,4 +77,13 @@ public class OpGT extends Operator {
return BooleanTypedValue.forValue(state.getTypeComparator().compare(left, right) > 0);
}
@Override
public boolean isCompilable() {
return isCompilableOperatorUsingNumerics();
}
@Override
public void generateCode(MethodVisitor mv, CodeFlow codeflow) {
generateComparisonCode(mv, codeflow, IFLE, IF_ICMPLE);
}
}

16
spring-expression/src/main/java/org/springframework/expression/spel/ast/OpLE.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,10 +16,11 @@ @@ -16,10 +16,11 @@
package org.springframework.expression.spel.ast;
import org.springframework.asm.MethodVisitor;
import java.math.BigDecimal;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.standard.CodeFlow;
import org.springframework.expression.spel.support.BooleanTypedValue;
import org.springframework.util.NumberUtils;
@ -35,6 +36,7 @@ public class OpLE extends Operator { @@ -35,6 +36,7 @@ public class OpLE extends Operator {
public OpLE(int pos, SpelNodeImpl... operands) {
super("<=", pos, operands);
this.exitTypeDescriptor="Z";
}
@ -70,5 +72,15 @@ public class OpLE extends Operator { @@ -70,5 +72,15 @@ public class OpLE extends Operator {
return BooleanTypedValue.forValue(state.getTypeComparator().compare(left, right) <= 0);
}
@Override
public boolean isCompilable() {
return isCompilableOperatorUsingNumerics();
}
@Override
public void generateCode(MethodVisitor mv, CodeFlow codeflow) {
generateComparisonCode(mv, codeflow, IFGT, IF_ICMPGT);
}
}

14
spring-expression/src/main/java/org/springframework/expression/spel/ast/OpLT.java

@ -16,10 +16,11 @@ @@ -16,10 +16,11 @@
package org.springframework.expression.spel.ast;
import org.springframework.asm.MethodVisitor;
import java.math.BigDecimal;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.standard.CodeFlow;
import org.springframework.expression.spel.support.BooleanTypedValue;
import org.springframework.util.NumberUtils;
@ -35,6 +36,7 @@ public class OpLT extends Operator { @@ -35,6 +36,7 @@ public class OpLT extends Operator {
public OpLT(int pos, SpelNodeImpl... operands) {
super("<", pos, operands);
this.exitTypeDescriptor = "Z";
}
@Override
@ -74,5 +76,15 @@ public class OpLT extends Operator { @@ -74,5 +76,15 @@ public class OpLT extends Operator {
return BooleanTypedValue.forValue(state.getTypeComparator().compare(left, right) < 0);
}
@Override
public boolean isCompilable() {
return isCompilableOperatorUsingNumerics();
}
@Override
public void generateCode(MethodVisitor mv, CodeFlow codeflow) {
generateComparisonCode(mv, codeflow, IFGE, IF_ICMPGE);
}
}

87
spring-expression/src/main/java/org/springframework/expression/spel/ast/OpMinus.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,12 +16,15 @@ @@ -16,12 +16,15 @@
package org.springframework.expression.spel.ast;
import org.springframework.asm.MethodVisitor;
import java.math.BigDecimal;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.Operation;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.standard.CodeFlow;
import org.springframework.util.NumberUtils;
/**
@ -67,17 +70,20 @@ public class OpMinus extends Operator { @@ -67,17 +70,20 @@ public class OpMinus extends Operator {
}
if (operand instanceof Double) {
this.exitTypeDescriptor = "D";
return new TypedValue(0 - n.doubleValue());
}
if (operand instanceof Float) {
this.exitTypeDescriptor = "F";
return new TypedValue(0 - n.floatValue());
}
if (operand instanceof Long) {
this.exitTypeDescriptor = "J";
return new TypedValue(0 - n.longValue());
}
this.exitTypeDescriptor = "I";
return new TypedValue(0 - n.intValue());
}
@ -96,19 +102,28 @@ public class OpMinus extends Operator { @@ -96,19 +102,28 @@ public class OpMinus extends Operator {
BigDecimal rightBigDecimal = NumberUtils.convertNumberToTargetClass(rightNumber, BigDecimal.class);
return new TypedValue(leftBigDecimal.subtract(rightBigDecimal));
}
if (leftNumber instanceof Double || rightNumber instanceof Double) {
if (leftNumber instanceof Double && rightNumber instanceof Double) {
this.exitTypeDescriptor = "D";
}
return new TypedValue(leftNumber.doubleValue() - rightNumber.doubleValue());
}
if (leftNumber instanceof Float || rightNumber instanceof Float) {
if (leftNumber instanceof Float && rightNumber instanceof Float) {
this.exitTypeDescriptor = "F";
}
return new TypedValue(leftNumber.floatValue() - rightNumber.floatValue());
}
if (leftNumber instanceof Long || rightNumber instanceof Long) {
if (leftNumber instanceof Long && rightNumber instanceof Long) {
this.exitTypeDescriptor = "J";
}
return new TypedValue(leftNumber.longValue() - rightNumber.longValue());
}
this.exitTypeDescriptor = "I";
return new TypedValue(leftNumber.intValue() - rightNumber.intValue());
}
else if (left instanceof String && right instanceof Integer
@ -130,10 +145,74 @@ public class OpMinus extends Operator { @@ -130,10 +145,74 @@ public class OpMinus extends Operator {
}
return super.toStringAST();
}
@Override
public SpelNodeImpl getRightOperand() {
if (this.children.length<2) {return null;}
return this.children[1];
}
@Override
public boolean isCompilable() {
if (!getLeftOperand().isCompilable()) {
return false;
}
if (this.children.length>1) {
if (!getRightOperand().isCompilable()) {
return false;
}
}
return this.exitTypeDescriptor!=null;
}
@Override
public void generateCode(MethodVisitor mv, CodeFlow codeflow) {
getLeftOperand().generateCode(mv, codeflow);
String leftdesc = getLeftOperand().getExitDescriptor();
if (!CodeFlow.isPrimitive(leftdesc)) {
CodeFlow.insertUnboxInsns(mv, this.exitTypeDescriptor.charAt(0), false);
}
if (this.children.length>1) {
getRightOperand().generateCode(mv, codeflow);
String rightdesc = getRightOperand().getExitDescriptor();
if (!CodeFlow.isPrimitive(rightdesc)) {
CodeFlow.insertUnboxInsns(mv, this.exitTypeDescriptor.charAt(0), false);
}
switch (this.exitTypeDescriptor.charAt(0)) {
case 'I':
mv.visitInsn(ISUB);
break;
case 'J':
mv.visitInsn(LSUB);
break;
case 'F':
mv.visitInsn(FSUB);
break;
case 'D':
mv.visitInsn(DSUB);
break;
default:
throw new IllegalStateException("Unrecognized exit descriptor: '"+this.exitTypeDescriptor+"'");
}
} else {
switch (this.exitTypeDescriptor.charAt(0)) {
case 'I':
mv.visitInsn(INEG);
break;
case 'J':
mv.visitInsn(LNEG);
break;
case 'F':
mv.visitInsn(FNEG);
break;
case 'D':
mv.visitInsn(DNEG);
break;
default:
throw new IllegalStateException("Unrecognized exit descriptor: '"+this.exitTypeDescriptor+"'");
}
}
codeflow.pushDescriptor(this.exitTypeDescriptor);
}
}

67
spring-expression/src/main/java/org/springframework/expression/spel/ast/OpMultiply.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,12 +16,15 @@ @@ -16,12 +16,15 @@
package org.springframework.expression.spel.ast;
import org.springframework.asm.MethodVisitor;
import java.math.BigDecimal;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.Operation;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.standard.CodeFlow;
import org.springframework.util.NumberUtils;
/**
@ -81,17 +84,29 @@ public class OpMultiply extends Operator { @@ -81,17 +84,29 @@ public class OpMultiply extends Operator {
}
if (leftNumber instanceof Double || rightNumber instanceof Double) {
return new TypedValue(leftNumber.doubleValue() * rightNumber.doubleValue());
if (leftNumber instanceof Double && rightNumber instanceof Double) {
this.exitTypeDescriptor = "D";
}
return new TypedValue(leftNumber.doubleValue()
* rightNumber.doubleValue());
}
if (leftNumber instanceof Float || rightNumber instanceof Float) {
if (leftNumber instanceof Float && rightNumber instanceof Float) {
this.exitTypeDescriptor = "F";
}
return new TypedValue(leftNumber.floatValue() * rightNumber.floatValue());
}
if (leftNumber instanceof Long || rightNumber instanceof Long) {
if (leftNumber instanceof Long && rightNumber instanceof Long) {
this.exitTypeDescriptor = "J";
}
return new TypedValue(leftNumber.longValue() * rightNumber.longValue());
}
if (leftNumber instanceof Integer && rightNumber instanceof Integer) {
this.exitTypeDescriptor = "I";
}
return new TypedValue(leftNumber.intValue() * rightNumber.intValue());
}
else if (leftOperand instanceof String && rightOperand instanceof Integer) {
@ -105,5 +120,51 @@ public class OpMultiply extends Operator { @@ -105,5 +120,51 @@ public class OpMultiply extends Operator {
return state.operate(Operation.MULTIPLY, leftOperand, rightOperand);
}
@Override
public boolean isCompilable() {
if (!getLeftOperand().isCompilable()) {
return false;
}
if (this.children.length>1) {
if (!getRightOperand().isCompilable()) {
return false;
}
}
return this.exitTypeDescriptor!=null;
}
@Override
public void generateCode(MethodVisitor mv, CodeFlow codeflow) {
getLeftOperand().generateCode(mv, codeflow);
String leftdesc = getLeftOperand().getExitDescriptor();
if (!CodeFlow.isPrimitive(leftdesc)) {
CodeFlow.insertUnboxInsns(mv, this.exitTypeDescriptor.charAt(0), false);
}
if (this.children.length>1) {
getRightOperand().generateCode(mv, codeflow);
String rightdesc = getRightOperand().getExitDescriptor();
if (!CodeFlow.isPrimitive(rightdesc)) {
CodeFlow.insertUnboxInsns(mv, this.exitTypeDescriptor.charAt(0), false);
}
switch (this.exitTypeDescriptor.charAt(0)) {
case 'I':
mv.visitInsn(IMUL);
break;
case 'J':
mv.visitInsn(LMUL);
break;
case 'F':
mv.visitInsn(FMUL);
break;
case 'D':
mv.visitInsn(DMUL);
break;
default:
throw new IllegalStateException("Unrecognized exit descriptor: '"+this.exitTypeDescriptor+"'");
}
}
codeflow.pushDescriptor(this.exitTypeDescriptor);
}
}

79
spring-expression/src/main/java/org/springframework/expression/spel/ast/OpNE.java

@ -16,8 +16,11 @@ @@ -16,8 +16,11 @@
package org.springframework.expression.spel.ast;
import org.springframework.asm.Label;
import org.springframework.asm.MethodVisitor;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.standard.CodeFlow;
import org.springframework.expression.spel.support.BooleanTypedValue;
/**
@ -30,6 +33,7 @@ public class OpNE extends Operator { @@ -30,6 +33,7 @@ public class OpNE extends Operator {
public OpNE(int pos, SpelNodeImpl... operands) {
super("!=", pos, operands);
this.exitTypeDescriptor = "Z";
}
@Override
@ -39,4 +43,79 @@ public class OpNE extends Operator { @@ -39,4 +43,79 @@ public class OpNE extends Operator {
return BooleanTypedValue.forValue(!equalityCheck(state, left, right));
}
// This check is different to the one in the other numeric operators (OpLt/etc)
// because we allow simple object comparison
@Override
public boolean isCompilable() {
SpelNodeImpl left = getLeftOperand();
SpelNodeImpl right= getRightOperand();
if (!left.isCompilable() || !right.isCompilable()) {
return false;
}
String leftdesc = left.getExitDescriptor();
String rightdesc = right.getExitDescriptor();
if ((CodeFlow.isPrimitiveOrUnboxableSupportedNumberOrBoolean(leftdesc) ||
CodeFlow.isPrimitiveOrUnboxableSupportedNumber(rightdesc))) {
if (!CodeFlow.areBoxingCompatible(leftdesc, rightdesc)) {
return false;
}
}
return true;
}
@Override
public void generateCode(MethodVisitor mv, CodeFlow codeflow) {
String leftDesc = getLeftOperand().getExitDescriptor();
String rightDesc = getRightOperand().getExitDescriptor();
Label elseTarget = new Label();
Label endOfIf = new Label();
boolean leftPrim = CodeFlow.isPrimitive(leftDesc);
boolean rightPrim = CodeFlow.isPrimitive(rightDesc);
if ((CodeFlow.isPrimitiveOrUnboxableSupportedNumberOrBoolean(leftDesc) ||
CodeFlow.isPrimitiveOrUnboxableSupportedNumberOrBoolean(rightDesc)) &&
CodeFlow.areBoxingCompatible(leftDesc,rightDesc)) {
char targetType = CodeFlow.toPrimitiveTargetDesc(leftDesc);
getLeftOperand().generateCode(mv, codeflow);
if (!leftPrim) {
CodeFlow.insertUnboxInsns(mv, targetType, false);
}
getRightOperand().generateCode(mv, codeflow);
if (!rightPrim) {
CodeFlow.insertUnboxInsns(mv, targetType, false);
}
// assert: SpelCompiler.boxingCompatible(leftDesc, rightDesc)
if (targetType == 'D') {
mv.visitInsn(DCMPL);
mv.visitJumpInsn(IFEQ, elseTarget);
}
else if (targetType == 'F') {
mv.visitInsn(FCMPL);
mv.visitJumpInsn(IFEQ, elseTarget);
}
else if (targetType == 'J') {
mv.visitInsn(LCMP);
mv.visitJumpInsn(IFEQ, elseTarget);
}
else if (targetType == 'I' || targetType == 'Z') {
mv.visitJumpInsn(IF_ICMPEQ, elseTarget);
}
else {
throw new IllegalStateException("Unexpected descriptor "+leftDesc);
}
} else {
getLeftOperand().generateCode(mv, codeflow);
getRightOperand().generateCode(mv, codeflow);
mv.visitJumpInsn(IF_ACMPEQ, elseTarget);
}
mv.visitInsn(ICONST_1);
mv.visitJumpInsn(GOTO,endOfIf);
mv.visitLabel(elseTarget);
mv.visitInsn(ICONST_0);
mv.visitLabel(endOfIf);
codeflow.pushDescriptor("Z");
}
}

39
spring-expression/src/main/java/org/springframework/expression/spel/ast/OpOr.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,10 +16,13 @@ @@ -16,10 +16,13 @@
package org.springframework.expression.spel.ast;
import org.springframework.asm.Label;
import org.springframework.asm.MethodVisitor;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.expression.spel.SpelMessage;
import org.springframework.expression.spel.standard.CodeFlow;
import org.springframework.expression.spel.support.BooleanTypedValue;
/**
@ -34,6 +37,7 @@ public class OpOr extends Operator { @@ -34,6 +37,7 @@ public class OpOr extends Operator {
public OpOr(int pos, SpelNodeImpl... operands) {
super("or", pos, operands);
this.exitTypeDescriptor = "Z";
}
@ -64,4 +68,37 @@ public class OpOr extends Operator { @@ -64,4 +68,37 @@ public class OpOr extends Operator {
}
}
@Override
public boolean isCompilable() {
SpelNodeImpl left = getLeftOperand();
SpelNodeImpl right= getRightOperand();
if (!left.isCompilable() || !right.isCompilable()) {
return false;
}
return
CodeFlow.isBooleanCompatible(left.getExitDescriptor()) &&
CodeFlow.isBooleanCompatible(right.getExitDescriptor());
}
@Override
public void generateCode(MethodVisitor mv, CodeFlow codeflow) {
// pseudo: if (leftOperandValue) { result=true; } else { result=rightOperandValue; }
Label elseTarget = new Label();
Label endOfIf = new Label();
codeflow.enterCompilationScope();
getLeftOperand().generateCode(mv, codeflow);
codeflow.unboxBooleanIfNecessary(mv);
codeflow.exitCompilationScope();
mv.visitJumpInsn(IFEQ, elseTarget);
mv.visitLdcInsn(1); // TRUE
mv.visitJumpInsn(GOTO,endOfIf);
mv.visitLabel(elseTarget);
codeflow.enterCompilationScope();
getRightOperand().generateCode(mv, codeflow);
codeflow.unboxBooleanIfNecessary(mv);
codeflow.exitCompilationScope();
mv.visitLabel(endOfIf);
codeflow.pushDescriptor(getExitDescriptor());
}
}

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

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,6 +16,8 @@ @@ -16,6 +16,8 @@
package org.springframework.expression.spel.ast;
import org.springframework.asm.MethodVisitor;
import java.math.BigDecimal;
import org.springframework.core.convert.TypeDescriptor;
@ -24,6 +26,7 @@ import org.springframework.expression.Operation; @@ -24,6 +26,7 @@ import org.springframework.expression.Operation;
import org.springframework.expression.TypeConverter;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.standard.CodeFlow;
import org.springframework.util.Assert;
import org.springframework.util.NumberUtils;
@ -63,11 +66,16 @@ public class OpPlus extends Operator { @@ -63,11 +66,16 @@ public class OpPlus extends Operator {
Object operandOne = leftOp.getValueInternal(state).getValue();
if (operandOne instanceof Number) {
if (operandOne instanceof Double || operandOne instanceof Long || operandOne instanceof BigDecimal) {
if (operandOne instanceof Double || operandOne instanceof Long) {
this.exitTypeDescriptor = (operandOne instanceof Double)?"D":"J";
}
return new TypedValue(operandOne);
}
if (operandOne instanceof Float) {
this.exitTypeDescriptor = "F";
return new TypedValue(((Number) operandOne).floatValue());
}
this.exitTypeDescriptor = "I";
return new TypedValue(((Number) operandOne).intValue());
}
return state.operate(Operation.ADD, operandOne, null);
@ -88,20 +96,27 @@ public class OpPlus extends Operator { @@ -88,20 +96,27 @@ public class OpPlus extends Operator {
BigDecimal rightBigDecimal = NumberUtils.convertNumberToTargetClass(rightNumber, BigDecimal.class);
return new TypedValue(leftBigDecimal.add(rightBigDecimal));
}
if (leftNumber instanceof Double || rightNumber instanceof Double) {
if (leftNumber instanceof Double && rightNumber instanceof Double) {
this.exitTypeDescriptor = "D";
}
return new TypedValue(leftNumber.doubleValue() + rightNumber.doubleValue());
}
if (leftNumber instanceof Float || rightNumber instanceof Float) {
if (leftNumber instanceof Float && rightNumber instanceof Float) {
this.exitTypeDescriptor = "F";
}
return new TypedValue(leftNumber.floatValue() + rightNumber.floatValue());
}
if (leftNumber instanceof Long || rightNumber instanceof Long) {
if (leftNumber instanceof Long && rightNumber instanceof Long) {
this.exitTypeDescriptor = "J";
}
return new TypedValue(leftNumber.longValue() + rightNumber.longValue());
}
// TODO what about overflow?
this.exitTypeDescriptor = "I";
return new TypedValue(leftNumber.intValue() + rightNumber.intValue());
}
@ -164,4 +179,50 @@ public class OpPlus extends Operator { @@ -164,4 +179,50 @@ public class OpPlus extends Operator {
return String.valueOf(value.getValue());
}
@Override
public boolean isCompilable() {
if (!getLeftOperand().isCompilable()) {
return false;
}
if (this.children.length>1) {
if (!getRightOperand().isCompilable()) {
return false;
}
}
return this.exitTypeDescriptor!=null;
}
@Override
public void generateCode(MethodVisitor mv, CodeFlow codeflow) {
getLeftOperand().generateCode(mv, codeflow);
String leftdesc = getLeftOperand().getExitDescriptor();
if (!CodeFlow.isPrimitive(leftdesc)) {
CodeFlow.insertUnboxInsns(mv, this.exitTypeDescriptor.charAt(0), false);
}
if (this.children.length>1) {
getRightOperand().generateCode(mv, codeflow);
String rightdesc = getRightOperand().getExitDescriptor();
if (!CodeFlow.isPrimitive(rightdesc)) {
CodeFlow.insertUnboxInsns(mv, this.exitTypeDescriptor.charAt(0), false);
}
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 descriptor: '"+this.exitTypeDescriptor+"'");
}
}
codeflow.pushDescriptor(this.exitTypeDescriptor);
}
}

72
spring-expression/src/main/java/org/springframework/expression/spel/ast/Operator.java

@ -16,6 +16,10 @@ @@ -16,6 +16,10 @@
package org.springframework.expression.spel.ast;
import org.springframework.asm.Label;
import org.springframework.asm.MethodVisitor;
import org.springframework.expression.spel.standard.CodeFlow;
import java.math.BigDecimal;
import org.springframework.expression.spel.ExpressionState;
@ -72,6 +76,74 @@ public abstract class Operator extends SpelNodeImpl { @@ -72,6 +76,74 @@ public abstract class Operator extends SpelNodeImpl {
return sb.toString();
}
protected boolean isCompilableOperatorUsingNumerics() {
SpelNodeImpl left = getLeftOperand();
SpelNodeImpl right= getRightOperand();
if (!left.isCompilable() || !right.isCompilable()) {
return false;
}
// Supported operand types for equals (at the moment)
String leftDesc = left.getExitDescriptor();
String rightDesc= right.getExitDescriptor();
if (CodeFlow.isPrimitiveOrUnboxableSupportedNumber(leftDesc) && CodeFlow.isPrimitiveOrUnboxableSupportedNumber(rightDesc)) {
if (CodeFlow.areBoxingCompatible(leftDesc, rightDesc)) {
return true;
}
}
return false;
}
/**
* Numeric comparison operators share very similar generated code, only differing in
* two comparison instructions.
*/
protected void generateComparisonCode(MethodVisitor mv, CodeFlow codeflow, int compareInstruction1,
int compareInstruction2) {
String leftDesc = getLeftOperand().getExitDescriptor();
String rightDesc = getRightOperand().getExitDescriptor();
boolean unboxLeft = !CodeFlow.isPrimitive(leftDesc);
boolean unboxRight = !CodeFlow.isPrimitive(rightDesc);
char targetType = CodeFlow.toPrimitiveTargetDesc(leftDesc);
getLeftOperand().generateCode(mv, codeflow);
if (unboxLeft) {
CodeFlow.insertUnboxInsns(mv, targetType, false);
}
getRightOperand().generateCode(mv, codeflow);
if (unboxRight) {
CodeFlow.insertUnboxInsns(mv, targetType, false);
}
// assert: SpelCompiler.boxingCompatible(leftDesc, rightDesc)
Label elseTarget = new Label();
Label endOfIf = new Label();
if (targetType=='D') {
mv.visitInsn(DCMPG);
mv.visitJumpInsn(compareInstruction1, elseTarget);
}
else if (targetType=='F') {
mv.visitInsn(FCMPG);
mv.visitJumpInsn(compareInstruction1, elseTarget);
}
else if (targetType=='J') {
mv.visitInsn(LCMP);
mv.visitJumpInsn(compareInstruction1, elseTarget);
}
else if (targetType=='I') {
mv.visitJumpInsn(compareInstruction2, elseTarget);
}
else {
throw new IllegalStateException("Unexpected descriptor "+leftDesc);
}
// Other numbers are not yet supported (isCompilable will not have returned true)
mv.visitInsn(ICONST_1);
mv.visitJumpInsn(GOTO,endOfIf);
mv.visitLabel(elseTarget);
mv.visitInsn(ICONST_0);
mv.visitLabel(endOfIf);
codeflow.pushDescriptor("Z");
}
protected boolean equalityCheck(ExpressionState state, Object left, Object right) {
if (left instanceof Number && right instanceof Number) {

32
spring-expression/src/main/java/org/springframework/expression/spel/ast/OperatorInstanceof.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,13 +16,17 @@ @@ -16,13 +16,17 @@
package org.springframework.expression.spel.ast;
import org.springframework.asm.MethodVisitor;
import org.springframework.asm.Type;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.expression.spel.SpelMessage;
import org.springframework.expression.spel.standard.CodeFlow;
import org.springframework.expression.spel.support.BooleanTypedValue;
/**
* The operator 'instanceof' checks if an object is of the class specified in the right
* hand operand, in the same way that {@code instanceof} does in Java.
@ -32,6 +36,8 @@ import org.springframework.expression.spel.support.BooleanTypedValue; @@ -32,6 +36,8 @@ import org.springframework.expression.spel.support.BooleanTypedValue;
*/
public class OperatorInstanceof extends Operator {
private Class<?> type;
public OperatorInstanceof(int pos, SpelNodeImpl... operands) {
super("instanceof", pos, operands);
}
@ -51,16 +57,32 @@ public class OperatorInstanceof extends Operator { @@ -51,16 +57,32 @@ public class OperatorInstanceof extends Operator {
TypedValue right = getRightOperand().getValueInternal(state);
Object leftValue = left.getValue();
Object rightValue = right.getValue();
if (leftValue == null) {
return BooleanTypedValue.FALSE; // null is not an instanceof anything
}
BooleanTypedValue result = null;
if (rightValue == null || !(rightValue instanceof Class<?>)) {
throw new SpelEvaluationException(getRightOperand().getStartPosition(),
SpelMessage.INSTANCEOF_OPERATOR_NEEDS_CLASS_OPERAND,
(rightValue == null ? "null" : rightValue.getClass().getName()));
}
Class<?> rightClass = (Class<?>) rightValue;
return BooleanTypedValue.forValue(rightClass.isAssignableFrom(leftValue.getClass()));
if (leftValue == null) {
result = BooleanTypedValue.FALSE; // null is not an instanceof anything
} else {
result = BooleanTypedValue.forValue(rightClass.isAssignableFrom(leftValue.getClass()));
}
this.type = rightClass;
this.exitTypeDescriptor = "Z";
return result;
}
@Override
public boolean isCompilable() {
return this.exitTypeDescriptor != null && getLeftOperand().isCompilable();
}
@Override
public void generateCode(MethodVisitor mv, CodeFlow codeflow) {
getLeftOperand().generateCode(mv, codeflow);
mv.visitTypeInsn(INSTANCEOF,Type.getInternalName(this.type));
codeflow.pushDescriptor(getExitDescriptor());
}
}

27
spring-expression/src/main/java/org/springframework/expression/spel/ast/OperatorNot.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,10 +16,13 @@ @@ -16,10 +16,13 @@
package org.springframework.expression.spel.ast;
import org.springframework.asm.Label;
import org.springframework.asm.MethodVisitor;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.expression.spel.SpelMessage;
import org.springframework.expression.spel.standard.CodeFlow;
import org.springframework.expression.spel.support.BooleanTypedValue;
/**
@ -34,6 +37,7 @@ public class OperatorNot extends SpelNodeImpl { // Not is a unary operator so do @@ -34,6 +37,7 @@ public class OperatorNot extends SpelNodeImpl { // Not is a unary operator so do
public OperatorNot(int pos, SpelNodeImpl operand) {
super(pos, operand);
this.exitTypeDescriptor = "Z";
}
@ -58,5 +62,26 @@ public class OperatorNot extends SpelNodeImpl { // Not is a unary operator so do @@ -58,5 +62,26 @@ public class OperatorNot extends SpelNodeImpl { // Not is a unary operator so do
sb.append("!").append(getChild(0).toStringAST());
return sb.toString();
}
@Override
public boolean isCompilable() {
SpelNodeImpl child = this.children[0];
return child.isCompilable() && CodeFlow.isBooleanCompatible(child.getExitDescriptor());
}
@Override
public void generateCode(MethodVisitor mv, CodeFlow codeflow) {
this.children[0].generateCode(mv, codeflow);
codeflow.unboxBooleanIfNecessary(mv);
Label elseTarget = new Label();
Label endOfIf = new Label();
mv.visitJumpInsn(IFNE,elseTarget);
mv.visitInsn(ICONST_1); // TRUE
mv.visitJumpInsn(GOTO,endOfIf);
mv.visitLabel(elseTarget);
mv.visitInsn(ICONST_0); // FALSE
mv.visitLabel(endOfIf);
codeflow.pushDescriptor(getExitDescriptor());
}
}

40
spring-expression/src/main/java/org/springframework/expression/spel/ast/PropertyOrFieldReference.java

@ -21,8 +21,10 @@ import java.util.HashMap; @@ -21,8 +21,10 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.asm.MethodVisitor;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.expression.AccessException;
import org.springframework.expression.CompilablePropertyAccessor;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.PropertyAccessor;
@ -30,6 +32,7 @@ import org.springframework.expression.TypedValue; @@ -30,6 +32,7 @@ import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.expression.spel.SpelMessage;
import org.springframework.expression.spel.standard.CodeFlow;
import org.springframework.expression.spel.support.ReflectivePropertyAccessor;
/**
@ -75,8 +78,17 @@ public class PropertyOrFieldReference extends SpelNodeImpl { @@ -75,8 +78,17 @@ public class PropertyOrFieldReference extends SpelNodeImpl {
@Override
public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
return getValueInternal(state.getActiveContextObject(), state.getEvaluationContext(),
state.getConfiguration().isAutoGrowNullReferences());
TypedValue tv = getValueInternal(state.getActiveContextObject(), state.getEvaluationContext(), state.getConfiguration().isAutoGrowNullReferences());
if (cachedReadAccessor instanceof CompilablePropertyAccessor) {
CompilablePropertyAccessor accessor = (CompilablePropertyAccessor)cachedReadAccessor;
exitTypeDescriptor = CodeFlow.toDescriptor(accessor.getPropertyType());
}
return tv;
}
@Override
public String getExitDescriptor() {
return exitTypeDescriptor;
}
private TypedValue getValueInternal(TypedValue contextObject, EvaluationContext eContext,
@ -316,6 +328,23 @@ public class PropertyOrFieldReference extends SpelNodeImpl { @@ -316,6 +328,23 @@ public class PropertyOrFieldReference extends SpelNodeImpl {
resolvers.addAll(generalAccessors);
return resolvers;
}
@Override
public boolean isCompilable() {
if (this.cachedReadAccessor == null) {
return false;
}
if (this.cachedReadAccessor instanceof CompilablePropertyAccessor) {
return ((CompilablePropertyAccessor)this.cachedReadAccessor).isCompilable();
}
return false;
}
@Override
public void generateCode(MethodVisitor mv,CodeFlow codeflow) {
((CompilablePropertyAccessor)this.cachedReadAccessor).generateCode(this, mv, codeflow);
codeflow.pushDescriptor(exitTypeDescriptor);
}
private static class AccessorLValue implements ValueRef {
@ -338,7 +367,12 @@ public class PropertyOrFieldReference extends SpelNodeImpl { @@ -338,7 +367,12 @@ public class PropertyOrFieldReference extends SpelNodeImpl {
@Override
public TypedValue getValue() {
return this.ref.getValueInternal(this.contextObject, this.eContext, this.autoGrowNullReferences);
TypedValue value = this.ref.getValueInternal(this.contextObject, this.eContext, this.autoGrowNullReferences);
if (ref.cachedReadAccessor instanceof CompilablePropertyAccessor) {
CompilablePropertyAccessor accessor = (CompilablePropertyAccessor)this.ref.cachedReadAccessor;
this.ref.exitTypeDescriptor = CodeFlow.toDescriptor(accessor.getPropertyType());
}
return value;
}
@Override

17
spring-expression/src/main/java/org/springframework/expression/spel/ast/RealLiteral.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,7 +16,9 @@ @@ -16,7 +16,9 @@
package org.springframework.expression.spel.ast;
import org.springframework.asm.MethodVisitor;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.standard.CodeFlow;
/**
* Expression language AST node that represents a real literal.
@ -32,6 +34,7 @@ public class RealLiteral extends Literal { @@ -32,6 +34,7 @@ public class RealLiteral extends Literal {
public RealLiteral(String payload, int pos, double value) {
super(payload, pos);
this.value = new TypedValue(value);
this.exitTypeDescriptor = "D";
}
@ -40,4 +43,16 @@ public class RealLiteral extends Literal { @@ -40,4 +43,16 @@ public class RealLiteral extends Literal {
return this.value;
}
@Override
public boolean isCompilable() {
return true;
}
@Override
public void generateCode(MethodVisitor mv, CodeFlow codeflow) {
mv.visitLdcInsn(this.value.getValue());
codeflow.pushDescriptor(getExitDescriptor());
}
}

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

@ -16,6 +16,8 @@ @@ -16,6 +16,8 @@
package org.springframework.expression.spel.ast;
import org.springframework.asm.MethodVisitor;
import org.springframework.asm.Opcodes;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.TypedValue;
import org.springframework.expression.common.ExpressionUtils;
@ -23,6 +25,7 @@ import org.springframework.expression.spel.ExpressionState; @@ -23,6 +25,7 @@ import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.expression.spel.SpelMessage;
import org.springframework.expression.spel.SpelNode;
import org.springframework.expression.spel.standard.CodeFlow;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.util.Assert;
@ -33,7 +36,7 @@ import org.springframework.util.Assert; @@ -33,7 +36,7 @@ import org.springframework.util.Assert;
* @author Andy Clement
* @since 3.0
*/
public abstract class SpelNodeImpl implements SpelNode {
public abstract class SpelNodeImpl implements SpelNode, Opcodes {
private static SpelNodeImpl[] NO_CHILDREN = new SpelNodeImpl[0];
@ -44,6 +47,16 @@ public abstract class SpelNodeImpl implements SpelNode { @@ -44,6 +47,16 @@ public abstract class SpelNodeImpl implements SpelNode {
private SpelNodeImpl parent;
/**
* Indicates the type descriptor for the result of this expression node. This is
* set as soon as it is known. For a literal node it is known immediately. For
* a property access or method invocation it is known after one evaluation of
* that node.
* The descriptor is like the bytecode form but is slightly easier to work with. It
* does not include the trailing semicolon (for non array reference types). Some examples:
* Ljava/lang/String, I, [I
*/
protected String exitTypeDescriptor;
public SpelNodeImpl(int pos, SpelNodeImpl... operands) {
this.pos = pos;
@ -167,6 +180,33 @@ public abstract class SpelNodeImpl implements SpelNode { @@ -167,6 +180,33 @@ public abstract class SpelNodeImpl implements SpelNode {
throw new SpelEvaluationException(this.pos, SpelMessage.NOT_ASSIGNABLE, toStringAST());
}
/**
* Check whether a node can be compiled to bytecode. The reasoning in each node may
* be different but will typically involve checking whether the exit type descriptor
* of the node is known and any relevant child nodes are compilable.
*
* @return true if this node can be compiled to bytecode
*/
public boolean isCompilable() {
return false;
}
/**
* Generate the bytecode for this node into the supplied visitor. Context info about
* the current expression being compiled is available in the codeflow object. For
* example it will include information about the type of the object currently
* on the stack.
*
* @param mv the ASM MethodVisitor into which code should be generated
* @param codeflow a context object with info about what is on the stack
*/
public void generateCode(MethodVisitor mv, CodeFlow codeflow) {
throw new IllegalStateException(this.getClass().getName()+" has no generateCode(..) method");
}
public String getExitDescriptor() {
return this.exitTypeDescriptor;
}
public abstract TypedValue getValueInternal(ExpressionState expressionState) throws EvaluationException;

15
spring-expression/src/main/java/org/springframework/expression/spel/ast/StringLiteral.java

@ -16,7 +16,9 @@ @@ -16,7 +16,9 @@
package org.springframework.expression.spel.ast;
import org.springframework.asm.MethodVisitor;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.standard.CodeFlow;
/**
* Expression language AST node that represents a string literal.
@ -32,9 +34,9 @@ public class StringLiteral extends Literal { @@ -32,9 +34,9 @@ public class StringLiteral extends Literal {
public StringLiteral(String payload, int pos, String value) {
super(payload,pos);
// TODO should these have been skipped being created by the parser rules? or not?
value = value.substring(1, value.length() - 1);
this.value = new TypedValue(value.replaceAll("''", "'").replaceAll("\"\"", "\""));
this.exitTypeDescriptor = "Ljava/lang/String";
}
@ -47,5 +49,16 @@ public class StringLiteral extends Literal { @@ -47,5 +49,16 @@ public class StringLiteral extends Literal {
public String toString() {
return "'" + getLiteralValue().getValue() + "'";
}
@Override
public boolean isCompilable() {
return true;
}
@Override
public void generateCode(MethodVisitor mv, CodeFlow codeflow) {
mv.visitLdcInsn(this.value.getValue());
codeflow.pushDescriptor(getExitDescriptor());
}
}

75
spring-expression/src/main/java/org/springframework/expression/spel/ast/Ternary.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,11 +16,14 @@ @@ -16,11 +16,14 @@
package org.springframework.expression.spel.ast;
import org.springframework.asm.Label;
import org.springframework.asm.MethodVisitor;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.expression.spel.SpelMessage;
import org.springframework.expression.spel.standard.CodeFlow;
/**
* Represents a ternary expression, for example: "someCheck()?true:false".
@ -50,18 +53,82 @@ public class Ternary extends SpelNodeImpl { @@ -50,18 +53,82 @@ public class Ternary extends SpelNodeImpl {
throw new SpelEvaluationException(getChild(0).getStartPosition(),
SpelMessage.TYPE_CONVERSION_ERROR, "null", "boolean");
}
TypedValue result = null;
if (value.booleanValue()) {
return this.children[1].getValueInternal(state);
result = this.children[1].getValueInternal(state);
}
else {
return this.children[2].getValueInternal(state);
result = this.children[2].getValueInternal(state);
}
computeExitTypeDescriptor();
return result;
}
@Override
public String toStringAST() {
return new StringBuilder().append(getChild(0).toStringAST()).append(" ? ").append(getChild(1).toStringAST())
.append(" : ").append(getChild(2).toStringAST()).toString();
}
private void computeExitTypeDescriptor() {
if (exitTypeDescriptor == null && this.children[1].getExitDescriptor()!=null && this.children[2].getExitDescriptor()!=null) {
String leftDescriptor = this.children[1].exitTypeDescriptor;
String rightDescriptor = this.children[2].exitTypeDescriptor;
if (leftDescriptor.equals(rightDescriptor)) {
this.exitTypeDescriptor = leftDescriptor;
}
else if (leftDescriptor.equals("Ljava/lang/Object") && !CodeFlow.isPrimitive(rightDescriptor)) {
this.exitTypeDescriptor = rightDescriptor;
}
else if (rightDescriptor.equals("Ljava/lang/Object") && !CodeFlow.isPrimitive(leftDescriptor)) {
this.exitTypeDescriptor = leftDescriptor;
}
else {
// Use the easiest to compute common super type
this.exitTypeDescriptor = "Ljava/lang/Object";
}
}
}
@Override
public boolean isCompilable() {
SpelNodeImpl condition = this.children[0];
SpelNodeImpl left = this.children[1];
SpelNodeImpl right = this.children[2];
if (!(condition.isCompilable() && left.isCompilable() && right.isCompilable())) {
return false;
}
return CodeFlow.isBooleanCompatible(condition.exitTypeDescriptor) &&
left.getExitDescriptor()!=null &&
right.getExitDescriptor()!=null;
}
@Override
public void generateCode(MethodVisitor mv, CodeFlow codeflow) {
// May reach here without it computed if all elements are literals
computeExitTypeDescriptor();
codeflow.enterCompilationScope();
this.children[0].generateCode(mv, codeflow);
codeflow.exitCompilationScope();
Label elseTarget = new Label();
Label endOfIf = new Label();
mv.visitJumpInsn(IFEQ, elseTarget);
codeflow.enterCompilationScope();
this.children[1].generateCode(mv, codeflow);
if (!CodeFlow.isPrimitive(getExitDescriptor())) {
CodeFlow.insertBoxIfNecessary(mv, codeflow.lastDescriptor().charAt(0));
}
codeflow.exitCompilationScope();
mv.visitJumpInsn(GOTO, endOfIf);
mv.visitLabel(elseTarget);
codeflow.enterCompilationScope();
this.children[2].generateCode(mv, codeflow);
if (!CodeFlow.isPrimitive(getExitDescriptor())) {
CodeFlow.insertBoxIfNecessary(mv, codeflow.lastDescriptor().charAt(0));
}
codeflow.exitCompilationScope();
mv.visitLabel(endOfIf);
codeflow.pushDescriptor(getExitDescriptor());
}
}

45
spring-expression/src/main/java/org/springframework/expression/spel/ast/TypeReference.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -18,9 +18,12 @@ package org.springframework.expression.spel.ast; @@ -18,9 +18,12 @@ package org.springframework.expression.spel.ast;
import java.lang.reflect.Array;
import org.springframework.asm.MethodVisitor;
import org.springframework.asm.Type;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.standard.CodeFlow;
/**
* Represents a reference to a type, for example "T(String)" or "T(com.somewhere.Foo)"
@ -31,6 +34,7 @@ public class TypeReference extends SpelNodeImpl { @@ -31,6 +34,7 @@ public class TypeReference extends SpelNodeImpl {
private final int dimensions;
private transient Class<?> type;
public TypeReference(int pos, SpelNodeImpl qualifiedId) {
this(pos,qualifiedId,0);
@ -52,11 +56,15 @@ public class TypeReference extends SpelNodeImpl { @@ -52,11 +56,15 @@ public class TypeReference extends SpelNodeImpl {
// it is a primitive type
Class<?> clazz = tc.getType();
clazz = makeArrayIfNecessary(clazz);
this.exitTypeDescriptor = "Ljava/lang/Class";
this.type = clazz;
return new TypedValue(clazz);
}
}
Class<?> clazz = state.findType(typename);
clazz = makeArrayIfNecessary(clazz);
this.exitTypeDescriptor = "Ljava/lang/Class";
this.type = clazz;
return new TypedValue(clazz);
}
@ -81,5 +89,40 @@ public class TypeReference extends SpelNodeImpl { @@ -81,5 +89,40 @@ public class TypeReference extends SpelNodeImpl {
sb.append(")");
return sb.toString();
}
@Override
public boolean isCompilable() {
return this.exitTypeDescriptor != null;
}
@Override
public void generateCode(MethodVisitor mv, CodeFlow codeflow) {
// TODO Future optimization - if followed by a static method call, skip generating code here
if (type.isPrimitive()) {
if (type == Integer.TYPE) {
mv.visitFieldInsn(GETSTATIC, "java/lang/Integer", "TYPE", "Ljava/lang/Class;");
} else if (type == Boolean.TYPE) {
mv.visitFieldInsn(GETSTATIC, "java/lang/Boolean", "TYPE", "Ljava/lang/Class;");
} else if (type == Byte.TYPE) {
mv.visitFieldInsn(GETSTATIC, "java/lang/Byte", "TYPE", "Ljava/lang/Class;");
} else if (type == Short.TYPE) {
mv.visitFieldInsn(GETSTATIC, "java/lang/Short", "TYPE", "Ljava/lang/Class;");
} else if (type == Double.TYPE) {
mv.visitFieldInsn(GETSTATIC, "java/lang/Double", "TYPE", "Ljava/lang/Class;");
} else if (type == Character.TYPE) {
mv.visitFieldInsn(GETSTATIC, "java/lang/Character", "TYPE", "Ljava/lang/Class;");
} else if (type == Float.TYPE) {
mv.visitFieldInsn(GETSTATIC, "java/lang/Float", "TYPE", "Ljava/lang/Class;");
} else if (type == Long.TYPE) {
mv.visitFieldInsn(GETSTATIC, "java/lang/Long", "TYPE", "Ljava/lang/Class;");
} else if (type == Boolean.TYPE) {
mv.visitFieldInsn(GETSTATIC, "java/lang/Boolean", "TYPE", "Ljava/lang/Class;");
}
}
else {
mv.visitLdcInsn(Type.getType(type));
}
codeflow.pushDescriptor(getExitDescriptor());
}
}

27
spring-expression/src/main/java/org/springframework/expression/spel/ast/VariableReference.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,10 +16,12 @@ @@ -16,10 +16,12 @@
package org.springframework.expression.spel.ast;
import org.springframework.asm.MethodVisitor;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.expression.spel.standard.CodeFlow;
/**
* Represents a variable reference, eg. #someVar. Note this is different to a *local*
@ -64,9 +66,12 @@ public class VariableReference extends SpelNodeImpl { @@ -64,9 +66,12 @@ public class VariableReference extends SpelNodeImpl {
return state.getActiveContextObject();
}
if (this.name.equals(ROOT)) {
return state.getRootContextObject();
TypedValue result = state.getRootContextObject();
this.exitTypeDescriptor = CodeFlow.toDescriptorFromObject(result.getValue());
return result;
}
TypedValue result = state.lookupVariable(this.name);
this.exitTypeDescriptor = CodeFlow.toDescriptorFromObject(result.getValue());
// a null value will mean either the value was null or the variable was not found
return result;
}
@ -120,5 +125,23 @@ public class VariableReference extends SpelNodeImpl { @@ -120,5 +125,23 @@ public class VariableReference extends SpelNodeImpl {
}
}
@Override
public boolean isCompilable() {
return getExitDescriptor()!=null;
}
@Override
public void generateCode(MethodVisitor mv, CodeFlow codeflow) {
if (this.name.equals(ROOT)) {
mv.visitVarInsn(ALOAD,1);
} else {
mv.visitVarInsn(ALOAD, 2);
mv.visitLdcInsn(name);
mv.visitMethodInsn(INVOKEINTERFACE, "org/springframework/expression/EvaluationContext", "lookupVariable", "(Ljava/lang/String;)Ljava/lang/Object;",true);
}
CodeFlow.insertCheckCast(mv,getExitDescriptor());
codeflow.pushDescriptor(getExitDescriptor());
}
}

603
spring-expression/src/main/java/org/springframework/expression/spel/standard/CodeFlow.java

@ -0,0 +1,603 @@ @@ -0,0 +1,603 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.expression.spel.standard;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Stack;
import org.springframework.asm.MethodVisitor;
import org.springframework.asm.Opcodes;
import org.springframework.util.Assert;
/**
* Records intermediate compilation state as the bytecode is generated for a parsed
* expression. Also contains bytecode generation helper functions.
*
* @author Andy Clement
* @since 4.1
*/
public class CodeFlow implements Opcodes {
/**
* Record the type of what is on top of the bytecode stack (i.e. the type of the
* output from the previous expression component). New scopes are used to evaluate
* sub-expressions like the expressions for the argument values in a method invocation
* expression.
*/
private Stack<ArrayList<String>> compilationScopes;
public CodeFlow() {
compilationScopes = new Stack<ArrayList<String>>();
compilationScopes.add(new ArrayList<String>());
}
/**
* Push the byte code to load the target (i.e. what was passed as the first argument
* to CompiledExpression.getValue(target, context))
* @param mv the visitor into which the load instruction should be inserted
*/
public void loadTarget(MethodVisitor mv) {
mv.visitVarInsn(ALOAD, 1);
}
/**
* Record the descriptor for the most recently evaluated expression element.
* @param descriptor type descriptor for most recently evaluated element
*/
public void pushDescriptor(String descriptor) {
Assert.notNull(descriptor);
compilationScopes.peek().add(descriptor);
}
/**
* Enter a new compilation scope, usually due to nested expression evaluation. For
* example when the arguments for a method invocation expression are being evaluated,
* each argument will be evaluated in a new scope.
*/
public void enterCompilationScope() {
compilationScopes.push(new ArrayList<String>());
}
/**
* Exit a compilation scope, usually after a nested expression has been evaluated. For
* example after an argument for a method invocation has been evaluated this method
* returns us to the previous (outer) scope.
*/
public void exitCompilationScope() {
compilationScopes.pop();
}
/**
* @return the descriptor for the item currently on top of the stack (in the current
* scope)
*/
public String lastDescriptor() {
if (compilationScopes.peek().size()==0) {
return null;
}
return compilationScopes.peek().get(compilationScopes.peek().size()-1);
}
/**
* If the codeflow shows the last expression evaluated to java.lang.Boolean then
* insert the necessary instructions to unbox that to a boolean primitive.
* @param mv the visitor into which new instructions should be inserted
*/
public void unboxBooleanIfNecessary(MethodVisitor mv) {
if (lastDescriptor().equals("Ljava/lang/Boolean")) {
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Boolean", "booleanValue", "()Z", false);
}
}
/**
* Insert any necessary cast and value call to convert from a boxed type to a
* primitive value
* @param mv the method visitor into which instructions should be inserted
* @param ch the primitive type desired as output
* @param isObject indicates whether the type on the stack is being thought of as
* Object (and so requires a cast)
*/
public static void insertUnboxInsns(MethodVisitor mv, char ch, boolean isObject) {
switch (ch) {
case 'I':
if (isObject) {
mv.visitTypeInsn(CHECKCAST, "java/lang/Integer");
}
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Integer", "intValue", "()I", false);
break;
case 'Z':
if (isObject) {
mv.visitTypeInsn(CHECKCAST, "java/lang/Boolean");
}
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Boolean", "booleanValue", "()Z", false);
break;
case 'B':
if (isObject) {
mv.visitTypeInsn(CHECKCAST, "java/lang/Byte");
}
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Byte", "byteValue", "()B", false);
break;
case 'C':
if (isObject) {
mv.visitTypeInsn(CHECKCAST, "java/lang/Character");
}
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Character", "charValue", "()C", false);
break;
case 'D':
if (isObject) {
mv.visitTypeInsn(CHECKCAST, "java/lang/Double");
}
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Double", "doubleValue", "()D", false);
break;
case 'S':
if (isObject) {
mv.visitTypeInsn(CHECKCAST, "java/lang/Short");
}
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Short", "shortValue", "()S", false);
break;
case 'F':
if (isObject) {
mv.visitTypeInsn(CHECKCAST, "java/lang/Float");
}
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Float", "floatValue", "()F", false);
break;
case 'J':
if (isObject) {
mv.visitTypeInsn(CHECKCAST, "java/lang/Long");
}
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Long", "longValue", "()J", false);
break;
default:
throw new IllegalArgumentException("Unboxing should not be attempted for descriptor '" + ch + "'");
}
}
/**
* Create the JVM signature descriptor for a method. This consists of the descriptors
* for the constructor parameters surrounded with parentheses, followed by the
* descriptor for the return type. Note the descriptors here are JVM descriptors,
* unlike the other descriptor forms the compiler is using which do not include the
* trailing semicolon.
* @param method the method
* @return a String signature descriptor (e.g. "(ILjava/lang/String;)V")
*/
public static String createSignatureDescriptor(Method method) {
Class<?>[] params = method.getParameterTypes();
StringBuilder s = new StringBuilder();
s.append("(");
for (int i = 0, max = params.length; i < max; i++) {
s.append(toJVMDescriptor(params[i]));
}
s.append(")");
s.append(toJVMDescriptor(method.getReturnType()));
return s.toString();
}
/**
* Create the JVM signature descriptor for a constructor. This consists of the
* descriptors for the constructor parameters surrounded with parentheses. Note the
* descriptors here are JVM descriptors, unlike the other descriptor forms the
* compiler is using which do not include the trailing semicolon.
* @param ctor the constructor
* @return a String signature descriptor (e.g. "(ILjava/lang/String;)")
*/
public static String createSignatureDescriptor(Constructor<?> ctor) {
Class<?>[] params = ctor.getParameterTypes();
StringBuilder s = new StringBuilder();
s.append("(");
for (int i = 0, max = params.length; i < max; i++) {
s.append(toJVMDescriptor(params[i]));
}
s.append(")V");
return s.toString();
}
/**
* Determine the JVM descriptor for a specified class. Unlike the other descriptors
* used in the compilation process, this is the one the JVM wants, so this one
* includes any necessary trailing semicolon (e.g. Ljava/lang/String; rather than
* Ljava/lang/String)
*
* @param clazz a class
* @return the JVM descriptor for the class
*/
public static String toJVMDescriptor(Class<?> clazz) {
StringBuilder s= new StringBuilder();
if (clazz.isArray()) {
while (clazz.isArray()) {
s.append("[");
clazz = clazz.getComponentType();
}
}
if (clazz.isPrimitive()) {
if (clazz == Void.TYPE) {
s.append('V');
}
else if (clazz == Integer.TYPE) {
s.append('I');
}
else if (clazz == Boolean.TYPE) {
s.append('Z');
}
else if (clazz == Character.TYPE) {
s.append('C');
}
else if (clazz == Long.TYPE) {
s.append('J');
}
else if (clazz == Double.TYPE) {
s.append('D');
}
else if (clazz == Float.TYPE) {
s.append('F');
}
else if (clazz == Byte.TYPE) {
s.append('B');
}
else if (clazz == Short.TYPE) {
s.append('S');
}
} else {
s.append("L");
s.append(clazz.getName().replace('.', '/'));
s.append(";");
}
return s.toString();
}
/**
* Determine the descriptor for an object instance (or null).
* @param value an object (possibly null)
* @return the type descriptor for the object (descriptor is "Ljava/lang/Object" for
* null value)
*/
public static String toDescriptorFromObject(Object value) {
if (value == null) {
return "Ljava/lang/Object";
} else {
return toDescriptor(value.getClass());
}
}
/**
* @param descriptor type descriptor
* @return true if the descriptor is for a boolean primitive or boolean reference type
*/
public static boolean isBooleanCompatible(String descriptor) {
return descriptor != null
&& (descriptor.equals("Z") || descriptor.equals("Ljava/lang/Boolean"));
}
/**
* @param descriptor type descriptor
* @return true if the descriptor is for a primitive type
*/
public static boolean isPrimitive(String descriptor) {
return descriptor!=null && descriptor.length()==1;
}
/**
* @param descriptor the descriptor for a possible primitive array
* @return true if the descriptor is for a primitive array (e.g. "[[I")
*/
public static boolean isPrimitiveArray(String descriptor) {
boolean primitive = true;
for (int i = 0, max = descriptor.length(); i < max; i++) {
char ch = descriptor.charAt(i);
if (ch == '[') {
continue;
}
primitive = (ch != 'L');
break;
}
return primitive;
}
/**
* Determine if boxing/unboxing can get from one type to the other. Assumes at least
* one of the types is in boxed form (i.e. single char descriptor).
*
* @return true if it is possible to get (via boxing) from one descriptor to the other
*/
public static boolean areBoxingCompatible(String desc1, String desc2) {
if (desc1.equals(desc2)) {
return true;
}
if (desc1.length()==1) {
if (desc1.equals("D")) {
return desc2.equals("Ljava/lang/Double");
}
else if (desc1.equals("F")) {
return desc2.equals("Ljava/lang/Float");
}
else if (desc1.equals("J")) {
return desc2.equals("Ljava/lang/Long");
}
else if (desc1.equals("I")) {
return desc2.equals("Ljava/lang/Integer");
}
else if (desc1.equals("Z")) {
return desc2.equals("Ljava/lang/Boolean");
}
}
else if (desc2.length()==1) {
if (desc2.equals("D")) {
return desc1.equals("Ljava/lang/Double");
}
else if (desc2.equals("F")) {
return desc1.equals("Ljava/lang/Float");
}
else if (desc2.equals("J")) {
return desc1.equals("Ljava/lang/Long");
}
else if (desc2.equals("I")) {
return desc1.equals("Ljava/lang/Integer");
}
else if (desc2.equals("Z")) {
return desc1.equals("Ljava/lang/Boolean");
}
}
return false;
}
/**
* Determine if the supplied descriptor is for a supported number type or boolean. The
* compilation process only (currently) supports certain number types. These are
* double, float, long and int.
* @param descriptor the descriptor for a type
* @return true if the descriptor is for a supported numeric type or boolean
*/
public static boolean isPrimitiveOrUnboxableSupportedNumberOrBoolean(String descriptor) {
if (descriptor==null) {
return false;
}
if (descriptor.length()==1) {
return "DFJZI".indexOf(descriptor.charAt(0))!=-1;
}
if (descriptor.startsWith("Ljava/lang/")) {
if (descriptor.equals("Ljava/lang/Double") || descriptor.equals("Ljava/lang/Integer") ||
descriptor.equals("Ljava/lang/Float") || descriptor.equals("Ljava/lang/Long") ||
descriptor.equals("Ljava/lang/Boolean")) {
return true;
}
}
return false;
}
/**
* Determine if the supplied descriptor is for a supported number. The compilation
* process only (currently) supports certain number types. These are double, float,
* long and int.
* @param descriptor the descriptor for a type
* @return true if the descriptor is for a supported numeric type
*/
public static boolean isPrimitiveOrUnboxableSupportedNumber(String descriptor) {
if (descriptor==null) {
return false;
}
if (descriptor.length()==1) {
return "DFJI".indexOf(descriptor.charAt(0))!=-1;
}
if (descriptor.startsWith("Ljava/lang/")) {
if (descriptor.equals("Ljava/lang/Double") || descriptor.equals("Ljava/lang/Integer") ||
descriptor.equals("Ljava/lang/Float") || descriptor.equals("Ljava/lang/Long")) {
return true;
}
}
return false;
}
/**
* @param descriptor a descriptor for a type that should have a primitive
* representation
* @return the single character descriptor for a primitive input descriptor
*/
public static char toPrimitiveTargetDesc(String descriptor) {
if (descriptor.length()==1) {
return descriptor.charAt(0);
}
if (descriptor.equals("Ljava/lang/Double")) {
return 'D';
}
else if (descriptor.equals("Ljava/lang/Integer")) {
return 'I';
}
else if (descriptor.equals("Ljava/lang/Float")) {
return 'F';
}
else if (descriptor.equals("Ljava/lang/Long")) {
return 'J';
}
else if (descriptor.equals("Ljava/lang/Boolean")) {
return 'Z';
}
else {
throw new IllegalStateException("No primitive for '"+descriptor+"'");
}
}
/**
* Insert the appropriate CHECKCAST instruction for the supplied descriptor.
* @param mv the target visitor into which the instruction should be inserted
* @param descriptor the descriptor of the type to cast to
*/
public static void insertCheckCast(MethodVisitor mv, String descriptor) {
if (descriptor.length()!=1) {
if (descriptor.charAt(0)=='[') {
if (CodeFlow.isPrimitiveArray(descriptor)) {
mv.visitTypeInsn(CHECKCAST, descriptor);
}
else {
mv.visitTypeInsn(CHECKCAST, descriptor+";");
}
}
else {
// This is chopping off the 'L' to leave us with "java/lang/String"
mv.visitTypeInsn(CHECKCAST, descriptor.substring(1));
}
}
}
/**
* Determine the appropriate boxing instruction for a specific type (if it needs
* boxing) and insert the instruction into the supplied visitor.
* @param mv the target visitor for the new instructions
* @param descriptor the descriptor of a type that may or may not need boxing
*/
public static void insertBoxIfNecessary(MethodVisitor mv, String descriptor) {
if (descriptor.length() == 1) {
insertBoxIfNecessary(mv, descriptor.charAt(0));
}
}
/**
* Determine the appropriate boxing instruction for a specific type (if it needs
* boxing) and insert the instruction into the supplied visitor.
* @param mv the target visitor for the new instructions
* @param ch the descriptor of the type that might need boxing
*/
public static void insertBoxIfNecessary(MethodVisitor mv, char ch) {
switch (ch) {
case 'I':
mv.visitMethodInsn(INVOKESTATIC, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;", false);
break;
case 'F':
mv.visitMethodInsn(INVOKESTATIC, "java/lang/Float", "valueOf", "(F)Ljava/lang/Float;", false);
break;
case 'S':
mv.visitMethodInsn(INVOKESTATIC, "java/lang/Short", "valueOf", "(S)Ljava/lang/Short;", false);
break;
case 'Z':
mv.visitMethodInsn(INVOKESTATIC, "java/lang/Boolean", "valueOf", "(Z)Ljava/lang/Boolean;", false);
break;
case 'J':
mv.visitMethodInsn(INVOKESTATIC, "java/lang/Long", "valueOf", "(J)Ljava/lang/Long;", false);
break;
case 'D':
mv.visitMethodInsn(INVOKESTATIC, "java/lang/Double", "valueOf", "(D)Ljava/lang/Double;", false);
break;
case 'C':
mv.visitMethodInsn(INVOKESTATIC, "java/lang/Character", "valueOf", "(C)Ljava/lang/Character;", false);
break;
case 'B':
mv.visitMethodInsn(INVOKESTATIC, "java/lang/Byte", "valueOf", "(B)Ljava/lang/Byte;", false);
break;
case 'L':
case 'V':
case '[':
// no box needed
break;
default:
throw new IllegalArgumentException("Boxing should not be attempted for descriptor '" + ch + "'");
}
}
/**
* Deduce the descriptor for a type. Descriptors are like JVM type names but missing
* the trailing ';' so for Object the descriptor is "Ljava/lang/Object" for int it is
* "I".
* @param type the type (may be primitive) for which to determine the descriptor
* @return the descriptor
*/
public static String toDescriptor(Class<?> type) {
String name = type.getName();
if (type.isPrimitive()) {
switch (name.length()) {
case 3:
return "I";
case 4:
if (name.equals("long")) {
return "J";
}
else if (name.equals("char")) {
return "C";
}
else if (name.equals("byte")) {
return "B";
}
else if (name.equals("void")) {
return "V";
}
break;
case 5:
if (name.equals("float")) {
return "F";
}
else if (name.equals("short")) {
return "S";
}
break;
case 6:
if (name.equals("double")) {
return "D";
}
break;
case 7:
if (name.equals("boolean")) {
return "Z";
}
break;
}
}
else {
if (name.charAt(0) != '[') {
return new StringBuilder("L").append(type.getName().replace('.', '/')).toString();
}
else {
if (name.endsWith(";")) {
return name.substring(0, name.length() - 1).replace('.', '/');
}
else {
return name; // array has primitive component type
}
}
}
return null;
}
/**
* Create an array of descriptors representing the parameter types for the supplied
* method. Returns a zero sized array if there are no parameters.
* @param method a Method
* @return a String array of descriptors, one entry for each method parameter
*/
public static String[] toParamDescriptors(Method method) {
return toDescriptors(method.getParameterTypes());
}
/**
* Create an array of descriptors representing the parameter types for the supplied
* constructor. Returns a zero sized array if there are no parameters.
* @param ctor a Constructor
* @return a String array of descriptors, one entry for each constructor parameter
*/
public static String[] toParamDescriptors(Constructor<?> ctor) {
return toDescriptors(ctor.getParameterTypes());
}
private static String[] toDescriptors(Class<?>[] types) {
int typesCount = types.length;
String[] descriptors = new String[typesCount];
for (int p = 0; p < typesCount; p++) {
descriptors[p] = CodeFlow.toDescriptor(types[p]);
}
return descriptors;
}
}

277
spring-expression/src/main/java/org/springframework/expression/spel/standard/SpelCompiler.java

@ -0,0 +1,277 @@ @@ -0,0 +1,277 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.expression.spel.standard;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Collections;
import java.util.Map;
import java.util.WeakHashMap;
import org.springframework.asm.ClassWriter;
import org.springframework.asm.MethodVisitor;
import org.springframework.asm.Opcodes;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.CompiledExpression;
import org.springframework.expression.spel.SpelParserConfiguration;
import org.springframework.expression.spel.ast.SpelNodeImpl;
import org.springframework.util.ClassUtils;
/**
* A SpelCompiler will take a regular parsed expression and create (and load) a class
* containing byte code that does the same thing as that expression. The compiled form of
* an expression will evaluate far faster than the interpreted form.
* <p>
* The SpelCompiler is not currently handling all expression types but covers many of the
* common cases. The framework is extensible to cover more cases in the future. For
* absolute maximum speed there is *no checking* in the compiled code. The compiled
* version of the expression uses information learned during interpreted runs of the
* expression when it generates the byte code. For example if it knows that a particular
* property dereference always seems to return a Map then it will generate byte code that
* expects the result of the property dereference to be a Map. This ensures maximal
* performance but should the dereference result in something other than a map, the
* compiled expression will fail - like a ClassCastException would occur if passing data
* of an unexpected type in a regular Java program.
* <p>
* Due to the lack of checking there are likely some expressions that should never be
* compiled, for example if an expression is continuously dealing with different types of
* data. Due to these cases the compiler is something that must be selectively turned on
* for an associated SpelExpressionParser (through the {@link SpelParserConfiguration}
* object), it is not on by default.
* <p>
* Individual expressions can be compiled by calling
* <tt>SpelCompiler.compile(expression)</tt>.
*
* @author Andy Clement
* @since 4.1
*/
public class SpelCompiler implements Opcodes {
// Default number of times to interpret an expression before compiling it
private static int DEFAULT_INTERPRETED_COUNT_THRESHOLD = 100;
// Once an expression is evaluated the threshold number of times, it will be a candidate for compilation
public static int interpretedCountThreshold = DEFAULT_INTERPRETED_COUNT_THRESHOLD;
// Useful for debugging
public static final boolean verbose = false;
// A compiler is created for each classloader, it manages a child class loader of that
// classloader and the child is used to load the compiled expressions.
private static Map<ClassLoader,SpelCompiler> compilers = Collections.synchronizedMap(new WeakHashMap<ClassLoader,SpelCompiler>());
// The child classloader used to load the compiled expression classes
private ChildClassLoader ccl;
// counter suffix for generated classes within this SpelCompiler instance
private int suffixId;
/**
* Factory method for compiler instances. The returned SpelCompiler will
* attach a class loader as the child of the default class loader and this
* child will be used to load compiled expressions.
*
* @return a SpelCompiler instance
*/
public static SpelCompiler getCompiler() {
ClassLoader classloader = ClassUtils.getDefaultClassLoader();
synchronized (compilers) {
SpelCompiler compiler = compilers.get(classloader);
if (compiler == null) {
compiler = new SpelCompiler(classloader);
compilers.put(classloader,compiler);
}
return compiler;
}
}
private SpelCompiler(ClassLoader classloader) {
this.ccl = new ChildClassLoader(classloader);
this.suffixId = 1;
}
/**
* Attempt compilation of the supplied expression. A check is
* made to see if it is compilable before compilation proceeds. The
* check involves visiting all the nodes in the expression Ast and
* ensuring enough state is known about them that bytecode can
* be generated for them.
* @param expression the expression to compile
* @return an instance of the class implementing the compiled expression, or null
* if compilation is not possible
*/
public CompiledExpression compile(SpelNodeImpl expression) {
if (expression.isCompilable()) {
if (verbose) {
System.out.println("SpEL: compiling " + expression.toStringAST());
}
Class<? extends CompiledExpression> clazz = createExpressionClass(expression);
try {
CompiledExpression instance = clazz.newInstance();
return instance;
}
catch (InstantiationException ie) {
ie.printStackTrace();
}
catch (IllegalAccessException iae) {
iae.printStackTrace();
}
}
else {
if (verbose) {
System.out.println("SpEL: unable to compile " + expression.toStringAST());
}
}
return null;
}
private synchronized int getNextSuffix() {
return suffixId++;
}
/**
* Generate the class that encapsulates the compiled expression and define it. The
* generated class will be a subtype of CompiledExpression.
* @param expressionToCompile the expression to be compiled
*/
@SuppressWarnings("unchecked")
private Class<? extends CompiledExpression> createExpressionClass(SpelNodeImpl expressionToCompile) {
// Create class outline 'spel/ExNNN extends org.springframework.expression.spel.CompiledExpression'
String clazzName = "spel/Ex" + getNextSuffix();
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS|ClassWriter.COMPUTE_FRAMES);
cw.visit(V1_5, ACC_PUBLIC, clazzName, null,
"org/springframework/expression/spel/CompiledExpression", null);
// Create default constructor
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKESPECIAL, "org/springframework/expression/spel/CompiledExpression", "<init>", "()V",false);
mv.visitInsn(RETURN);
mv.visitMaxs(1, 1);
mv.visitEnd();
// Create getValue() method
mv = cw.visitMethod(ACC_PUBLIC, "getValue", "(Ljava/lang/Object;Lorg/springframework/expression/EvaluationContext;)Ljava/lang/Object;", null,
new String[]{"org/springframework/expression/EvaluationException"});
mv.visitCode();
CodeFlow codeflow = new CodeFlow();
// Ask the expression Ast to generate the body of the method
expressionToCompile.generateCode(mv,codeflow);
CodeFlow.insertBoxIfNecessary(mv,codeflow.lastDescriptor());
if (codeflow.lastDescriptor() == "V") {
mv.visitInsn(ACONST_NULL);
}
mv.visitInsn(ARETURN);
mv.visitMaxs(0,0); // not supplied due to COMPUTE_MAXS
mv.visitEnd();
cw.visitEnd();
byte[] data = cw.toByteArray();
// TODO need to make this conditionally occur based on a debug flag
// dump(expressionToCompile.toStringAST(), clazzName, data);
Class<? extends CompiledExpression> clazz = (Class<? extends CompiledExpression>) ccl.defineClass(clazzName.replaceAll("/","."),data);
return clazz;
}
/**
* For debugging purposes, dump the specified byte code into a file on the disk. Not
* yet hooked in, needs conditionally calling based on a sys prop.
*
* @param expressionText the text of the expression compiled
* @param name the name of the class being used for the compiled expression
* @param bytecode the bytecode for the generated class
*/
@SuppressWarnings("unused")
private static void dump(String expressionText, String name, byte[] bytecode) {
name = name.replace('.', '/');
String dir = "";
if (name.indexOf('/') != -1) {
dir = name.substring(0, name.lastIndexOf('/'));
}
String dumplocation = null;
try {
File tempfile = null;
tempfile = File.createTempFile("tmp", null);
tempfile.delete();
File f = new File(tempfile, dir);
f.mkdirs();
dumplocation = tempfile + File.separator + name + ".class";
System.out.println("Expression '" + expressionText + "' compiled code dumped to "
+ dumplocation);
f = new File(dumplocation);
FileOutputStream fos = new FileOutputStream(f);
fos.write(bytecode);
fos.flush();
fos.close();
}
catch (IOException ioe) {
throw new IllegalStateException("Unexpected problem dumping class "
+ name + " into " + dumplocation, ioe);
}
}
/**
* A ChildClassLoader will load the generated compiled expression classes
*/
public static class ChildClassLoader extends URLClassLoader {
private static URL[] NO_URLS = new URL[0];
public ChildClassLoader(ClassLoader classloader) {
super(NO_URLS, classloader);
}
public Class<?> defineClass(String name, byte[] bytes) {
return super.defineClass(name, bytes, 0, bytes.length);
}
}
/**
* Request that an attempt is made to compile the specified expression. It may fail if
* components of the expression are not suitable for compilation or the data types
* involved are not suitable for compilation. Used for testing.
* @return true if the expression was successfully compiled
*/
public static boolean compile(Expression expression) {
if (expression instanceof SpelExpression) {
SpelExpression spelExpression = (SpelExpression)expression;
return spelExpression.compileExpression();
}
return false;
}
/**
* Request to revert to the interpreter for expression evaluation. Any compiled form
* is discarded but can be recreated by later recompiling again.
* @param expression the expression
*/
public static void revertToInterpreted(Expression expression) {
if (expression instanceof SpelExpression) {
SpelExpression spelExpression = (SpelExpression)expression;
spelExpression.revertToInterpreted();
}
}
}

254
spring-expression/src/main/java/org/springframework/expression/spel/standard/SpelExpression.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -22,7 +22,11 @@ import org.springframework.expression.EvaluationException; @@ -22,7 +22,11 @@ import org.springframework.expression.EvaluationException;
import org.springframework.expression.Expression;
import org.springframework.expression.TypedValue;
import org.springframework.expression.common.ExpressionUtils;
import org.springframework.expression.spel.CompiledExpression;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.SpelCompilerMode;
import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.expression.spel.SpelMessage;
import org.springframework.expression.spel.SpelNode;
import org.springframework.expression.spel.SpelParserConfiguration;
import org.springframework.expression.spel.ast.SpelNodeImpl;
@ -42,13 +46,24 @@ public class SpelExpression implements Expression { @@ -42,13 +46,24 @@ public class SpelExpression implements Expression {
private final String expression;
private final SpelNodeImpl ast;
// Holds the compiled form of the expression (if it has been compiled)
private CompiledExpression compiledAst;
private SpelNodeImpl ast;
private final SpelParserConfiguration configuration;
// the default context is used if no override is supplied by the user
private EvaluationContext defaultContext;
// Count of many times as the expression been interpreted - can trigger compilation
// when certain limit reached
private int interpretedCount = 0;
// The number of times compilation was attempted and failed - enables us to eventually
// give up trying to compile it when it just doesn't seem to be possible.
private int failedAttempts = 0;
/**
* Construct an expression, only used by the parser.
@ -64,51 +79,214 @@ public class SpelExpression implements Expression { @@ -64,51 +79,214 @@ public class SpelExpression implements Expression {
@Override
public Object getValue() throws EvaluationException {
Object result = null;
if (compiledAst != null) {
try {
return this.compiledAst.getValue(null,null);
} catch (Throwable t) {
// If running in mixed mode, revert to interpreted
if (this.configuration.getCompilerMode() == SpelCompilerMode.mixed) {
interpretedCount = 0;
compiledAst = null;
}
else {
// Running in SpelCompilerMode.immediate mode - propagate exception to caller
throw new SpelEvaluationException(t,SpelMessage.EXCEPTION_RUNNING_COMPILED_EXPRESSION);
}
}
}
ExpressionState expressionState = new ExpressionState(getEvaluationContext(), this.configuration);
return this.ast.getValue(expressionState);
result = this.ast.getValue(expressionState);
checkCompile(expressionState);
return result;
}
@Override
public Object getValue(Object rootObject) throws EvaluationException {
Object result = null;
if (compiledAst!=null) {
try {
return this.compiledAst.getValue(rootObject,null);
} catch (Throwable t) {
// If running in mixed mode, revert to interpreted
if (this.configuration.getCompilerMode() == SpelCompilerMode.mixed) {
interpretedCount = 0;
compiledAst = null;
}
else {
// Running in SpelCompilerMode.immediate mode - propagate exception to caller
throw new SpelEvaluationException(t,SpelMessage.EXCEPTION_RUNNING_COMPILED_EXPRESSION);
}
}
}
ExpressionState expressionState = new ExpressionState(getEvaluationContext(), toTypedValue(rootObject), this.configuration);
return this.ast.getValue(expressionState);
result = this.ast.getValue(expressionState);
checkCompile(expressionState);
return result;
}
@SuppressWarnings("unchecked")
@Override
public <T> T getValue(Class<T> expectedResultType) throws EvaluationException {
if (compiledAst!=null) {
try {
Object result = this.compiledAst.getValue(null,null);
if (expectedResultType == null) {
return (T)result;
} else {
return ExpressionUtils.convertTypedValue(getEvaluationContext(), new TypedValue(result), expectedResultType);
}
} catch (Throwable t) {
// If running in mixed mode, revert to interpreted
if (this.configuration.getCompilerMode() == SpelCompilerMode.mixed) {
interpretedCount = 0;
compiledAst = null;
}
else {
// Running in SpelCompilerMode.immediate mode - propagate exception to caller
throw new SpelEvaluationException(t,SpelMessage.EXCEPTION_RUNNING_COMPILED_EXPRESSION);
}
}
}
ExpressionState expressionState = new ExpressionState(getEvaluationContext(), this.configuration);
TypedValue typedResultValue = this.ast.getTypedValue(expressionState);
checkCompile(expressionState);
return ExpressionUtils.convertTypedValue(expressionState.getEvaluationContext(), typedResultValue, expectedResultType);
}
@SuppressWarnings("unchecked")
@Override
public <T> T getValue(Object rootObject, Class<T> expectedResultType) throws EvaluationException {
if (compiledAst!=null) {
try {
Object result = this.compiledAst.getValue(rootObject,null);
if (expectedResultType == null) {
return (T)result;
} else {
return ExpressionUtils.convertTypedValue(getEvaluationContext(), new TypedValue(result), expectedResultType);
}
} catch (Throwable t) {
// If running in mixed mode, revert to interpreted
if (this.configuration.getCompilerMode() == SpelCompilerMode.mixed) {
interpretedCount = 0;
compiledAst = null;
}
else {
// Running in SpelCompilerMode.immediate mode - propagate exception to caller
throw new SpelEvaluationException(t,SpelMessage.EXCEPTION_RUNNING_COMPILED_EXPRESSION);
}
}
}
ExpressionState expressionState = new ExpressionState(getEvaluationContext(), toTypedValue(rootObject), this.configuration);
TypedValue typedResultValue = this.ast.getTypedValue(expressionState);
checkCompile(expressionState);
return ExpressionUtils.convertTypedValue(expressionState.getEvaluationContext(), typedResultValue, expectedResultType);
}
@Override
public Object getValue(EvaluationContext context) throws EvaluationException {
Assert.notNull(context, "The EvaluationContext is required");
return this.ast.getValue(new ExpressionState(context, this.configuration));
if (compiledAst!= null) {
try {
Object result = this.compiledAst.getValue(null,context);
return result;
} catch (Throwable t) {
// If running in mixed mode, revert to interpreted
if (this.configuration.getCompilerMode() == SpelCompilerMode.mixed) {
interpretedCount = 0;
compiledAst = null;
}
else {
// Running in SpelCompilerMode.immediate mode - propagate exception to caller
throw new SpelEvaluationException(t,SpelMessage.EXCEPTION_RUNNING_COMPILED_EXPRESSION);
}
}
}
ExpressionState expressionState = new ExpressionState(context, this.configuration);
Object result = this.ast.getValue(expressionState);
checkCompile(expressionState);
return result;
}
@Override
public Object getValue(EvaluationContext context, Object rootObject) throws EvaluationException {
Assert.notNull(context, "The EvaluationContext is required");
return this.ast.getValue(new ExpressionState(context, toTypedValue(rootObject), this.configuration));
if (compiledAst!=null) {
try {
return this.compiledAst.getValue(rootObject,context);
} catch (Throwable t) {
// If running in mixed mode, revert to interpreted
if (this.configuration.getCompilerMode() == SpelCompilerMode.mixed) {
interpretedCount = 0;
compiledAst = null;
}
else {
// Running in SpelCompilerMode.immediate mode - propagate exception to caller
throw new SpelEvaluationException(t,SpelMessage.EXCEPTION_RUNNING_COMPILED_EXPRESSION);
}
}
}
ExpressionState expressionState = new ExpressionState(context, toTypedValue(rootObject), this.configuration);
Object result = this.ast.getValue(expressionState);
checkCompile(expressionState);
return result;
}
@SuppressWarnings("unchecked")
@Override
public <T> T getValue(EvaluationContext context, Class<T> expectedResultType) throws EvaluationException {
TypedValue typedResultValue = this.ast.getTypedValue(new ExpressionState(context, this.configuration));
if (compiledAst!=null) {
try {
Object result = this.compiledAst.getValue(null,context);
if (expectedResultType!=null) {
return (T) result;
} else {
return ExpressionUtils.convertTypedValue(context, new TypedValue(result), expectedResultType);
}
} catch (Throwable t) {
// If running in mixed mode, revert to interpreted
if (this.configuration.getCompilerMode() == SpelCompilerMode.mixed) {
interpretedCount = 0;
compiledAst = null;
}
else {
// Running in SpelCompilerMode.immediate mode - propagate exception to caller
throw new SpelEvaluationException(t,SpelMessage.EXCEPTION_RUNNING_COMPILED_EXPRESSION);
}
}
}
ExpressionState expressionState = new ExpressionState(context, this.configuration);
TypedValue typedResultValue = this.ast.getTypedValue(expressionState);
checkCompile(expressionState);
return ExpressionUtils.convertTypedValue(context, typedResultValue, expectedResultType);
}
@SuppressWarnings("unchecked")
@Override
public <T> T getValue(EvaluationContext context, Object rootObject, Class<T> expectedResultType) throws EvaluationException {
TypedValue typedResultValue = this.ast.getTypedValue(new ExpressionState(context, toTypedValue(rootObject), this.configuration));
if (compiledAst!=null) {
try {
Object result = this.compiledAst.getValue(rootObject,context);
if (expectedResultType!=null) {
return (T) result;
} else {
return ExpressionUtils.convertTypedValue(context, new TypedValue(result), expectedResultType);
}
} catch (Throwable t) {
// If running in mixed mode, revert to interpreted
if (this.configuration.getCompilerMode() == SpelCompilerMode.mixed) {
interpretedCount = 0;
compiledAst = null;
}
else {
// Running in SpelCompilerMode.immediate mode - propagate exception to caller
throw new SpelEvaluationException(t,SpelMessage.EXCEPTION_RUNNING_COMPILED_EXPRESSION);
}
}
}
ExpressionState expressionState = new ExpressionState(context, toTypedValue(rootObject), this.configuration);
TypedValue typedResultValue = this.ast.getTypedValue(expressionState);
checkCompile(expressionState);
return ExpressionUtils.convertTypedValue(context, typedResultValue, expectedResultType);
}
@ -203,6 +381,66 @@ public class SpelExpression implements Expression { @@ -203,6 +381,66 @@ public class SpelExpression implements Expression {
// impl only
/**
* Compile the expression if it has been evaluated more than the threshold number of times to trigger compilation.
* @param expressionState the expression state used to determine compilation mode
*/
private void checkCompile(ExpressionState expressionState) {
interpretedCount++;
SpelCompilerMode compilerMode = expressionState.getConfiguration().getCompilerMode();
if (compilerMode != SpelCompilerMode.off) {
if (compilerMode == SpelCompilerMode.immediate) {
if (interpretedCount > 1) {
compileExpression();
}
}
else {
// compilerMode = SpelCompilerMode.mixed
if (interpretedCount > SpelCompiler.interpretedCountThreshold) {
compileExpression();
}
}
}
}
/**
* Perform expression compilation. This will only succeed once exit descriptors for all nodes have
* been determined. If the compilation fails and has failed more than 100 times the expression is
* no longer considered suitable for compilation.
*/
public boolean compileExpression() {
if (failedAttempts > 100) {
// Don't try again
return false;
}
if (this.compiledAst == null) {
synchronized (expression) {
// Possibly compiled by another thread before this thread got into the
// sync block
if (this.compiledAst != null) {
return true;
}
this.compiledAst = SpelCompiler.getCompiler().compile(this.ast);
if (this.compiledAst == null) {
failedAttempts++;
}
}
}
return (this.compiledAst != null);
}
/**
* Cause an expression to revert to being interpreted if it has been using a compiled
* form. It also resets the compilation attempt failure count (an expression is normally no
* longer considered compilable if it cannot be compiled after 100 attempts).
*/
public void revertToInterpreted() {
this.compiledAst = null;
this.interpretedCount = 0;
this.failedAttempts = 0;
}
/**
* @return return the Abstract Syntax Tree for the expression
*/

19
spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectionHelper.java

@ -228,16 +228,18 @@ public class ReflectionHelper { @@ -228,16 +228,18 @@ public class ReflectionHelper {
* @param arguments the actual arguments that need conversion
* @param methodOrCtor the target Method or Constructor
* @param varargsPosition the known position of the varargs argument, if any
* @return true if some kind of conversion occurred on an argument
* @throws EvaluationException if a problem occurs during conversion
*/
static void convertArguments(TypeConverter converter, Object[] arguments, Object methodOrCtor,
static boolean convertArguments(TypeConverter converter, Object[] arguments, Object methodOrCtor,
Integer varargsPosition) throws EvaluationException {
boolean conversionOccurred = false;
if (varargsPosition == null) {
for (int i = 0; i < arguments.length; i++) {
TypeDescriptor targetType = new TypeDescriptor(MethodParameter.forMethodOrConstructor(methodOrCtor, i));
Object argument = arguments[i];
arguments[i] = converter.convertValue(argument, TypeDescriptor.forObject(argument), targetType);
conversionOccurred |= (argument != arguments[i]);
}
}
else {
@ -245,21 +247,25 @@ public class ReflectionHelper { @@ -245,21 +247,25 @@ public class ReflectionHelper {
TypeDescriptor targetType = new TypeDescriptor(MethodParameter.forMethodOrConstructor(methodOrCtor, i));
Object argument = arguments[i];
arguments[i] = converter.convertValue(argument, TypeDescriptor.forObject(argument), targetType);
conversionOccurred |= (argument != arguments[i]);
}
MethodParameter methodParam = MethodParameter.forMethodOrConstructor(methodOrCtor, varargsPosition);
if (varargsPosition == arguments.length - 1) {
TypeDescriptor targetType = new TypeDescriptor(methodParam);
Object argument = arguments[varargsPosition];
arguments[varargsPosition] = converter.convertValue(argument, TypeDescriptor.forObject(argument), targetType);
conversionOccurred |= (argument != arguments[varargsPosition]);
}
else {
TypeDescriptor targetType = new TypeDescriptor(methodParam).getElementTypeDescriptor();
for (int i = varargsPosition; i < arguments.length; i++) {
Object argument = arguments[i];
arguments[i] = converter.convertValue(argument, TypeDescriptor.forObject(argument), targetType);
conversionOccurred |= (argument != arguments[i]);
}
}
}
return conversionOccurred;
}
/**
@ -272,15 +278,16 @@ public class ReflectionHelper { @@ -272,15 +278,16 @@ public class ReflectionHelper {
* @param converter the converter to use for type conversions
* @param arguments the arguments to convert to the requested parameter types
* @param method the target Method
* @return true if some kind of conversion occurred on the argument
* @throws SpelEvaluationException if there is a problem with conversion
*/
public static void convertAllArguments(TypeConverter converter, Object[] arguments, Method method)
throws SpelEvaluationException {
public static boolean convertAllArguments(TypeConverter converter, Object[] arguments, Method method) throws SpelEvaluationException {
Integer varargsPosition = null;
boolean conversionOccurred = false;
if (method.isVarArgs()) {
Class<?>[] paramTypes = method.getParameterTypes();
varargsPosition = paramTypes.length - 1;
conversionOccurred = true;
}
for (int argPos = 0; argPos < arguments.length; argPos++) {
TypeDescriptor targetType;
@ -299,6 +306,7 @@ public class ReflectionHelper { @@ -299,6 +306,7 @@ public class ReflectionHelper {
SpelMessage.TYPE_CONVERSION_ERROR, argument.getClass().getName(), targetType);
}
arguments[argPos] = converter.convertValue(argument, TypeDescriptor.forObject(argument), targetType);
conversionOccurred |= (argument != arguments[argPos]);
}
}
catch (EvaluationException ex) {
@ -312,6 +320,7 @@ public class ReflectionHelper { @@ -312,6 +320,7 @@ public class ReflectionHelper {
}
}
}
return conversionOccurred;
}
/**

8
spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectiveConstructorExecutor.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -32,7 +32,7 @@ import org.springframework.util.ReflectionUtils; @@ -32,7 +32,7 @@ import org.springframework.util.ReflectionUtils;
* @author Juergen Hoeller
* @since 3.0
*/
class ReflectiveConstructorExecutor implements ConstructorExecutor {
public class ReflectiveConstructorExecutor implements ConstructorExecutor {
private final Constructor<?> ctor;
@ -67,4 +67,8 @@ class ReflectiveConstructorExecutor implements ConstructorExecutor { @@ -67,4 +67,8 @@ class ReflectiveConstructorExecutor implements ConstructorExecutor {
}
}
public Constructor<?> getConstructor() {
return this.ctor;
}
}

15
spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectiveMethodExecutor.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -31,12 +31,13 @@ import org.springframework.util.ReflectionUtils; @@ -31,12 +31,13 @@ import org.springframework.util.ReflectionUtils;
* @author Juergen Hoeller
* @since 3.0
*/
class ReflectiveMethodExecutor implements MethodExecutor {
public class ReflectiveMethodExecutor implements MethodExecutor {
private final Method method;
private final Integer varargsPosition;
private boolean argumentConversionOccurred = false;
public ReflectiveMethodExecutor(Method method) {
this.method = method;
@ -49,12 +50,20 @@ class ReflectiveMethodExecutor implements MethodExecutor { @@ -49,12 +50,20 @@ class ReflectiveMethodExecutor implements MethodExecutor {
}
}
public Method getMethod() {
return this.method;
}
public boolean didArgumentConversionOccur() {
return this.argumentConversionOccurred;
}
@Override
public TypedValue execute(EvaluationContext context, Object target, Object... arguments) throws AccessException {
try {
if (arguments != null) {
ReflectionHelper.convertArguments(context.getTypeConverter(), arguments, this.method, this.varargsPosition);
this.argumentConversionOccurred = ReflectionHelper.convertArguments(context.getTypeConverter(), arguments, this.method, this.varargsPosition);
}
if (this.method.isVarArgs()) {
arguments = ReflectionHelper.setupArgumentsForVarargsInvocation(this.method.getParameterTypes(), arguments);

57
spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectivePropertyAccessor.java

@ -29,15 +29,19 @@ import java.util.Map; @@ -29,15 +29,19 @@ import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.asm.MethodVisitor;
import org.springframework.core.MethodParameter;
import org.springframework.core.convert.Property;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.style.ToStringCreator;
import org.springframework.expression.AccessException;
import org.springframework.expression.CompilablePropertyAccessor;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.PropertyAccessor;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.ast.PropertyOrFieldReference;
import org.springframework.expression.spel.standard.CodeFlow;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
@ -72,6 +76,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor { @@ -72,6 +76,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
private final Map<CacheKey, TypeDescriptor> typeDescriptorCache = new ConcurrentHashMap<CacheKey, TypeDescriptor>(64);
private InvokerPair lastReadInvokerPair;
/**
* Returns {@code null} which means this is a general purpose accessor.
@ -115,6 +120,10 @@ public class ReflectivePropertyAccessor implements PropertyAccessor { @@ -115,6 +120,10 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
}
return false;
}
public Member getLastReadInvokerPair() {
return lastReadInvokerPair.member;
}
@Override
public TypedValue read(EvaluationContext context, Object target, String name) throws AccessException {
@ -132,6 +141,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor { @@ -132,6 +141,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
CacheKey cacheKey = new CacheKey(type, name, target instanceof Class);
InvokerPair invoker = this.readerCache.get(cacheKey);
lastReadInvokerPair = invoker;
if (invoker == null || invoker.member instanceof Method) {
Method method = (Method) (invoker != null ? invoker.member : null);
@ -144,6 +154,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor { @@ -144,6 +154,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
Property property = new Property(type, method, null);
TypeDescriptor typeDescriptor = new TypeDescriptor(property);
invoker = new InvokerPair(method, typeDescriptor);
lastReadInvokerPair = invoker;
this.readerCache.put(cacheKey, invoker);
}
}
@ -165,6 +176,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor { @@ -165,6 +176,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
field = findField(name, type, target);
if (field != null) {
invoker = new InvokerPair(field, new TypeDescriptor(field));
lastReadInvokerPair = invoker;
this.readerCache.put(cacheKey, invoker);
}
}
@ -557,9 +569,9 @@ public class ReflectivePropertyAccessor implements PropertyAccessor { @@ -557,9 +569,9 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
* accessor exists because looking up the appropriate reflective object by class/name
* on each read is not cheap.
*/
private static class OptimalPropertyAccessor implements PropertyAccessor {
public static class OptimalPropertyAccessor implements CompilablePropertyAccessor {
private final Member member;
public final Member member;
private final TypeDescriptor typeDescriptor;
@ -637,6 +649,16 @@ public class ReflectivePropertyAccessor implements PropertyAccessor { @@ -637,6 +649,16 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
}
throw new AccessException("Neither getter nor field found for property '" + name + "'");
}
@Override
public Class<?> getPropertyType() {
if (member instanceof Field) {
return ((Field)member).getType();
}
else {
return ((Method)member).getReturnType();
}
}
@Override
public boolean canWrite(EvaluationContext context, Object target, String name) {
@ -647,6 +669,37 @@ public class ReflectivePropertyAccessor implements PropertyAccessor { @@ -647,6 +669,37 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
public void write(EvaluationContext context, Object target, String name, Object newValue) {
throw new UnsupportedOperationException("Should not be called on an OptimalPropertyAccessor");
}
@Override
public boolean isCompilable() {
// If non public must continue to use reflection
if (!Modifier.isPublic(member.getModifiers()) || !Modifier.isPublic(member.getDeclaringClass().getModifiers())) {
return false;
}
return true;
}
@Override
public void generateCode(PropertyOrFieldReference propertyReference, MethodVisitor mv,CodeFlow codeflow) {
boolean isStatic = Modifier.isStatic(member.getModifiers());
String descriptor = codeflow.lastDescriptor();
String memberDeclaringClassSlashedDescriptor = member.getDeclaringClass().getName().replace('.','/');
if (!isStatic) {
if (descriptor == null) {
codeflow.loadTarget(mv);
}
if (descriptor == null || !memberDeclaringClassSlashedDescriptor.equals(descriptor.substring(1))) {
mv.visitTypeInsn(CHECKCAST, memberDeclaringClassSlashedDescriptor);
}
}
if (member instanceof Field) {
mv.visitFieldInsn(isStatic?GETSTATIC:GETFIELD,memberDeclaringClassSlashedDescriptor,member.getName(),CodeFlow.toJVMDescriptor(((Field) member).getType()));
} else {
mv.visitMethodInsn(isStatic?INVOKESTATIC:INVOKEVIRTUAL, memberDeclaringClassSlashedDescriptor, member.getName(),CodeFlow.createSignatureDescriptor((Method)member),false);
}
}
}
}

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

File diff suppressed because it is too large Load Diff

471
spring-expression/src/test/java/org/springframework/expression/spel/SpelCompilationPerformanceTests.java

@ -0,0 +1,471 @@ @@ -0,0 +1,471 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.expression.spel;
import org.junit.Ignore;
import org.junit.Test;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelCompiler;
import static org.junit.Assert.*;
/**
* Checks the speed of compiled SpEL expressions.
* By default these tests are marked Ignore since they can fail on a busy machine because they
* compare relative performance of interpreted vs compiled.
*
* @author Andy Clement
* @since 4.1
*/
@Ignore
public class SpelCompilationPerformanceTests extends AbstractExpressionTests {
int count = 50000; // Number of evaluations that are timed in one run
int iterations = 10; // Number of times to repeat 'count' evaluations (for averaging)
private final static boolean noisyTests = true;
Expression expression;
public static class Payload {
Two[] DR = new Two[]{new Two()};
public Two[] getDR() {
return DR;
}
}
public static class Two {
Three DRFixedSection = new Three();
public Three getDRFixedSection() {
return DRFixedSection;
}
}
public static class Three {
double duration = 0.4d;
public double getDuration() {
return duration;
}
}
@Test
public void complexExpressionPerformance() throws Exception {
Payload payload = new Payload();
Expression expression = parser.parseExpression("DR[0].DRFixedSection.duration lt 0.1");
boolean b = false;
long iTotal = 0,cTotal = 0;
// warmup
for (int i=0;i<count;i++) {
b = expression.getValue(payload,Boolean.TYPE);
}
log("timing interpreted: ");
for (int iter=0;iter<iterations;iter++) {
long stime = System.currentTimeMillis();
for (int i=0;i<count;i++) {
b = expression.getValue(payload,Boolean.TYPE);
}
long etime = System.currentTimeMillis();
long interpretedSpeed = (etime - stime);
iTotal+=interpretedSpeed;
log(interpretedSpeed+"ms ");
}
logln();
compile(expression);
boolean bc = false;
expression.getValue(payload,Boolean.TYPE);
log("timing compiled: ");
for (int iter=0;iter<iterations;iter++) {
long stime = System.currentTimeMillis();
for (int i=0;i<count;i++) {
bc = expression.getValue(payload,Boolean.TYPE);
}
long etime = System.currentTimeMillis();
long compiledSpeed = (etime - stime);
cTotal+=compiledSpeed;
log(compiledSpeed+"ms ");
}
logln();
reportPerformance("complex expression",iTotal, cTotal);
// Verify the result
assertFalse(b);
// Verify the same result for compiled vs interpreted
assertEquals(b,bc);
// Verify if the input changes, the result changes
payload.DR[0].DRFixedSection.duration = 0.04d;
bc = expression.getValue(payload,Boolean.TYPE);
assertTrue(bc);
}
public static class HW {
public String hello() {
return "foobar";
}
}
@Test
public void compilingMethodReference() throws Exception {
long interpretedTotal = 0, compiledTotal = 0;
long stime,etime;
String interpretedResult = null,compiledResult = null;
HW testdata = new HW();
Expression expression = parser.parseExpression("hello()");
// warmup
for (int i=0;i<count;i++) {
interpretedResult = expression.getValue(testdata,String.class);
}
log("timing interpreted: ");
for (int iter=0;iter<iterations;iter++) {
stime = System.currentTimeMillis();
for (int i=0;i<count;i++) {
interpretedResult = expression.getValue(testdata,String.class);
}
etime = System.currentTimeMillis();
long interpretedSpeed = (etime - stime);
interpretedTotal+=interpretedSpeed;
log(interpretedSpeed+"ms ");
}
logln();
compile(expression);
log("timing compiled: ");
expression.getValue(testdata,String.class);
for (int iter=0;iter<iterations;iter++) {
stime = System.currentTimeMillis();
for (int i=0;i<count;i++) {
compiledResult = expression.getValue(testdata,String.class);
}
etime = System.currentTimeMillis();
long compiledSpeed = (etime - stime);
compiledTotal+=compiledSpeed;
log(compiledSpeed+"ms ");
}
logln();
assertEquals(interpretedResult,compiledResult);
reportPerformance("method reference", interpretedTotal, compiledTotal);
if (compiledTotal>=interpretedTotal) {
fail("Compiled version is slower than interpreted!");
}
}
public static class TestClass2 {
public String name = "Santa";
private String name2 = "foobar";
public String getName2() {
return name2;
}
public Foo foo = new Foo();
public static class Foo {
public Bar bar = new Bar();
Bar b = new Bar();
public Bar getBaz() {
return b;
}
public Bar bay() {
return b;
}
}
public static class Bar {
public String boo = "oranges";
}
}
@Test
public void compilingPropertyReferenceField() throws Exception {
long interpretedTotal = 0, compiledTotal = 0, stime, etime;
String interpretedResult = null, compiledResult = null;
TestClass2 testdata = new TestClass2();
Expression expression = parser.parseExpression("name");
// warmup
for (int i=0;i<count;i++) {
expression.getValue(testdata,String.class);
}
log("timing interpreted: ");
for (int iter=0;iter<iterations;iter++) {
stime = System.currentTimeMillis();
for (int i=0;i<count;i++) {
interpretedResult = expression.getValue(testdata,String.class);
}
etime = System.currentTimeMillis();
long interpretedSpeed = (etime - stime);
interpretedTotal+=interpretedSpeed;
log(interpretedSpeed+"ms ");
}
logln();
compile(expression);
log("timing compiled: ");
expression.getValue(testdata,String.class);
for (int iter=0;iter<iterations;iter++) {
stime = System.currentTimeMillis();
for (int i=0;i<count;i++) {
compiledResult = expression.getValue(testdata,String.class);
}
etime = System.currentTimeMillis();
long compiledSpeed = (etime - stime);
compiledTotal+=compiledSpeed;
log(compiledSpeed+"ms ");
}
logln();
assertEquals(interpretedResult,compiledResult);
reportPerformance("property reference (field)",interpretedTotal, compiledTotal);
}
@Test
public void compilingPropertyReferenceNestedField() throws Exception {
long interpretedTotal = 0, compiledTotal = 0, stime, etime;
String interpretedResult = null, compiledResult = null;
TestClass2 testdata = new TestClass2();
Expression expression = parser.parseExpression("foo.bar.boo");
// warmup
for (int i=0;i<count;i++) {
expression.getValue(testdata,String.class);
}
log("timing interpreted: ");
for (int iter=0;iter<iterations;iter++) {
stime = System.currentTimeMillis();
for (int i=0;i<count;i++) {
interpretedResult = expression.getValue(testdata,String.class);
}
etime = System.currentTimeMillis();
long interpretedSpeed = (etime - stime);
interpretedTotal+=interpretedSpeed;
log(interpretedSpeed+"ms ");
}
logln();
compile(expression);
log("timing compiled: ");
expression.getValue(testdata,String.class);
for (int iter=0;iter<iterations;iter++) {
stime = System.currentTimeMillis();
for (int i=0;i<count;i++) {
compiledResult = expression.getValue(testdata,String.class);
}
etime = System.currentTimeMillis();
long compiledSpeed = (etime - stime);
compiledTotal+=compiledSpeed;
log(compiledSpeed+"ms ");
}
logln();
assertEquals(interpretedResult,compiledResult);
reportPerformance("property reference (nested field)",interpretedTotal, compiledTotal);
}
@Test
public void compilingPropertyReferenceNestedMixedFieldGetter() throws Exception {
long interpretedTotal = 0, compiledTotal = 0, stime, etime;
String interpretedResult = null, compiledResult = null;
TestClass2 testdata = new TestClass2();
Expression expression = parser.parseExpression("foo.baz.boo");
// warmup
for (int i=0;i<count;i++) {
expression.getValue(testdata,String.class);
}
log("timing interpreted: ");
for (int iter=0;iter<iterations;iter++) {
stime = System.currentTimeMillis();
for (int i=0;i<count;i++) {
interpretedResult = expression.getValue(testdata,String.class);
}
etime = System.currentTimeMillis();
long interpretedSpeed = (etime - stime);
interpretedTotal+=interpretedSpeed;
log(interpretedSpeed+"ms ");
}
logln();
compile(expression);
log("timing compiled: ");
expression.getValue(testdata,String.class);
for (int iter=0;iter<iterations;iter++) {
stime = System.currentTimeMillis();
for (int i=0;i<count;i++) {
compiledResult = expression.getValue(testdata,String.class);
}
etime = System.currentTimeMillis();
long compiledSpeed = (etime - stime);
compiledTotal+=compiledSpeed;
log(compiledSpeed+"ms ");
}
logln();
assertEquals(interpretedResult,compiledResult);
reportPerformance("nested property reference (mixed field/getter)",interpretedTotal, compiledTotal);
}
@Test
public void compilingNestedMixedFieldPropertyReferenceMethodReference() throws Exception {
long interpretedTotal = 0, compiledTotal = 0, stime, etime;
String interpretedResult = null, compiledResult = null;
TestClass2 testdata = new TestClass2();
Expression expression = parser.parseExpression("foo.bay().boo");
// warmup
for (int i=0;i<count;i++) {
expression.getValue(testdata,String.class);
}
log("timing interpreted: ");
for (int iter=0;iter<iterations;iter++) {
stime = System.currentTimeMillis();
for (int i=0;i<count;i++) {
interpretedResult = expression.getValue(testdata,String.class);
}
etime = System.currentTimeMillis();
long interpretedSpeed = (etime - stime);
interpretedTotal+=interpretedSpeed;
log(interpretedSpeed+"ms ");
}
logln();
compile(expression);
log("timing compiled: ");
expression.getValue(testdata,String.class);
for (int iter=0;iter<iterations;iter++) {
stime = System.currentTimeMillis();
for (int i=0;i<count;i++) {
compiledResult = expression.getValue(testdata,String.class);
}
etime = System.currentTimeMillis();
long compiledSpeed = (etime - stime);
compiledTotal+=compiledSpeed;
log(compiledSpeed+"ms ");
}
logln();
assertEquals(interpretedResult,compiledResult);
reportPerformance("nested reference (mixed field/method)",interpretedTotal, compiledTotal);
}
@Test
public void compilingPropertyReferenceGetter() throws Exception {
long interpretedTotal = 0, compiledTotal = 0, stime, etime;
String interpretedResult = null, compiledResult = null;
TestClass2 testdata = new TestClass2();
Expression expression = parser.parseExpression("name2");
// warmup
for (int i=0;i<count;i++) {
expression.getValue(testdata,String.class);
}
log("timing interpreted: ");
for (int iter=0;iter<iterations;iter++) {
stime = System.currentTimeMillis();
for (int i=0;i<count;i++) {
interpretedResult = expression.getValue(testdata,String.class);
}
etime = System.currentTimeMillis();
long interpretedSpeed = (etime - stime);
interpretedTotal+=interpretedSpeed;
log(interpretedSpeed+"ms ");
}
logln();
compile(expression);
log("timing compiled: ");
expression.getValue(testdata,String.class);
for (int iter=0;iter<iterations;iter++) {
stime = System.currentTimeMillis();
for (int i=0;i<count;i++) {
compiledResult = expression.getValue(testdata,String.class);
}
etime = System.currentTimeMillis();
long compiledSpeed = (etime - stime);
compiledTotal+=compiledSpeed;
log(compiledSpeed+"ms ");
}
logln();
assertEquals(interpretedResult,compiledResult);
reportPerformance("property reference (getter)", interpretedTotal, compiledTotal);
if (compiledTotal>=interpretedTotal) {
fail("Compiled version is slower than interpreted!");
}
}
// ---
private void reportPerformance(String title, long interpretedTotal, long compiledTotal) {
double averageInterpreted = interpretedTotal/(iterations);
double averageCompiled = compiledTotal/(iterations);
double ratio = (averageCompiled/averageInterpreted)*100.0d;
logln(">>"+title+": average for "+count+": compiled="+averageCompiled+"ms interpreted="+averageInterpreted+"ms: compiled takes "+((int)ratio)+"% of the interpreted time");
if (averageCompiled>averageInterpreted) {
fail("Compiled version took longer than interpreted! CompiledSpeed=~"+averageCompiled+
"ms InterpretedSpeed="+averageInterpreted+"ms");
}
logln();
}
private void log(String message) {
if (noisyTests) {
System.out.print(message);
}
}
private void logln(String... message) {
if (noisyTests) {
if (message!=null && message.length>0) {
System.out.println(message[0]);
} else {
System.out.println();
}
}
}
private void compile(Expression expression) {
assertTrue(SpelCompiler.compile(expression));
}
}
Loading…
Cancel
Save