001/*
002 * Copyright 2002-2017 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;
018
019import java.util.Collections;
020import java.util.HashMap;
021import java.util.List;
022import java.util.Map;
023import java.util.Stack;
024
025import org.springframework.core.convert.TypeDescriptor;
026import org.springframework.expression.EvaluationContext;
027import org.springframework.expression.EvaluationException;
028import org.springframework.expression.Operation;
029import org.springframework.expression.OperatorOverloader;
030import org.springframework.expression.PropertyAccessor;
031import org.springframework.expression.TypeComparator;
032import org.springframework.expression.TypeConverter;
033import org.springframework.expression.TypedValue;
034import org.springframework.util.Assert;
035import org.springframework.util.CollectionUtils;
036
037/**
038 * An ExpressionState is for maintaining per-expression-evaluation state, any changes to
039 * it are not seen by other expressions but it gives a place to hold local variables and
040 * for component expressions in a compound expression to communicate state. This is in
041 * contrast to the EvaluationContext, which is shared amongst expression evaluations, and
042 * any changes to it will be seen by other expressions or any code that chooses to ask
043 * questions of the context.
044 *
045 * <p>It also acts as a place for to define common utility routines that the various AST
046 * nodes might need.
047 *
048 * @author Andy Clement
049 * @since 3.0
050 */
051public class ExpressionState {
052
053        private final EvaluationContext relatedContext;
054
055        private final TypedValue rootObject;
056
057        private final SpelParserConfiguration configuration;
058
059        private Stack<TypedValue> contextObjects;
060
061        private Stack<VariableScope> variableScopes;
062
063        // When entering a new scope there is a new base object which should be used
064        // for '#this' references (or to act as a target for unqualified references).
065        // This stack captures those objects at each nested scope level.
066        // For example:
067        // #list1.?[#list2.contains(#this)]
068        // On entering the selection we enter a new scope, and #this is now the
069        // element from list1
070        private Stack<TypedValue> scopeRootObjects;
071
072
073        public ExpressionState(EvaluationContext context) {
074                this(context, context.getRootObject(), new SpelParserConfiguration(false, false));
075        }
076
077        public ExpressionState(EvaluationContext context, SpelParserConfiguration configuration) {
078                this(context, context.getRootObject(), configuration);
079        }
080
081        public ExpressionState(EvaluationContext context, TypedValue rootObject) {
082                this(context, rootObject, new SpelParserConfiguration(false, false));
083        }
084
085        public ExpressionState(EvaluationContext context, TypedValue rootObject, SpelParserConfiguration configuration) {
086                Assert.notNull(context, "EvaluationContext must not be null");
087                Assert.notNull(configuration, "SpelParserConfiguration must not be null");
088                this.relatedContext = context;
089                this.rootObject = rootObject;
090                this.configuration = configuration;
091        }
092
093
094        private void ensureVariableScopesInitialized() {
095                if (this.variableScopes == null) {
096                        this.variableScopes = new Stack<VariableScope>();
097                        // top level empty variable scope
098                        this.variableScopes.add(new VariableScope());
099                }
100                if (this.scopeRootObjects == null) {
101                        this.scopeRootObjects = new Stack<TypedValue>();
102                }
103        }
104
105        /**
106         * The active context object is what unqualified references to properties/etc are resolved against.
107         */
108        public TypedValue getActiveContextObject() {
109                if (CollectionUtils.isEmpty(this.contextObjects)) {
110                        return this.rootObject;
111                }
112                return this.contextObjects.peek();
113        }
114
115        public void pushActiveContextObject(TypedValue obj) {
116                if (this.contextObjects == null) {
117                        this.contextObjects = new Stack<TypedValue>();
118                }
119                this.contextObjects.push(obj);
120        }
121
122        public void popActiveContextObject() {
123                if (this.contextObjects == null) {
124                        this.contextObjects = new Stack<TypedValue>();
125                }
126                this.contextObjects.pop();
127        }
128
129        public TypedValue getRootContextObject() {
130                return this.rootObject;
131        }
132
133        public TypedValue getScopeRootContextObject() {
134                if (CollectionUtils.isEmpty(this.scopeRootObjects)) {
135                        return this.rootObject;
136                }
137                return this.scopeRootObjects.peek();
138        }
139
140        public void setVariable(String name, Object value) {
141                this.relatedContext.setVariable(name, value);
142        }
143
144        public TypedValue lookupVariable(String name) {
145                Object value = this.relatedContext.lookupVariable(name);
146                return (value != null ? new TypedValue(value) : TypedValue.NULL);
147        }
148
149        public TypeComparator getTypeComparator() {
150                return this.relatedContext.getTypeComparator();
151        }
152
153        public Class<?> findType(String type) throws EvaluationException {
154                return this.relatedContext.getTypeLocator().findType(type);
155        }
156
157        public Object convertValue(Object value, TypeDescriptor targetTypeDescriptor) throws EvaluationException {
158                return this.relatedContext.getTypeConverter().convertValue(value,
159                                TypeDescriptor.forObject(value), targetTypeDescriptor);
160        }
161
162        public TypeConverter getTypeConverter() {
163                return this.relatedContext.getTypeConverter();
164        }
165
166        public Object convertValue(TypedValue value, TypeDescriptor targetTypeDescriptor) throws EvaluationException {
167                Object val = value.getValue();
168                return this.relatedContext.getTypeConverter().convertValue(val, TypeDescriptor.forObject(val), targetTypeDescriptor);
169        }
170
171        /*
172         * A new scope is entered when a function is invoked.
173         */
174        public void enterScope(Map<String, Object> argMap) {
175                ensureVariableScopesInitialized();
176                this.variableScopes.push(new VariableScope(argMap));
177                this.scopeRootObjects.push(getActiveContextObject());
178        }
179
180        public void enterScope() {
181                ensureVariableScopesInitialized();
182                this.variableScopes.push(new VariableScope(Collections.<String,Object>emptyMap()));
183                this.scopeRootObjects.push(getActiveContextObject());
184        }
185
186        public void enterScope(String name, Object value) {
187                ensureVariableScopesInitialized();
188                this.variableScopes.push(new VariableScope(name, value));
189                this.scopeRootObjects.push(getActiveContextObject());
190        }
191
192        public void exitScope() {
193                ensureVariableScopesInitialized();
194                this.variableScopes.pop();
195                this.scopeRootObjects.pop();
196        }
197
198        public void setLocalVariable(String name, Object value) {
199                ensureVariableScopesInitialized();
200                this.variableScopes.peek().setVariable(name, value);
201        }
202
203        public Object lookupLocalVariable(String name) {
204                ensureVariableScopesInitialized();
205                int scopeNumber = this.variableScopes.size() - 1;
206                for (int i = scopeNumber; i >= 0; i--) {
207                        if (this.variableScopes.get(i).definesVariable(name)) {
208                                return this.variableScopes.get(i).lookupVariable(name);
209                        }
210                }
211                return null;
212        }
213
214        public TypedValue operate(Operation op, Object left, Object right) throws EvaluationException {
215                OperatorOverloader overloader = this.relatedContext.getOperatorOverloader();
216                if (overloader.overridesOperation(op, left, right)) {
217                        Object returnValue = overloader.operate(op, left, right);
218                        return new TypedValue(returnValue);
219                }
220                else {
221                        String leftType = (left == null ? "null" : left.getClass().getName());
222                        String rightType = (right == null? "null" : right.getClass().getName());
223                        throw new SpelEvaluationException(SpelMessage.OPERATOR_NOT_SUPPORTED_BETWEEN_TYPES, op, leftType, rightType);
224                }
225        }
226
227        public List<PropertyAccessor> getPropertyAccessors() {
228                return this.relatedContext.getPropertyAccessors();
229        }
230
231        public EvaluationContext getEvaluationContext() {
232                return this.relatedContext;
233        }
234
235        public SpelParserConfiguration getConfiguration() {
236                return this.configuration;
237        }
238
239
240        /**
241         * A new scope is entered when a function is called and it is used to hold the
242         * parameters to the function call. If the names of the parameters clash with
243         * those in a higher level scope, those in the higher level scope will not be
244         * accessible whilst the function is executing. When the function returns,
245         * the scope is exited.
246         */
247        private static class VariableScope {
248
249                private final Map<String, Object> vars = new HashMap<String, Object>();
250
251                public VariableScope() {
252                }
253
254                public VariableScope(Map<String, Object> arguments) {
255                        if (arguments != null) {
256                                this.vars.putAll(arguments);
257                        }
258                }
259
260                public VariableScope(String name, Object value) {
261                        this.vars.put(name,value);
262                }
263
264                public Object lookupVariable(String name) {
265                        return this.vars.get(name);
266                }
267
268                public void setVariable(String name, Object value) {
269                        this.vars.put(name,value);
270                }
271
272                public boolean definesVariable(String name) {
273                        return this.vars.containsKey(name);
274                }
275        }
276
277}