restored DataBinder's ability to bind to an auto-growing List with unknown element type (SPR-8828)

This commit is contained in:
Juergen Hoeller
2011-12-07 21:27:26 +00:00
parent f9144ea1ea
commit 569426dfdf
8 changed files with 207 additions and 66 deletions

View File

@@ -20,6 +20,8 @@ import java.lang.annotation.Annotation;
import java.util.Collection;
import java.util.Map;
import org.springframework.util.Assert;
/**
* @author Keith Donald
* @since 3.1
@@ -28,15 +30,15 @@ abstract class AbstractDescriptor {
private final Class<?> type;
protected AbstractDescriptor(Class<?> type) {
if (type == null) {
throw new IllegalArgumentException("type cannot be null");
}
Assert.notNull(type, "Type must not be null");
this.type = type;
}
public Class<?> getType() {
return type;
return this.type;
}
public TypeDescriptor getElementTypeDescriptor() {
@@ -73,27 +75,33 @@ abstract class AbstractDescriptor {
}
}
public abstract Annotation[] getAnnotations();
public AbstractDescriptor nested() {
if (isCollection()) {
Class<?> elementType = resolveCollectionElementType();
return elementType != null ? nested(elementType, 0) : null;
return (elementType != null ? nested(elementType, 0) : null);
}
else if (isArray()) {
return nested(getType().getComponentType(), 0);
}
else if (isMap()) {
Class<?> mapValueType = resolveMapValueType();
return mapValueType != null ? nested(mapValueType, 1) : null;
return (mapValueType != null ? nested(mapValueType, 1) : null);
}
else if (Object.class.equals(getType())) {
// could be a collection type but we don't know about its element type,
// so let's just assume there is an element type of type Object
return this;
}
else {
throw new IllegalStateException("Not a collection, array, or map: cannot resolve nested value types");
}
}
// subclassing hooks
public abstract Annotation[] getAnnotations();
protected abstract Class<?> resolveCollectionElementType();
protected abstract Class<?> resolveMapKeyType();
@@ -102,6 +110,7 @@ abstract class AbstractDescriptor {
protected abstract AbstractDescriptor nested(Class<?> type, int typeIndex);
// internal helpers
private boolean isCollection() {

View File

@@ -20,6 +20,10 @@ import java.lang.annotation.Annotation;
import org.springframework.core.GenericCollectionTypeResolver;
import org.springframework.core.MethodParameter;
/**
* @author Keith Donald
* @since 3.1
*/
class BeanPropertyDescriptor extends AbstractDescriptor {
private final Property property;
@@ -28,6 +32,7 @@ class BeanPropertyDescriptor extends AbstractDescriptor {
private final Annotation[] annotations;
public BeanPropertyDescriptor(Property property) {
super(property.getType());
this.property = property;
@@ -35,24 +40,25 @@ class BeanPropertyDescriptor extends AbstractDescriptor {
this.annotations = property.getAnnotations();
}
@Override
public Annotation[] getAnnotations() {
return annotations;
return this.annotations;
}
@Override
protected Class<?> resolveCollectionElementType() {
return GenericCollectionTypeResolver.getCollectionParameterType(methodParameter);
return GenericCollectionTypeResolver.getCollectionParameterType(this.methodParameter);
}
@Override
protected Class<?> resolveMapKeyType() {
return GenericCollectionTypeResolver.getMapKeyParameterType(methodParameter);
return GenericCollectionTypeResolver.getMapKeyParameterType(this.methodParameter);
}
@Override
protected Class<?> resolveMapValueType() {
return GenericCollectionTypeResolver.getMapValueParameterType(methodParameter);
return GenericCollectionTypeResolver.getMapValueParameterType(this.methodParameter);
}
@Override
@@ -60,9 +66,10 @@ class BeanPropertyDescriptor extends AbstractDescriptor {
MethodParameter methodParameter = new MethodParameter(this.methodParameter);
methodParameter.increaseNestingLevel();
methodParameter.setTypeIndexForCurrentLevel(typeIndex);
return new BeanPropertyDescriptor(type, property, methodParameter, annotations);
return new BeanPropertyDescriptor(type, this.property, methodParameter, this.annotations);
}
// internal
private BeanPropertyDescriptor(Class<?> type, Property propertyDescriptor, MethodParameter methodParameter, Annotation[] annotations) {
@@ -72,4 +79,4 @@ class BeanPropertyDescriptor extends AbstractDescriptor {
this.annotations = annotations;
}
}
}

View File

@@ -53,7 +53,7 @@ class FieldDescriptor extends AbstractDescriptor {
@Override
public Annotation[] getAnnotations() {
return TypeDescriptor.nullSafeAnnotations(field.getAnnotations());
return TypeDescriptor.nullSafeAnnotations(this.field.getAnnotations());
}
@Override

View File

@@ -27,11 +27,16 @@ import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
/**
* A description of a JavaBeans Property that allows us to avoid a dependency on java.beans.PropertyDescriptor.
* java.beans is not available in a number of environments (e.g. Android, Java ME), so this is desirable.
* Used to build a TypeDescriptor from a property location.
* A description of a JavaBeans Property that allows us to avoid a dependency on
* <code>java.beans.PropertyDescriptor</code>. The <code>java.beans</code> package
* is not available in a number of environments (e.g. Android, Java ME), so this is
* desirable for portability of Spring's core conversion facility.
*
* <p>Used to build a TypeDescriptor from a property location.
* The built TypeDescriptor can then be used to convert from/to the property type.
*
* @author Keith Donald
* @since 3.1
* @see TypeDescriptor#TypeDescriptor(Property)
* @see TypeDescriptor#nested(Property, int)
*/
@@ -49,6 +54,7 @@ public final class Property {
private final Annotation[] annotations;
public Property(Class<?> objectType, Method readMethod, Method writeMethod) {
this.objectType = objectType;
this.readMethod = readMethod;
@@ -58,72 +64,77 @@ public final class Property {
this.annotations = resolveAnnotations();
}
/**
* The object declaring this property, either directly or in a superclass the object extends.
*/
public Class<?> getObjectType() {
return objectType;
return this.objectType;
}
/**
* The name of the property e.g. 'foo'.
* The name of the property: e.g. 'foo'
*/
public String getName() {
return name;
return this.name;
}
/**
* The property type e.g. java.lang.String.
* The property type: e.g. <code>java.lang.String</code>
*/
public Class<?> getType() {
return methodParameter.getParameterType();
return this.methodParameter.getParameterType();
}
/**
* The property getter method e.g. getFoo()
* The property getter method: e.g. <code>getFoo()</code>
*/
public Method getReadMethod() {
return readMethod;
return this.readMethod;
}
/**
* The property setter method e.g. setFoo(String).
* The property setter method: e.g. <code>setFoo(String)</code>
*/
public Method getWriteMethod() {
return writeMethod;
return this.writeMethod;
}
// package private
MethodParameter getMethodParameter() {
return methodParameter;
return this.methodParameter;
}
Annotation[] getAnnotations() {
return annotations;
return this.annotations;
}
// internal helpers
private String resolveName() {
if (readMethod != null) {
int index = readMethod.getName().indexOf("get");
if (this.readMethod != null) {
int index = this.readMethod.getName().indexOf("get");
if (index != -1) {
index += 3;
} else {
index = readMethod.getName().indexOf("is");
}
else {
index = this.readMethod.getName().indexOf("is");
if (index == -1) {
throw new IllegalArgumentException("Not a getter method");
}
index += 2;
}
return StringUtils.uncapitalize(readMethod.getName().substring(index));
} else {
int index = writeMethod.getName().indexOf("set") + 3;
return StringUtils.uncapitalize(this.readMethod.getName().substring(index));
}
else {
int index = this.writeMethod.getName().indexOf("set") + 3;
if (index == -1) {
throw new IllegalArgumentException("Not a setter method");
}
return StringUtils.uncapitalize(writeMethod.getName().substring(index));
return StringUtils.uncapitalize(this.writeMethod.getName().substring(index));
}
}
@@ -204,7 +215,8 @@ public final class Property {
private Class<?> declaringClass() {
if (getReadMethod() != null) {
return getReadMethod().getDeclaringClass();
} else {
}
else {
return getWriteMethod().getDeclaringClass();
}
}

View File

@@ -164,12 +164,12 @@ public class TypeDescriptor {
/**
* Creates a type descriptor for a nested type declared within the field.
* For example, if the field is a List&lt;String&gt; and the nestingLevel is 1, the nested type descriptor will be String.class.
* If the field is a List<List<String>> and the nestingLevel is 2, the nested type descriptor will also be a String.class.
* If the field is a Map<Integer, String> and the nestingLevel is 1, the nested type descriptor will be String, derived from the map value.
* If the field is a List<Map<Integer, String>> and the nestingLevel is 2, the nested type descriptor will be String, derived from the map value.
* Returns null if a nested type cannot be obtained because it was not declared.
* For example, if the field is a List&lt;?&gt;, the nested type descriptor returned will be null.
* <p>For example, if the field is a <code>List&lt;String&gt;</code> and the nestingLevel is 1, the nested type descriptor will be <code>String.class</code>.
* If the field is a <code>List&lt;List&lt;String&gt;&gt;</code> and the nestingLevel is 2, the nested type descriptor will also be a <code>String.class</code>.
* If the field is a <code>Map&lt;Integer, String&gt;</code> and the nestingLevel is 1, the nested type descriptor will be String, derived from the map value.
* If the field is a <code>List&lt;Map&lt;Integer, String&gt;&gt;</code> and the nestingLevel is 2, the nested type descriptor will be String, derived from the map value.
* Returns <code>null</code> if a nested type cannot be obtained because it was not declared.
* For example, if the field is a <code>List&lt;?&gt;</code>, the nested type descriptor returned will be <code>null</code>.
* @param field the field
* @param nestingLevel the nesting level of the collection/array element or map key/value declaration within the field.
* @return the nested type descriptor at the specified nestingLevel, or null if it could not be obtained
@@ -181,15 +181,15 @@ public class TypeDescriptor {
/**
* Creates a type descriptor for a nested type declared within the property.
* For example, if the property is a List&lt;String&gt; and the nestingLevel is 1, the nested type descriptor will be String.class.
* If the property is a List<List<String>> and the nestingLevel is 2, the nested type descriptor will also be a String.class.
* If the field is a Map<Integer, String> and the nestingLevel is 1, the nested type descriptor will be String, derived from the map value.
* If the property is a List<Map<Integer, String>> and the nestingLevel is 2, the nested type descriptor will be String, derived from the map value.
* Returns null if a nested type cannot be obtained because it was not declared.
* For example, if the property is a List&lt;?&gt;, the nested type descriptor returned will be null.
* <p>For example, if the property is a <code>List&lt;String&gt;</code> and the nestingLevel is 1, the nested type descriptor will be <code>String.class</code>.
* If the property is a <code>List&lt;List&lt;String&gt;&gt;</code> and the nestingLevel is 2, the nested type descriptor will also be a <code>String.class</code>.
* If the property is a <code>Map&lt;Integer, String&gt;</code> and the nestingLevel is 1, the nested type descriptor will be String, derived from the map value.
* If the property is a <code>List&lt;Map&lt;Integer, String&gt;&gt;</code> and the nestingLevel is 2, the nested type descriptor will be String, derived from the map value.
* Returns <code>null</code> if a nested type cannot be obtained because it was not declared.
* For example, if the property is a <code>List&lt;?&gt;</code>, the nested type descriptor returned will be <code>null</code>.
* @param property the property
* @param nestingLevel the nesting level of the collection/array element or map key/value declaration within the property.
* @return the nested type descriptor at the specified nestingLevel, or null if it could not be obtained
* @return the nested type descriptor at the specified nestingLevel, or <code>null</code> if it could not be obtained
* @throws IllegalArgumentException if the types up to the specified nesting level are not of collection, array, or map types.
*/
public static TypeDescriptor nested(Property property, int nestingLevel) {