Merge pull request #554 from aclement/SPR-10943
Add a compiler for SpEL
This commit is contained in:
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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 @@
|
||||
|
||||
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 {
|
||||
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 {
|
||||
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
|
||||
|
||||
@@ -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 @@
|
||||
*/
|
||||
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 {
|
||||
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 {
|
||||
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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
*/
|
||||
@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 {
|
||||
}
|
||||
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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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 @@
|
||||
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;
|
||||
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 {
|
||||
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 {
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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 @@
|
||||
|
||||
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 {
|
||||
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 {
|
||||
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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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 @@
|
||||
|
||||
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 {
|
||||
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 {
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
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;
|
||||
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 {
|
||||
|
||||
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 {
|
||||
* @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 {
|
||||
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 {
|
||||
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 {
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
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 {
|
||||
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
// 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 {
|
||||
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 {
|
||||
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 {
|
||||
|
||||
|
||||
@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 {
|
||||
@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 {
|
||||
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 {
|
||||
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;
|
||||
|
||||
@@ -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 @@
|
||||
*/
|
||||
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 {
|
||||
|
||||
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 {
|
||||
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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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 @@
|
||||
|
||||
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 {
|
||||
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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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 @@
|
||||
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;
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
|
||||
@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 {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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 @@
|
||||
|
||||
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 {
|
||||
|
||||
public NullLiteral(int pos) {
|
||||
super(null,pos);
|
||||
this.exitTypeDescriptor = "Ljava/lang/Object";
|
||||
}
|
||||
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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 @@
|
||||
|
||||
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 {
|
||||
|
||||
public OpAnd(int pos, SpelNodeImpl... operands) {
|
||||
super("and", pos, operands);
|
||||
this.exitTypeDescriptor = "Z";
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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 @@
|
||||
|
||||
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;
|
||||
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 {
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
public OpEQ(int pos, SpelNodeImpl... operands) {
|
||||
super("==", pos, operands);
|
||||
this.exitTypeDescriptor = "Z";
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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 @@
|
||||
*/
|
||||
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 {
|
||||
|
||||
public OpGE(int pos, SpelNodeImpl... operands) {
|
||||
super(">=", pos, operands);
|
||||
this.exitTypeDescriptor="Z";
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
public OpGT(int pos, SpelNodeImpl... operands) {
|
||||
super(">", pos, operands);
|
||||
this.exitTypeDescriptor = "Z";
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 @@
|
||||
|
||||
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 {
|
||||
|
||||
public OpLE(int pos, SpelNodeImpl... operands) {
|
||||
super("<=", pos, operands);
|
||||
this.exitTypeDescriptor="Z";
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
public OpLT(int pos, SpelNodeImpl... operands) {
|
||||
super("<", pos, operands);
|
||||
this.exitTypeDescriptor = "Z";
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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 @@
|
||||
|
||||
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 {
|
||||
}
|
||||
|
||||
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 {
|
||||
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 {
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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 @@
|
||||
|
||||
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 {
|
||||
}
|
||||
|
||||
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 {
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
public OpNE(int pos, SpelNodeImpl... operands) {
|
||||
super("!=", pos, operands);
|
||||
this.exitTypeDescriptor = "Z";
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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 @@
|
||||
|
||||
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 {
|
||||
|
||||
public OpOr(int pos, SpelNodeImpl... operands) {
|
||||
super("or", pos, operands);
|
||||
this.exitTypeDescriptor = "Z";
|
||||
}
|
||||
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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 @@
|
||||
|
||||
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;
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
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) {
|
||||
|
||||
@@ -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 @@
|
||||
|
||||
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;
|
||||
*/
|
||||
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 {
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 @@
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
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 {
|
||||
|
||||
@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 {
|
||||
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 {
|
||||
|
||||
@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
|
||||
|
||||
@@ -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 @@
|
||||
|
||||
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 {
|
||||
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 {
|
||||
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());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
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;
|
||||
* @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 {
|
||||
|
||||
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 {
|
||||
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;
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
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 {
|
||||
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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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 @@
|
||||
|
||||
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 {
|
||||
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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
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 {
|
||||
|
||||
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 {
|
||||
// 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 {
|
||||
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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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 @@
|
||||
|
||||
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 {
|
||||
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 {
|
||||
}
|
||||
}
|
||||
|
||||
@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());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
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 {
|
||||
|
||||
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 {
|
||||
|
||||
@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 {
|
||||
|
||||
// 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
|
||||
*/
|
||||
|
||||
@@ -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 {
|
||||
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 {
|
||||
* @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 {
|
||||
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 {
|
||||
}
|
||||
}
|
||||
}
|
||||
return conversionOccurred;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
* @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 {
|
||||
}
|
||||
}
|
||||
|
||||
public Constructor<?> getConstructor() {
|
||||
return this.ctor;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
* @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 {
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
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 {
|
||||
}
|
||||
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 {
|
||||
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
* 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 {
|
||||
}
|
||||
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 {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user