Browse Source

Merge pull request #554 from aclement/SPR-10943

Add a compiler for SpEL
pull/588/merge
Andy Clement 12 years ago
parent
commit
6937eea48a
  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