new feature for binder - automatically create lists and entries in lists upon referencing nulls
git-svn-id: https://src.springframework.org/svn/spring-framework/trunk@1300 50f2f4bb-b051-0410-bef5-90022cba6387
This commit is contained in:
parent
e9288fdb4e
commit
1e2cecfd76
|
|
@ -19,6 +19,7 @@ import org.springframework.expression.Expression;
|
||||||
import org.springframework.expression.ExpressionException;
|
import org.springframework.expression.ExpressionException;
|
||||||
import org.springframework.expression.ExpressionParser;
|
import org.springframework.expression.ExpressionParser;
|
||||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||||
|
import org.springframework.expression.spel.standard.SpelExpressionParserConfiguration;
|
||||||
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||||
import org.springframework.expression.spel.support.StandardTypeConverter;
|
import org.springframework.expression.spel.support.StandardTypeConverter;
|
||||||
import org.springframework.ui.format.Formatter;
|
import org.springframework.ui.format.Formatter;
|
||||||
|
|
@ -68,7 +69,10 @@ public class Binder<T> {
|
||||||
public Binder(T model) {
|
public Binder(T model) {
|
||||||
this.model = model;
|
this.model = model;
|
||||||
bindings = new HashMap<String, Binding>();
|
bindings = new HashMap<String, Binding>();
|
||||||
expressionParser = new SpelExpressionParser();
|
int parserConfig =
|
||||||
|
SpelExpressionParserConfiguration.CreateListsOnAttemptToIndexIntoNull |
|
||||||
|
SpelExpressionParserConfiguration.GrowListsOnIndexBeyondSize;
|
||||||
|
expressionParser = new SpelExpressionParser(parserConfig);
|
||||||
typeConverter = new DefaultTypeConverter();
|
typeConverter = new DefaultTypeConverter();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,8 @@ import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import junit.framework.Assert;
|
||||||
|
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Ignore;
|
import org.junit.Ignore;
|
||||||
|
|
@ -146,21 +148,39 @@ public class BinderTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Ignore
|
|
||||||
public void bindHandleNullValueInNestedPath() {
|
public void bindHandleNullValueInNestedPath() {
|
||||||
Binder<TestBean> binder = new Binder<TestBean>(new TestBean());
|
TestBean testbean = new TestBean();
|
||||||
|
Binder<TestBean> binder = new Binder<TestBean>(testbean);
|
||||||
Map<String, String> propertyValues = new HashMap<String, String>();
|
Map<String, String> propertyValues = new HashMap<String, String>();
|
||||||
// TODO should auto add(new Address) at 0
|
|
||||||
|
// EL configured with some options from SpelExpressionParserConfiguration:
|
||||||
|
// (see where Binder creates the parser)
|
||||||
|
// - new addresses List is created if null
|
||||||
|
// - new entries automatically built if List is currently too short - all new entries
|
||||||
|
// are new instances of the type of the list entry, they are not null.
|
||||||
|
// not currently doing anything for maps or arrays
|
||||||
|
|
||||||
propertyValues.put("addresses[0].street", "4655 Macy Lane");
|
propertyValues.put("addresses[0].street", "4655 Macy Lane");
|
||||||
propertyValues.put("addresses[0].city", "Melbourne");
|
propertyValues.put("addresses[0].city", "Melbourne");
|
||||||
propertyValues.put("addresses[0].state", "FL");
|
propertyValues.put("addresses[0].state", "FL");
|
||||||
propertyValues.put("addresses[0].state", "35452");
|
propertyValues.put("addresses[0].state", "35452");
|
||||||
// TODO should auto add(new Address) at 1
|
|
||||||
|
// Auto adds new Address at 1
|
||||||
propertyValues.put("addresses[1].street", "1234 Rostock Circle");
|
propertyValues.put("addresses[1].street", "1234 Rostock Circle");
|
||||||
propertyValues.put("addresses[1].city", "Palm Bay");
|
propertyValues.put("addresses[1].city", "Palm Bay");
|
||||||
propertyValues.put("addresses[1].state", "FL");
|
propertyValues.put("addresses[1].state", "FL");
|
||||||
propertyValues.put("addresses[1].state", "32901");
|
propertyValues.put("addresses[1].state", "32901");
|
||||||
|
|
||||||
|
// Auto adds new Address at 5 (plus intermediates 2,3,4)
|
||||||
|
propertyValues.put("addresses[5].street", "1234 Rostock Circle");
|
||||||
|
propertyValues.put("addresses[5].city", "Palm Bay");
|
||||||
|
propertyValues.put("addresses[5].state", "FL");
|
||||||
|
propertyValues.put("addresses[5].state", "32901");
|
||||||
|
|
||||||
binder.bind(propertyValues);
|
binder.bind(propertyValues);
|
||||||
|
Assert.assertEquals(6,testbean.addresses.size());
|
||||||
|
Assert.assertEquals("Palm Bay",testbean.addresses.get(1).city);
|
||||||
|
Assert.assertNotNull(testbean.addresses.get(2));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ import org.springframework.expression.OperatorOverloader;
|
||||||
import org.springframework.expression.PropertyAccessor;
|
import org.springframework.expression.PropertyAccessor;
|
||||||
import org.springframework.expression.TypeComparator;
|
import org.springframework.expression.TypeComparator;
|
||||||
import org.springframework.expression.TypedValue;
|
import org.springframework.expression.TypedValue;
|
||||||
|
import org.springframework.expression.spel.standard.SpelExpressionParserConfiguration;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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 it are not seen by other
|
||||||
|
|
@ -48,11 +49,19 @@ public class ExpressionState {
|
||||||
|
|
||||||
private final Stack<TypedValue> contextObjects = new Stack<TypedValue>();
|
private final Stack<TypedValue> contextObjects = new Stack<TypedValue>();
|
||||||
|
|
||||||
|
private int configuration = 0;
|
||||||
|
|
||||||
public ExpressionState(EvaluationContext context) {
|
public ExpressionState(EvaluationContext context) {
|
||||||
this.relatedContext = context;
|
this.relatedContext = context;
|
||||||
createVariableScope();
|
createVariableScope();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ExpressionState(EvaluationContext context, int configuration) {
|
||||||
|
this.relatedContext = context;
|
||||||
|
this.configuration = configuration;
|
||||||
|
createVariableScope();
|
||||||
|
}
|
||||||
|
|
||||||
// create an empty top level VariableScope
|
// create an empty top level VariableScope
|
||||||
private void createVariableScope() {
|
private void createVariableScope() {
|
||||||
this.variableScopes.add(new VariableScope());
|
this.variableScopes.add(new VariableScope());
|
||||||
|
|
@ -204,4 +213,12 @@ public class ExpressionState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean configuredToGrowCollection() {
|
||||||
|
return (configuration & SpelExpressionParserConfiguration.GrowListsOnIndexBeyondSize)!=0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean configuredToCreateCollection() {
|
||||||
|
return (configuration & SpelExpressionParserConfiguration.CreateListsOnAttemptToIndexIntoNull)!=0;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -37,13 +37,15 @@ public class SpelExpression implements Expression {
|
||||||
|
|
||||||
public final SpelNodeImpl ast;
|
public final SpelNodeImpl ast;
|
||||||
|
|
||||||
|
public final int configuration;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct an expression, only used by the parser.
|
* Construct an expression, only used by the parser.
|
||||||
*/
|
*/
|
||||||
public SpelExpression(String expression, SpelNodeImpl ast) {
|
public SpelExpression(String expression, SpelNodeImpl ast, int configuration) {
|
||||||
this.expression = expression;
|
this.expression = expression;
|
||||||
this.ast = ast;
|
this.ast = ast;
|
||||||
|
this.configuration = configuration;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -57,7 +59,7 @@ public class SpelExpression implements Expression {
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
*/
|
*/
|
||||||
public Object getValue() throws EvaluationException {
|
public Object getValue() throws EvaluationException {
|
||||||
ExpressionState expressionState = new ExpressionState(new StandardEvaluationContext());
|
ExpressionState expressionState = new ExpressionState(new StandardEvaluationContext(),configuration);
|
||||||
return this.ast.getValue(expressionState);
|
return this.ast.getValue(expressionState);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -65,7 +67,7 @@ public class SpelExpression implements Expression {
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
*/
|
*/
|
||||||
public Object getValue(EvaluationContext context) throws EvaluationException {
|
public Object getValue(EvaluationContext context) throws EvaluationException {
|
||||||
return this.ast.getValue(new ExpressionState(context));
|
return this.ast.getValue(new ExpressionState(context,configuration));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -73,7 +75,7 @@ public class SpelExpression implements Expression {
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public <T> T getValue(EvaluationContext context, Class<T> expectedResultType) throws EvaluationException {
|
public <T> T getValue(EvaluationContext context, Class<T> expectedResultType) throws EvaluationException {
|
||||||
Object result = ast.getValue(new ExpressionState(context));
|
Object result = ast.getValue(new ExpressionState(context,configuration));
|
||||||
|
|
||||||
if (result != null && expectedResultType != null) {
|
if (result != null && expectedResultType != null) {
|
||||||
Class<?> resultType = result.getClass();
|
Class<?> resultType = result.getClass();
|
||||||
|
|
@ -89,14 +91,14 @@ public class SpelExpression implements Expression {
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
*/
|
*/
|
||||||
public void setValue(EvaluationContext context, Object value) throws EvaluationException {
|
public void setValue(EvaluationContext context, Object value) throws EvaluationException {
|
||||||
this.ast.setValue(new ExpressionState(context), value);
|
this.ast.setValue(new ExpressionState(context,configuration), value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
*/
|
*/
|
||||||
public boolean isWritable(EvaluationContext context) throws EvaluationException {
|
public boolean isWritable(EvaluationContext context) throws EvaluationException {
|
||||||
return this.ast.isWritable(new ExpressionState(context));
|
return this.ast.isWritable(new ExpressionState(context,configuration));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -121,7 +123,7 @@ public class SpelExpression implements Expression {
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
*/
|
*/
|
||||||
public Class getValueType(EvaluationContext context) throws EvaluationException {
|
public Class getValueType(EvaluationContext context) throws EvaluationException {
|
||||||
ExpressionState eState = new ExpressionState(context);
|
ExpressionState eState = new ExpressionState(context,configuration);
|
||||||
TypeDescriptor typeDescriptor = this.ast.getValueInternal(eState).getTypeDescriptor();
|
TypeDescriptor typeDescriptor = this.ast.getValueInternal(eState).getTypeDescriptor();
|
||||||
return typeDescriptor.getType();
|
return typeDescriptor.getType();
|
||||||
}
|
}
|
||||||
|
|
@ -130,7 +132,7 @@ public class SpelExpression implements Expression {
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
*/
|
*/
|
||||||
public TypeDescriptor getValueTypeDescriptor(EvaluationContext context) throws EvaluationException {
|
public TypeDescriptor getValueTypeDescriptor(EvaluationContext context) throws EvaluationException {
|
||||||
ExpressionState eState = new ExpressionState(context);
|
ExpressionState eState = new ExpressionState(context,configuration);
|
||||||
TypeDescriptor typeDescriptor = this.ast.getValueInternal(eState).getTypeDescriptor();
|
TypeDescriptor typeDescriptor = this.ast.getValueInternal(eState).getTypeDescriptor();
|
||||||
return typeDescriptor;
|
return typeDescriptor;
|
||||||
}
|
}
|
||||||
|
|
@ -139,14 +141,14 @@ public class SpelExpression implements Expression {
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
*/
|
*/
|
||||||
public Class getValueType() throws EvaluationException {
|
public Class getValueType() throws EvaluationException {
|
||||||
return this.ast.getValueInternal(new ExpressionState(new StandardEvaluationContext())).getTypeDescriptor().getType();
|
return this.ast.getValueInternal(new ExpressionState(new StandardEvaluationContext(),configuration)).getTypeDescriptor().getType();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
*/
|
*/
|
||||||
public TypeDescriptor getValueTypeDescriptor() throws EvaluationException {
|
public TypeDescriptor getValueTypeDescriptor() throws EvaluationException {
|
||||||
return this.ast.getValueInternal(new ExpressionState(new StandardEvaluationContext())).getTypeDescriptor();
|
return this.ast.getValueInternal(new ExpressionState(new StandardEvaluationContext(),configuration)).getTypeDescriptor();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -154,7 +156,7 @@ public class SpelExpression implements Expression {
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
*/
|
*/
|
||||||
public <T> T getValue(Class<T> expectedResultType) throws EvaluationException {
|
public <T> T getValue(Class<T> expectedResultType) throws EvaluationException {
|
||||||
ExpressionState expressionState = new ExpressionState(new StandardEvaluationContext());
|
ExpressionState expressionState = new ExpressionState(new StandardEvaluationContext(),configuration);
|
||||||
Object result = this.ast.getValue(expressionState);
|
Object result = this.ast.getValue(expressionState);
|
||||||
return ExpressionUtils.convert(expressionState.getEvaluationContext(), result, expectedResultType);
|
return ExpressionUtils.convert(expressionState.getEvaluationContext(), result, expectedResultType);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -88,6 +88,9 @@ public enum SpelMessages {
|
||||||
UNEXPECTED_DATA_AFTER_DOT(Kind.ERROR,1049,"Unexpected data after ''.'': ''{0}''"),//
|
UNEXPECTED_DATA_AFTER_DOT(Kind.ERROR,1049,"Unexpected data after ''.'': ''{0}''"),//
|
||||||
MISSING_CONSTRUCTOR_ARGS(Kind.ERROR,1050,"The arguments '(...)' for the constructor call are missing"),//
|
MISSING_CONSTRUCTOR_ARGS(Kind.ERROR,1050,"The arguments '(...)' for the constructor call are missing"),//
|
||||||
RUN_OUT_OF_ARGUMENTS(Kind.ERROR,1051,"Unexpected ran out of arguments"),//
|
RUN_OUT_OF_ARGUMENTS(Kind.ERROR,1051,"Unexpected ran out of arguments"),//
|
||||||
|
UNABLE_TO_GROW_COLLECTION(Kind.ERROR,1052,"Unable to grow collection"),//
|
||||||
|
UNABLE_TO_GROW_COLLECTION_UNKNOWN_ELEMENT_TYPE(Kind.ERROR,1053,"Unable to grow collection: unable to determine list element type"),//
|
||||||
|
UNABLE_TO_CREATE_LIST_FOR_INDEXING(Kind.ERROR,1054,"Unable to create a List for the following indexer"),//
|
||||||
;
|
;
|
||||||
|
|
||||||
private Kind kind;
|
private Kind kind;
|
||||||
|
|
|
||||||
|
|
@ -67,9 +67,32 @@ public class Indexer extends SpelNodeImpl {
|
||||||
if (targetObject.getClass().isArray()) {
|
if (targetObject.getClass().isArray()) {
|
||||||
return new TypedValue(accessArrayElement(targetObject, idx),TypeDescriptor.valueOf(targetObjectTypeDescriptor.getElementType()));
|
return new TypedValue(accessArrayElement(targetObject, idx),TypeDescriptor.valueOf(targetObjectTypeDescriptor.getElementType()));
|
||||||
} else if (targetObject instanceof Collection) {
|
} else if (targetObject instanceof Collection) {
|
||||||
Collection<?> c = (Collection<?>) targetObject;
|
Collection c = (Collection) targetObject;
|
||||||
if (idx >= c.size()) {
|
if (idx >= c.size()) {
|
||||||
throw new SpelEvaluationException(getStartPosition(),SpelMessages.COLLECTION_INDEX_OUT_OF_BOUNDS, c.size(), idx);
|
if (state.configuredToGrowCollection()) {
|
||||||
|
// Grow the collection
|
||||||
|
Object newCollectionElement = null;
|
||||||
|
try {
|
||||||
|
int newElements = idx-c.size();
|
||||||
|
Class elementClass = targetObjectTypeDescriptor.getElementType();
|
||||||
|
if (elementClass == null) {
|
||||||
|
throw new SpelEvaluationException(getStartPosition(), SpelMessages.UNABLE_TO_GROW_COLLECTION_UNKNOWN_ELEMENT_TYPE);
|
||||||
|
}
|
||||||
|
while (newElements>0) {
|
||||||
|
c.add(elementClass.newInstance());
|
||||||
|
newElements--;
|
||||||
|
}
|
||||||
|
newCollectionElement = targetObjectTypeDescriptor.getElementType().newInstance();
|
||||||
|
} catch (InstantiationException e) {
|
||||||
|
throw new SpelEvaluationException(getStartPosition(), e, SpelMessages.UNABLE_TO_GROW_COLLECTION);
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
throw new SpelEvaluationException(getStartPosition(), e, SpelMessages.UNABLE_TO_GROW_COLLECTION);
|
||||||
|
}
|
||||||
|
c.add(newCollectionElement);
|
||||||
|
return new TypedValue(newCollectionElement,TypeDescriptor.valueOf(targetObjectTypeDescriptor.getElementType()));
|
||||||
|
} else {
|
||||||
|
throw new SpelEvaluationException(getStartPosition(),SpelMessages.COLLECTION_INDEX_OUT_OF_BOUNDS, c.size(), idx);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
int pos = 0;
|
int pos = 0;
|
||||||
for (Object o : c) {
|
for (Object o : c) {
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ package org.springframework.expression.spel.ast;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.springframework.core.convert.TypeDescriptor;
|
||||||
import org.springframework.expression.AccessException;
|
import org.springframework.expression.AccessException;
|
||||||
import org.springframework.expression.EvaluationContext;
|
import org.springframework.expression.EvaluationContext;
|
||||||
import org.springframework.expression.PropertyAccessor;
|
import org.springframework.expression.PropertyAccessor;
|
||||||
|
|
@ -52,7 +53,22 @@ public class PropertyOrFieldReference extends SpelNodeImpl {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TypedValue getValueInternal(ExpressionState state) throws SpelEvaluationException {
|
public TypedValue getValueInternal(ExpressionState state) throws SpelEvaluationException {
|
||||||
return readProperty(state, this.name);
|
TypedValue result = readProperty(state, this.name);
|
||||||
|
if (result.getValue()==null && state.configuredToCreateCollection() && result.getTypeDescriptor().getType().equals(List.class) && nextChildIs(Indexer.class)) {
|
||||||
|
// Create a new list ready for the indexer
|
||||||
|
try {
|
||||||
|
if (isWritable(state)) {
|
||||||
|
List newList = ArrayList.class.newInstance();
|
||||||
|
writeProperty(state, name, newList);
|
||||||
|
result = readProperty(state, this.name);
|
||||||
|
}
|
||||||
|
} catch (InstantiationException e) {
|
||||||
|
throw new SpelEvaluationException(getStartPosition(), e, SpelMessages.UNABLE_TO_CREATE_LIST_FOR_INDEXING);
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
throw new SpelEvaluationException(getStartPosition(), e, SpelMessages.UNABLE_TO_CREATE_LIST_FOR_INDEXING);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -172,6 +188,7 @@ public class PropertyOrFieldReference extends SpelNodeImpl {
|
||||||
|
|
||||||
public boolean isWritableProperty(String name, ExpressionState state) throws SpelEvaluationException {
|
public boolean isWritableProperty(String name, ExpressionState state) throws SpelEvaluationException {
|
||||||
Object contextObject = state.getActiveContextObject().getValue();
|
Object contextObject = state.getActiveContextObject().getValue();
|
||||||
|
TypeDescriptor td = state.getActiveContextObject().getTypeDescriptor();
|
||||||
EvaluationContext eContext = state.getEvaluationContext();
|
EvaluationContext eContext = state.getEvaluationContext();
|
||||||
|
|
||||||
List<PropertyAccessor> resolversToTry = getPropertyAccessorsToTry(getObjectClass(contextObject),state);
|
List<PropertyAccessor> resolversToTry = getPropertyAccessorsToTry(getObjectClass(contextObject),state);
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@ public abstract class SpelNodeImpl implements SpelNode, CommonTypeDescriptors {
|
||||||
|
|
||||||
protected int pos; // start = top 16bits, end = bottom 16bits
|
protected int pos; // start = top 16bits, end = bottom 16bits
|
||||||
protected SpelNodeImpl[] children = SpelNodeImpl.NO_CHILDREN;
|
protected SpelNodeImpl[] children = SpelNodeImpl.NO_CHILDREN;
|
||||||
|
private SpelNodeImpl parent;
|
||||||
|
|
||||||
public SpelNodeImpl(int pos, SpelNodeImpl... operands) {
|
public SpelNodeImpl(int pos, SpelNodeImpl... operands) {
|
||||||
this.pos = pos;
|
this.pos = pos;
|
||||||
|
|
@ -44,13 +45,40 @@ public abstract class SpelNodeImpl implements SpelNode, CommonTypeDescriptors {
|
||||||
assert pos!=0;
|
assert pos!=0;
|
||||||
if (operands!=null && operands.length>0) {
|
if (operands!=null && operands.length>0) {
|
||||||
this.children = operands;
|
this.children = operands;
|
||||||
|
for (SpelNodeImpl childnode: operands) {
|
||||||
|
childnode.parent = this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected SpelNodeImpl getPreviousChild() {
|
||||||
|
SpelNodeImpl result = null;
|
||||||
|
if (parent!=null) {
|
||||||
|
for (SpelNodeImpl child: parent.children) {
|
||||||
|
if (this==child) break;
|
||||||
|
result = child;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean nextChildIs(Class clazz) {
|
||||||
|
if (parent!=null) {
|
||||||
|
SpelNodeImpl[] peers = parent.children;
|
||||||
|
for (int i=0,max=peers.length;i<max;i++) {
|
||||||
|
if (peers[i]==this) {
|
||||||
|
return (i+1)<max && peers[i+1].getClass().equals(clazz);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public final Object getValue(ExpressionState expressionState) throws EvaluationException {
|
public final Object getValue(ExpressionState expressionState) throws EvaluationException {
|
||||||
if (expressionState != null) {
|
if (expressionState != null) {
|
||||||
return getValueInternal(expressionState).getValue();
|
return getValueInternal(expressionState).getValue();
|
||||||
} else {
|
} else {
|
||||||
|
// configuration not set - does that matter?
|
||||||
return getValue(new ExpressionState(new StandardEvaluationContext()));
|
return getValue(new ExpressionState(new StandardEvaluationContext()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -87,7 +87,19 @@ public class SpelExpressionParser extends TemplateAwareExpressionParser {
|
||||||
// For rules that build nodes, they are stacked here for return
|
// For rules that build nodes, they are stacked here for return
|
||||||
private Stack<SpelNodeImpl> constructedNodes = new Stack<SpelNodeImpl>();
|
private Stack<SpelNodeImpl> constructedNodes = new Stack<SpelNodeImpl>();
|
||||||
|
|
||||||
|
private int configuration;
|
||||||
|
|
||||||
public SpelExpressionParser() {
|
public SpelExpressionParser() {
|
||||||
|
this(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a parser with some configured behaviour. Supported configuration
|
||||||
|
* bit flags can be seen in @see {@link SpelExpressionParserConfiguration}
|
||||||
|
* @param configuration bitflags for configuration options
|
||||||
|
*/
|
||||||
|
public SpelExpressionParser(int configuration) {
|
||||||
|
this.configuration = configuration;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SpelExpression parse(String expressionString) throws ParseException {
|
public SpelExpression parse(String expressionString) throws ParseException {
|
||||||
|
|
@ -108,7 +120,7 @@ public class SpelExpressionParser extends TemplateAwareExpressionParser {
|
||||||
throw new SpelParseException(peekToken().startpos,SpelMessages.MORE_INPUT,toString(nextToken()));
|
throw new SpelParseException(peekToken().startpos,SpelMessages.MORE_INPUT,toString(nextToken()));
|
||||||
}
|
}
|
||||||
assert constructedNodes.isEmpty();
|
assert constructedNodes.isEmpty();
|
||||||
return new SpelExpression(expressionString,ast);
|
return new SpelExpression(expressionString, ast, configuration);
|
||||||
} catch (InternalParseException ipe) {
|
} catch (InternalParseException ipe) {
|
||||||
throw ipe.getCause();
|
throw ipe.getCause();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2008-2009 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.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.springframework.expression.spel.standard;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bit flags that configure optional behaviour in the parser. Pass the necessary
|
||||||
|
* bits when calling the expression parser constructor.
|
||||||
|
*
|
||||||
|
* @author Andy Clement
|
||||||
|
* @since 3.0
|
||||||
|
*/
|
||||||
|
public interface SpelExpressionParserConfiguration {
|
||||||
|
|
||||||
|
static final int CreateListsOnAttemptToIndexIntoNull = 0x0001;
|
||||||
|
static final int GrowListsOnIndexBeyondSize = 0x0002;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -16,14 +16,18 @@
|
||||||
|
|
||||||
package org.springframework.expression.spel;
|
package org.springframework.expression.spel;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import junit.framework.Assert;
|
import junit.framework.Assert;
|
||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.springframework.expression.EvaluationContext;
|
import org.springframework.expression.EvaluationContext;
|
||||||
import org.springframework.expression.EvaluationException;
|
import org.springframework.expression.EvaluationException;
|
||||||
import org.springframework.expression.Expression;
|
import org.springframework.expression.Expression;
|
||||||
|
import org.springframework.expression.ExpressionParser;
|
||||||
import org.springframework.expression.ParseException;
|
import org.springframework.expression.ParseException;
|
||||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||||
|
import org.springframework.expression.spel.standard.SpelExpressionParserConfiguration;
|
||||||
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||||
import org.springframework.expression.spel.support.StandardTypeLocator;
|
import org.springframework.expression.spel.support.StandardTypeLocator;
|
||||||
|
|
||||||
|
|
@ -34,6 +38,38 @@ import org.springframework.expression.spel.support.StandardTypeLocator;
|
||||||
*/
|
*/
|
||||||
public class EvaluationTests extends ExpressionTestCase {
|
public class EvaluationTests extends ExpressionTestCase {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCreateListsOnAttemptToIndexNull01() throws EvaluationException, ParseException {
|
||||||
|
ExpressionParser parser = new SpelExpressionParser(SpelExpressionParserConfiguration.CreateListsOnAttemptToIndexIntoNull | SpelExpressionParserConfiguration.GrowListsOnIndexBeyondSize);
|
||||||
|
Expression expression = parser.parseExpression("list[0]");
|
||||||
|
TestClass testClass = new TestClass();
|
||||||
|
Object o = null;
|
||||||
|
o = expression.getValue(new StandardEvaluationContext(testClass));
|
||||||
|
Assert.assertEquals("",o);
|
||||||
|
o = parser.parseExpression("list[3]").getValue(new StandardEvaluationContext(testClass));
|
||||||
|
Assert.assertEquals("",o);
|
||||||
|
Assert.assertEquals(4, testClass.list.size());
|
||||||
|
try {
|
||||||
|
o = parser.parseExpression("list2[3]").getValue(new StandardEvaluationContext(testClass));
|
||||||
|
Assert.fail();
|
||||||
|
} catch (EvaluationException ee) {
|
||||||
|
// success!
|
||||||
|
}
|
||||||
|
o = parser.parseExpression("foo[3]").getValue(new StandardEvaluationContext(testClass));
|
||||||
|
Assert.assertEquals("",o);
|
||||||
|
Assert.assertEquals(4, testClass.getFoo().size());
|
||||||
|
}
|
||||||
|
|
||||||
|
static class TestClass {
|
||||||
|
public List<String> list;
|
||||||
|
public List list2;
|
||||||
|
|
||||||
|
private List<String> foo;
|
||||||
|
public List<String> getFoo() { return this.foo; }
|
||||||
|
public void setFoo(List<String> newfoo) { this.foo = newfoo; }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testElvis01() {
|
public void testElvis01() {
|
||||||
evaluate("'Andy'?:'Dave'","Andy",String.class);
|
evaluate("'Andy'?:'Dave'","Andy",String.class);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue