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 c8069591527..ef9520ad369 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 @@ -61,7 +61,6 @@ public class Indexer extends SpelNodeImpl { super(pos, expr); } - @SuppressWarnings("unchecked") @Override public TypedValue getValueInternal(ExpressionState state) throws EvaluationException { TypedValue context = state.getActiveContextObject(); @@ -119,28 +118,7 @@ public class Indexer extends SpelNodeImpl { } else if (targetObject instanceof Collection) { Collection c = (Collection) targetObject; if (idx >= c.size()) { - if (state.getConfiguration().isAutoGrowCollections()) { - // Grow the collection - Object newCollectionElement = null; - try { - int newElements = idx-c.size(); - Class elementClass = targetObjectTypeDescriptor.getElementType(); - if (elementClass == null) { - throw new SpelEvaluationException(getStartPosition(), SpelMessage.UNABLE_TO_GROW_COLLECTION_UNKNOWN_ELEMENT_TYPE); - } - while (newElements>0) { - c.add(elementClass.newInstance()); - newElements--; - } - newCollectionElement = targetObjectTypeDescriptor.getElementType().newInstance(); - } - catch (Exception ex) { - throw new SpelEvaluationException(getStartPosition(), ex, SpelMessage.UNABLE_TO_GROW_COLLECTION); - } - c.add(newCollectionElement); - return new TypedValue(newCollectionElement,TypeDescriptor.valueOf(targetObjectTypeDescriptor.getElementType())); - } - else { + if (!growCollection(state, targetObjectTypeDescriptor.getElementType(), idx, c)) { throw new SpelEvaluationException(getStartPosition(),SpelMessage.COLLECTION_INDEX_OUT_OF_BOUNDS, c.size(), idx); } } @@ -234,7 +212,9 @@ public class Indexer extends SpelNodeImpl { int idx = (Integer)state.convertValue(index, TypeDescriptor.valueOf(Integer.class)); Collection c = (Collection) targetObject; if (idx >= c.size()) { - throw new SpelEvaluationException(getStartPosition(),SpelMessage.COLLECTION_INDEX_OUT_OF_BOUNDS, c.size(), idx); + if (!growCollection(state, targetObjectTypeDescriptor.getElementType(), idx, c)) { + throw new SpelEvaluationException(getStartPosition(),SpelMessage.COLLECTION_INDEX_OUT_OF_BOUNDS, c.size(), idx); + } } if (targetObject instanceof List) { List list = (List)targetObject; @@ -282,6 +262,40 @@ public class Indexer extends SpelNodeImpl { throw new SpelEvaluationException(getStartPosition(),SpelMessage.INDEXING_NOT_SUPPORTED_FOR_TYPE, targetObjectTypeDescriptor.asString()); } + /** + * Attempt to grow the specified collection so that the specified index is valid. + * + * @param state the expression state + * @param elementType the type of the elements in the collection + * @param index the index into the collection that needs to be valid + * @param collection the collection to grow with elements + * @return true if collection growing succeeded, otherwise false + */ + @SuppressWarnings("unchecked") + private boolean growCollection(ExpressionState state, Class elementType, int index, + Collection collection) { + if (state.getConfiguration().isAutoGrowCollections()) { + Object newCollectionElement = null; + try { + int newElements = index-collection.size(); + if (elementType == null) { + throw new SpelEvaluationException(getStartPosition(), SpelMessage.UNABLE_TO_GROW_COLLECTION_UNKNOWN_ELEMENT_TYPE); + } + while (newElements>0) { + collection.add(elementType.newInstance()); + newElements--; + } + newCollectionElement = elementType.newInstance(); + } + catch (Exception ex) { + throw new SpelEvaluationException(getStartPosition(), ex, SpelMessage.UNABLE_TO_GROW_COLLECTION); + } + collection.add(newCollectionElement); + return true; + } + return false; + } + @Override public String toStringAST() { StringBuilder sb = new StringBuilder(); diff --git a/org.springframework.expression/src/test/java/org/springframework/expression/spel/EvaluationTests.java b/org.springframework.expression/src/test/java/org/springframework/expression/spel/EvaluationTests.java index 354ccedddeb..abd11dfabd7 100644 --- a/org.springframework.expression/src/test/java/org/springframework/expression/spel/EvaluationTests.java +++ b/org.springframework.expression/src/test/java/org/springframework/expression/spel/EvaluationTests.java @@ -31,6 +31,7 @@ import org.springframework.expression.spel.standard.SpelExpression; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.expression.spel.support.StandardEvaluationContext; import org.springframework.expression.spel.support.StandardTypeLocator; +import org.springframework.expression.spel.testresources.TestPerson; /** * Tests the evaluation of real expressions in a real context. @@ -520,6 +521,33 @@ public class EvaluationTests extends ExpressionTestCase { public void testResolvingString() throws Exception { Class stringClass = parser.parseExpression("T(String)").getValue(Class.class); Assert.assertEquals(String.class,stringClass); - } + } + + /** + * SPR-6984: attempting to index a collection on write using an index that doesn't currently exist in the collection (address.crossStreets[0] below) + */ + @Test + public void initializingCollectionElementsOnWrite() throws Exception { + TestPerson person = new TestPerson(); + EvaluationContext context = new StandardEvaluationContext(person); + SpelParserConfiguration config = new SpelParserConfiguration(true, true); + ExpressionParser parser = new SpelExpressionParser(config); + Expression expression = parser.parseExpression("name"); + expression.setValue(context, "Oleg"); + Assert.assertEquals("Oleg",person.getName()); + expression = parser.parseExpression("address.street"); + expression.setValue(context, "123 High St"); + Assert.assertEquals("123 High St",person.getAddress().getStreet()); + + expression = parser.parseExpression("address.crossStreets[0]"); + expression.setValue(context, "Blah"); + Assert.assertEquals("Blah",person.getAddress().getCrossStreets().get(0)); + + expression = parser.parseExpression("address.crossStreets[3]"); + expression.setValue(context, "Wibble"); + Assert.assertEquals("Blah",person.getAddress().getCrossStreets().get(0)); + Assert.assertEquals("Wibble",person.getAddress().getCrossStreets().get(3)); + } + } diff --git a/org.springframework.expression/src/test/java/org/springframework/expression/spel/testresources/TestAddress.java b/org.springframework.expression/src/test/java/org/springframework/expression/spel/testresources/TestAddress.java new file mode 100644 index 00000000000..069a42958f3 --- /dev/null +++ b/org.springframework.expression/src/test/java/org/springframework/expression/spel/testresources/TestAddress.java @@ -0,0 +1,21 @@ +package org.springframework.expression.spel.testresources; + +import java.util.List; + +public class TestAddress{ + private String street; + private List crossStreets; + + public String getStreet() { + return street; + } + public void setStreet(String street) { + this.street = street; + } + public List getCrossStreets() { + return crossStreets; + } + public void setCrossStreets(List crossStreets) { + this.crossStreets = crossStreets; + } + } diff --git a/org.springframework.expression/src/test/java/org/springframework/expression/spel/testresources/TestPerson.java b/org.springframework.expression/src/test/java/org/springframework/expression/spel/testresources/TestPerson.java new file mode 100644 index 00000000000..94acb313808 --- /dev/null +++ b/org.springframework.expression/src/test/java/org/springframework/expression/spel/testresources/TestPerson.java @@ -0,0 +1,19 @@ +package org.springframework.expression.spel.testresources; + +public class TestPerson { + private String name; + private TestAddress address; + + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } + public TestAddress getAddress() { + return address; + } + public void setAddress(TestAddress address) { + this.address = address; + } + } \ No newline at end of file