Java tutorial
/* * Copyright 2002-2019 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 * * https://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.lang.Nullable; 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 * @author Juergen Hoeller * @since 3.0 */ public abstract class SpelNodeImpl implements SpelNode, Opcodes { private static final SpelNodeImpl[] NO_CHILDREN = new SpelNodeImpl[0]; private final int startPos; private final int endPos; protected SpelNodeImpl[] children = SpelNodeImpl.NO_CHILDREN; @Nullable 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 */ @Nullable protected volatile String exitTypeDescriptor; public SpelNodeImpl(int startPos, int endPos, SpelNodeImpl... operands) { this.startPos = startPos; this.endPos = endPos; if (!ObjectUtils.isEmpty(operands)) { this.children = operands; for (SpelNodeImpl operand : operands) { Assert.notNull(operand, "Operand must not be null"); operand.parent = this; } } } /** * Return {@code true} if the next child is one of the specified classes. */ protected boolean nextChildIs(Class<?>... classes) { 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<?> peerClass = peers[i + 1].getClass(); for (Class<?> desiredClass : classes) { if (peerClass == desiredClass) { return true; } } return false; } } } return false; } @Override @Nullable public final Object getValue(ExpressionState expressionState) throws EvaluationException { return getValueInternal(expressionState).getValue(); } @Override public final TypedValue getTypedValue(ExpressionState expressionState) throws EvaluationException { return getValueInternal(expressionState); } // by default Ast nodes are not writable @Override public boolean isWritable(ExpressionState expressionState) throws EvaluationException { return false; } @Override public void setValue(ExpressionState expressionState, @Nullable 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 @Nullable public Class<?> getObjectClass(@Nullable Object obj) { if (obj == null) { return null; } return (obj instanceof Class ? ((Class<?>) obj) : obj.getClass()); } @Override public int getStartPosition() { return this.startPos; } @Override public int getEndPosition() { return this.endPos; } /** * 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, e.g. * including 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"); } @Nullable public String getExitDescriptor() { return this.exitTypeDescriptor; } @Nullable protected final <T> T getValue(ExpressionState state, Class<T> desiredReturnType) throws EvaluationException { return ExpressionUtils.convertTypedValue(state.getEvaluationContext(), getValueInternal(state), desiredReturnType); } protected ValueRef getValueRef(ExpressionState state) throws EvaluationException { throw new SpelEvaluationException(getStartPosition(), SpelMessage.NOT_ASSIGNABLE, toStringAST()); } 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 && arrayType.equals(lastChild.getExitDescriptor())) { 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); String lastDesc = cf.lastDescriptor(); Assert.state(lastDesc != null, "No last descriptor"); boolean primitiveOnStack = CodeFlow.isPrimitive(lastDesc); // Check if need to box it for the method reference? if (primitiveOnStack && paramDesc.charAt(0) == 'L') { CodeFlow.insertBoxIfNecessary(mv, lastDesc.charAt(0)); } else if (paramDesc.length() == 1 && !primitiveOnStack) { CodeFlow.insertUnboxInsns(mv, paramDesc.charAt(0), lastDesc); } else if (!paramDesc.equals(lastDesc)) { // This would be unnecessary in the case of subtyping (e.g. method takes Number but Integer passed in) CodeFlow.insertCheckCast(mv, paramDesc); } cf.exitCompilationScope(); } }