Resolved package tangle in spring-expression

This commit is contained in:
Andy Clement 2014-10-25 11:22:15 -07:00
parent a40e42479c
commit 28a3cd50aa
5 changed files with 98 additions and 90 deletions

View File

@ -17,7 +17,6 @@
package org.springframework.expression.spel;
import java.lang.reflect.Constructor;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
@ -26,7 +25,6 @@ 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;
/**
@ -646,7 +644,12 @@ public class CodeFlow implements Opcodes {
return toDescriptors(ctor.getParameterTypes());
}
private static String[] toDescriptors(Class<?>[] types) {
/**
* Create an array of descriptors from an array of classes.
* @param types the input array of classes
* @return an array of descriptors
*/
public static String[] toDescriptors(Class<?>[] types) {
int typesCount = types.length;
String[] descriptors = new String[typesCount];
for (int p = 0; p < typesCount; p++) {
@ -831,90 +834,6 @@ public class CodeFlow implements Opcodes {
}
}
}
/**
* 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 member the method or constructor 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, Member member, SpelNodeImpl[] arguments) {
String[] paramDescriptors = null;
boolean isVarargs = false;
if (member instanceof Constructor) {
Constructor<?> ctor = (Constructor<?>)member;
paramDescriptors = toDescriptors(ctor.getParameterTypes());
isVarargs = ctor.isVarArgs();
}
else { // Method
Method method = (Method)member;
paramDescriptors = toDescriptors(method.getParameterTypes());
isVarargs = method.isVarArgs();
}
if (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();
}
}

View File

@ -451,7 +451,7 @@ public class ConstructorReference extends SpelNodeImpl {
// children[0] is the type of the constructor, don't want to include that in argument processing
SpelNodeImpl[] arguments = new SpelNodeImpl[children.length-1];
System.arraycopy(children, 1, arguments, 0, children.length-1);
CodeFlow.generateCodeForArguments(mv, cf, constructor, arguments);
generateCodeForArguments(mv, cf, constructor, arguments);
mv.visitMethodInsn(INVOKESPECIAL, classSlashedDescriptor, "<init>",
CodeFlow.createSignatureDescriptor(constructor), false);
cf.pushDescriptor(this.exitTypeDescriptor);

View File

@ -180,7 +180,7 @@ public class FunctionReference extends SpelNodeImpl {
@Override
public void generateCode(MethodVisitor mv,CodeFlow cf) {
String methodDeclaringClassSlashedDescriptor = this.method.getDeclaringClass().getName().replace('.', '/');
CodeFlow.generateCodeForArguments(mv, cf, method, this.children);
generateCodeForArguments(mv, cf, method, this.children);
mv.visitMethodInsn(INVOKESTATIC, methodDeclaringClassSlashedDescriptor, this.method.getName(),
CodeFlow.createSignatureDescriptor(this.method), false);
cf.pushDescriptor(this.exitTypeDescriptor);

View File

@ -313,7 +313,7 @@ public class MethodReference extends SpelNodeImpl {
CodeFlow.insertCheckCast(mv, "L"+ methodDeclaringClassSlashedDescriptor);
}
}
CodeFlow.generateCodeForArguments(mv, cf, method, children);
generateCodeForArguments(mv, cf, method, children);
mv.visitMethodInsn(isStaticMethod ? INVOKESTATIC : INVOKEVIRTUAL,
methodDeclaringClassSlashedDescriptor, method.getName(), CodeFlow.createSignatureDescriptor(method), itf);
cf.pushDescriptor(this.exitTypeDescriptor);

View File

@ -16,6 +16,10 @@
package org.springframework.expression.spel.ast;
import java.lang.reflect.Constructor;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import org.springframework.asm.MethodVisitor;
import org.springframework.asm.Opcodes;
import org.springframework.expression.EvaluationException;
@ -208,4 +212,89 @@ public abstract class SpelNodeImpl implements SpelNode, Opcodes {
public abstract TypedValue getValueInternal(ExpressionState expressionState) throws EvaluationException;
/**
* 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 member the method or constructor for which arguments are being setup
* @param arguments the expression nodes for the expression supplied argument values
*/
protected static void generateCodeForArguments(MethodVisitor mv, CodeFlow cf, Member member, SpelNodeImpl[] arguments) {
String[] paramDescriptors = null;
boolean isVarargs = false;
if (member instanceof Constructor) {
Constructor<?> ctor = (Constructor<?>)member;
paramDescriptors = CodeFlow.toDescriptors(ctor.getParameterTypes());
isVarargs = ctor.isVarArgs();
}
else { // Method
Method method = (Method)member;
paramDescriptors = CodeFlow.toDescriptors(method.getParameterTypes());
isVarargs = method.isVarArgs();
}
if (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.
*/
protected 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();
}
}