ExpressionState.getConfiguration() should never return null

Issue: SPR-11031
(cherry picked from commit 4aab315)
This commit is contained in:
Juergen Hoeller 2013-10-26 15:18:34 +02:00
parent 35d53af2ca
commit db056ae0e0
3 changed files with 84 additions and 76 deletions

View File

@ -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,23 +92,22 @@ 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) {

View File

@ -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;

View File

@ -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;
}
}
} }