simplified TypeDescriptor usage and updated use of the API across BeanWrapper and SpEL; collapsed PropertyTypeDescriptor into TypeDescriptor for simplicity and ease of use; improved docs
This commit is contained in:
@@ -131,8 +131,14 @@ public class MethodParameter {
|
||||
this.constructor = original.constructor;
|
||||
this.parameterIndex = original.parameterIndex;
|
||||
this.parameterType = original.parameterType;
|
||||
this.genericParameterType = original.genericParameterType;
|
||||
this.parameterAnnotations = original.parameterAnnotations;
|
||||
this.parameterNameDiscoverer = original.parameterNameDiscoverer;
|
||||
this.parameterName = original.parameterName;
|
||||
this.nestingLevel = original.nestingLevel;
|
||||
this.typeIndexesPerLevel = original.typeIndexesPerLevel;
|
||||
this.typeVariableMap = original.typeVariableMap;
|
||||
this.hash = original.hash;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -16,21 +16,26 @@
|
||||
|
||||
package org.springframework.core.convert;
|
||||
|
||||
import java.beans.PropertyDescriptor;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.core.GenericCollectionTypeResolver;
|
||||
import org.springframework.core.GenericTypeResolver;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Context about a type to convert to.
|
||||
@@ -70,43 +75,41 @@ public class TypeDescriptor {
|
||||
}
|
||||
|
||||
|
||||
private Class<?> type;
|
||||
private final Class<?> type;
|
||||
|
||||
private MethodParameter methodParameter;
|
||||
private final TypeDescriptor elementType;
|
||||
|
||||
private Field field;
|
||||
private final TypeDescriptor mapKeyType;
|
||||
|
||||
private int fieldNestingLevel = 1;
|
||||
|
||||
private TypeDescriptor elementType;
|
||||
|
||||
private TypeDescriptor mapKeyType;
|
||||
|
||||
private TypeDescriptor mapValueType;
|
||||
|
||||
private Annotation[] annotations;
|
||||
private final TypeDescriptor mapValueType;
|
||||
|
||||
private final Annotation[] annotations;
|
||||
|
||||
/**
|
||||
* Create a new type descriptor from a method or constructor parameter.
|
||||
* Use this constructor when a target conversion point originates from a method parameter, such as a setter method argument.
|
||||
* @param methodParameter the MethodParameter to wrap
|
||||
* Use this constructor when a target conversion point is a method parameter.
|
||||
* @param methodParameter the method parameter
|
||||
*/
|
||||
public TypeDescriptor(MethodParameter methodParameter) {
|
||||
Assert.notNull(methodParameter, "MethodParameter must not be null");
|
||||
this.type = methodParameter.getParameterType();
|
||||
this.methodParameter = methodParameter;
|
||||
this(new ParameterDescriptor(methodParameter));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new type descriptor for a field.
|
||||
* Use this constructor when a target conversion point originates from a field.
|
||||
* @param field the field to wrap
|
||||
* Use this constructor when a target conversion point is a field.
|
||||
* @param field the field
|
||||
*/
|
||||
public TypeDescriptor(Field field) {
|
||||
Assert.notNull(field, "Field must not be null");
|
||||
this.type = field.getType();
|
||||
this.field = field;
|
||||
this(new FieldDescriptor(field));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new type descriptor for a property.
|
||||
* Use this constructor when a target conversion point is a property.
|
||||
* @param property the property
|
||||
*/
|
||||
public TypeDescriptor(Class<?> beanClass, PropertyDescriptor property) {
|
||||
this(new BeanPropertyDescriptor(beanClass, property));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -123,6 +126,31 @@ public class TypeDescriptor {
|
||||
TypeDescriptor desc = typeDescriptorCache.get(type);
|
||||
return (desc != null ? desc : new TypeDescriptor(type));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new type descriptor for a java.util.Collection class.
|
||||
* Useful for supporting conversion of source Collection objects to other types.
|
||||
* Serves as an alternative to {@link #forObject(Object)} to be used when you cannot rely on Collection element introspection to resolve the element type.
|
||||
* @param collectionType the collection type, which must implement {@link Collection}.
|
||||
* @param elementType the collection's element type, used to convert collection elements
|
||||
* @return the collection type descriptor
|
||||
*/
|
||||
public static TypeDescriptor collection(Class<?> collectionType, TypeDescriptor elementType) {
|
||||
return new TypeDescriptor(collectionType, elementType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new type descriptor for a java.util.Map class.
|
||||
* Useful for supporting the conversion of source Map objects to other types.
|
||||
* Serves as an alternative to {@link #forObject(Object)} to be used when you cannot rely on Map entry introspection to resolve the key and value type.
|
||||
* @param mapType the map type, which must implement {@link Map}.
|
||||
* @param keyType the map's key type, used to convert map keys
|
||||
* @param valueType the map's value type, used to convert map values
|
||||
* @return the map type descriptor
|
||||
*/
|
||||
public static TypeDescriptor map(Class<?> mapType, TypeDescriptor keyType, TypeDescriptor valueType) {
|
||||
return new TypeDescriptor(mapType, keyType, valueType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new type descriptor for an object.
|
||||
@@ -153,24 +181,47 @@ public class TypeDescriptor {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new type descriptor for a nested type declared on an array, collection, or map-based method parameter.
|
||||
* Use this factory method when you've resolved a nested source object such as a collection element or map value and wish to have it converted.
|
||||
* @param methodParameter the method parameter declaring the collection or map
|
||||
* Creates a type descriptor for a nested type declared by the method parameter.
|
||||
* For example, if the methodParameter is a List<String> and the nestingLevel is 1, the nested type descriptor will be String.class.
|
||||
* If the methodParameter is a List<List<String>> and the nestingLevel is 2, the nested type descriptor will also be a String.class.
|
||||
* If the methodParameter is a Map<Integer, String> and the nesting level is 1, the nested type descriptor will be String, derived from the map value.
|
||||
* If the methodParameter is a List<Map<Integer, String>> and the nesting level is 2, the nested type descriptor will be String, derived from the map value.
|
||||
* @param methodParameter the method parameter
|
||||
* @return the nested type descriptor
|
||||
* @throws IllegalArgumentException if the method parameter is not of a collection, array, or map type.
|
||||
*/
|
||||
public static TypeDescriptor forNestedType(MethodParameter methodParameter) {
|
||||
return new TypeDescriptor(resolveNestedType(methodParameter), methodParameter);
|
||||
public static TypeDescriptor nested(MethodParameter methodParameter, int nestingLevel) {
|
||||
return nested(new ParameterDescriptor(methodParameter), nestingLevel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new type descriptor for a nested type declared on an array, collection, or map-based method parameter.
|
||||
* Use this factory method when you've resolved a nested source object such as a collection element or map value and wish to have it converted.
|
||||
* @param nestedType the nested type
|
||||
* @param methodParameter the method parameter declaring the collection or map
|
||||
* Creates a type descriptor for a nested type declared by 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.
|
||||
* @param field the field
|
||||
* @param nestingLevel the nesting level
|
||||
* @return the nested type descriptor
|
||||
* @throws IllegalArgumentException if the field is not of a collection, array, or map type.
|
||||
*/
|
||||
public static TypeDescriptor forNestedType(Class<?> nestedType, MethodParameter methodParameter) {
|
||||
return new TypeDescriptor(nestedType, methodParameter);
|
||||
public static TypeDescriptor nested(Field field, int nestingLevel) {
|
||||
return nested(new FieldDescriptor(field), nestingLevel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a type descriptor for a nested type declared by 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.
|
||||
* @param property the property
|
||||
* @param nestingLevel the nesting level
|
||||
* @return the nested type descriptor
|
||||
* @throws IllegalArgumentException if the property is not of a collection, array, or map type.
|
||||
*/
|
||||
public static TypeDescriptor nested(Class<?> beanClass, PropertyDescriptor property, int nestingLevel) {
|
||||
return nested(new BeanPropertyDescriptor(beanClass, property), nestingLevel);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -206,10 +257,7 @@ public class TypeDescriptor {
|
||||
/**
|
||||
* Obtain the annotations associated with the wrapped parameter/field, if any.
|
||||
*/
|
||||
public synchronized Annotation[] getAnnotations() {
|
||||
if (this.annotations == null) {
|
||||
this.annotations = resolveAnnotations();
|
||||
}
|
||||
public Annotation[] getAnnotations() {
|
||||
return this.annotations;
|
||||
}
|
||||
|
||||
@@ -248,13 +296,6 @@ public class TypeDescriptor {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A textual representation of the type descriptor (eg. Map<String,Foo>) for use in messages.
|
||||
*/
|
||||
public String asString() {
|
||||
return toString();
|
||||
}
|
||||
|
||||
// indexable type descriptor operations
|
||||
|
||||
/**
|
||||
@@ -273,7 +314,9 @@ public class TypeDescriptor {
|
||||
|
||||
/**
|
||||
* If this type is an array type or {@link Collection} type, returns the underlying element type.
|
||||
* Returns <code>null</code> if the type is neither an array or collection.
|
||||
* Returns <code>null</code> if this type is neither an array or collection.
|
||||
* Returns Object.class if the element type is for a collection and was not explicitly declared.
|
||||
* @return the map element type, or <code>null</code> if not a collection or array.
|
||||
*/
|
||||
public Class<?> getElementType() {
|
||||
return getElementTypeDescriptor().getType();
|
||||
@@ -282,10 +325,7 @@ public class TypeDescriptor {
|
||||
/**
|
||||
* Return the element type as a type descriptor.
|
||||
*/
|
||||
public synchronized TypeDescriptor getElementTypeDescriptor() {
|
||||
if (this.elementType == null) {
|
||||
this.elementType = resolveElementTypeDescriptor();
|
||||
}
|
||||
public TypeDescriptor getElementTypeDescriptor() {
|
||||
return this.elementType;
|
||||
}
|
||||
|
||||
@@ -300,7 +340,9 @@ public class TypeDescriptor {
|
||||
|
||||
/**
|
||||
* Determine the generic key type of the wrapped Map parameter/field, if any.
|
||||
* @return the generic type, or <code>null</code> if none
|
||||
* Returns <code>null</code> if this type is not map.
|
||||
* Returns Object.class if the map's key type was not explicitly declared.
|
||||
* @return the map key type, or <code>null</code> if not a map.
|
||||
*/
|
||||
public Class<?> getMapKeyType() {
|
||||
return getMapKeyTypeDescriptor().getType();
|
||||
@@ -309,16 +351,15 @@ public class TypeDescriptor {
|
||||
/**
|
||||
* Returns map key type as a type descriptor.
|
||||
*/
|
||||
public synchronized TypeDescriptor getMapKeyTypeDescriptor() {
|
||||
if (this.mapKeyType == null) {
|
||||
this.mapKeyType = resolveMapKeyTypeDescriptor();
|
||||
}
|
||||
public TypeDescriptor getMapKeyTypeDescriptor() {
|
||||
return this.mapKeyType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the generic value type of the wrapped Map parameter/field, if any.
|
||||
* @return the generic type, or <code>null</code> if none
|
||||
* Returns <code>null</code> if this type is not map.
|
||||
* Returns Object.class if the map's value type was not explicitly declared.
|
||||
* @return the map value type, or <code>null</code> if not a map.
|
||||
*/
|
||||
public Class<?> getMapValueType() {
|
||||
return getMapValueTypeDescriptor().getType();
|
||||
@@ -327,25 +368,10 @@ public class TypeDescriptor {
|
||||
/**
|
||||
* Returns map value type as a type descriptor.
|
||||
*/
|
||||
public synchronized TypeDescriptor getMapValueTypeDescriptor() {
|
||||
if (this.mapValueType == null) {
|
||||
this.mapValueType = resolveMapValueTypeDescriptor();
|
||||
}
|
||||
public TypeDescriptor getMapValueTypeDescriptor() {
|
||||
return this.mapValueType;
|
||||
}
|
||||
|
||||
// special case public operations
|
||||
|
||||
/**
|
||||
* Exposes the underlying MethodParameter providing context for this TypeDescriptor.
|
||||
* Used to support legacy code scenarios where callers are already using the MethodParameter API (BeanWrapper).
|
||||
* In general, favor use of the TypeDescriptor API over the MethodParameter API as it is independent of type context location.
|
||||
* May be null if no MethodParameter was provided when this TypeDescriptor was constructed.
|
||||
*/
|
||||
public MethodParameter getMethodParameter() {
|
||||
return methodParameter;
|
||||
}
|
||||
|
||||
// extending Object
|
||||
|
||||
public boolean equals(Object obj) {
|
||||
@@ -395,128 +421,53 @@ public class TypeDescriptor {
|
||||
}
|
||||
}
|
||||
|
||||
// subclassing hooks
|
||||
// internal constructors
|
||||
|
||||
protected TypeDescriptor(Class<?> nestedType, MethodParameter methodParameter) {
|
||||
this.type = handleUnknownNestedType(nestedType);
|
||||
this.methodParameter = createNestedMethodParameter(methodParameter);
|
||||
private TypeDescriptor(Class<?> type) {
|
||||
this(new ClassDescriptor(type));
|
||||
}
|
||||
|
||||
private TypeDescriptor(AbstractDescriptor descriptor) {
|
||||
this.type = descriptor.getType();
|
||||
this.elementType = descriptor.getElementType();
|
||||
this.mapKeyType = descriptor.getMapKeyType();
|
||||
this.mapValueType = descriptor.getMapValueType();
|
||||
this.annotations = descriptor.getAnnotations();
|
||||
}
|
||||
|
||||
private TypeDescriptor() {
|
||||
this(null, TypeDescriptor.NULL, TypeDescriptor.NULL, TypeDescriptor.NULL);
|
||||
}
|
||||
|
||||
private TypeDescriptor(Class<?> collectionType, TypeDescriptor elementType) {
|
||||
this(collectionType, elementType, TypeDescriptor.NULL, TypeDescriptor.NULL);
|
||||
}
|
||||
|
||||
private TypeDescriptor(Class<?> mapType, TypeDescriptor keyType, TypeDescriptor valueType) {
|
||||
this(mapType, TypeDescriptor.NULL, keyType, valueType);
|
||||
}
|
||||
|
||||
private TypeDescriptor(Class<?> collectionType, CommonElement commonElement) {
|
||||
this(collectionType, fromCommonElement(commonElement), TypeDescriptor.NULL, TypeDescriptor.NULL);
|
||||
}
|
||||
|
||||
private TypeDescriptor(Class<?> mapType, CommonElement commonKey, CommonElement commonValue) {
|
||||
this(mapType, TypeDescriptor.NULL, fromCommonElement(commonKey), fromCommonElement(commonValue));
|
||||
}
|
||||
|
||||
protected Annotation[] resolveAnnotations() {
|
||||
if (this.field != null) {
|
||||
return this.field.getAnnotations();
|
||||
}
|
||||
else if (this.methodParameter != null) {
|
||||
if (this.methodParameter.getParameterIndex() < 0) {
|
||||
return this.methodParameter.getMethodAnnotations();
|
||||
}
|
||||
else {
|
||||
return this.methodParameter.getParameterAnnotations();
|
||||
}
|
||||
}
|
||||
else {
|
||||
return EMPTY_ANNOTATION_ARRAY;
|
||||
}
|
||||
private TypeDescriptor(Class<?> type, TypeDescriptor elementType, TypeDescriptor mapKeyType, TypeDescriptor mapValueType) {
|
||||
this.type = type;
|
||||
this.elementType = elementType;
|
||||
this.mapKeyType = mapKeyType;
|
||||
this.mapValueType = mapValueType;
|
||||
this.annotations = EMPTY_ANNOTATION_ARRAY;
|
||||
}
|
||||
|
||||
protected TypeDescriptor newNestedTypeDescriptor(Class<?> nestedType, MethodParameter nested) {
|
||||
return new TypeDescriptor(nestedType, nested);
|
||||
private static Annotation[] nullSafeAnnotations(Annotation[] annotations) {
|
||||
return annotations != null ? annotations : EMPTY_ANNOTATION_ARRAY;
|
||||
}
|
||||
|
||||
protected static Class<?> resolveNestedType(MethodParameter methodParameter) {
|
||||
if (Collection.class.isAssignableFrom(methodParameter.getParameterType())) {
|
||||
return GenericCollectionTypeResolver.getCollectionParameterType(methodParameter);
|
||||
} else if (Map.class.isAssignableFrom(methodParameter.getParameterType())) {
|
||||
return GenericCollectionTypeResolver.getMapValueParameterType(methodParameter);
|
||||
} else if (methodParameter.getParameterType().isArray()) {
|
||||
return methodParameter.getParameterType().getComponentType();
|
||||
} else {
|
||||
throw new IllegalStateException("Not a collection, map, or array method parameter type " + methodParameter.getParameterType());
|
||||
}
|
||||
}
|
||||
|
||||
// internal helpers
|
||||
|
||||
private TypeDescriptor resolveElementTypeDescriptor() {
|
||||
if (isCollection()) {
|
||||
return createNestedTypeDescriptor(resolveCollectionElementType());
|
||||
}
|
||||
else {
|
||||
// TODO: GenericCollectionTypeResolver is not capable of applying nesting levels to array fields;
|
||||
// this means generic info of nested lists or maps stored inside array method parameters or fields is not obtainable
|
||||
return createNestedTypeDescriptor(getType().getComponentType());
|
||||
}
|
||||
}
|
||||
|
||||
private TypeDescriptor resolveMapKeyTypeDescriptor() {
|
||||
return createNestedTypeDescriptor(resolveMapKeyType());
|
||||
}
|
||||
|
||||
private TypeDescriptor resolveMapValueTypeDescriptor() {
|
||||
return createNestedTypeDescriptor(resolveMapValueType());
|
||||
}
|
||||
|
||||
private TypeDescriptor createNestedTypeDescriptor(Class<?> nestedType) {
|
||||
nestedType = handleUnknownNestedType(nestedType);
|
||||
if (this.methodParameter != null) {
|
||||
return newNestedTypeDescriptor(nestedType, createNestedMethodParameter(this.methodParameter));
|
||||
}
|
||||
else if (this.field != null) {
|
||||
return new TypeDescriptor(nestedType, this.field, this.fieldNestingLevel + 1);
|
||||
}
|
||||
else {
|
||||
return TypeDescriptor.valueOf(nestedType);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Class<?> resolveCollectionElementType() {
|
||||
if (this.methodParameter != null) {
|
||||
return GenericCollectionTypeResolver.getCollectionParameterType(this.methodParameter);
|
||||
}
|
||||
else if (this.field != null) {
|
||||
return GenericCollectionTypeResolver.getCollectionFieldType(this.field, this.fieldNestingLevel);
|
||||
}
|
||||
else {
|
||||
return GenericCollectionTypeResolver.getCollectionType((Class<? extends Collection<?>>) this.type);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Class<?> resolveMapKeyType() {
|
||||
if (this.methodParameter != null) {
|
||||
return GenericCollectionTypeResolver.getMapKeyParameterType(this.methodParameter);
|
||||
}
|
||||
else if (this.field != null) {
|
||||
return GenericCollectionTypeResolver.getMapKeyFieldType(this.field, this.fieldNestingLevel);
|
||||
}
|
||||
else {
|
||||
return GenericCollectionTypeResolver.getMapKeyType((Class<? extends Map<?, ?>>) this.type);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Class<?> resolveMapValueType() {
|
||||
if (this.methodParameter != null) {
|
||||
return GenericCollectionTypeResolver.getMapValueParameterType(this.methodParameter);
|
||||
}
|
||||
else if (this.field != null) {
|
||||
return GenericCollectionTypeResolver.getMapValueFieldType(this.field, this.fieldNestingLevel);
|
||||
}
|
||||
else {
|
||||
return GenericCollectionTypeResolver.getMapValueType((Class<? extends Map<?, ?>>) this.type);
|
||||
}
|
||||
}
|
||||
|
||||
private Class<?> handleUnknownNestedType(Class<?> nestedType) {
|
||||
return nestedType != null ? nestedType : Object.class;
|
||||
}
|
||||
|
||||
private MethodParameter createNestedMethodParameter(MethodParameter parentMethodParameter) {
|
||||
MethodParameter methodParameter = new MethodParameter(parentMethodParameter);
|
||||
methodParameter.increaseNestingLevel();
|
||||
return methodParameter;
|
||||
}
|
||||
// forObject-related internal helpers
|
||||
|
||||
private static CommonElement findCommonElement(Collection<?> values) {
|
||||
Class<?> commonType = null;
|
||||
@@ -569,34 +520,7 @@ public class TypeDescriptor {
|
||||
}
|
||||
}
|
||||
|
||||
// internal constructors
|
||||
|
||||
private TypeDescriptor() {
|
||||
}
|
||||
|
||||
private TypeDescriptor(Class<?> type) {
|
||||
Assert.notNull(type, "Type must not be null");
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
private TypeDescriptor(Class<?> nestedType, Field field, int nestingLevel) {
|
||||
this.type = nestedType;
|
||||
this.field = field;
|
||||
this.fieldNestingLevel = nestingLevel;
|
||||
}
|
||||
|
||||
private TypeDescriptor(Class<?> mapType, CommonElement commonKey, CommonElement commonValue) {
|
||||
this.type = mapType;
|
||||
this.mapKeyType = applyCommonElement(commonKey);
|
||||
this.mapValueType = applyCommonElement(commonValue);
|
||||
}
|
||||
|
||||
private TypeDescriptor(Class<?> collectionType, CommonElement commonElement) {
|
||||
this.type = collectionType;
|
||||
this.elementType = applyCommonElement(commonElement);
|
||||
}
|
||||
|
||||
private TypeDescriptor applyCommonElement(CommonElement commonElement) {
|
||||
private static TypeDescriptor fromCommonElement(CommonElement commonElement) {
|
||||
if (commonElement == null) {
|
||||
return TypeDescriptor.valueOf(Object.class);
|
||||
}
|
||||
@@ -619,7 +543,352 @@ public class TypeDescriptor {
|
||||
}
|
||||
}
|
||||
|
||||
private static TypeDescriptor nested(AbstractDescriptor descriptor, int nestingLevel) {
|
||||
for (int i = 0; i < nestingLevel; i++) {
|
||||
descriptor = descriptor.nested();
|
||||
}
|
||||
return new TypeDescriptor(descriptor);
|
||||
}
|
||||
|
||||
// inner classes
|
||||
|
||||
private abstract static class AbstractDescriptor {
|
||||
|
||||
private final Class<?> type;
|
||||
|
||||
public AbstractDescriptor(Class<?> type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public Class<?> getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public TypeDescriptor getElementType() {
|
||||
if (isCollection()) {
|
||||
Class<?> elementType = wildcard(getCollectionElementClass());
|
||||
return new TypeDescriptor(nested(elementType, 0));
|
||||
} else if (isArray()) {
|
||||
Class<?> elementType = getType().getComponentType();
|
||||
return new TypeDescriptor(nested(elementType, 0));
|
||||
} else {
|
||||
return TypeDescriptor.NULL;
|
||||
}
|
||||
}
|
||||
|
||||
public TypeDescriptor getMapKeyType() {
|
||||
if (isMap()) {
|
||||
Class<?> keyType = wildcard(getMapKeyClass());
|
||||
return new TypeDescriptor(nested(keyType, 0));
|
||||
} else {
|
||||
return TypeDescriptor.NULL;
|
||||
}
|
||||
}
|
||||
|
||||
public TypeDescriptor getMapValueType() {
|
||||
if (isMap()) {
|
||||
Class<?> valueType = wildcard(getMapValueClass());
|
||||
return new TypeDescriptor(nested(valueType, 1));
|
||||
} else {
|
||||
return TypeDescriptor.NULL;
|
||||
}
|
||||
}
|
||||
|
||||
public abstract Annotation[] getAnnotations();
|
||||
|
||||
public AbstractDescriptor nested() {
|
||||
if (isCollection()) {
|
||||
return nested(wildcard(getCollectionElementClass()), 0);
|
||||
} else if (isArray()) {
|
||||
return nested(getType().getComponentType(), 0);
|
||||
} else if (isMap()) {
|
||||
return nested(wildcard(getMapValueClass()), 1);
|
||||
} else {
|
||||
throw new IllegalStateException("Not a collection, array, or map: cannot resolve nested value types");
|
||||
}
|
||||
}
|
||||
|
||||
// subclassing hooks
|
||||
|
||||
protected abstract Class<?> getCollectionElementClass();
|
||||
|
||||
protected abstract Class<?> getMapKeyClass();
|
||||
|
||||
protected abstract Class<?> getMapValueClass();
|
||||
|
||||
protected abstract AbstractDescriptor nested(Class<?> type, int typeIndex);
|
||||
|
||||
// internal helpers
|
||||
|
||||
private boolean isCollection() {
|
||||
return Collection.class.isAssignableFrom(getType());
|
||||
}
|
||||
|
||||
private boolean isArray() {
|
||||
return getType().isArray();
|
||||
}
|
||||
|
||||
private boolean isMap() {
|
||||
return Map.class.isAssignableFrom(getType());
|
||||
}
|
||||
|
||||
private Class<?> wildcard(Class<?> type) {
|
||||
return type != null ? type : Object.class;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class FieldDescriptor extends AbstractDescriptor {
|
||||
|
||||
private final Field field;
|
||||
|
||||
private final int nestingLevel;
|
||||
|
||||
public FieldDescriptor(Field field) {
|
||||
this(field.getType(), field, 1, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Annotation[] getAnnotations() {
|
||||
return nullSafeAnnotations(field.getAnnotations());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Class<?> getCollectionElementClass() {
|
||||
return GenericCollectionTypeResolver.getCollectionFieldType(this.field, this.nestingLevel);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Class<?> getMapKeyClass() {
|
||||
return GenericCollectionTypeResolver.getMapKeyFieldType(this.field, this.nestingLevel);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Class<?> getMapValueClass() {
|
||||
return GenericCollectionTypeResolver.getMapValueFieldType(this.field, this.nestingLevel);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AbstractDescriptor nested(Class<?> type, int typeIndex) {
|
||||
return new FieldDescriptor(type, this.field, this.nestingLevel + 1, typeIndex);
|
||||
}
|
||||
|
||||
// internal
|
||||
|
||||
private FieldDescriptor(Class<?> type, Field field, int nestingLevel, int typeIndex) {
|
||||
super(type);
|
||||
this.field = field;
|
||||
this.nestingLevel = nestingLevel;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class ParameterDescriptor extends AbstractDescriptor {
|
||||
|
||||
private final MethodParameter methodParameter;
|
||||
|
||||
public ParameterDescriptor(MethodParameter methodParameter) {
|
||||
super(methodParameter.getParameterType());
|
||||
if (methodParameter.getNestingLevel() != 1) {
|
||||
throw new IllegalArgumentException("The MethodParameter argument must have its nestingLevel set to 1");
|
||||
}
|
||||
this.methodParameter = methodParameter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Annotation[] getAnnotations() {
|
||||
if (methodParameter.getParameterIndex() == -1) {
|
||||
return nullSafeAnnotations(methodParameter.getMethodAnnotations());
|
||||
}
|
||||
else {
|
||||
return nullSafeAnnotations(methodParameter.getParameterAnnotations());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Class<?> getCollectionElementClass() {
|
||||
return GenericCollectionTypeResolver.getCollectionParameterType(methodParameter);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Class<?> getMapKeyClass() {
|
||||
return GenericCollectionTypeResolver.getMapKeyParameterType(methodParameter);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Class<?> getMapValueClass() {
|
||||
return GenericCollectionTypeResolver.getMapValueParameterType(methodParameter);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AbstractDescriptor nested(Class<?> type, int typeIndex) {
|
||||
MethodParameter methodParameter = new MethodParameter(this.methodParameter);
|
||||
methodParameter.increaseNestingLevel();
|
||||
methodParameter.setTypeIndexForCurrentLevel(typeIndex);
|
||||
return new ParameterDescriptor(type, methodParameter);
|
||||
}
|
||||
|
||||
// internal
|
||||
|
||||
private ParameterDescriptor(Class<?> type, MethodParameter methodParameter) {
|
||||
super(type);
|
||||
this.methodParameter = methodParameter;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class BeanPropertyDescriptor extends AbstractDescriptor {
|
||||
|
||||
private final Class<?> beanClass;
|
||||
|
||||
private final PropertyDescriptor property;
|
||||
|
||||
private final MethodParameter methodParameter;
|
||||
|
||||
private final Annotation[] annotations;
|
||||
|
||||
public BeanPropertyDescriptor(Class<?> beanClass, PropertyDescriptor property) {
|
||||
super(property.getPropertyType());
|
||||
this.beanClass = beanClass;
|
||||
this.property = property;
|
||||
this.methodParameter = resolveMethodParameter();
|
||||
this.annotations = resolveAnnotations();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Annotation[] getAnnotations() {
|
||||
return annotations;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Class<?> getCollectionElementClass() {
|
||||
return GenericCollectionTypeResolver.getCollectionParameterType(methodParameter);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Class<?> getMapKeyClass() {
|
||||
return GenericCollectionTypeResolver.getMapKeyParameterType(methodParameter);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Class<?> getMapValueClass() {
|
||||
return GenericCollectionTypeResolver.getMapValueParameterType(methodParameter);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AbstractDescriptor nested(Class<?> type, int typeIndex) {
|
||||
MethodParameter methodParameter = new MethodParameter(this.methodParameter);
|
||||
methodParameter.increaseNestingLevel();
|
||||
methodParameter.setTypeIndexForCurrentLevel(typeIndex);
|
||||
return new BeanPropertyDescriptor(type, beanClass, property, methodParameter, annotations);
|
||||
}
|
||||
|
||||
// internal
|
||||
|
||||
private MethodParameter resolveMethodParameter() {
|
||||
if (property.getReadMethod() != null) {
|
||||
MethodParameter parameter = new MethodParameter(property.getReadMethod(), -1);
|
||||
GenericTypeResolver.resolveParameterType(parameter, beanClass);
|
||||
return parameter;
|
||||
} else if (property.getWriteMethod() != null) {
|
||||
MethodParameter parameter = new MethodParameter(property.getWriteMethod(), 0);
|
||||
GenericTypeResolver.resolveParameterType(parameter, beanClass);
|
||||
return parameter;
|
||||
} else {
|
||||
throw new IllegalArgumentException("Property is neither readable or writeable");
|
||||
}
|
||||
}
|
||||
|
||||
private Annotation[] resolveAnnotations() {
|
||||
Map<Class<?>, Annotation> annMap = new LinkedHashMap<Class<?>, Annotation>();
|
||||
Method readMethod = this.property.getReadMethod();
|
||||
if (readMethod != null) {
|
||||
for (Annotation ann : readMethod.getAnnotations()) {
|
||||
annMap.put(ann.annotationType(), ann);
|
||||
}
|
||||
}
|
||||
Method writeMethod = this.property.getWriteMethod();
|
||||
if (writeMethod != null) {
|
||||
for (Annotation ann : writeMethod.getAnnotations()) {
|
||||
annMap.put(ann.annotationType(), ann);
|
||||
}
|
||||
}
|
||||
Field field = getField();
|
||||
if (field != null) {
|
||||
for (Annotation ann : field.getAnnotations()) {
|
||||
annMap.put(ann.annotationType(), ann);
|
||||
}
|
||||
}
|
||||
return annMap.values().toArray(new Annotation[annMap.size()]);
|
||||
}
|
||||
|
||||
private Field getField() {
|
||||
String name = this.property.getName();
|
||||
if (!StringUtils.hasLength(name)) {
|
||||
return null;
|
||||
}
|
||||
Class<?> declaringClass = declaringClass();
|
||||
Field field = ReflectionUtils.findField(declaringClass, name);
|
||||
if (field == null) {
|
||||
// Same lenient fallback checking as in CachedIntrospectionResults...
|
||||
field = ReflectionUtils.findField(declaringClass, name.substring(0, 1).toLowerCase() + name.substring(1));
|
||||
if (field == null) {
|
||||
field = ReflectionUtils.findField(declaringClass, name.substring(0, 1).toUpperCase() + name.substring(1));
|
||||
}
|
||||
}
|
||||
return field;
|
||||
}
|
||||
|
||||
private Class<?> declaringClass() {
|
||||
if (this.property.getReadMethod() != null) {
|
||||
return this.property.getReadMethod().getDeclaringClass();
|
||||
} else {
|
||||
return this.property.getWriteMethod().getDeclaringClass();
|
||||
}
|
||||
}
|
||||
|
||||
private BeanPropertyDescriptor(Class<?> type, Class<?> beanClass, java.beans.PropertyDescriptor propertyDescriptor, MethodParameter methodParameter, Annotation[] annotations) {
|
||||
super(type);
|
||||
this.beanClass = beanClass;
|
||||
this.property = propertyDescriptor;
|
||||
this.methodParameter = methodParameter;
|
||||
this.annotations = annotations;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class ClassDescriptor extends AbstractDescriptor {
|
||||
|
||||
private ClassDescriptor(Class<?> type) {
|
||||
super(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Annotation[] getAnnotations() {
|
||||
return EMPTY_ANNOTATION_ARRAY;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Class<?> getCollectionElementClass() {
|
||||
return Object.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Class<?> getMapKeyClass() {
|
||||
return Object.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Class<?> getMapValueClass() {
|
||||
return Object.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AbstractDescriptor nested(Class<?> type, int typeIndex) {
|
||||
return new ClassDescriptor(type);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class CommonElement {
|
||||
|
||||
|
||||
@@ -1,135 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2010 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.core.convert.support;
|
||||
|
||||
import java.beans.PropertyDescriptor;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* {@link TypeDescriptor} extension that exposes additional annotations
|
||||
* as conversion metadata: namely, annotations on other accessor methods
|
||||
* (getter/setter) and on the underlying field, if found.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @author Keith Donald
|
||||
* @since 3.0.2
|
||||
*/
|
||||
public class PropertyTypeDescriptor extends TypeDescriptor {
|
||||
|
||||
private final PropertyDescriptor propertyDescriptor;
|
||||
|
||||
/**
|
||||
* Create a new type descriptor for the given bean property.
|
||||
* @param methodParameter the target method parameter
|
||||
* @param propertyDescriptor the corresponding JavaBean PropertyDescriptor
|
||||
*/
|
||||
public PropertyTypeDescriptor(MethodParameter methodParameter, PropertyDescriptor propertyDescriptor) {
|
||||
super(methodParameter);
|
||||
this.propertyDescriptor = propertyDescriptor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new type descriptor for a nested type declared on an array, collection, or map-based property.
|
||||
* Use this factory method when you've resolved a nested source object such as a collection element or map value and wish to have it converted.
|
||||
* Builds in protection for increasing the nesting level of the provided MethodParameter if the nestedType is itself a collection.
|
||||
* @param methodParameter the method parameter
|
||||
* @return the property descriptor
|
||||
*/
|
||||
public static PropertyTypeDescriptor forNestedType(MethodParameter methodParameter, PropertyDescriptor propertyDescriptor) {
|
||||
return forNestedType(resolveNestedType(methodParameter), methodParameter, propertyDescriptor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new type descriptor for a nested type declared on an array, collection, or map-based property.
|
||||
* Use this factory method when you've resolved a nested source object such as a collection element or map value and wish to have it converted.
|
||||
* Builds in protection for increasing the nesting level of the provided MethodParameter if the nestedType is itself a collection.
|
||||
* @param nestedType the nested type
|
||||
* @param methodParameter the method parameter
|
||||
* @return the property descriptor
|
||||
*/
|
||||
public static PropertyTypeDescriptor forNestedType(Class<?> nestedType, MethodParameter methodParameter, PropertyDescriptor propertyDescriptor) {
|
||||
return new PropertyTypeDescriptor(nestedType, methodParameter, propertyDescriptor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the underlying PropertyDescriptor.
|
||||
*/
|
||||
public PropertyDescriptor getPropertyDescriptor() {
|
||||
return this.propertyDescriptor;
|
||||
}
|
||||
|
||||
protected Annotation[] resolveAnnotations() {
|
||||
Map<Class<?>, Annotation> annMap = new LinkedHashMap<Class<?>, Annotation>();
|
||||
String name = this.propertyDescriptor.getName();
|
||||
if (StringUtils.hasLength(name)) {
|
||||
Class<?> clazz = getMethodParameter().getMethod().getDeclaringClass();
|
||||
Field field = ReflectionUtils.findField(clazz, name);
|
||||
if (field == null) {
|
||||
// Same lenient fallback checking as in CachedIntrospectionResults...
|
||||
field = ReflectionUtils.findField(clazz, name.substring(0, 1).toLowerCase() + name.substring(1));
|
||||
if (field == null) {
|
||||
field = ReflectionUtils.findField(clazz, name.substring(0, 1).toUpperCase() + name.substring(1));
|
||||
}
|
||||
}
|
||||
if (field != null) {
|
||||
for (Annotation ann : field.getAnnotations()) {
|
||||
annMap.put(ann.annotationType(), ann);
|
||||
}
|
||||
}
|
||||
}
|
||||
Method writeMethod = this.propertyDescriptor.getWriteMethod();
|
||||
Method readMethod = this.propertyDescriptor.getReadMethod();
|
||||
if (writeMethod != null && writeMethod != getMethodParameter().getMethod()) {
|
||||
for (Annotation ann : writeMethod.getAnnotations()) {
|
||||
annMap.put(ann.annotationType(), ann);
|
||||
}
|
||||
}
|
||||
if (readMethod != null && readMethod != getMethodParameter().getMethod()) {
|
||||
for (Annotation ann : readMethod.getAnnotations()) {
|
||||
annMap.put(ann.annotationType(), ann);
|
||||
}
|
||||
}
|
||||
for (Annotation ann : getMethodParameter().getMethodAnnotations()) {
|
||||
annMap.put(ann.annotationType(), ann);
|
||||
}
|
||||
for (Annotation ann : getMethodParameter().getParameterAnnotations()) {
|
||||
annMap.put(ann.annotationType(), ann);
|
||||
}
|
||||
return annMap.values().toArray(new Annotation[annMap.size()]);
|
||||
}
|
||||
|
||||
protected TypeDescriptor newNestedTypeDescriptor(Class<?> nestedType, MethodParameter nested) {
|
||||
return new PropertyTypeDescriptor(nestedType, nested, this.propertyDescriptor);
|
||||
}
|
||||
|
||||
// internal constructors
|
||||
|
||||
private PropertyTypeDescriptor(Class<?> nestedType, MethodParameter methodParameter, PropertyDescriptor propertyDescriptor) {
|
||||
super(nestedType, methodParameter);
|
||||
this.propertyDescriptor = propertyDescriptor;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -19,17 +19,33 @@ package org.springframework.core.convert;
|
||||
import static junit.framework.Assert.assertEquals;
|
||||
import static junit.framework.Assert.assertTrue;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
import java.beans.IntrospectionException;
|
||||
import java.beans.PropertyDescriptor;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.springframework.core.BridgeMethodResolver;
|
||||
import org.springframework.core.GenericTypeResolver;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* @author Andy Clement
|
||||
@@ -163,7 +179,7 @@ public class TypeDescriptorTests {
|
||||
assertEquals(List.class, typeDescriptor.getType());
|
||||
assertEquals(String.class, typeDescriptor.getElementType());
|
||||
// TODO caught shorten these names but it is OK that they are fully qualified for now
|
||||
assertEquals("java.util.List<java.lang.String>", typeDescriptor.asString());
|
||||
assertEquals("java.util.List<java.lang.String>", typeDescriptor.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -173,7 +189,7 @@ public class TypeDescriptorTests {
|
||||
assertEquals(List.class, typeDescriptor.getType());
|
||||
assertEquals(List.class, typeDescriptor.getElementType());
|
||||
assertEquals(String.class, typeDescriptor.getElementTypeDescriptor().getElementType());
|
||||
assertEquals("java.util.List<java.util.List<java.lang.String>>", typeDescriptor.asString());
|
||||
assertEquals("java.util.List<java.util.List<java.lang.String>>", typeDescriptor.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -183,7 +199,7 @@ public class TypeDescriptorTests {
|
||||
assertEquals(List.class, typeDescriptor.getType());
|
||||
assertEquals(List.class, typeDescriptor.getElementType());
|
||||
assertEquals(Object.class, typeDescriptor.getElementTypeDescriptor().getElementType());
|
||||
assertEquals("java.util.List<java.util.List<java.lang.Object>>", typeDescriptor.asString());
|
||||
assertEquals("java.util.List<java.util.List<java.lang.Object>>", typeDescriptor.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -191,14 +207,14 @@ public class TypeDescriptorTests {
|
||||
TypeDescriptor typeDescriptor = new TypeDescriptor(TypeDescriptorTests.class.getDeclaredField("intArray"));
|
||||
assertTrue(typeDescriptor.isArray());
|
||||
assertEquals(Integer.TYPE,typeDescriptor.getElementType());
|
||||
assertEquals("int[]",typeDescriptor.asString());
|
||||
assertEquals("int[]",typeDescriptor.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildingArrayTypeDescriptor() throws Exception {
|
||||
TypeDescriptor typeDescriptor = TypeDescriptor.valueOf(int[].class);
|
||||
assertTrue(typeDescriptor.isArray());
|
||||
assertEquals(Integer.TYPE ,typeDescriptor.getElementType());
|
||||
assertEquals(Integer.TYPE, typeDescriptor.getElementType());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -208,7 +224,7 @@ public class TypeDescriptorTests {
|
||||
assertTrue(typeDescriptor.isArray());
|
||||
assertEquals(List.class,typeDescriptor.getElementType());
|
||||
assertEquals(String.class, typeDescriptor.getElementTypeDescriptor().getElementType());
|
||||
assertEquals("java.util.List[]",typeDescriptor.asString());
|
||||
assertEquals("java.util.List[]",typeDescriptor.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -218,7 +234,7 @@ public class TypeDescriptorTests {
|
||||
assertEquals(String.class,typeDescriptor.getMapKeyType());
|
||||
assertEquals(List.class, typeDescriptor.getMapValueType());
|
||||
assertEquals(Integer.class, typeDescriptor.getMapValueTypeDescriptor().getElementType());
|
||||
assertEquals("java.util.Map<java.lang.String, java.util.List<java.lang.Integer>>", typeDescriptor.asString());
|
||||
assertEquals("java.util.Map<java.lang.String, java.util.List<java.lang.Integer>>", typeDescriptor.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -244,5 +260,373 @@ public class TypeDescriptorTests {
|
||||
TypeDescriptor t12 = new TypeDescriptor(getClass().getField("mapField"));
|
||||
assertEquals(t11, t12);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void annotatedMethod() throws Exception {
|
||||
TypeDescriptor t1 = new TypeDescriptor(new MethodParameter(getClass().getMethod("testAnnotatedMethod", String.class), 0));
|
||||
assertEquals(String.class, t1.getType());
|
||||
assertNotNull(t1.getAnnotation(ParameterAnnotation.class));
|
||||
}
|
||||
|
||||
@Target({ElementType.PARAMETER})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface ParameterAnnotation {
|
||||
|
||||
}
|
||||
|
||||
public void testAnnotatedMethod(@ParameterAnnotation String parameter) {
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nestedMethodParameterType() throws Exception {
|
||||
TypeDescriptor t1 = TypeDescriptor.nested(new MethodParameter(getClass().getMethod("test1", List.class), 0), 1);
|
||||
assertEquals(String.class, t1.getType());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nestedMethodParameterType2Levels() throws Exception {
|
||||
TypeDescriptor t1 = TypeDescriptor.nested(new MethodParameter(getClass().getMethod("test2", List.class), 0), 2);
|
||||
assertEquals(String.class, t1.getType());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nestedMethodParameterTypeMap() throws Exception {
|
||||
TypeDescriptor t1 = TypeDescriptor.nested(new MethodParameter(getClass().getMethod("test3", Map.class), 0), 1);
|
||||
assertEquals(String.class, t1.getType());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nestedMethodParameterTypeMapTwoLevels() throws Exception {
|
||||
TypeDescriptor t1 = TypeDescriptor.nested(new MethodParameter(getClass().getMethod("test4", List.class), 0), 2);
|
||||
assertEquals(String.class, t1.getType());
|
||||
}
|
||||
|
||||
@Test(expected=IllegalStateException.class)
|
||||
public void nestedMethodParameterTypeNotNestable() throws Exception {
|
||||
TypeDescriptor.nested(new MethodParameter(getClass().getMethod("test5", String.class), 0), 2);
|
||||
}
|
||||
|
||||
@Test(expected=IllegalArgumentException.class)
|
||||
public void nestedMethodParameterTypeInvalidNestingLevel() throws Exception {
|
||||
TypeDescriptor.nested(new MethodParameter(getClass().getMethod("test5", String.class), 0, 2), 2);
|
||||
}
|
||||
|
||||
public void test1(List<String> param1) {
|
||||
|
||||
}
|
||||
|
||||
public void test2(List<List<String>> param1) {
|
||||
|
||||
}
|
||||
|
||||
public void test3(Map<Integer, String> param1) {
|
||||
|
||||
}
|
||||
|
||||
public void test4(List<Map<Integer, String>> param1) {
|
||||
|
||||
}
|
||||
|
||||
public void test5(String param1) {
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nestedFieldTypeMapTwoLevels() throws Exception {
|
||||
TypeDescriptor t1 = TypeDescriptor.nested(getClass().getField("test4"), 2);
|
||||
assertEquals(String.class, t1.getType());
|
||||
}
|
||||
|
||||
public List<Map<Integer, String>> test4;
|
||||
|
||||
@Test
|
||||
public void nestedPropertyTypeMapTwoLevels() throws Exception {
|
||||
PropertyDescriptor property = new PropertyDescriptor("test4", getClass().getMethod("getTest4", null), getClass().getMethod("setTest4", List.class));
|
||||
TypeDescriptor t1 = TypeDescriptor.nested(getClass(), property, 2);
|
||||
assertEquals(String.class, t1.getType());
|
||||
}
|
||||
|
||||
public List<Map<Integer, String>> getTest4() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void setTest4(List<Map<Integer, String>> test4) {
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void property() throws Exception {
|
||||
PropertyDescriptor property = new PropertyDescriptor("property", getClass().getMethod("getProperty", null), getClass().getMethod("setProperty", Map.class));
|
||||
TypeDescriptor desc = new TypeDescriptor(getClass(), property);
|
||||
assertEquals(Integer.class, desc.getMapKeyTypeDescriptor().getElementType());
|
||||
assertEquals(Long.class, desc.getMapValueTypeDescriptor().getElementType());
|
||||
assertNotNull(desc.getAnnotation(MethodAnnotation1.class));
|
||||
assertNotNull(desc.getAnnotation(MethodAnnotation2.class));
|
||||
assertNotNull(desc.getAnnotation(MethodAnnotation3.class));
|
||||
}
|
||||
|
||||
@MethodAnnotation1
|
||||
public Map<List<Integer>, List<Long>> getProperty() {
|
||||
return property;
|
||||
}
|
||||
|
||||
@MethodAnnotation2
|
||||
public void setProperty(Map<List<Integer>, List<Long>> property) {
|
||||
this.property = property;
|
||||
}
|
||||
|
||||
@MethodAnnotation3
|
||||
private Map<List<Integer>, List<Long>> property;
|
||||
|
||||
@Target({ElementType.METHOD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface MethodAnnotation1 {
|
||||
|
||||
}
|
||||
|
||||
@Target({ElementType.METHOD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface MethodAnnotation2 {
|
||||
|
||||
}
|
||||
|
||||
@Target({ElementType.FIELD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface MethodAnnotation3 {
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void field() throws Exception {
|
||||
// typeIndex handling not currently supported by fields
|
||||
TypeDescriptor desc = new TypeDescriptor(getClass().getField("field"));
|
||||
assertEquals(Integer.class, desc.getMapKeyTypeDescriptor().getElementType());
|
||||
assertEquals(Long.class, desc.getMapValueTypeDescriptor().getElementType());
|
||||
}
|
||||
|
||||
public Map<List<Integer>, List<Long>> field;
|
||||
|
||||
|
||||
@Test
|
||||
public void methodParameter() throws Exception {
|
||||
TypeDescriptor desc = new TypeDescriptor(new MethodParameter(getClass().getMethod("setProperty", Map.class), 0));
|
||||
assertEquals(Integer.class, desc.getMapKeyTypeDescriptor().getElementType());
|
||||
assertEquals(Long.class, desc.getMapValueTypeDescriptor().getElementType());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void complexProperty() throws Exception {
|
||||
PropertyDescriptor property = new PropertyDescriptor("complexProperty", getClass().getMethod("getComplexProperty", null), getClass().getMethod("setComplexProperty", Map.class));
|
||||
TypeDescriptor desc = new TypeDescriptor(getClass(), property);
|
||||
//assertEquals(String.class, desc.getMapKeyType());
|
||||
assertEquals(Integer.class, desc.getMapValueTypeDescriptor().getElementTypeDescriptor().getElementType());
|
||||
}
|
||||
|
||||
public Map<String, List<List<Integer>>> getComplexProperty() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void setComplexProperty(Map<String, List<List<Integer>>> complexProperty) {
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void genericType() throws Exception {
|
||||
GenericType<Integer> genericBean = new IntegerType();
|
||||
PropertyDescriptor property = new PropertyDescriptor("property", genericBean.getClass().getMethod("getProperty", null), genericBean.getClass().getMethod("setProperty", Integer.class));
|
||||
TypeDescriptor desc = new TypeDescriptor(genericBean.getClass(), property);
|
||||
assertEquals(Integer.class, desc.getType());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void genericTypeList() throws Exception {
|
||||
GenericType<Integer> genericBean = new IntegerType();
|
||||
PropertyDescriptor property = new PropertyDescriptor("listProperty", genericBean.getClass().getMethod("getListProperty", null), genericBean.getClass().getMethod("setListProperty", List.class));
|
||||
TypeDescriptor desc = new TypeDescriptor(genericBean.getClass(), property);
|
||||
assertEquals(List.class, desc.getType());
|
||||
assertEquals(Integer.class, desc.getElementType());
|
||||
}
|
||||
|
||||
public interface GenericType<T> {
|
||||
T getProperty();
|
||||
|
||||
void setProperty(T t);
|
||||
|
||||
List<T> getListProperty();
|
||||
|
||||
void setListProperty(List<T> t);
|
||||
|
||||
}
|
||||
|
||||
public class IntegerType implements GenericType<Integer> {
|
||||
|
||||
public Integer getProperty() {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
public void setProperty(Integer t) {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
public List<Integer> getListProperty() {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
public void setListProperty(List<Integer> t) {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void genericClassList() throws Exception {
|
||||
IntegerClass genericBean = new IntegerClass();
|
||||
PropertyDescriptor property = new GenericTypeAwarePropertyDescriptor(genericBean.getClass(), "listProperty", genericBean.getClass().getMethod("getListProperty", null), genericBean.getClass().getMethod("setListProperty", List.class), null);
|
||||
TypeDescriptor desc = new TypeDescriptor(genericBean.getClass(), property);
|
||||
assertEquals(List.class, desc.getType());
|
||||
assertEquals(Integer.class, desc.getElementType());
|
||||
}
|
||||
|
||||
public static class GenericClass<T> {
|
||||
|
||||
public T getProperty() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void setProperty(T t) {
|
||||
}
|
||||
|
||||
public List<T> getListProperty() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void setListProperty(List<T> t) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class IntegerClass extends GenericClass<Integer> {
|
||||
|
||||
}
|
||||
|
||||
private static class GenericTypeAwarePropertyDescriptor extends PropertyDescriptor {
|
||||
|
||||
private final Class beanClass;
|
||||
|
||||
private final Method readMethod;
|
||||
|
||||
private final Method writeMethod;
|
||||
|
||||
private final Class propertyEditorClass;
|
||||
|
||||
private volatile Set<Method> ambiguousWriteMethods;
|
||||
|
||||
private Class propertyType;
|
||||
|
||||
private MethodParameter writeMethodParameter;
|
||||
|
||||
|
||||
public GenericTypeAwarePropertyDescriptor(Class beanClass, String propertyName,
|
||||
Method readMethod, Method writeMethod, Class propertyEditorClass)
|
||||
throws IntrospectionException {
|
||||
|
||||
super(propertyName, null, null);
|
||||
this.beanClass = beanClass;
|
||||
this.propertyEditorClass = propertyEditorClass;
|
||||
|
||||
Method readMethodToUse = BridgeMethodResolver.findBridgedMethod(readMethod);
|
||||
Method writeMethodToUse = BridgeMethodResolver.findBridgedMethod(writeMethod);
|
||||
if (writeMethodToUse == null && readMethodToUse != null) {
|
||||
// Fallback: Original JavaBeans introspection might not have found matching setter
|
||||
// method due to lack of bridge method resolution, in case of the getter using a
|
||||
// covariant return type whereas the setter is defined for the concrete property type.
|
||||
writeMethodToUse = ClassUtils.getMethodIfAvailable(this.beanClass,
|
||||
"set" + StringUtils.capitalize(getName()), readMethodToUse.getReturnType());
|
||||
}
|
||||
this.readMethod = readMethodToUse;
|
||||
this.writeMethod = writeMethodToUse;
|
||||
|
||||
if (this.writeMethod != null && this.readMethod == null) {
|
||||
// Write method not matched against read method: potentially ambiguous through
|
||||
// several overloaded variants, in which case an arbitrary winner has been chosen
|
||||
// by the JDK's JavaBeans Introspector...
|
||||
Set<Method> ambiguousCandidates = new HashSet<Method>();
|
||||
for (Method method : beanClass.getMethods()) {
|
||||
if (method.getName().equals(writeMethodToUse.getName()) &&
|
||||
!method.equals(writeMethodToUse) && !method.isBridge()) {
|
||||
ambiguousCandidates.add(method);
|
||||
}
|
||||
}
|
||||
if (!ambiguousCandidates.isEmpty()) {
|
||||
this.ambiguousWriteMethods = ambiguousCandidates;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Method getReadMethod() {
|
||||
return this.readMethod;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Method getWriteMethod() {
|
||||
return this.writeMethod;
|
||||
}
|
||||
|
||||
public Method getWriteMethodForActualAccess() {
|
||||
Set<Method> ambiguousCandidates = this.ambiguousWriteMethods;
|
||||
if (ambiguousCandidates != null) {
|
||||
this.ambiguousWriteMethods = null;
|
||||
LogFactory.getLog(GenericTypeAwarePropertyDescriptor.class).warn("Invalid JavaBean property '" +
|
||||
getName() + "' being accessed! Ambiguous write methods found next to actually used [" +
|
||||
this.writeMethod + "]: " + ambiguousCandidates);
|
||||
}
|
||||
return this.writeMethod;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class getPropertyEditorClass() {
|
||||
return this.propertyEditorClass;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized Class getPropertyType() {
|
||||
if (this.propertyType == null) {
|
||||
if (this.readMethod != null) {
|
||||
this.propertyType = GenericTypeResolver.resolveReturnType(this.readMethod, this.beanClass);
|
||||
}
|
||||
else {
|
||||
MethodParameter writeMethodParam = getWriteMethodParameter();
|
||||
if (writeMethodParam != null) {
|
||||
this.propertyType = writeMethodParam.getParameterType();
|
||||
}
|
||||
else {
|
||||
this.propertyType = super.getPropertyType();
|
||||
}
|
||||
}
|
||||
}
|
||||
return this.propertyType;
|
||||
}
|
||||
|
||||
public synchronized MethodParameter getWriteMethodParameter() {
|
||||
if (this.writeMethod == null) {
|
||||
return null;
|
||||
}
|
||||
if (this.writeMethodParameter == null) {
|
||||
this.writeMethodParameter = new MethodParameter(this.writeMethod, 0);
|
||||
GenericTypeResolver.resolveParameterType(this.writeMethodParameter, this.beanClass);
|
||||
}
|
||||
return this.writeMethodParameter;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user