org.springframework.expression.spel.ast.SpelNodeImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.springframework.expression.spel.ast.SpelNodeImpl.java

Source

/*
 * 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();
    }

}