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.

This commit is contained in:
Andy Clement 2009-07-10 19:43:37 +00:00
parent 85eda27cd4
commit e3e34b04d4
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;
}