for Keith and his binder: fault in support for maps (if attempt made to reference into null map) and for general objects, e.g. foo.bar if foo was null, attempt to dynamically build foo.

git-svn-id: https://src.springframework.org/svn/spring-framework/trunk@1513 50f2f4bb-b051-0410-bef5-90022cba6387
This commit is contained in:
Andy Clement 2009-07-10 19:43:37 +00:00
parent 85a197f5b8
commit 4b94f3b381
5 changed files with 79 additions and 16 deletions

View File

@ -90,7 +90,9 @@ public enum SpelMessage {
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"),//
UNABLE_TO_CREATE_LIST_FOR_INDEXING(Kind.ERROR,1054,"Unable to dynamically create a List to replace a null value"),//
UNABLE_TO_CREATE_MAP_FOR_INDEXING(Kind.ERROR,1055,"Unable to dynamically create a Map to replace a null value"),//
UNABLE_TO_DYNAMICALLY_CREATE_OBJECT(Kind.ERROR,1056,"Unable to dynamically create instance of ''{0}'' to replace a null value"),//
;
private Kind kind;

View File

@ -69,7 +69,16 @@ public class Indexer extends SpelNodeImpl {
}
// Indexing into a Map
if (targetObject instanceof Map) {
if (targetObjectTypeDescriptor.isMap()) {
if (targetObject == null) {
// Current decision: attempt to index into null map == exception and does not just return null
throw new SpelEvaluationException(getStartPosition(),SpelMessage.CANNOT_INDEX_INTO_NULL_VALUE);
// if (targetObjectTypeDescriptor.isMapEntryTypeKnown()) {
// return new TypedValue(null,TypeDescriptor.valueOf(targetObjectTypeDescriptor.getMapValueType()));
// } else {
// return new TypedValue(null,TypeDescriptor.NULL);
// }
}
Object possiblyConvertedKey = index;
if (targetObjectTypeDescriptor.isMapEntryTypeKnown()) {
possiblyConvertedKey = state.convertValue(index,TypeDescriptor.valueOf(targetObjectTypeDescriptor.getMapKeyType()));

View File

@ -17,6 +17,7 @@
package org.springframework.expression.spel.ast;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -56,18 +57,50 @@ public class PropertyOrFieldReference extends SpelNodeImpl {
@Override
public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
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);
TypeDescriptor resultDescriptor = result.getTypeDescriptor();
// Dynamically create the objects if the user has requested that optional behaviour
if (result.getValue()==null && state.configuredToCreateCollectionOrMap() && nextChildIs(Indexer.class,PropertyOrFieldReference.class)) {
// Creating lists and maps
if ((resultDescriptor.getType().equals(List.class) || resultDescriptor.getType().equals(Map.class))) {
// Create a new collection or map ready for the indexer
if (resultDescriptor.getType().equals(List.class)) {
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, SpelMessage.UNABLE_TO_CREATE_LIST_FOR_INDEXING);
} catch (IllegalAccessException e) {
throw new SpelEvaluationException(getStartPosition(), e, SpelMessage.UNABLE_TO_CREATE_LIST_FOR_INDEXING);
}
} else {
try {
if (isWritable(state)) {
Map newMap = HashMap.class.newInstance();
writeProperty(state, name, newMap);
result = readProperty(state, this.name);
}
} catch (InstantiationException e) {
throw new SpelEvaluationException(getStartPosition(), e, SpelMessage.UNABLE_TO_CREATE_MAP_FOR_INDEXING);
} catch (IllegalAccessException e) {
throw new SpelEvaluationException(getStartPosition(), e, SpelMessage.UNABLE_TO_CREATE_MAP_FOR_INDEXING);
}
}
} catch (InstantiationException e) {
throw new SpelEvaluationException(getStartPosition(), e, SpelMessage.UNABLE_TO_CREATE_LIST_FOR_INDEXING);
} catch (IllegalAccessException e) {
throw new SpelEvaluationException(getStartPosition(), e, SpelMessage.UNABLE_TO_CREATE_LIST_FOR_INDEXING);
} else {
// 'simple' object
try {
if (isWritable(state)) {
Object newObject = result.getTypeDescriptor().getType().newInstance();
writeProperty(state, name, newObject);
result = readProperty(state, this.name);
}
} catch (InstantiationException e) {
throw new SpelEvaluationException(getStartPosition(), e, SpelMessage.UNABLE_TO_DYNAMICALLY_CREATE_OBJECT,result.getTypeDescriptor().getType());
} catch (IllegalAccessException e) {
throw new SpelEvaluationException(getStartPosition(), e, SpelMessage.UNABLE_TO_DYNAMICALLY_CREATE_OBJECT,result.getTypeDescriptor().getType());
}
}
}
return result;

View File

@ -62,12 +62,25 @@ public abstract class SpelNodeImpl implements SpelNode, CommonTypeDescriptors {
return result;
}
protected boolean nextChildIs(Class clazz) {
/**
* @return true if the next child is one of the specified classes
*/
protected boolean nextChildIs(Class... clazzes) {
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);
if ((i+1)>=max) {
return false;
} else {
Class clazz = peers[i+1].getClass();
for (Class desiredClazz: clazzes) {
if (clazz.equals(desiredClazz)) {
return true;
}
}
return false;
}
}
}
}

View File

@ -24,7 +24,13 @@ package org.springframework.expression.spel.standard;
*/
public interface SpelExpressionParserConfiguration {
static final int CreateListsOnAttemptToIndexIntoNull = 0x0001;
/**
* This option applies to maps/collections and regular objects. If the initial part of an expression evaluates to null and then an
* attempt is made to resolve an index '[]' or property against it, and this option is set, then the relevant object will be constructed so that
* the index/property resolution can proceed.
*/
static final int CreateObjectIfAttemptToReferenceNull = 0x0001;
static final int GrowListsOnIndexBeyondSize = 0x0002;
}