/* * 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. * 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.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; import org.springframework.expression.TypedValue; import org.springframework.expression.common.ExpressionUtils; import org.springframework.expression.spel.CodeFlow; 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.support.StandardEvaluationContext; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; /** * The common supertype of all AST nodes in a parsed Spring Expression Language * format expression. * * @author Andy Clement * @since 3.0 */ public abstract class SpelNodeImpl implements SpelNode, Opcodes { private static SpelNodeImpl[] NO_CHILDREN = new SpelNodeImpl[0]; protected int pos; // start = top 16bits, end = bottom 16bits protected SpelNodeImpl[] children = SpelNodeImpl.NO_CHILDREN; 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. * <p>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 volatile String exitTypeDescriptor; public SpelNodeImpl(int pos, SpelNodeImpl... operands) { this.pos = pos; // pos combines start and end so can never be zero because tokens cannot be zero length Assert.isTrue(pos != 0, "Pos must not be 0"); if (!ObjectUtils.isEmpty(operands)) { this.children = operands; for (SpelNodeImpl childNode : operands) { childNode.parent = this; } } } protected SpelNodeImpl getPreviousChild() { SpelNodeImpl result = null; if (this.parent != null) { for (SpelNodeImpl child : this.parent.children) { if (this == child) { break; } result = child; } } return result; } /** * @return true if the next child is one of the specified classes */ protected boolean nextChildIs(Class<?>... clazzes) { if (this.parent != null) { SpelNodeImpl[] peers = this.parent.children; for (int i = 0, max = peers.length; i < max; i++) { if (this == peers[i]) { if (i + 1 >= max) { return false; } Class<?> clazz = peers[i + 1].getClass(); for (Class<?> desiredClazz : clazzes) { if (clazz.equals(desiredClazz)) { return true; } } return false; } } } return false; } @Override public final Object getValue(ExpressionState expressionState) throws EvaluationException { if (expressionState != null) { return getValueInternal(expressionState).getValue(); } else { // configuration not set - does that matter? return getValue(new ExpressionState(new StandardEvaluationContext())); } } @Override public final TypedValue getTypedValue(ExpressionState expressionState) throws EvaluationException { if (expressionState != null) { return getValueInternal(expressionState); } else { // configuration not set - does that matter? return getTypedValue(new ExpressionState(new StandardEvaluationContext())); } } // by default Ast nodes are not writable @Override public boolean isWritable(ExpressionState expressionState) throws EvaluationException { return false; } @Override public void setValue(ExpressionState expressionState, Object newValue) throws EvaluationException { throw new SpelEvaluationException(getStartPosition(), SpelMessage.SETVALUE_NOT_SUPPORTED, getClass()); } @Override public SpelNode getChild(int index) { return this.children[index]; } @Override public int getChildCount() { return this.children.length; } @Override public Class<?> getObjectClass(Object obj) { if (obj == null) { return null; } return (obj instanceof Class ? ((Class<?>) obj) : obj.getClass()); } protected final <T> T getValue(ExpressionState state, Class<T> desiredReturnType) throws EvaluationException { return ExpressionUtils.convertTypedValue(state.getEvaluationContext(), getValueInternal(state), desiredReturnType); } @Override public int getStartPosition() { return (this.pos >> 16); } @Override public int getEndPosition() { return (this.pos & 0xffff); } protected ValueRef getValueRef(ExpressionState state) throws EvaluationException { 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 {@code 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 cf a context object with info about what is on the stack */ public void generateCode(MethodVisitor mv, CodeFlow cf) { throw new IllegalStateException(getClass().getName() +" has no generateCode(..) method"); } public String getExitDescriptor() { return this.exitTypeDescriptor; } 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 paramDesc) { cf.enterCompilationScope(); argument.generateCode(mv, cf); boolean primitiveOnStack = CodeFlow.isPrimitive(cf.lastDescriptor()); // Check if need to box it for the method reference? if (primitiveOnStack && paramDesc.charAt(0) == 'L') { CodeFlow.insertBoxIfNecessary(mv, cf.lastDescriptor().charAt(0)); } else if (paramDesc.length() == 1 && !primitiveOnStack) { CodeFlow.insertUnboxInsns(mv, paramDesc.charAt(0), cf.lastDescriptor()); } else if (!cf.lastDescriptor().equals(paramDesc)) { // This would be unnecessary in the case of subtyping (e.g. method takes Number but Integer passed in) CodeFlow.insertCheckCast(mv, paramDesc); } cf.exitCompilationScope(); } }