View Javadoc
1   /*
2    * Copyright 2002-2013 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.springframework.expression.spel;
18  
19  import java.util.HashMap;
20  import java.util.List;
21  import java.util.Map;
22  import java.util.Stack;
23  
24  import org.springframework.core.convert.TypeDescriptor;
25  import org.springframework.expression.EvaluationContext;
26  import org.springframework.expression.EvaluationException;
27  import org.springframework.expression.Operation;
28  import org.springframework.expression.OperatorOverloader;
29  import org.springframework.expression.PropertyAccessor;
30  import org.springframework.expression.TypeComparator;
31  import org.springframework.expression.TypeConverter;
32  import org.springframework.expression.TypedValue;
33  import org.springframework.util.Assert;
34  
35  /**
36   * An ExpressionState is for maintaining per-expression-evaluation state, any changes to
37   * it are not seen by other expressions but it gives a place to hold local variables and
38   * for component expressions in a compound expression to communicate state. This is in
39   * contrast to the EvaluationContext, which is shared amongst expression evaluations, and
40   * any changes to it will be seen by other expressions or any code that chooses to ask
41   * questions of the context.
42   *
43   * <p>It also acts as a place for to define common utility routines that the various AST
44   * nodes might need.
45   *
46   * @author Andy Clement
47   * @since 3.0
48   */
49  public class ExpressionState {
50  
51  	private final EvaluationContext relatedContext;
52  
53  	private final TypedValue rootObject;
54  
55  	private final SpelParserConfiguration configuration;
56  
57  	private Stack<VariableScope> variableScopes;
58  
59  	private Stack<TypedValue> contextObjects;
60  
61  
62  	public ExpressionState(EvaluationContext context) {
63  		this(context, context.getRootObject(), new SpelParserConfiguration(false, false));
64  	}
65  
66  	public ExpressionState(EvaluationContext context, SpelParserConfiguration configuration) {
67  		this(context, context.getRootObject(), configuration);
68  	}
69  
70  	public ExpressionState(EvaluationContext context, TypedValue rootObject) {
71  		this(context, rootObject, new SpelParserConfiguration(false, false));
72  	}
73  
74  	public ExpressionState(EvaluationContext context, TypedValue rootObject, SpelParserConfiguration configuration) {
75  		Assert.notNull(context, "EvaluationContext must not be null");
76  		Assert.notNull(configuration, "SpelParserConfiguration must not be null");
77  		this.relatedContext = context;
78  		this.rootObject = rootObject;
79  		this.configuration = configuration;
80  	}
81  
82  
83  	private void ensureVariableScopesInitialized() {
84  		if (this.variableScopes == null) {
85  			this.variableScopes = new Stack<VariableScope>();
86  			// top level empty variable scope
87  			this.variableScopes.add(new VariableScope());
88  		}
89  	}
90  
91  	/**
92  	 * The active context object is what unqualified references to properties/etc are resolved against.
93  	 */
94  	public TypedValue getActiveContextObject() {
95  		if (this.contextObjects == null || this.contextObjects.isEmpty()) {
96  			return this.rootObject;
97  		}
98  		return this.contextObjects.peek();
99  	}
100 
101 	public void pushActiveContextObject(TypedValue obj) {
102 		if (this.contextObjects == null) {
103 			this.contextObjects = new Stack<TypedValue>();
104 		}
105 		this.contextObjects.push(obj);
106 	}
107 
108 	public void popActiveContextObject() {
109 		if (this.contextObjects == null) {
110 			this.contextObjects = new Stack<TypedValue>();
111 		}
112 		this.contextObjects.pop();
113 	}
114 
115 	public TypedValue getRootContextObject() {
116 		return this.rootObject;
117 	}
118 
119 	public void setVariable(String name, Object value) {
120 		this.relatedContext.setVariable(name, value);
121 	}
122 
123 	public TypedValue lookupVariable(String name) {
124 		Object value = this.relatedContext.lookupVariable(name);
125 		if (value == null) {
126 			return TypedValue.NULL;
127 		}
128 		else {
129 			return new TypedValue(value);
130 		}
131 	}
132 
133 	public TypeComparator getTypeComparator() {
134 		return this.relatedContext.getTypeComparator();
135 	}
136 
137 	public Class<?> findType(String type) throws EvaluationException {
138 		return this.relatedContext.getTypeLocator().findType(type);
139 	}
140 
141 	public Object convertValue(Object value, TypeDescriptor targetTypeDescriptor) throws EvaluationException {
142 		return this.relatedContext.getTypeConverter().convertValue(value,
143 				TypeDescriptor.forObject(value), targetTypeDescriptor);
144 	}
145 
146 	public TypeConverter getTypeConverter() {
147 		return this.relatedContext.getTypeConverter();
148 	}
149 
150 	public Object convertValue(TypedValue value, TypeDescriptor targetTypeDescriptor) throws EvaluationException {
151 		Object val = value.getValue();
152 		return this.relatedContext.getTypeConverter().convertValue(val, TypeDescriptor.forObject(val), targetTypeDescriptor);
153 	}
154 
155 	/*
156 	 * A new scope is entered when a function is invoked.
157 	 */
158 	public void enterScope(Map<String, Object> argMap) {
159 		ensureVariableScopesInitialized();
160 		this.variableScopes.push(new VariableScope(argMap));
161 	}
162 
163 	public void enterScope(String name, Object value) {
164 		ensureVariableScopesInitialized();
165 		this.variableScopes.push(new VariableScope(name, value));
166 	}
167 
168 	public void exitScope() {
169 		ensureVariableScopesInitialized();
170 		this.variableScopes.pop();
171 	}
172 
173 	public void setLocalVariable(String name, Object value) {
174 		ensureVariableScopesInitialized();
175 		this.variableScopes.peek().setVariable(name, value);
176 	}
177 
178 	public Object lookupLocalVariable(String name) {
179 		ensureVariableScopesInitialized();
180 		int scopeNumber = this.variableScopes.size() - 1;
181 		for (int i = scopeNumber; i >= 0; i--) {
182 			if (this.variableScopes.get(i).definesVariable(name)) {
183 				return this.variableScopes.get(i).lookupVariable(name);
184 			}
185 		}
186 		return null;
187 	}
188 
189 	public TypedValue operate(Operation op, Object left, Object right) throws EvaluationException {
190 		OperatorOverloader overloader = this.relatedContext.getOperatorOverloader();
191 		if (overloader.overridesOperation(op, left, right)) {
192 			Object returnValue = overloader.operate(op, left, right);
193 			return new TypedValue(returnValue);
194 		}
195 		else {
196 			String leftType = (left == null ? "null" : left.getClass().getName());
197 			String rightType = (right == null? "null" : right.getClass().getName());
198 			throw new SpelEvaluationException(SpelMessage.OPERATOR_NOT_SUPPORTED_BETWEEN_TYPES, op, leftType, rightType);
199 		}
200 	}
201 
202 	public List<PropertyAccessor> getPropertyAccessors() {
203 		return this.relatedContext.getPropertyAccessors();
204 	}
205 
206 	public EvaluationContext getEvaluationContext() {
207 		return this.relatedContext;
208 	}
209 
210 	public SpelParserConfiguration getConfiguration() {
211 		return this.configuration;
212 	}
213 
214 
215 	/**
216 	 * A new scope is entered when a function is called and it is used to hold the
217 	 * parameters to the function call. If the names of the parameters clash with
218 	 * those in a higher level scope, those in the higher level scope will not be
219 	 * accessible whilst the function is executing. When the function returns,
220 	 * the scope is exited.
221 	 */
222 	private static class VariableScope {
223 
224 		private final Map<String, Object> vars = new HashMap<String, Object>();
225 
226 		public VariableScope() {
227 		}
228 
229 		public VariableScope(Map<String, Object> arguments) {
230 			if (arguments != null) {
231 				this.vars.putAll(arguments);
232 			}
233 		}
234 
235 		public VariableScope(String name, Object value) {
236 			this.vars.put(name,value);
237 		}
238 
239 		public Object lookupVariable(String name) {
240 			return this.vars.get(name);
241 		}
242 
243 		public void setVariable(String name, Object value) {
244 			this.vars.put(name,value);
245 		}
246 
247 		public boolean definesVariable(String name) {
248 			return this.vars.containsKey(name);
249 		}
250 	}
251 
252 }