SPR-6984: auto grow collections on write through indexer

This commit is contained in:
Andy Clement 2010-03-15 18:15:48 +00:00
parent 0cb7e4dcb3
commit d932c043da
4 changed files with 107 additions and 25 deletions

View File

@ -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();

View File

@ -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.
@ -522,4 +523,31 @@ public class EvaluationTests extends ExpressionTestCase {
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));
}
}

View File

@ -0,0 +1,21 @@
package org.springframework.expression.spel.testresources;
import java.util.List;
public class TestAddress{
private String street;
private List<String> crossStreets;
public String getStreet() {
return street;
}
public void setStreet(String street) {
this.street = street;
}
public List<String> getCrossStreets() {
return crossStreets;
}
public void setCrossStreets(List<String> crossStreets) {
this.crossStreets = crossStreets;
}
}

View File

@ -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;
}
}