diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/SpelMessage.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/SpelMessage.java index 164313aba7a..c9788fed57a 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/SpelMessage.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/SpelMessage.java @@ -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; diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/Indexer.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/Indexer.java index 7717a21b932..29ed5fbf51c 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/Indexer.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/Indexer.java @@ -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())); diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/PropertyOrFieldReference.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/PropertyOrFieldReference.java index 1b2942ad425..d095d4081ce 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/PropertyOrFieldReference.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/PropertyOrFieldReference.java @@ -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; diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/SpelNodeImpl.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/SpelNodeImpl.java index 861432f149a..f5ff1ab8c67 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/SpelNodeImpl.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/SpelNodeImpl.java @@ -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) { + return false; + } else { + Class clazz = peers[i+1].getClass(); + for (Class desiredClazz: clazzes) { + if (clazz.equals(desiredClazz)) { + return true; + } + } + return false; + } } } } diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/standard/SpelExpressionParserConfiguration.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/standard/SpelExpressionParserConfiguration.java index a927e94ac62..9589a25ef06 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/standard/SpelExpressionParserConfiguration.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/standard/SpelExpressionParserConfiguration.java @@ -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; }