Skip to content

Commit c1f0faa

Browse files
committed
Polish ExpressionState
1 parent cd2a3fd commit c1f0faa

File tree

1 file changed

+85
-49
lines changed

1 file changed

+85
-49
lines changed

spring-expression/src/main/java/org/springframework/expression/spel/ExpressionState.java

Lines changed: 85 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2023 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,7 +17,6 @@
1717
package org.springframework.expression.spel;
1818

1919
import java.util.ArrayDeque;
20-
import java.util.Collections;
2120
import java.util.Deque;
2221
import java.util.HashMap;
2322
import java.util.List;
@@ -74,9 +73,9 @@ public class ExpressionState {
7473
// For example:
7574
// #list1.?[#list2.contains(#this)]
7675
// On entering the selection we enter a new scope, and #this is now the
77-
// element from list1
76+
// element from list1.
7877
@Nullable
79-
private ArrayDeque<TypedValue> scopeRootObjects;
78+
private Deque<TypedValue> scopeRootObjects;
8079

8180

8281
public ExpressionState(EvaluationContext context) {
@@ -112,18 +111,12 @@ public TypedValue getActiveContextObject() {
112111
}
113112

114113
public void pushActiveContextObject(TypedValue obj) {
115-
if (this.contextObjects == null) {
116-
this.contextObjects = new ArrayDeque<>();
117-
}
118-
this.contextObjects.push(obj);
114+
initContextObjects().push(obj);
119115
}
120116

121117
public void popActiveContextObject() {
122-
if (this.contextObjects == null) {
123-
this.contextObjects = new ArrayDeque<>();
124-
}
125118
try {
126-
this.contextObjects.pop();
119+
initContextObjects().pop();
127120
}
128121
catch (NoSuchElementException ex) {
129122
throw new IllegalStateException("Cannot pop active context object: stack is empty");
@@ -168,6 +161,14 @@ public void setVariable(String name, @Nullable Object value) {
168161
this.relatedContext.setVariable(name, value);
169162
}
170163

164+
/**
165+
* Look up a named global variable in the evaluation context.
166+
* @param name the name of the variable to look up
167+
* @return a {@link TypedValue} containing the value of the variable, or
168+
* {@link TypedValue#NULL} if the variable does not exist
169+
* @see #assignVariable(String, Supplier)
170+
* @see #setVariable(String, Object)
171+
*/
171172
public TypedValue lookupVariable(String name) {
172173
Object value = this.relatedContext.lookupVariable(name);
173174
return (value != null ? new TypedValue(value) : TypedValue.NULL);
@@ -181,6 +182,10 @@ public Class<?> findType(String type) throws EvaluationException {
181182
return this.relatedContext.getTypeLocator().findType(type);
182183
}
183184

185+
public TypeConverter getTypeConverter() {
186+
return this.relatedContext.getTypeConverter();
187+
}
188+
184189
public Object convertValue(Object value, TypeDescriptor targetTypeDescriptor) throws EvaluationException {
185190
Object result = this.relatedContext.getTypeConverter().convertValue(
186191
value, TypeDescriptor.forObject(value), targetTypeDescriptor);
@@ -190,44 +195,68 @@ public Object convertValue(Object value, TypeDescriptor targetTypeDescriptor) th
190195
return result;
191196
}
192197

193-
public TypeConverter getTypeConverter() {
194-
return this.relatedContext.getTypeConverter();
195-
}
196-
197198
@Nullable
198199
public Object convertValue(TypedValue value, TypeDescriptor targetTypeDescriptor) throws EvaluationException {
199200
Object val = value.getValue();
200201
return this.relatedContext.getTypeConverter().convertValue(
201202
val, TypeDescriptor.forObject(val), targetTypeDescriptor);
202203
}
203204

204-
/*
205-
* A new scope is entered when a function is invoked.
205+
/**
206+
* Enter a new scope with a new {@linkplain #getActiveContextObject() root
207+
* context object} and a new local variable scope.
206208
*/
207-
public void enterScope(@Nullable Map<String, Object> argMap) {
208-
initVariableScopes().push(new VariableScope(argMap));
209-
initScopeRootObjects().push(getActiveContextObject());
210-
}
211-
212209
public void enterScope() {
213-
initVariableScopes().push(new VariableScope(Collections.emptyMap()));
210+
initVariableScopes().push(new VariableScope());
214211
initScopeRootObjects().push(getActiveContextObject());
215212
}
216213

214+
/**
215+
* Enter a new scope with a new {@linkplain #getActiveContextObject() root
216+
* context object} and a new local variable scope containing the supplied
217+
* name/value pair.
218+
* @param name the name of the local variable
219+
* @param value the value of the local variable
220+
*/
217221
public void enterScope(String name, Object value) {
218222
initVariableScopes().push(new VariableScope(name, value));
219223
initScopeRootObjects().push(getActiveContextObject());
220224
}
221225

226+
/**
227+
* Enter a new scope with a new {@linkplain #getActiveContextObject() root
228+
* context object} and a new local variable scope containing the supplied
229+
* name/value pairs.
230+
* @param variables a map containing name/value pairs for local variables
231+
*/
232+
public void enterScope(@Nullable Map<String, Object> variables) {
233+
initVariableScopes().push(new VariableScope(variables));
234+
initScopeRootObjects().push(getActiveContextObject());
235+
}
236+
222237
public void exitScope() {
223238
initVariableScopes().pop();
224239
initScopeRootObjects().pop();
225240
}
226241

242+
/**
243+
* Set a local variable with the given name to the supplied value within the
244+
* current scope.
245+
* <p>If a local variable with the given name already exists, it will be
246+
* overwritten.
247+
* @param name the name of the local variable
248+
* @param value the value of the local variable
249+
*/
227250
public void setLocalVariable(String name, Object value) {
228251
initVariableScopes().element().setVariable(name, value);
229252
}
230253

254+
/**
255+
* Look up the value of the local variable with the given name.
256+
* @param name the name of the local variable
257+
* @return the value of the local variable, or {@code null} if the variable
258+
* does not exist in the current scope
259+
*/
231260
@Nullable
232261
public Object lookupLocalVariable(String name) {
233262
for (VariableScope scope : initVariableScopes()) {
@@ -238,13 +267,11 @@ public Object lookupLocalVariable(String name) {
238267
return null;
239268
}
240269

241-
private Deque<VariableScope> initVariableScopes() {
242-
if (this.variableScopes == null) {
243-
this.variableScopes = new ArrayDeque<>();
244-
// top-level empty variable scope
245-
this.variableScopes.add(new VariableScope());
270+
private Deque<TypedValue> initContextObjects() {
271+
if (this.contextObjects == null) {
272+
this.contextObjects = new ArrayDeque<>();
246273
}
247-
return this.variableScopes;
274+
return this.contextObjects;
248275
}
249276

250277
private Deque<TypedValue> initScopeRootObjects() {
@@ -254,6 +281,15 @@ private Deque<TypedValue> initScopeRootObjects() {
254281
return this.scopeRootObjects;
255282
}
256283

284+
private Deque<VariableScope> initVariableScopes() {
285+
if (this.variableScopes == null) {
286+
this.variableScopes = new ArrayDeque<>();
287+
// top-level empty variable scope
288+
this.variableScopes.add(new VariableScope());
289+
}
290+
return this.variableScopes;
291+
}
292+
257293
public TypedValue operate(Operation op, @Nullable Object left, @Nullable Object right) throws EvaluationException {
258294
OperatorOverloader overloader = this.relatedContext.getOperatorOverloader();
259295
if (overloader.overridesOperation(op, left, right)) {
@@ -281,40 +317,40 @@ public SpelParserConfiguration getConfiguration() {
281317

282318

283319
/**
284-
* A new scope is entered when a function is called and it is used to hold the
285-
* parameters to the function call. If the names of the parameters clash with
286-
* those in a higher level scope, those in the higher level scope will not be
287-
* accessible whilst the function is executing. When the function returns,
288-
* the scope is exited.
320+
* A new local variable scope is entered when a new expression scope is
321+
* entered and exited when the corresponding expression scope is exited.
322+
*
323+
* <p>If variable names clash with those in a higher level scope, those in
324+
* the higher level scope will not be accessible within the current scope.
289325
*/
290326
private static class VariableScope {
291327

292-
private final Map<String, Object> vars = new HashMap<>();
328+
private final Map<String, Object> variables = new HashMap<>();
293329

294-
public VariableScope() {
330+
VariableScope() {
295331
}
296332

297-
public VariableScope(@Nullable Map<String, Object> arguments) {
298-
if (arguments != null) {
299-
this.vars.putAll(arguments);
300-
}
333+
VariableScope(String name, Object value) {
334+
this.variables.put(name, value);
301335
}
302336

303-
public VariableScope(String name, Object value) {
304-
this.vars.put(name, value);
337+
VariableScope(@Nullable Map<String, Object> variables) {
338+
if (variables != null) {
339+
this.variables.putAll(variables);
340+
}
305341
}
306342

307343
@Nullable
308-
public Object lookupVariable(String name) {
309-
return this.vars.get(name);
344+
Object lookupVariable(String name) {
345+
return this.variables.get(name);
310346
}
311347

312-
public void setVariable(String name, Object value) {
313-
this.vars.put(name,value);
348+
void setVariable(String name, Object value) {
349+
this.variables.put(name,value);
314350
}
315351

316-
public boolean definesVariable(String name) {
317-
return this.vars.containsKey(name);
352+
boolean definesVariable(String name) {
353+
return this.variables.containsKey(name);
318354
}
319355
}
320356

0 commit comments

Comments
 (0)