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}