1
1
/*
2
- * Copyright 2002-2023 the original author or authors.
2
+ * Copyright 2002-2024 the original author or authors.
3
3
*
4
4
* Licensed under the Apache License, Version 2.0 (the "License");
5
5
* you may not use this file except in compliance with the License.
17
17
package org .springframework .expression .spel ;
18
18
19
19
import java .util .ArrayDeque ;
20
- import java .util .Collections ;
21
20
import java .util .Deque ;
22
21
import java .util .HashMap ;
23
22
import java .util .List ;
@@ -74,9 +73,9 @@ public class ExpressionState {
74
73
// For example:
75
74
// #list1.?[#list2.contains(#this)]
76
75
// On entering the selection we enter a new scope, and #this is now the
77
- // element from list1
76
+ // element from list1.
78
77
@ Nullable
79
- private ArrayDeque <TypedValue > scopeRootObjects ;
78
+ private Deque <TypedValue > scopeRootObjects ;
80
79
81
80
82
81
public ExpressionState (EvaluationContext context ) {
@@ -112,18 +111,12 @@ public TypedValue getActiveContextObject() {
112
111
}
113
112
114
113
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 );
119
115
}
120
116
121
117
public void popActiveContextObject () {
122
- if (this .contextObjects == null ) {
123
- this .contextObjects = new ArrayDeque <>();
124
- }
125
118
try {
126
- this . contextObjects .pop ();
119
+ initContextObjects () .pop ();
127
120
}
128
121
catch (NoSuchElementException ex ) {
129
122
throw new IllegalStateException ("Cannot pop active context object: stack is empty" );
@@ -168,6 +161,14 @@ public void setVariable(String name, @Nullable Object value) {
168
161
this .relatedContext .setVariable (name , value );
169
162
}
170
163
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
+ */
171
172
public TypedValue lookupVariable (String name ) {
172
173
Object value = this .relatedContext .lookupVariable (name );
173
174
return (value != null ? new TypedValue (value ) : TypedValue .NULL );
@@ -181,6 +182,10 @@ public Class<?> findType(String type) throws EvaluationException {
181
182
return this .relatedContext .getTypeLocator ().findType (type );
182
183
}
183
184
185
+ public TypeConverter getTypeConverter () {
186
+ return this .relatedContext .getTypeConverter ();
187
+ }
188
+
184
189
public Object convertValue (Object value , TypeDescriptor targetTypeDescriptor ) throws EvaluationException {
185
190
Object result = this .relatedContext .getTypeConverter ().convertValue (
186
191
value , TypeDescriptor .forObject (value ), targetTypeDescriptor );
@@ -190,44 +195,68 @@ public Object convertValue(Object value, TypeDescriptor targetTypeDescriptor) th
190
195
return result ;
191
196
}
192
197
193
- public TypeConverter getTypeConverter () {
194
- return this .relatedContext .getTypeConverter ();
195
- }
196
-
197
198
@ Nullable
198
199
public Object convertValue (TypedValue value , TypeDescriptor targetTypeDescriptor ) throws EvaluationException {
199
200
Object val = value .getValue ();
200
201
return this .relatedContext .getTypeConverter ().convertValue (
201
202
val , TypeDescriptor .forObject (val ), targetTypeDescriptor );
202
203
}
203
204
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.
206
208
*/
207
- public void enterScope (@ Nullable Map <String , Object > argMap ) {
208
- initVariableScopes ().push (new VariableScope (argMap ));
209
- initScopeRootObjects ().push (getActiveContextObject ());
210
- }
211
-
212
209
public void enterScope () {
213
- initVariableScopes ().push (new VariableScope (Collections . emptyMap () ));
210
+ initVariableScopes ().push (new VariableScope ());
214
211
initScopeRootObjects ().push (getActiveContextObject ());
215
212
}
216
213
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
+ */
217
221
public void enterScope (String name , Object value ) {
218
222
initVariableScopes ().push (new VariableScope (name , value ));
219
223
initScopeRootObjects ().push (getActiveContextObject ());
220
224
}
221
225
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
+
222
237
public void exitScope () {
223
238
initVariableScopes ().pop ();
224
239
initScopeRootObjects ().pop ();
225
240
}
226
241
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
+ */
227
250
public void setLocalVariable (String name , Object value ) {
228
251
initVariableScopes ().element ().setVariable (name , value );
229
252
}
230
253
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
+ */
231
260
@ Nullable
232
261
public Object lookupLocalVariable (String name ) {
233
262
for (VariableScope scope : initVariableScopes ()) {
@@ -238,13 +267,11 @@ public Object lookupLocalVariable(String name) {
238
267
return null ;
239
268
}
240
269
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 <>();
246
273
}
247
- return this .variableScopes ;
274
+ return this .contextObjects ;
248
275
}
249
276
250
277
private Deque <TypedValue > initScopeRootObjects () {
@@ -254,6 +281,15 @@ private Deque<TypedValue> initScopeRootObjects() {
254
281
return this .scopeRootObjects ;
255
282
}
256
283
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
+
257
293
public TypedValue operate (Operation op , @ Nullable Object left , @ Nullable Object right ) throws EvaluationException {
258
294
OperatorOverloader overloader = this .relatedContext .getOperatorOverloader ();
259
295
if (overloader .overridesOperation (op , left , right )) {
@@ -281,40 +317,40 @@ public SpelParserConfiguration getConfiguration() {
281
317
282
318
283
319
/**
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 .
289
325
*/
290
326
private static class VariableScope {
291
327
292
- private final Map <String , Object > vars = new HashMap <>();
328
+ private final Map <String , Object > variables = new HashMap <>();
293
329
294
- public VariableScope () {
330
+ VariableScope () {
295
331
}
296
332
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 );
301
335
}
302
336
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
+ }
305
341
}
306
342
307
343
@ Nullable
308
- public Object lookupVariable (String name ) {
309
- return this .vars .get (name );
344
+ Object lookupVariable (String name ) {
345
+ return this .variables .get (name );
310
346
}
311
347
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 );
314
350
}
315
351
316
- public boolean definesVariable (String name ) {
317
- return this .vars .containsKey (name );
352
+ boolean definesVariable (String name ) {
353
+ return this .variables .containsKey (name );
318
354
}
319
355
}
320
356
0 commit comments