Browse Source

DataBinder uses a default limit of 256 for array/collection auto-growing (SPR-7842)

3.0.x
Juergen Hoeller 15 years ago
parent
commit
eb50eec85b
  1. 13
      org.springframework.beans/src/main/java/org/springframework/beans/BeanWrapper.java
  2. 70
      org.springframework.beans/src/main/java/org/springframework/beans/BeanWrapperImpl.java
  3. 18
      org.springframework.beans/src/test/java/org/springframework/beans/BeanWrapperAutoGrowingTests.java
  4. 11
      org.springframework.context/src/main/java/org/springframework/validation/BeanPropertyBindingResult.java
  5. 32
      org.springframework.context/src/main/java/org/springframework/validation/DataBinder.java
  6. 60
      org.springframework.context/src/test/java/org/springframework/validation/DataBinderTests.java

13
org.springframework.beans/src/main/java/org/springframework/beans/BeanWrapper.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2009 the original author or authors. * Copyright 2002-2011 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -92,4 +92,15 @@ public interface BeanWrapper extends ConfigurablePropertyAccessor {
*/ */
boolean isAutoGrowNestedPaths(); boolean isAutoGrowNestedPaths();
/**
* Specify a limit for array and collection auto-growing.
* <p>Default is unlimited on a plain BeanWrapper.
*/
void setAutoGrowCollectionLimit(int autoGrowCollectionLimit);
/**
* Return the limit for array and collection auto-growing.
*/
int getAutoGrowCollectionLimit();
} }

70
org.springframework.beans/src/main/java/org/springframework/beans/BeanWrapperImpl.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2010 the original author or authors. * Copyright 2002-2011 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -120,6 +120,8 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra
private boolean autoGrowNestedPaths = false; private boolean autoGrowNestedPaths = false;
private int autoGrowCollectionLimit = Integer.MAX_VALUE;
/** /**
* Create new empty BeanWrapperImpl. Wrapped instance needs to be set afterwards. * Create new empty BeanWrapperImpl. Wrapped instance needs to be set afterwards.
@ -184,6 +186,7 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra
setWrappedInstance(object, nestedPath, superBw.getWrappedInstance()); setWrappedInstance(object, nestedPath, superBw.getWrappedInstance());
setExtractOldValueForEditor(superBw.isExtractOldValueForEditor()); setExtractOldValueForEditor(superBw.isExtractOldValueForEditor());
setAutoGrowNestedPaths(superBw.isAutoGrowNestedPaths()); setAutoGrowNestedPaths(superBw.isAutoGrowNestedPaths());
setAutoGrowCollectionLimit(superBw.getAutoGrowCollectionLimit());
setConversionService(superBw.getConversionService()); setConversionService(superBw.getConversionService());
setSecurityContext(superBw.acc); setSecurityContext(superBw.acc);
} }
@ -251,22 +254,38 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra
} }
/** /**
* If this BeanWrapper should "auto grow" nested paths. * Set whether this BeanWrapper should attempt to "auto-grow" a nested path that contains a null value.
* When true, auto growth is triggered on nested paths when null values are encountered. * <p>If "true", a null path location will be populated with a default object value and traversed
* When true, auto growth is triggered on collection properties when out of bounds indexes are accessed. * instead of resulting in a {@link NullValueInNestedPathException}. Turning this flag on also
* Default is false. * enables auto-growth of collection elements when accessing an out-of-bounds index.
* <p>Default is "false" on a plain BeanWrapper.
*/ */
public void setAutoGrowNestedPaths(boolean autoGrowNestedPaths) { public void setAutoGrowNestedPaths(boolean autoGrowNestedPaths) {
this.autoGrowNestedPaths = autoGrowNestedPaths; this.autoGrowNestedPaths = autoGrowNestedPaths;
} }
/** /**
* If this BeanWrapper should "auto grow" nested paths. * Return whether "auto-growing" of nested paths has been activated.
*/ */
public boolean isAutoGrowNestedPaths() { public boolean isAutoGrowNestedPaths() {
return this.autoGrowNestedPaths; return this.autoGrowNestedPaths;
} }
/**
* Specify a limit for array and collection auto-growing.
* <p>Default is unlimited on a plain BeanWrapper.
*/
public void setAutoGrowCollectionLimit(int autoGrowCollectionLimit) {
this.autoGrowCollectionLimit = autoGrowCollectionLimit;
}
/**
* Return the limit for array and collection auto-growing.
*/
public int getAutoGrowCollectionLimit() {
return this.autoGrowCollectionLimit;
}
/** /**
* Set the security context used during the invocation of the wrapped instance methods. * Set the security context used during the invocation of the wrapped instance methods.
* Can be null. * Can be null.
@ -594,7 +613,7 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra
setPropertyValue(tokens, pv); setPropertyValue(tokens, pv);
return pv.getValue(); return pv.getValue();
} }
private PropertyValue createDefaultPropertyValue(PropertyTokenHolder tokens) { private PropertyValue createDefaultPropertyValue(PropertyTokenHolder tokens) {
Class type = getPropertyType(tokens.canonicalName); Class type = getPropertyType(tokens.canonicalName);
if (type == null) { if (type == null) {
@ -604,7 +623,7 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra
Object defaultValue = newValue(type, tokens.canonicalName); Object defaultValue = newValue(type, tokens.canonicalName);
return new PropertyValue(tokens.canonicalName, defaultValue); return new PropertyValue(tokens.canonicalName, defaultValue);
} }
private Object newValue(Class<?> type, String name) { private Object newValue(Class<?> type, String name) {
try { try {
if (type.isArray()) { if (type.isArray()) {
@ -634,7 +653,7 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra
"Could not instantiate property type [" + type.getName() + "] to auto-grow nested property path: " + ex); "Could not instantiate property type [" + type.getName() + "] to auto-grow nested property path: " + ex);
} }
} }
/** /**
* Create a new nested BeanWrapper instance. * Create a new nested BeanWrapper instance.
* <p>Default implementation creates a BeanWrapperImpl instance. * <p>Default implementation creates a BeanWrapperImpl instance.
@ -834,15 +853,16 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra
return array; return array;
} }
int length = Array.getLength(array); int length = Array.getLength(array);
if (index >= length) { if (index >= length && index < this.autoGrowCollectionLimit) {
Class<?> componentType = array.getClass().getComponentType(); Class<?> componentType = array.getClass().getComponentType();
Object newArray = Array.newInstance(componentType, index + 1); Object newArray = Array.newInstance(componentType, index + 1);
System.arraycopy(array, 0, newArray, 0, length); System.arraycopy(array, 0, newArray, 0, length);
for (int i = length; i < Array.getLength(newArray); i++) { for (int i = length; i < Array.getLength(newArray); i++) {
Array.set(newArray, i, newValue(componentType, name)); Array.set(newArray, i, newValue(componentType, name));
} }
// TODO this is not efficient because conversion may create a copy ... set directly because we know it is assignable.
setPropertyValue(name, newArray); setPropertyValue(name, newArray);
return newArray; return getPropertyValue(name);
} }
else { else {
return array; return array;
@ -856,7 +876,8 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra
if (!this.autoGrowNestedPaths) { if (!this.autoGrowNestedPaths) {
return; return;
} }
if (index >= collection.size()) { int size = collection.size();
if (index >= size && index < this.autoGrowCollectionLimit) {
Class elementType = GenericCollectionTypeResolver.getCollectionReturnType(pd.getReadMethod(), nestingLevel); Class elementType = GenericCollectionTypeResolver.getCollectionReturnType(pd.getReadMethod(), nestingLevel);
if (elementType != null) { if (elementType != null) {
for (int i = collection.size(); i < index + 1; i++) { for (int i = collection.size(); i < index + 1; i++) {
@ -932,13 +953,13 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra
"Cannot access indexed value in property referenced " + "Cannot access indexed value in property referenced " +
"in indexed property path '" + propertyName + "': returned null"); "in indexed property path '" + propertyName + "': returned null");
} }
else if (propValue.getClass().isArray()) { if (propValue.getClass().isArray()) {
PropertyDescriptor pd = getCachedIntrospectionResults().getPropertyDescriptor(actualName); PropertyDescriptor pd = getCachedIntrospectionResults().getPropertyDescriptor(actualName);
Class requiredType = propValue.getClass().getComponentType(); Class requiredType = propValue.getClass().getComponentType();
int arrayIndex = Integer.parseInt(key); int arrayIndex = Integer.parseInt(key);
Object oldValue = null; Object oldValue = null;
try { try {
if (isExtractOldValueForEditor()) { if (isExtractOldValueForEditor() && arrayIndex < Array.getLength(propValue)) {
oldValue = Array.get(propValue, arrayIndex); oldValue = Array.get(propValue, arrayIndex);
} }
Object convertedValue = convertIfNecessary(propertyName, oldValue, pv.getValue(), requiredType, Object convertedValue = convertIfNecessary(propertyName, oldValue, pv.getValue(), requiredType,
@ -956,29 +977,36 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra
pd.getReadMethod(), tokens.keys.length); pd.getReadMethod(), tokens.keys.length);
List list = (List) propValue; List list = (List) propValue;
int index = Integer.parseInt(key); int index = Integer.parseInt(key);
int size = list.size();
Object oldValue = null; Object oldValue = null;
if (isExtractOldValueForEditor() && index < list.size()) { if (isExtractOldValueForEditor() && index < size) {
oldValue = list.get(index); oldValue = list.get(index);
} }
Object convertedValue = convertIfNecessary(propertyName, oldValue, pv.getValue(), requiredType, Object convertedValue = convertIfNecessary(propertyName, oldValue, pv.getValue(), requiredType,
new PropertyTypeDescriptor(pd, new MethodParameter(pd.getReadMethod(), -1), requiredType)); new PropertyTypeDescriptor(pd, new MethodParameter(pd.getReadMethod(), -1), requiredType));
if (index < list.size()) { if (index >= size && index < this.autoGrowCollectionLimit) {
list.set(index, convertedValue); for (int i = size; i < index; i++) {
}
else if (index >= list.size()) {
for (int i = list.size(); i < index; i++) {
try { try {
list.add(null); list.add(null);
} }
catch (NullPointerException ex) { catch (NullPointerException ex) {
throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName, throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName,
"Cannot set element with index " + index + " in List of size " + "Cannot set element with index " + index + " in List of size " +
list.size() + ", accessed using property path '" + propertyName + size + ", accessed using property path '" + propertyName +
"': List does not support filling up gaps with null elements"); "': List does not support filling up gaps with null elements");
} }
} }
list.add(convertedValue); list.add(convertedValue);
} }
else {
try {
list.set(index, convertedValue);
}
catch (IndexOutOfBoundsException ex) {
throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName,
"Invalid list index in property path '" + propertyName + "'", ex);
}
}
} }
else if (propValue instanceof Map) { else if (propValue instanceof Map) {
PropertyDescriptor pd = getCachedIntrospectionResults().getPropertyDescriptor(actualName); PropertyDescriptor pd = getCachedIntrospectionResults().getPropertyDescriptor(actualName);

18
org.springframework.beans/src/test/java/org/springframework/beans/BeanWrapperAutoGrowingTests.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2010 the original author or authors. * Copyright 2002-2011 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -19,10 +19,11 @@ package org.springframework.beans;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import static org.junit.Assert.*;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import static org.junit.Assert.*;
/** /**
* @author Keith Donald * @author Keith Donald
* @author Juergen Hoeller * @author Juergen Hoeller
@ -117,6 +118,19 @@ public class BeanWrapperAutoGrowingTests {
assertNotNull(wrapper.getPropertyValue("list[3]")); assertNotNull(wrapper.getPropertyValue("list[3]"));
} }
@Test
public void getPropertyValueAutoGrowListFailsAgainstLimit() {
wrapper.setAutoGrowCollectionLimit(2);
try {
assertNotNull(wrapper.getPropertyValue("list[4]"));
fail("Should have thrown InvalidPropertyException");
}
catch (InvalidPropertyException ex) {
// expected
assertTrue(ex.getRootCause() instanceof IndexOutOfBoundsException);
}
}
@Test @Test
public void getPropertyValueAutoGrowMultiDimensionalList() { public void getPropertyValueAutoGrowMultiDimensionalList() {
assertNotNull(wrapper.getPropertyValue("multiList[0][0]")); assertNotNull(wrapper.getPropertyValue("multiList[0][0]"));

11
org.springframework.context/src/main/java/org/springframework/validation/BeanPropertyBindingResult.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2009 the original author or authors. * Copyright 2002-2011 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -46,6 +46,8 @@ public class BeanPropertyBindingResult extends AbstractPropertyBindingResult imp
private final boolean autoGrowNestedPaths; private final boolean autoGrowNestedPaths;
private final int autoGrowCollectionLimit;
private transient BeanWrapper beanWrapper; private transient BeanWrapper beanWrapper;
@ -55,7 +57,7 @@ public class BeanPropertyBindingResult extends AbstractPropertyBindingResult imp
* @param objectName the name of the target object * @param objectName the name of the target object
*/ */
public BeanPropertyBindingResult(Object target, String objectName) { public BeanPropertyBindingResult(Object target, String objectName) {
this(target, objectName, true); this(target, objectName, true, Integer.MAX_VALUE);
} }
/** /**
@ -63,11 +65,13 @@ public class BeanPropertyBindingResult extends AbstractPropertyBindingResult imp
* @param target the target bean to bind onto * @param target the target bean to bind onto
* @param objectName the name of the target object * @param objectName the name of the target object
* @param autoGrowNestedPaths whether to "auto-grow" a nested path that contains a null value * @param autoGrowNestedPaths whether to "auto-grow" a nested path that contains a null value
* @param autoGrowCollectionLimit the limit for array and collection auto-growing
*/ */
public BeanPropertyBindingResult(Object target, String objectName, boolean autoGrowNestedPaths) { public BeanPropertyBindingResult(Object target, String objectName, boolean autoGrowNestedPaths, int autoGrowCollectionLimit) {
super(objectName); super(objectName);
this.target = target; this.target = target;
this.autoGrowNestedPaths = autoGrowNestedPaths; this.autoGrowNestedPaths = autoGrowNestedPaths;
this.autoGrowCollectionLimit = autoGrowCollectionLimit;
} }
@ -87,6 +91,7 @@ public class BeanPropertyBindingResult extends AbstractPropertyBindingResult imp
this.beanWrapper = createBeanWrapper(); this.beanWrapper = createBeanWrapper();
this.beanWrapper.setExtractOldValueForEditor(true); this.beanWrapper.setExtractOldValueForEditor(true);
this.beanWrapper.setAutoGrowNestedPaths(this.autoGrowNestedPaths); this.beanWrapper.setAutoGrowNestedPaths(this.autoGrowNestedPaths);
this.beanWrapper.setAutoGrowCollectionLimit(this.autoGrowCollectionLimit);
} }
return this.beanWrapper; return this.beanWrapper;
} }

32
org.springframework.context/src/main/java/org/springframework/validation/DataBinder.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2010 the original author or authors. * Copyright 2002-2011 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -105,6 +105,9 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
/** Default object name used for binding: "target" */ /** Default object name used for binding: "target" */
public static final String DEFAULT_OBJECT_NAME = "target"; public static final String DEFAULT_OBJECT_NAME = "target";
/** Default limit for array and collection growing: 256 */
public static final int DEFAULT_AUTO_GROW_COLLECTION_LIMIT = 256;
/** /**
* We'll create a lot of DataBinder instances: Let's use a static logger. * We'll create a lot of DataBinder instances: Let's use a static logger.
@ -127,6 +130,8 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
private boolean autoGrowNestedPaths = true; private boolean autoGrowNestedPaths = true;
private int autoGrowCollectionLimit = DEFAULT_AUTO_GROW_COLLECTION_LIMIT;
private String[] allowedFields; private String[] allowedFields;
private String[] disallowedFields; private String[] disallowedFields;
@ -199,6 +204,22 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
return this.autoGrowNestedPaths; return this.autoGrowNestedPaths;
} }
/**
* Specify the limit for array and collection auto-growing.
* <p>Default is 256, preventing OutOfMemoryErrors in case of large indexes.
* Raise this limit if your auto-growing needs are unusually high.
*/
public void setAutoGrowCollectionLimit(int autoGrowCollectionLimit) {
this.autoGrowCollectionLimit = autoGrowCollectionLimit;
}
/**
* Return the current limit for array and collection auto-growing.
*/
public int getAutoGrowCollectionLimit() {
return this.autoGrowCollectionLimit;
}
/** /**
* Initialize standard JavaBean property access for this DataBinder. * Initialize standard JavaBean property access for this DataBinder.
* <p>This is the default; an explicit call just leads to eager initialization. * <p>This is the default; an explicit call just leads to eager initialization.
@ -207,7 +228,8 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
public void initBeanPropertyAccess() { public void initBeanPropertyAccess() {
Assert.state(this.bindingResult == null, Assert.state(this.bindingResult == null,
"DataBinder is already initialized - call initBeanPropertyAccess before other configuration methods"); "DataBinder is already initialized - call initBeanPropertyAccess before other configuration methods");
this.bindingResult = new BeanPropertyBindingResult(getTarget(), getObjectName(), isAutoGrowNestedPaths()); this.bindingResult = new BeanPropertyBindingResult(
getTarget(), getObjectName(), isAutoGrowNestedPaths(), getAutoGrowCollectionLimit());
if (this.conversionService != null) { if (this.conversionService != null) {
this.bindingResult.initConversion(this.conversionService); this.bindingResult.initConversion(this.conversionService);
} }
@ -509,17 +531,14 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
return this.conversionService; return this.conversionService;
} }
@SuppressWarnings("unchecked")
public void registerCustomEditor(Class requiredType, PropertyEditor propertyEditor) { public void registerCustomEditor(Class requiredType, PropertyEditor propertyEditor) {
getPropertyEditorRegistry().registerCustomEditor(requiredType, propertyEditor); getPropertyEditorRegistry().registerCustomEditor(requiredType, propertyEditor);
} }
@SuppressWarnings("unchecked")
public void registerCustomEditor(Class requiredType, String field, PropertyEditor propertyEditor) { public void registerCustomEditor(Class requiredType, String field, PropertyEditor propertyEditor) {
getPropertyEditorRegistry().registerCustomEditor(requiredType, field, propertyEditor); getPropertyEditorRegistry().registerCustomEditor(requiredType, field, propertyEditor);
} }
@SuppressWarnings("unchecked")
public PropertyEditor findCustomEditor(Class requiredType, String propertyPath) { public PropertyEditor findCustomEditor(Class requiredType, String propertyPath) {
return getPropertyEditorRegistry().findCustomEditor(requiredType, propertyPath); return getPropertyEditorRegistry().findCustomEditor(requiredType, propertyPath);
} }
@ -700,8 +719,7 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
* @throws BindException if there were any errors in the bind operation * @throws BindException if there were any errors in the bind operation
* @see BindingResult#getModel() * @see BindingResult#getModel()
*/ */
@SuppressWarnings("unchecked") public Map<?, ?> close() throws BindException {
public Map close() throws BindException {
if (getBindingResult().hasErrors()) { if (getBindingResult().hasErrors()) {
throw new BindException(getBindingResult()); throw new BindException(getBindingResult());
} }

60
org.springframework.context/src/test/java/org/springframework/validation/DataBinderTests.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2010 the original author or authors. * Copyright 2002-2011 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -34,6 +34,7 @@ import org.springframework.beans.BeanWithObjectProperty;
import org.springframework.beans.DerivedTestBean; import org.springframework.beans.DerivedTestBean;
import org.springframework.beans.ITestBean; import org.springframework.beans.ITestBean;
import org.springframework.beans.IndexedTestBean; import org.springframework.beans.IndexedTestBean;
import org.springframework.beans.InvalidPropertyException;
import org.springframework.beans.MutablePropertyValues; import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.NotWritablePropertyException; import org.springframework.beans.NotWritablePropertyException;
import org.springframework.beans.NullValueInNestedPathException; import org.springframework.beans.NullValueInNestedPathException;
@ -1486,7 +1487,6 @@ public class DataBinderTests extends TestCase {
MutablePropertyValues mpvs = new MutablePropertyValues(); MutablePropertyValues mpvs = new MutablePropertyValues();
mpvs.add("name", name); mpvs.add("name", name);
mpvs.add("beanName", beanName); mpvs.add("beanName", beanName);
binder.bind(mpvs); binder.bind(mpvs);
assertEquals(name, testBean.getName()); assertEquals(name, testBean.getName());
@ -1495,6 +1495,62 @@ public class DataBinderTests extends TestCase {
assertEquals("beanName", disallowedFields[0]); assertEquals("beanName", disallowedFields[0]);
} }
public void testAutoGrowWithinDefaultLimit() throws Exception {
TestBean testBean = new TestBean();
DataBinder binder = new DataBinder(testBean, "testBean");
MutablePropertyValues mpvs = new MutablePropertyValues();
mpvs.add("friends[4]", "");
binder.bind(mpvs);
assertEquals(5, testBean.getFriends().size());
}
public void testAutoGrowBeyondDefaultLimit() throws Exception {
TestBean testBean = new TestBean();
DataBinder binder = new DataBinder(testBean, "testBean");
MutablePropertyValues mpvs = new MutablePropertyValues();
mpvs.add("friends[256]", "");
try {
binder.bind(mpvs);
fail("Should have thrown InvalidPropertyException");
}
catch (InvalidPropertyException ex) {
// expected
assertTrue(ex.getRootCause() instanceof IndexOutOfBoundsException);
}
}
public void testAutoGrowWithinCustomLimit() throws Exception {
TestBean testBean = new TestBean();
DataBinder binder = new DataBinder(testBean, "testBean");
binder.setAutoGrowCollectionLimit(10);
MutablePropertyValues mpvs = new MutablePropertyValues();
mpvs.add("friends[4]", "");
binder.bind(mpvs);
assertEquals(5, testBean.getFriends().size());
}
public void testAutoGrowBeyondCustomLimit() throws Exception {
TestBean testBean = new TestBean();
DataBinder binder = new DataBinder(testBean, "testBean");
binder.setAutoGrowCollectionLimit(10);
MutablePropertyValues mpvs = new MutablePropertyValues();
mpvs.add("friends[16]", "");
try {
binder.bind(mpvs);
fail("Should have thrown InvalidPropertyException");
}
catch (InvalidPropertyException ex) {
// expected
assertTrue(ex.getRootCause() instanceof IndexOutOfBoundsException);
}
}
private static class BeanWithIntegerList { private static class BeanWithIntegerList {

Loading…
Cancel
Save