Browse Source

Polish ExpressionState

pull/32294/head
Sam Brannen 2 years ago
parent
commit
c1f0faade7
  1. 134
      spring-expression/src/main/java/org/springframework/expression/spel/ExpressionState.java

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

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -17,7 +17,6 @@ @@ -17,7 +17,6 @@
package org.springframework.expression.spel;
import java.util.ArrayDeque;
import java.util.Collections;
import java.util.Deque;
import java.util.HashMap;
import java.util.List;
@ -74,9 +73,9 @@ public class ExpressionState { @@ -74,9 +73,9 @@ public class ExpressionState {
// For example:
// #list1.?[#list2.contains(#this)]
// On entering the selection we enter a new scope, and #this is now the
// element from list1
// element from list1.
@Nullable
private ArrayDeque<TypedValue> scopeRootObjects;
private Deque<TypedValue> scopeRootObjects;
public ExpressionState(EvaluationContext context) {
@ -112,18 +111,12 @@ public class ExpressionState { @@ -112,18 +111,12 @@ public class ExpressionState {
}
public void pushActiveContextObject(TypedValue obj) {
if (this.contextObjects == null) {
this.contextObjects = new ArrayDeque<>();
}
this.contextObjects.push(obj);
initContextObjects().push(obj);
}
public void popActiveContextObject() {
if (this.contextObjects == null) {
this.contextObjects = new ArrayDeque<>();
}
try {
this.contextObjects.pop();
initContextObjects().pop();
}
catch (NoSuchElementException ex) {
throw new IllegalStateException("Cannot pop active context object: stack is empty");
@ -168,6 +161,14 @@ public class ExpressionState { @@ -168,6 +161,14 @@ public class ExpressionState {
this.relatedContext.setVariable(name, value);
}
/**
* Look up a named global variable in the evaluation context.
* @param name the name of the variable to look up
* @return a {@link TypedValue} containing the value of the variable, or
* {@link TypedValue#NULL} if the variable does not exist
* @see #assignVariable(String, Supplier)
* @see #setVariable(String, Object)
*/
public TypedValue lookupVariable(String name) {
Object value = this.relatedContext.lookupVariable(name);
return (value != null ? new TypedValue(value) : TypedValue.NULL);
@ -181,6 +182,10 @@ public class ExpressionState { @@ -181,6 +182,10 @@ public class ExpressionState {
return this.relatedContext.getTypeLocator().findType(type);
}
public TypeConverter getTypeConverter() {
return this.relatedContext.getTypeConverter();
}
public Object convertValue(Object value, TypeDescriptor targetTypeDescriptor) throws EvaluationException {
Object result = this.relatedContext.getTypeConverter().convertValue(
value, TypeDescriptor.forObject(value), targetTypeDescriptor);
@ -190,10 +195,6 @@ public class ExpressionState { @@ -190,10 +195,6 @@ public class ExpressionState {
return result;
}
public TypeConverter getTypeConverter() {
return this.relatedContext.getTypeConverter();
}
@Nullable
public Object convertValue(TypedValue value, TypeDescriptor targetTypeDescriptor) throws EvaluationException {
Object val = value.getValue();
@ -201,33 +202,61 @@ public class ExpressionState { @@ -201,33 +202,61 @@ public class ExpressionState {
val, TypeDescriptor.forObject(val), targetTypeDescriptor);
}
/*
* A new scope is entered when a function is invoked.
/**
* Enter a new scope with a new {@linkplain #getActiveContextObject() root
* context object} and a new local variable scope.
*/
public void enterScope(@Nullable Map<String, Object> argMap) {
initVariableScopes().push(new VariableScope(argMap));
initScopeRootObjects().push(getActiveContextObject());
}
public void enterScope() {
initVariableScopes().push(new VariableScope(Collections.emptyMap()));
initVariableScopes().push(new VariableScope());
initScopeRootObjects().push(getActiveContextObject());
}
/**
* Enter a new scope with a new {@linkplain #getActiveContextObject() root
* context object} and a new local variable scope containing the supplied
* name/value pair.
* @param name the name of the local variable
* @param value the value of the local variable
*/
public void enterScope(String name, Object value) {
initVariableScopes().push(new VariableScope(name, value));
initScopeRootObjects().push(getActiveContextObject());
}
/**
* Enter a new scope with a new {@linkplain #getActiveContextObject() root
* context object} and a new local variable scope containing the supplied
* name/value pairs.
* @param variables a map containing name/value pairs for local variables
*/
public void enterScope(@Nullable Map<String, Object> variables) {
initVariableScopes().push(new VariableScope(variables));
initScopeRootObjects().push(getActiveContextObject());
}
public void exitScope() {
initVariableScopes().pop();
initScopeRootObjects().pop();
}
/**
* Set a local variable with the given name to the supplied value within the
* current scope.
* <p>If a local variable with the given name already exists, it will be
* overwritten.
* @param name the name of the local variable
* @param value the value of the local variable
*/
public void setLocalVariable(String name, Object value) {
initVariableScopes().element().setVariable(name, value);
}
/**
* Look up the value of the local variable with the given name.
* @param name the name of the local variable
* @return the value of the local variable, or {@code null} if the variable
* does not exist in the current scope
*/
@Nullable
public Object lookupLocalVariable(String name) {
for (VariableScope scope : initVariableScopes()) {
@ -238,13 +267,11 @@ public class ExpressionState { @@ -238,13 +267,11 @@ public class ExpressionState {
return null;
}
private Deque<VariableScope> initVariableScopes() {
if (this.variableScopes == null) {
this.variableScopes = new ArrayDeque<>();
// top-level empty variable scope
this.variableScopes.add(new VariableScope());
private Deque<TypedValue> initContextObjects() {
if (this.contextObjects == null) {
this.contextObjects = new ArrayDeque<>();
}
return this.variableScopes;
return this.contextObjects;
}
private Deque<TypedValue> initScopeRootObjects() {
@ -254,6 +281,15 @@ public class ExpressionState { @@ -254,6 +281,15 @@ public class ExpressionState {
return this.scopeRootObjects;
}
private Deque<VariableScope> initVariableScopes() {
if (this.variableScopes == null) {
this.variableScopes = new ArrayDeque<>();
// top-level empty variable scope
this.variableScopes.add(new VariableScope());
}
return this.variableScopes;
}
public TypedValue operate(Operation op, @Nullable Object left, @Nullable Object right) throws EvaluationException {
OperatorOverloader overloader = this.relatedContext.getOperatorOverloader();
if (overloader.overridesOperation(op, left, right)) {
@ -281,40 +317,40 @@ public class ExpressionState { @@ -281,40 +317,40 @@ public class ExpressionState {
/**
* A new scope is entered when a function is called and it is used to hold the
* parameters to the function call. If the names of the parameters clash with
* those in a higher level scope, those in the higher level scope will not be
* accessible whilst the function is executing. When the function returns,
* the scope is exited.
* A new local variable scope is entered when a new expression scope is
* entered and exited when the corresponding expression scope is exited.
*
* <p>If variable names clash with those in a higher level scope, those in
* the higher level scope will not be accessible within the current scope.
*/
private static class VariableScope {
private final Map<String, Object> vars = new HashMap<>();
private final Map<String, Object> variables = new HashMap<>();
public VariableScope() {
VariableScope() {
}
public VariableScope(@Nullable Map<String, Object> arguments) {
if (arguments != null) {
this.vars.putAll(arguments);
}
VariableScope(String name, Object value) {
this.variables.put(name, value);
}
public VariableScope(String name, Object value) {
this.vars.put(name, value);
VariableScope(@Nullable Map<String, Object> variables) {
if (variables != null) {
this.variables.putAll(variables);
}
}
@Nullable
public Object lookupVariable(String name) {
return this.vars.get(name);
Object lookupVariable(String name) {
return this.variables.get(name);
}
public void setVariable(String name, Object value) {
this.vars.put(name,value);
void setVariable(String name, Object value) {
this.variables.put(name,value);
}
public boolean definesVariable(String name) {
return this.vars.containsKey(name);
boolean definesVariable(String name) {
return this.variables.containsKey(name);
}
}

Loading…
Cancel
Save