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

Loading…
Cancel
Save