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 org.springframework.asm.MethodVisitor;
020import org.springframework.expression.EvaluationException;
021import org.springframework.expression.TypedValue;
022import org.springframework.expression.spel.CodeFlow;
023import org.springframework.expression.spel.ExpressionState;
024import org.springframework.expression.spel.SpelEvaluationException;
025
026/**
027 * Represents a DOT separated expression sequence, such as
028 * {@code 'property1.property2.methodOne()'}.
029 *
030 * @author Andy Clement
031 * @since 3.0
032 */
033public class CompoundExpression extends SpelNodeImpl {
034
035        public CompoundExpression(int pos, SpelNodeImpl... expressionComponents) {
036                super(pos, expressionComponents);
037                if (expressionComponents.length < 2) {
038                        throw new IllegalStateException("Do not build compound expressions with less than two entries: " +
039                                        expressionComponents.length);
040                }
041        }
042
043
044        @Override
045        protected ValueRef getValueRef(ExpressionState state) throws EvaluationException {
046                if (getChildCount() == 1) {
047                        return this.children[0].getValueRef(state);
048                }
049
050                SpelNodeImpl nextNode = this.children[0];
051                try {
052                        TypedValue result = nextNode.getValueInternal(state);
053                        int cc = getChildCount();
054                        for (int i = 1; i < cc - 1; i++) {
055                                try {
056                                        state.pushActiveContextObject(result);
057                                        nextNode = this.children[i];
058                                        result = nextNode.getValueInternal(state);
059                                }
060                                finally {
061                                        state.popActiveContextObject();
062                                }
063                        }
064                        try {
065                                state.pushActiveContextObject(result);
066                                nextNode = this.children[cc - 1];
067                                return nextNode.getValueRef(state);
068                        }
069                        finally {
070                                state.popActiveContextObject();
071                        }
072                }
073                catch (SpelEvaluationException ex) {
074                        // Correct the position for the error before re-throwing
075                        ex.setPosition(nextNode.getStartPosition());
076                        throw ex;
077                }
078        }
079
080        /**
081         * Evaluates a compound expression. This involves evaluating each piece in turn and the
082         * return value from each piece is the active context object for the subsequent piece.
083         * @param state the state in which the expression is being evaluated
084         * @return the final value from the last piece of the compound expression
085         */
086        @Override
087        public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
088                ValueRef ref = getValueRef(state);
089                TypedValue result = ref.getValue();
090                this.exitTypeDescriptor = this.children[this.children.length - 1].exitTypeDescriptor;
091                return result;
092        }
093
094        @Override
095        public void setValue(ExpressionState state, Object value) throws EvaluationException {
096                getValueRef(state).setValue(value);
097        }
098
099        @Override
100        public boolean isWritable(ExpressionState state) throws EvaluationException {
101                return getValueRef(state).isWritable();
102        }
103
104        @Override
105        public String toStringAST() {
106                StringBuilder sb = new StringBuilder();
107                for (int i = 0; i < getChildCount(); i++) {
108                        if (i > 0) {
109                                sb.append(".");
110                        }
111                        sb.append(getChild(i).toStringAST());
112                }
113                return sb.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}