001/*
002 * Copyright 2002-2019 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.expression.spel.ast;
018
019import java.util.StringJoiner;
020
021import org.springframework.asm.MethodVisitor;
022import org.springframework.expression.EvaluationException;
023import org.springframework.expression.TypedValue;
024import org.springframework.expression.spel.CodeFlow;
025import org.springframework.expression.spel.ExpressionState;
026import org.springframework.expression.spel.SpelEvaluationException;
027import org.springframework.lang.Nullable;
028
029/**
030 * Represents a DOT separated expression sequence, such as
031 * {@code 'property1.property2.methodOne()'}.
032 *
033 * @author Andy Clement
034 * @since 3.0
035 */
036public class CompoundExpression extends SpelNodeImpl {
037
038        public CompoundExpression(int startPos, int endPos, SpelNodeImpl... expressionComponents) {
039                super(startPos, endPos, expressionComponents);
040                if (expressionComponents.length < 2) {
041                        throw new IllegalStateException("Do not build compound expressions with less than two entries: " +
042                                        expressionComponents.length);
043                }
044        }
045
046
047        @Override
048        protected ValueRef getValueRef(ExpressionState state) throws EvaluationException {
049                if (getChildCount() == 1) {
050                        return this.children[0].getValueRef(state);
051                }
052
053                SpelNodeImpl nextNode = this.children[0];
054                try {
055                        TypedValue result = nextNode.getValueInternal(state);
056                        int cc = getChildCount();
057                        for (int i = 1; i < cc - 1; i++) {
058                                try {
059                                        state.pushActiveContextObject(result);
060                                        nextNode = this.children[i];
061                                        result = nextNode.getValueInternal(state);
062                                }
063                                finally {
064                                        state.popActiveContextObject();
065                                }
066                        }
067                        try {
068                                state.pushActiveContextObject(result);
069                                nextNode = this.children[cc - 1];
070                                return nextNode.getValueRef(state);
071                        }
072                        finally {
073                                state.popActiveContextObject();
074                        }
075                }
076                catch (SpelEvaluationException ex) {
077                        // Correct the position for the error before re-throwing
078                        ex.setPosition(nextNode.getStartPosition());
079                        throw ex;
080                }
081        }
082
083        /**
084         * Evaluates a compound expression. This involves evaluating each piece in turn and the
085         * return value from each piece is the active context object for the subsequent piece.
086         * @param state the state in which the expression is being evaluated
087         * @return the final value from the last piece of the compound expression
088         */
089        @Override
090        public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
091                ValueRef ref = getValueRef(state);
092                TypedValue result = ref.getValue();
093                this.exitTypeDescriptor = this.children[this.children.length - 1].exitTypeDescriptor;
094                return result;
095        }
096
097        @Override
098        public void setValue(ExpressionState state, @Nullable Object value) throws EvaluationException {
099                getValueRef(state).setValue(value);
100        }
101
102        @Override
103        public boolean isWritable(ExpressionState state) throws EvaluationException {
104                return getValueRef(state).isWritable();
105        }
106
107        @Override
108        public String toStringAST() {
109                StringJoiner sj = new StringJoiner(".");
110                for (int i = 0; i < getChildCount(); i++) {
111                        sj.add(getChild(i).toStringAST());
112                }
113                return sj.toString();
114        }
115
116        @Override
117        public boolean isCompilable() {
118                for (SpelNodeImpl child: this.children) {
119                        if (!child.isCompilable()) {
120                                return false;
121                        }
122                }
123                return true;
124        }
125
126        @Override
127        public void generateCode(MethodVisitor mv, CodeFlow cf) {
128                for (SpelNodeImpl child : this.children) {
129                        child.generateCode(mv, cf);
130                }
131                cf.pushDescriptor(this.exitTypeDescriptor);
132        }
133
134}