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