restored DataBinder's ability to bind to an auto-growing List with unknown element type (SPR-8828)
This commit is contained in:
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ class FieldDescriptor extends AbstractDescriptor {
|
||||
|
||||
@Override
|
||||
public Annotation[] getAnnotations() {
|
||||
return TypeDescriptor.nullSafeAnnotations(field.getAnnotations());
|
||||
return TypeDescriptor.nullSafeAnnotations(this.field.getAnnotations());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<String> 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<?>, the nested type descriptor returned will be null.
|
||||
* <p>For example, if the field is a <code>List<String></code> and the nestingLevel is 1, the nested type descriptor will be <code>String.class</code>.
|
||||
* If the field is a <code>List<List<String>></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<Integer, String></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<Map<Integer, String>></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<?></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<String> 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<?>, the nested type descriptor returned will be null.
|
||||
* <p>For example, if the property is a <code>List<String></code> and the nestingLevel is 1, the nested type descriptor will be <code>String.class</code>.
|
||||
* If the property is a <code>List<List<String>></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<Integer, String></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<Map<Integer, String>></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<?></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) {
|
||||
|
||||
Reference in New Issue
Block a user