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
git-svn-id: https://src.springframework.org/svn/spring-framework/trunk@4424 50f2f4bb-b051-0410-bef5-90022cba6387
This commit is contained in:
parent
5d69429178
commit
c7cae10364
|
|
@ -43,7 +43,6 @@ import org.springframework.core.MethodParameter;
|
|||
import org.springframework.core.convert.ConversionException;
|
||||
import org.springframework.core.convert.ConverterNotFoundException;
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.core.convert.support.PropertyTypeDescriptor;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
|
@ -369,22 +368,12 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra
|
|||
PropertyDescriptor pd = nestedBw.getCachedIntrospectionResults().getPropertyDescriptor(tokens.actualName);
|
||||
if (pd != null) {
|
||||
if (tokens.keys != null) {
|
||||
if (pd.getReadMethod() != null) {
|
||||
return PropertyTypeDescriptor.forNestedType(new MethodParameter(pd.getReadMethod(), -1, tokens.keys.length), pd);
|
||||
}
|
||||
else if (pd.getWriteMethod() != null) {
|
||||
MethodParameter methodParameter = new MethodParameter(BeanUtils.getWriteMethodParameter(pd));
|
||||
for (int i = 0; i < tokens.keys.length; i++) {
|
||||
methodParameter.increaseNestingLevel();
|
||||
}
|
||||
return PropertyTypeDescriptor.forNestedType(methodParameter, pd);
|
||||
if (pd.getReadMethod() != null || pd.getWriteMethod() != null) {
|
||||
return TypeDescriptor.nested(nestedBw.getWrappedClass(), pd, tokens.keys.length);
|
||||
}
|
||||
} else {
|
||||
if (pd.getReadMethod() != null) {
|
||||
return new PropertyTypeDescriptor(new MethodParameter(pd.getReadMethod(), -1), pd);
|
||||
}
|
||||
else if (pd.getWriteMethod() != null) {
|
||||
return new PropertyTypeDescriptor(BeanUtils.getWriteMethodParameter(pd), pd);
|
||||
if (pd.getReadMethod() != null || pd.getWriteMethod() != null) {
|
||||
return new TypeDescriptor(nestedBw.getWrappedClass(), pd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -502,9 +491,9 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra
|
|||
|
||||
private Object convertForProperty(String propertyName, Object oldValue, Object newValue, PropertyDescriptor pd)
|
||||
throws TypeMismatchException {
|
||||
|
||||
return convertIfNecessary(propertyName, oldValue, newValue, pd.getPropertyType(),
|
||||
new PropertyTypeDescriptor(BeanUtils.getWriteMethodParameter(pd), pd));
|
||||
GenericTypeAwarePropertyDescriptor gpd = (GenericTypeAwarePropertyDescriptor) pd;
|
||||
Class<?> beanClass = gpd.getBeanClass();
|
||||
return convertIfNecessary(propertyName, oldValue, newValue, pd.getPropertyType(), new TypeDescriptor(beanClass, pd));
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -959,8 +948,7 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra
|
|||
if (isExtractOldValueForEditor() && arrayIndex < Array.getLength(propValue)) {
|
||||
oldValue = Array.get(propValue, arrayIndex);
|
||||
}
|
||||
Object convertedValue = convertIfNecessary(propertyName, oldValue, pv.getValue(), requiredType,
|
||||
PropertyTypeDescriptor.forNestedType(requiredType, new MethodParameter(pd.getReadMethod(), -1, tokens.keys.length), pd));
|
||||
Object convertedValue = convertIfNecessary(propertyName, oldValue, pv.getValue(), requiredType, TypeDescriptor.nested(getWrappedClass(), pd, tokens.keys.length));
|
||||
// TODO review this grow algorithm along side the null gap algorithm for setting lists below ... the two are inconsistent
|
||||
propValue = growArrayIfNecessary(propValue, arrayIndex, actualName);
|
||||
Array.set(propValue, arrayIndex, convertedValue);
|
||||
|
|
@ -980,8 +968,7 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra
|
|||
if (isExtractOldValueForEditor() && index < list.size()) {
|
||||
oldValue = list.get(index);
|
||||
}
|
||||
Object convertedValue = convertIfNecessary(propertyName, oldValue, pv.getValue(), requiredType,
|
||||
PropertyTypeDescriptor.forNestedType(requiredType, new MethodParameter(pd.getReadMethod(), -1, tokens.keys.length), pd));
|
||||
Object convertedValue = convertIfNecessary(propertyName, oldValue, pv.getValue(), requiredType, TypeDescriptor.nested(getWrappedClass(), pd, tokens.keys.length));
|
||||
if (index < list.size()) {
|
||||
list.set(index, convertedValue);
|
||||
}
|
||||
|
|
@ -1018,8 +1005,7 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra
|
|||
// Pass full property name and old value in here, since we want full
|
||||
// conversion ability for map values.
|
||||
Object convertedMapValue = convertIfNecessary(
|
||||
propertyName, oldValue, pv.getValue(), mapValueType,
|
||||
PropertyTypeDescriptor.forNestedType(mapValueType, new MethodParameter(pd.getReadMethod(), -1, tokens.keys.length), pd));
|
||||
propertyName, oldValue, pv.getValue(), mapValueType, TypeDescriptor.nested(getWrappedClass(), pd, tokens.keys.length));
|
||||
map.put(convertedMapKey, convertedMapValue);
|
||||
}
|
||||
else {
|
||||
|
|
|
|||
|
|
@ -92,6 +92,9 @@ class GenericTypeAwarePropertyDescriptor extends PropertyDescriptor {
|
|||
}
|
||||
}
|
||||
|
||||
public Class<?> getBeanClass() {
|
||||
return beanClass;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Method getReadMethod() {
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@
|
|||
|
||||
package org.springframework.beans;
|
||||
|
||||
import java.beans.PropertyDescriptor;
|
||||
import java.beans.PropertyEditor;
|
||||
import java.lang.reflect.Array;
|
||||
import java.lang.reflect.Constructor;
|
||||
|
|
@ -27,13 +26,10 @@ import java.util.Map;
|
|||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.core.CollectionFactory;
|
||||
import org.springframework.core.GenericCollectionTypeResolver;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.core.convert.support.PropertyTypeDescriptor;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
|
|
@ -144,9 +140,8 @@ class TypeConverterDelegate {
|
|||
|
||||
// Value not of required type?
|
||||
if (editor != null || (requiredType != null && !ClassUtils.isAssignableValue(requiredType, convertedValue))) {
|
||||
if (requiredType != null && Collection.class.isAssignableFrom(requiredType) &&
|
||||
convertedValue instanceof String && typeDescriptor.getMethodParameter() != null) {
|
||||
Class elemType = GenericCollectionTypeResolver.getCollectionParameterType(typeDescriptor.getMethodParameter());
|
||||
if (requiredType != null && Collection.class.isAssignableFrom(requiredType) && convertedValue instanceof String) {
|
||||
Class elemType = typeDescriptor.getElementType();
|
||||
if (elemType != null && Enum.class.isAssignableFrom(elemType)) {
|
||||
convertedValue = StringUtils.commaDelimitedListToStringArray((String) convertedValue);
|
||||
}
|
||||
|
|
@ -290,10 +285,10 @@ class TypeConverterDelegate {
|
|||
*/
|
||||
protected PropertyEditor findDefaultEditor(Class requiredType, TypeDescriptor typeDescriptor) {
|
||||
PropertyEditor editor = null;
|
||||
if (typeDescriptor instanceof PropertyTypeDescriptor) {
|
||||
PropertyDescriptor pd = ((PropertyTypeDescriptor) typeDescriptor).getPropertyDescriptor();
|
||||
editor = pd.createPropertyEditor(this.targetObject);
|
||||
}
|
||||
//if (typeDescriptor instanceof PropertyTypeDescriptor) {
|
||||
//PropertyDescriptor pd = ((PropertyTypeDescriptor) typeDescriptor).getPropertyDescriptor();
|
||||
//editor = pd.createPropertyEditor(this.targetObject);
|
||||
//}
|
||||
if (editor == null && requiredType != null) {
|
||||
// No custom editor -> check BeanWrapperImpl's default editors.
|
||||
editor = this.propertyEditorRegistry.getDefaultEditor(requiredType);
|
||||
|
|
@ -464,12 +459,8 @@ class TypeConverterDelegate {
|
|||
return original;
|
||||
}
|
||||
|
||||
MethodParameter methodParam = typeDescriptor.getMethodParameter();
|
||||
Class elementType = null;
|
||||
if (methodParam != null) {
|
||||
elementType = GenericCollectionTypeResolver.getCollectionParameterType(methodParam);
|
||||
}
|
||||
if (elementType == null && originalAllowed &&
|
||||
Class elementType = typeDescriptor.getElementType();
|
||||
if (elementType == Object.class && originalAllowed &&
|
||||
!this.propertyEditorRegistry.hasCustomEditorForElement(null, propertyName)) {
|
||||
return original;
|
||||
}
|
||||
|
|
@ -514,14 +505,8 @@ class TypeConverterDelegate {
|
|||
for (; it.hasNext(); i++) {
|
||||
Object element = it.next();
|
||||
String indexedPropertyName = buildIndexedPropertyName(propertyName, i);
|
||||
if (methodParam != null) {
|
||||
methodParam.increaseNestingLevel();
|
||||
}
|
||||
Object convertedElement = convertIfNecessary(
|
||||
indexedPropertyName, null, element, elementType, typeDescriptor);
|
||||
if (methodParam != null) {
|
||||
methodParam.decreaseNestingLevel();
|
||||
}
|
||||
indexedPropertyName, null, element, elementType, typeDescriptor.getElementTypeDescriptor());
|
||||
try {
|
||||
convertedCopy.add(convertedElement);
|
||||
}
|
||||
|
|
@ -546,14 +531,9 @@ class TypeConverterDelegate {
|
|||
return original;
|
||||
}
|
||||
|
||||
Class keyType = null;
|
||||
Class valueType = null;
|
||||
MethodParameter methodParam = typeDescriptor.getMethodParameter();
|
||||
if (methodParam != null) {
|
||||
keyType = GenericCollectionTypeResolver.getMapKeyParameterType(methodParam);
|
||||
valueType = GenericCollectionTypeResolver.getMapValueParameterType(methodParam);
|
||||
}
|
||||
if (keyType == null && valueType == null && originalAllowed &&
|
||||
Class keyType = typeDescriptor.getMapKeyType();
|
||||
Class valueType = typeDescriptor.getMapValueType();
|
||||
if (keyType == Object.class && valueType == Object.class && originalAllowed &&
|
||||
!this.propertyEditorRegistry.hasCustomEditorForElement(null, propertyName)) {
|
||||
return original;
|
||||
}
|
||||
|
|
@ -599,18 +579,8 @@ class TypeConverterDelegate {
|
|||
Object key = entry.getKey();
|
||||
Object value = entry.getValue();
|
||||
String keyedPropertyName = buildKeyedPropertyName(propertyName, key);
|
||||
if (methodParam != null) {
|
||||
methodParam.increaseNestingLevel();
|
||||
methodParam.setTypeIndexForCurrentLevel(0);
|
||||
}
|
||||
Object convertedKey = convertIfNecessary(keyedPropertyName, null, key, keyType, typeDescriptor);
|
||||
if (methodParam != null) {
|
||||
methodParam.setTypeIndexForCurrentLevel(1);
|
||||
}
|
||||
Object convertedValue = convertIfNecessary(keyedPropertyName, null, value, valueType, typeDescriptor);
|
||||
if (methodParam != null) {
|
||||
methodParam.decreaseNestingLevel();
|
||||
}
|
||||
Object convertedKey = convertIfNecessary(keyedPropertyName, null, key, keyType, typeDescriptor.getMapKeyTypeDescriptor());
|
||||
Object convertedValue = convertIfNecessary(keyedPropertyName, null, value, valueType, typeDescriptor.getMapValueTypeDescriptor());
|
||||
try {
|
||||
convertedCopy.put(convertedKey, convertedValue);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -124,6 +127,31 @@ public class TypeDescriptor {
|
|||
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.
|
||||
* Use this factory method to introspect a source object's type before asking the conversion system to convert it to some another type.
|
||||
|
|
@ -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));
|
||||
}
|
||||
|
||||
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(AbstractDescriptor descriptor) {
|
||||
this.type = descriptor.getType();
|
||||
this.elementType = descriptor.getElementType();
|
||||
this.mapKeyType = descriptor.getMapKeyType();
|
||||
this.mapValueType = descriptor.getMapValueType();
|
||||
this.annotations = descriptor.getAnnotations();
|
||||
}
|
||||
|
||||
protected TypeDescriptor newNestedTypeDescriptor(Class<?> nestedType, MethodParameter nested) {
|
||||
return new TypeDescriptor(nestedType, nested);
|
||||
private TypeDescriptor() {
|
||||
this(null, TypeDescriptor.NULL, TypeDescriptor.NULL, TypeDescriptor.NULL);
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
private TypeDescriptor(Class<?> collectionType, TypeDescriptor elementType) {
|
||||
this(collectionType, elementType, TypeDescriptor.NULL, TypeDescriptor.NULL);
|
||||
}
|
||||
|
||||
// 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(Class<?> mapType, TypeDescriptor keyType, TypeDescriptor valueType) {
|
||||
this(mapType, TypeDescriptor.NULL, keyType, valueType);
|
||||
}
|
||||
|
||||
private TypeDescriptor resolveMapKeyTypeDescriptor() {
|
||||
return createNestedTypeDescriptor(resolveMapKeyType());
|
||||
private TypeDescriptor(Class<?> collectionType, CommonElement commonElement) {
|
||||
this(collectionType, fromCommonElement(commonElement), TypeDescriptor.NULL, TypeDescriptor.NULL);
|
||||
}
|
||||
|
||||
private TypeDescriptor resolveMapValueTypeDescriptor() {
|
||||
return createNestedTypeDescriptor(resolveMapValueType());
|
||||
private TypeDescriptor(Class<?> mapType, CommonElement commonKey, CommonElement commonValue) {
|
||||
this(mapType, TypeDescriptor.NULL, fromCommonElement(commonKey), fromCommonElement(commonValue));
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
private static Annotation[] nullSafeAnnotations(Annotation[] annotations) {
|
||||
return annotations != null ? annotations : EMPTY_ANNOTATION_ARRAY;
|
||||
}
|
||||
|
||||
@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,8 +543,353 @@ 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 {
|
||||
|
||||
private Class<?> type;
|
||||
|
|
|
|||
|
|
@ -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,7 +207,7 @@ 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
|
||||
|
|
@ -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
|
||||
|
|
@ -245,4 +261,372 @@ public class TypeDescriptorTests {
|
|||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -90,10 +90,13 @@ public class Indexer extends SpelNodeImpl {
|
|||
|
||||
// Indexing into a Map
|
||||
if (targetObject instanceof Map) {
|
||||
Object possiblyConvertedKey = index;
|
||||
possiblyConvertedKey = state.convertValue(index, targetObjectTypeDescriptor.getMapKeyTypeDescriptor());
|
||||
if (targetObjectTypeDescriptor.isMap()) {
|
||||
Object possiblyConvertedKey = state.convertValue(index, targetObjectTypeDescriptor.getMapKeyTypeDescriptor());
|
||||
Object o = ((Map<?, ?>) targetObject).get(possiblyConvertedKey);
|
||||
return new TypedValue(o, targetObjectTypeDescriptor.getMapValueTypeDescriptor());
|
||||
} else {
|
||||
return new TypedValue(((Map<?, ?>) targetObject).get(index));
|
||||
}
|
||||
}
|
||||
|
||||
if (targetObject == null) {
|
||||
|
|
@ -158,11 +161,11 @@ public class Indexer extends SpelNodeImpl {
|
|||
}
|
||||
}
|
||||
} catch (AccessException e) {
|
||||
throw new SpelEvaluationException(getStartPosition(), e, SpelMessage.INDEXING_NOT_SUPPORTED_FOR_TYPE, targetObjectTypeDescriptor.asString());
|
||||
throw new SpelEvaluationException(getStartPosition(), e, SpelMessage.INDEXING_NOT_SUPPORTED_FOR_TYPE, targetObjectTypeDescriptor.toString());
|
||||
}
|
||||
}
|
||||
|
||||
throw new SpelEvaluationException(getStartPosition(),SpelMessage.INDEXING_NOT_SUPPORTED_FOR_TYPE, targetObjectTypeDescriptor.asString());
|
||||
throw new SpelEvaluationException(getStartPosition(),SpelMessage.INDEXING_NOT_SUPPORTED_FOR_TYPE, targetObjectTypeDescriptor.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -212,7 +215,7 @@ public class Indexer extends SpelNodeImpl {
|
|||
return;
|
||||
}
|
||||
else {
|
||||
throw new SpelEvaluationException(getStartPosition(),SpelMessage.INDEXING_NOT_SUPPORTED_FOR_TYPE, targetObjectTypeDescriptor.asString());
|
||||
throw new SpelEvaluationException(getStartPosition(),SpelMessage.INDEXING_NOT_SUPPORTED_FOR_TYPE, targetObjectTypeDescriptor.toString());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -248,7 +251,7 @@ public class Indexer extends SpelNodeImpl {
|
|||
|
||||
}
|
||||
|
||||
throw new SpelEvaluationException(getStartPosition(),SpelMessage.INDEXING_NOT_SUPPORTED_FOR_TYPE, targetObjectTypeDescriptor.asString());
|
||||
throw new SpelEvaluationException(getStartPosition(),SpelMessage.INDEXING_NOT_SUPPORTED_FOR_TYPE, targetObjectTypeDescriptor.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ import org.springframework.expression.spel.SpelEvaluationException;
|
|||
import org.springframework.expression.spel.SpelMessage;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.MethodInvoker;
|
||||
|
||||
/**
|
||||
* Utility methods used by the reflection resolver code to discover the appropriate
|
||||
|
|
@ -104,7 +105,7 @@ public class ReflectionHelper {
|
|||
}
|
||||
|
||||
/**
|
||||
* Based on {@link MethodInvoker.getTypeDifferenceWeight} but operates on TypeDescriptors.
|
||||
* Based on {@link MethodInvoker#getTypeDifferenceWeight(Class[], Object[])} but operates on TypeDescriptors.
|
||||
*/
|
||||
public static int getTypeDifferenceWeight(List<TypeDescriptor> paramTypes, List<TypeDescriptor> argTypes) {
|
||||
int result = 0;
|
||||
|
|
@ -279,7 +280,7 @@ public class ReflectionHelper {
|
|||
TypeDescriptor targetType;
|
||||
if (varargsPosition != null && argPosition >= varargsPosition) {
|
||||
MethodParameter methodParam = MethodParameter.forMethodOrConstructor(methodOrCtor, varargsPosition);
|
||||
targetType = TypeDescriptor.forNestedType(methodParam);
|
||||
targetType = TypeDescriptor.nested(methodParam, 1);
|
||||
}
|
||||
else {
|
||||
targetType = new TypeDescriptor(MethodParameter.forMethodOrConstructor(methodOrCtor, argPosition));
|
||||
|
|
@ -311,7 +312,7 @@ public class ReflectionHelper {
|
|||
TypeDescriptor targetType;
|
||||
if (varargsPosition != null && argPosition >= varargsPosition) {
|
||||
MethodParameter methodParam = new MethodParameter(method, varargsPosition);
|
||||
targetType = TypeDescriptor.forNestedType(methodParam);
|
||||
targetType = TypeDescriptor.nested(methodParam, 1);
|
||||
}
|
||||
else {
|
||||
targetType = new TypeDescriptor(new MethodParameter(method, argPosition));
|
||||
|
|
|
|||
|
|
@ -28,7 +28,6 @@ import java.util.concurrent.ConcurrentHashMap;
|
|||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.core.convert.support.PropertyTypeDescriptor;
|
||||
import org.springframework.expression.AccessException;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.EvaluationException;
|
||||
|
|
@ -79,8 +78,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
|
|||
try {
|
||||
// The readerCache will only contain gettable properties (let's not worry about setters for now)
|
||||
PropertyDescriptor propertyDescriptor = new PropertyDescriptor(name, method, null);
|
||||
TypeDescriptor typeDescriptor =
|
||||
new PropertyTypeDescriptor(new MethodParameter(method, -1), propertyDescriptor);
|
||||
TypeDescriptor typeDescriptor = new TypeDescriptor(type, propertyDescriptor);
|
||||
this.readerCache.put(cacheKey, new InvokerPair(method, typeDescriptor));
|
||||
this.typeDescriptorCache.put(cacheKey, typeDescriptor);
|
||||
return true;
|
||||
|
|
@ -127,8 +125,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
|
|||
try {
|
||||
// The readerCache will only contain gettable properties (let's not worry about setters for now)
|
||||
PropertyDescriptor propertyDescriptor = new PropertyDescriptor(name, method, null);
|
||||
TypeDescriptor typeDescriptor =
|
||||
new PropertyTypeDescriptor(new MethodParameter(method, -1), propertyDescriptor);
|
||||
TypeDescriptor typeDescriptor = new TypeDescriptor(type, propertyDescriptor);
|
||||
invoker = new InvokerPair(method, typeDescriptor);
|
||||
this.readerCache.put(cacheKey, invoker);
|
||||
}
|
||||
|
|
@ -191,8 +188,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
|
|||
catch (IntrospectionException ex) {
|
||||
throw new AccessException("Unable to access property '" + name + "' through setter "+method, ex);
|
||||
}
|
||||
MethodParameter mp = new MethodParameter(method,0);
|
||||
TypeDescriptor typeDescriptor = new PropertyTypeDescriptor(mp, propertyDescriptor);
|
||||
TypeDescriptor typeDescriptor = new TypeDescriptor(type, propertyDescriptor);
|
||||
this.writerCache.put(cacheKey, method);
|
||||
this.typeDescriptorCache.put(cacheKey, typeDescriptor);
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -66,12 +66,10 @@ public class StandardTypeConverter implements TypeConverter {
|
|||
return this.conversionService.convert(value, sourceType, targetType);
|
||||
}
|
||||
catch (ConverterNotFoundException cenfe) {
|
||||
throw new SpelEvaluationException(cenfe, SpelMessage.TYPE_CONVERSION_ERROR,
|
||||
sourceType.toString(), targetType.asString());
|
||||
throw new SpelEvaluationException(cenfe, SpelMessage.TYPE_CONVERSION_ERROR, sourceType.toString(), targetType.toString());
|
||||
}
|
||||
catch (ConversionException ce) {
|
||||
throw new SpelEvaluationException(ce, SpelMessage.TYPE_CONVERSION_ERROR,
|
||||
sourceType.toString(), targetType.asString());
|
||||
throw new SpelEvaluationException(ce, SpelMessage.TYPE_CONVERSION_ERROR, sourceType.toString(), targetType.toString());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ public class SpelDocumentationTests extends ExpressionTestCase {
|
|||
public List Members2 = new ArrayList();
|
||||
public Map<String,Object> officers = new HashMap<String,Object>();
|
||||
|
||||
public List reverse = new ArrayList<Map<String, Object>>();
|
||||
public List<Map<String, Object>> reverse = new ArrayList<Map<String, Object>>();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
IEEE() {
|
||||
|
|
|
|||
Loading…
Reference in New Issue