ExpressionState.getConfiguration() should never return null
Issue: SPR-11031
(cherry picked from commit 4aab315)
This commit is contained in:
parent
35d53af2ca
commit
db056ae0e0
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2012 the original author or authors.
|
* Copyright 2002-2013 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.
|
||||||
|
|
@ -30,14 +30,18 @@ import org.springframework.expression.PropertyAccessor;
|
||||||
import org.springframework.expression.TypeComparator;
|
import org.springframework.expression.TypeComparator;
|
||||||
import org.springframework.expression.TypeConverter;
|
import org.springframework.expression.TypeConverter;
|
||||||
import org.springframework.expression.TypedValue;
|
import org.springframework.expression.TypedValue;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An ExpressionState is for maintaining per-expression-evaluation state, any changes to it are not seen by other
|
* An ExpressionState is for maintaining per-expression-evaluation state, any changes to
|
||||||
* expressions but it gives a place to hold local variables and for component expressions in a compound expression to
|
* it are not seen by other expressions but it gives a place to hold local variables and
|
||||||
* communicate state. This is in contrast to the EvaluationContext, which is shared amongst expression evaluations, and
|
* for component expressions in a compound expression to communicate state. This is in
|
||||||
* any changes to it will be seen by other expressions or any code that chooses to ask questions of the context.
|
* contrast to the EvaluationContext, which is shared amongst expression evaluations, and
|
||||||
|
* any changes to it will be seen by other expressions or any code that chooses to ask
|
||||||
|
* questions of the context.
|
||||||
*
|
*
|
||||||
* <p>It also acts as a place for to define common utility routines that the various Ast nodes might need.
|
* <p>It also acts as a place for to define common utility routines that the various AST
|
||||||
|
* nodes might need.
|
||||||
*
|
*
|
||||||
* @author Andy Clement
|
* @author Andy Clement
|
||||||
* @since 3.0
|
* @since 3.0
|
||||||
|
|
@ -46,35 +50,33 @@ public class ExpressionState {
|
||||||
|
|
||||||
private final EvaluationContext relatedContext;
|
private final EvaluationContext relatedContext;
|
||||||
|
|
||||||
|
private final TypedValue rootObject;
|
||||||
|
|
||||||
|
private final SpelParserConfiguration configuration;
|
||||||
|
|
||||||
private Stack<VariableScope> variableScopes;
|
private Stack<VariableScope> variableScopes;
|
||||||
|
|
||||||
private Stack<TypedValue> contextObjects;
|
private Stack<TypedValue> contextObjects;
|
||||||
|
|
||||||
private final TypedValue rootObject;
|
|
||||||
|
|
||||||
private SpelParserConfiguration configuration;
|
|
||||||
|
|
||||||
|
|
||||||
public ExpressionState(EvaluationContext context) {
|
public ExpressionState(EvaluationContext context) {
|
||||||
this.relatedContext = context;
|
this(context, context.getRootObject(), new SpelParserConfiguration(false, false));
|
||||||
this.rootObject = context.getRootObject();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ExpressionState(EvaluationContext context, SpelParserConfiguration configuration) {
|
public ExpressionState(EvaluationContext context, SpelParserConfiguration configuration) {
|
||||||
this.relatedContext = context;
|
this(context, context.getRootObject(), configuration);
|
||||||
this.configuration = configuration;
|
|
||||||
this.rootObject = context.getRootObject();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ExpressionState(EvaluationContext context, TypedValue rootObject) {
|
public ExpressionState(EvaluationContext context, TypedValue rootObject) {
|
||||||
this.relatedContext = context;
|
this(context, rootObject, new SpelParserConfiguration(false, false));
|
||||||
this.rootObject = rootObject;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ExpressionState(EvaluationContext context, TypedValue rootObject, SpelParserConfiguration configuration) {
|
public ExpressionState(EvaluationContext context, TypedValue rootObject, SpelParserConfiguration configuration) {
|
||||||
|
Assert.notNull(context, "EvaluationContext must not be null");
|
||||||
|
Assert.notNull(configuration, "SpelParserConfiguration must not be null");
|
||||||
this.relatedContext = context;
|
this.relatedContext = context;
|
||||||
this.configuration = configuration;
|
|
||||||
this.rootObject = rootObject;
|
this.rootObject = rootObject;
|
||||||
|
this.configuration = configuration;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -90,22 +92,21 @@ public class ExpressionState {
|
||||||
* The active context object is what unqualified references to properties/etc are resolved against.
|
* The active context object is what unqualified references to properties/etc are resolved against.
|
||||||
*/
|
*/
|
||||||
public TypedValue getActiveContextObject() {
|
public TypedValue getActiveContextObject() {
|
||||||
if (this.contextObjects==null || this.contextObjects.isEmpty()) {
|
if (this.contextObjects == null || this.contextObjects.isEmpty()) {
|
||||||
return this.rootObject;
|
return this.rootObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.contextObjects.peek();
|
return this.contextObjects.peek();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void pushActiveContextObject(TypedValue obj) {
|
public void pushActiveContextObject(TypedValue obj) {
|
||||||
if (this.contextObjects==null) {
|
if (this.contextObjects == null) {
|
||||||
this.contextObjects = new Stack<TypedValue>();
|
this.contextObjects = new Stack<TypedValue>();
|
||||||
}
|
}
|
||||||
this.contextObjects.push(obj);
|
this.contextObjects.push(obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void popActiveContextObject() {
|
public void popActiveContextObject() {
|
||||||
if (this.contextObjects==null) {
|
if (this.contextObjects == null) {
|
||||||
this.contextObjects = new Stack<TypedValue>();
|
this.contextObjects = new Stack<TypedValue>();
|
||||||
}
|
}
|
||||||
this.contextObjects.pop();
|
this.contextObjects.pop();
|
||||||
|
|
@ -138,7 +139,8 @@ public class ExpressionState {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Object convertValue(Object value, TypeDescriptor targetTypeDescriptor) throws EvaluationException {
|
public Object convertValue(Object value, TypeDescriptor targetTypeDescriptor) throws EvaluationException {
|
||||||
return this.relatedContext.getTypeConverter().convertValue(value, TypeDescriptor.forObject(value), targetTypeDescriptor);
|
return this.relatedContext.getTypeConverter().convertValue(value,
|
||||||
|
TypeDescriptor.forObject(value), targetTypeDescriptor);
|
||||||
}
|
}
|
||||||
|
|
||||||
public TypeConverter getTypeConverter() {
|
public TypeConverter getTypeConverter() {
|
||||||
|
|
@ -151,9 +153,8 @@ public class ExpressionState {
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* A new scope is entered when a function is invoked
|
* A new scope is entered when a function is invoked.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public void enterScope(Map<String, Object> argMap) {
|
public void enterScope(Map<String, Object> argMap) {
|
||||||
ensureVariableScopesInitialized();
|
ensureVariableScopesInitialized();
|
||||||
this.variableScopes.push(new VariableScope(argMap));
|
this.variableScopes.push(new VariableScope(argMap));
|
||||||
|
|
@ -192,8 +193,8 @@ public class ExpressionState {
|
||||||
return new TypedValue(returnValue);
|
return new TypedValue(returnValue);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
String leftType = (left==null?"null":left.getClass().getName());
|
String leftType = (left == null ? "null" : left.getClass().getName());
|
||||||
String rightType = (right==null?"null":right.getClass().getName());
|
String rightType = (right == null? "null" : right.getClass().getName());
|
||||||
throw new SpelEvaluationException(SpelMessage.OPERATOR_NOT_SUPPORTED_BETWEEN_TYPES, op, leftType, rightType);
|
throw new SpelEvaluationException(SpelMessage.OPERATOR_NOT_SUPPORTED_BETWEEN_TYPES, op, leftType, rightType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -210,16 +211,20 @@ public class ExpressionState {
|
||||||
return this.configuration;
|
return this.configuration;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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
|
* A new scope is entered when a function is called and it is used to hold the
|
||||||
* of the parameters clash with those in a higher level scope, those in the higher level scope will not be accessible whilst
|
* parameters to the function call. If the names of the parameters clash with
|
||||||
* the function is executing. When the function returns the 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,
|
||||||
|
* the scope is exited.
|
||||||
*/
|
*/
|
||||||
private static class VariableScope {
|
private static class VariableScope {
|
||||||
|
|
||||||
private final Map<String, Object> vars = new HashMap<String, Object>();
|
private final Map<String, Object> vars = new HashMap<String, Object>();
|
||||||
|
|
||||||
public VariableScope() { }
|
public VariableScope() {
|
||||||
|
}
|
||||||
|
|
||||||
public VariableScope(Map<String, Object> arguments) {
|
public VariableScope(Map<String, Object> arguments) {
|
||||||
if (arguments != null) {
|
if (arguments != null) {
|
||||||
|
|
|
||||||
|
|
@ -49,8 +49,7 @@ public class SpelParserConfiguration {
|
||||||
* @param autoGrowCollections if collections should automatically grow
|
* @param autoGrowCollections if collections should automatically grow
|
||||||
* @param maximumAutoGrowSize the maximum size that the collection can auto grow
|
* @param maximumAutoGrowSize the maximum size that the collection can auto grow
|
||||||
*/
|
*/
|
||||||
public SpelParserConfiguration(boolean autoGrowNullReferences,
|
public SpelParserConfiguration(boolean autoGrowNullReferences, boolean autoGrowCollections, int maximumAutoGrowSize) {
|
||||||
boolean autoGrowCollections, int maximumAutoGrowSize) {
|
|
||||||
this.autoGrowNullReferences = autoGrowNullReferences;
|
this.autoGrowNullReferences = autoGrowNullReferences;
|
||||||
this.autoGrowCollections = autoGrowCollections;
|
this.autoGrowCollections = autoGrowCollections;
|
||||||
this.maximumAutoGrowSize = maximumAutoGrowSize;
|
this.maximumAutoGrowSize = maximumAutoGrowSize;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2012 the original author or authors.
|
* Copyright 2002-2013 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.
|
||||||
|
|
@ -67,47 +67,20 @@ public class PropertyOrFieldReference extends SpelNodeImpl {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static class AccessorLValue implements ValueRef {
|
|
||||||
private PropertyOrFieldReference ref;
|
|
||||||
private TypedValue contextObject;
|
|
||||||
private EvaluationContext eContext;
|
|
||||||
private boolean isAutoGrowNullReferences;
|
|
||||||
|
|
||||||
public AccessorLValue(
|
|
||||||
PropertyOrFieldReference propertyOrFieldReference,
|
|
||||||
TypedValue activeContextObject,
|
|
||||||
EvaluationContext evaluationContext, boolean isAutoGrowNullReferences) {
|
|
||||||
this.ref = propertyOrFieldReference;
|
|
||||||
this.contextObject = activeContextObject;
|
|
||||||
this.eContext =evaluationContext;
|
|
||||||
this.isAutoGrowNullReferences = isAutoGrowNullReferences;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TypedValue getValue() {
|
|
||||||
return ref.getValueInternal(contextObject,eContext,isAutoGrowNullReferences);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setValue(Object newValue) {
|
|
||||||
ref.writeProperty(contextObject,eContext, ref.name, newValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isWritable() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ValueRef getValueRef(ExpressionState state) throws EvaluationException {
|
public ValueRef getValueRef(ExpressionState state) throws EvaluationException {
|
||||||
return new AccessorLValue(this,state.getActiveContextObject(),state.getEvaluationContext(),state.getConfiguration().isAutoGrowNullReferences());
|
return new AccessorLValue(this, state.getActiveContextObject(), state.getEvaluationContext(),
|
||||||
|
state.getConfiguration().isAutoGrowNullReferences());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
|
public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
|
||||||
return getValueInternal(state.getActiveContextObject(), state.getEvaluationContext(), state.getConfiguration().isAutoGrowNullReferences());
|
return getValueInternal(state.getActiveContextObject(), state.getEvaluationContext(),
|
||||||
|
state.getConfiguration().isAutoGrowNullReferences());
|
||||||
}
|
}
|
||||||
|
|
||||||
private TypedValue getValueInternal(TypedValue contextObject, EvaluationContext eContext, boolean isAutoGrowNullReferences) throws EvaluationException {
|
private TypedValue getValueInternal(TypedValue contextObject, EvaluationContext eContext,
|
||||||
|
boolean isAutoGrowNullReferences) throws EvaluationException {
|
||||||
|
|
||||||
TypedValue result = readProperty(contextObject, eContext, this.name);
|
TypedValue result = readProperty(contextObject, eContext, this.name);
|
||||||
|
|
||||||
|
|
@ -139,7 +112,7 @@ public class PropertyOrFieldReference extends SpelNodeImpl {
|
||||||
try {
|
try {
|
||||||
if (isWritableProperty(this.name,contextObject,eContext)) {
|
if (isWritableProperty(this.name,contextObject,eContext)) {
|
||||||
Map<?,?> newMap = HashMap.class.newInstance();
|
Map<?,?> newMap = HashMap.class.newInstance();
|
||||||
writeProperty(contextObject, eContext, name, newMap);
|
writeProperty(contextObject, eContext, this.name, newMap);
|
||||||
result = readProperty(contextObject, eContext, this.name);
|
result = readProperty(contextObject, eContext, this.name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -158,7 +131,7 @@ public class PropertyOrFieldReference extends SpelNodeImpl {
|
||||||
try {
|
try {
|
||||||
if (isWritableProperty(this.name,contextObject,eContext)) {
|
if (isWritableProperty(this.name,contextObject,eContext)) {
|
||||||
Object newObject = result.getTypeDescriptor().getType().newInstance();
|
Object newObject = result.getTypeDescriptor().getType().newInstance();
|
||||||
writeProperty(contextObject, eContext, name, newObject);
|
writeProperty(contextObject, eContext, this.name, newObject);
|
||||||
result = readProperty(contextObject, eContext, this.name);
|
result = readProperty(contextObject, eContext, this.name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -192,14 +165,11 @@ public class PropertyOrFieldReference extends SpelNodeImpl {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempt to read the named property from the current context object.
|
* Attempt to read the named property from the current context object.
|
||||||
* @param state the evaluation state
|
|
||||||
* @param name the name of the property
|
|
||||||
* @return the value of the property
|
* @return the value of the property
|
||||||
* @throws SpelEvaluationException if any problem accessing the property or it cannot be found
|
* @throws SpelEvaluationException if any problem accessing the property or it cannot be found
|
||||||
*/
|
*/
|
||||||
private TypedValue readProperty(TypedValue contextObject, EvaluationContext eContext, String name) throws EvaluationException {
|
private TypedValue readProperty(TypedValue contextObject, EvaluationContext eContext, String name) throws EvaluationException {
|
||||||
Object targetObject = contextObject.getValue();
|
Object targetObject = contextObject.getValue();
|
||||||
|
|
||||||
if (targetObject == null && this.nullSafe) {
|
if (targetObject == null && this.nullSafe) {
|
||||||
return TypedValue.NULL;
|
return TypedValue.NULL;
|
||||||
}
|
}
|
||||||
|
|
@ -249,8 +219,7 @@ public class PropertyOrFieldReference extends SpelNodeImpl {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void writeProperty(TypedValue contextObject, EvaluationContext eContext, String name, Object newValue) throws SpelEvaluationException {
|
private void writeProperty(TypedValue contextObject, EvaluationContext eContext, String name, Object newValue) throws SpelEvaluationException {
|
||||||
|
if (contextObject.getValue() == null && this.nullSafe) {
|
||||||
if (contextObject.getValue() == null && nullSafe) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -353,4 +322,39 @@ public class PropertyOrFieldReference extends SpelNodeImpl {
|
||||||
return resolvers;
|
return resolvers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static class AccessorLValue implements ValueRef {
|
||||||
|
|
||||||
|
private final PropertyOrFieldReference ref;
|
||||||
|
|
||||||
|
private final TypedValue contextObject;
|
||||||
|
|
||||||
|
private final EvaluationContext eContext;
|
||||||
|
|
||||||
|
private final boolean autoGrowNullReferences;
|
||||||
|
|
||||||
|
public AccessorLValue(PropertyOrFieldReference propertyOrFieldReference, TypedValue activeContextObject,
|
||||||
|
EvaluationContext evaluationContext, boolean autoGrowNullReferences) {
|
||||||
|
this.ref = propertyOrFieldReference;
|
||||||
|
this.contextObject = activeContextObject;
|
||||||
|
this.eContext = evaluationContext;
|
||||||
|
this.autoGrowNullReferences = autoGrowNullReferences;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TypedValue getValue() {
|
||||||
|
return this.ref.getValueInternal(this.contextObject, this.eContext, this.autoGrowNullReferences);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setValue(Object newValue) {
|
||||||
|
this.ref.writeProperty(this.contextObject, this.eContext, this.ref.name, newValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isWritable() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue