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