diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/BeanWrapperImpl.java b/org.springframework.beans/src/main/java/org/springframework/beans/BeanWrapperImpl.java index 1d500efa862..7e79119747d 100644 --- a/org.springframework.beans/src/main/java/org/springframework/beans/BeanWrapperImpl.java +++ b/org.springframework.beans/src/main/java/org/springframework/beans/BeanWrapperImpl.java @@ -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,23 +368,13 @@ 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); + if (pd.getReadMethod() != null || pd.getWriteMethod() != null) { + return TypeDescriptor.nested(nestedBw.getWrappedClass(), pd, tokens.keys.length); } - 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); - } } else { - if (pd.getReadMethod() != null) { - return new PropertyTypeDescriptor(new MethodParameter(pd.getReadMethod(), -1), pd); + if (pd.getReadMethod() != null || pd.getWriteMethod() != null) { + return new TypeDescriptor(nestedBw.getWrappedClass(), pd); } - else if (pd.getWriteMethod() != null) { - return new PropertyTypeDescriptor(BeanUtils.getWriteMethodParameter(pd), 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 { diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/GenericTypeAwarePropertyDescriptor.java b/org.springframework.beans/src/main/java/org/springframework/beans/GenericTypeAwarePropertyDescriptor.java index 50d48eb24f0..0355cf0ba6a 100644 --- a/org.springframework.beans/src/main/java/org/springframework/beans/GenericTypeAwarePropertyDescriptor.java +++ b/org.springframework.beans/src/main/java/org/springframework/beans/GenericTypeAwarePropertyDescriptor.java @@ -92,7 +92,10 @@ class GenericTypeAwarePropertyDescriptor extends PropertyDescriptor { } } - + public Class getBeanClass() { + return beanClass; + } + @Override public Method getReadMethod() { return this.readMethod; diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/TypeConverterDelegate.java b/org.springframework.beans/src/main/java/org/springframework/beans/TypeConverterDelegate.java index a676439cbe9..76d8d519458 100644 --- a/org.springframework.beans/src/main/java/org/springframework/beans/TypeConverterDelegate.java +++ b/org.springframework.beans/src/main/java/org/springframework/beans/TypeConverterDelegate.java @@ -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); } diff --git a/org.springframework.core/src/main/java/org/springframework/core/MethodParameter.java b/org.springframework.core/src/main/java/org/springframework/core/MethodParameter.java index 30bd1fdbb07..4bb24bdac4c 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/MethodParameter.java +++ b/org.springframework.core/src/main/java/org/springframework/core/MethodParameter.java @@ -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; } diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/TypeDescriptor.java b/org.springframework.core/src/main/java/org/springframework/core/convert/TypeDescriptor.java index 33416e8f166..9e5959999b5 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/convert/TypeDescriptor.java +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/TypeDescriptor.java @@ -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> and the nestingLevel is 2, the nested type descriptor will also be a String.class. + * If the methodParameter is a Map and the nesting level is 1, the nested type descriptor will be String, derived from the map value. + * If the methodParameter is a List> 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> and the nestingLevel is 2, the nested type descriptor will also be a String.class. + * If the field is a Map and the nestingLevel is 1, the nested type descriptor will be String, derived from the map value. + * If the field is a List> 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> and the nestingLevel is 2, the nested type descriptor will also be a String.class. + * If the field is a Map and the nestingLevel is 1, the nested type descriptor will be String, derived from the map value. + * If the property is a List> 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) 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 null if the type is neither an array or collection. + * Returns null 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 null 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 null if none + * Returns null 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 null 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 null if none + * Returns null 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 null 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>) 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>) 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>) 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, Annotation> annMap = new LinkedHashMap, 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 { diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/support/PropertyTypeDescriptor.java b/org.springframework.core/src/main/java/org/springframework/core/convert/support/PropertyTypeDescriptor.java deleted file mode 100644 index 007e36f048a..00000000000 --- a/org.springframework.core/src/main/java/org/springframework/core/convert/support/PropertyTypeDescriptor.java +++ /dev/null @@ -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, Annotation> annMap = new LinkedHashMap, 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; - } - -} \ No newline at end of file diff --git a/org.springframework.core/src/test/java/org/springframework/core/convert/TypeDescriptorTests.java b/org.springframework.core/src/test/java/org/springframework/core/convert/TypeDescriptorTests.java index 7ad044dc21f..6b00df5785d 100644 --- a/org.springframework.core/src/test/java/org/springframework/core/convert/TypeDescriptorTests.java +++ b/org.springframework.core/src/test/java/org/springframework/core/convert/TypeDescriptorTests.java @@ -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", typeDescriptor.asString()); + assertEquals("java.util.List", 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>", typeDescriptor.asString()); + assertEquals("java.util.List>", 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>", typeDescriptor.asString()); + assertEquals("java.util.List>", 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>", typeDescriptor.asString()); + assertEquals("java.util.Map>", 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 param1) { + + } + + public void test2(List> param1) { + + } + + public void test3(Map param1) { + + } + + public void test4(List> 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> 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> getTest4() { + return null; + } + + public void setTest4(List> 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> getProperty() { + return property; + } + + @MethodAnnotation2 + public void setProperty(Map, List> property) { + this.property = property; + } + + @MethodAnnotation3 + private Map, List> 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> 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>> getComplexProperty() { + return null; + } + + public void setComplexProperty(Map>> complexProperty) { + + } + + @Test + public void genericType() throws Exception { + GenericType 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 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 getProperty(); + + void setProperty(T t); + + List getListProperty(); + + void setListProperty(List t); + + } + + public class IntegerType implements GenericType { + + public Integer getProperty() { + // TODO Auto-generated method stub + return null; + } + + public void setProperty(Integer t) { + // TODO Auto-generated method stub + + } + + public List getListProperty() { + // TODO Auto-generated method stub + return null; + } + + public void setListProperty(List 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 { + + public T getProperty() { + return null; + } + + public void setProperty(T t) { + } + + public List getListProperty() { + return null; + } + + public void setListProperty(List t) { + } + + } + + public static class IntegerClass extends GenericClass { + + } + + 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 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 ambiguousCandidates = new HashSet(); + 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 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; + } + + } + + } diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/Indexer.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/Indexer.java index c760a7b3c01..0496a2a2d4e 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/Indexer.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/Indexer.java @@ -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()); - Object o = ((Map) targetObject).get(possiblyConvertedKey); - return new TypedValue(o, targetObjectTypeDescriptor.getMapValueTypeDescriptor()); + 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()); } /** diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/ReflectionHelper.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/ReflectionHelper.java index d5771678dda..73b0ed5ab85 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/ReflectionHelper.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/ReflectionHelper.java @@ -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 paramTypes, List 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)); diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/ReflectivePropertyAccessor.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/ReflectivePropertyAccessor.java index 8d14a74ed4d..1f9e808398d 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/ReflectivePropertyAccessor.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/ReflectivePropertyAccessor.java @@ -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; diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/StandardTypeConverter.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/StandardTypeConverter.java index 96d6eb9c20c..52039e35c77 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/StandardTypeConverter.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/StandardTypeConverter.java @@ -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()); } } diff --git a/org.springframework.expression/src/test/java/org/springframework/expression/spel/SpelDocumentationTests.java b/org.springframework.expression/src/test/java/org/springframework/expression/spel/SpelDocumentationTests.java index 71e56642fb7..402888f1e67 100644 --- a/org.springframework.expression/src/test/java/org/springframework/expression/spel/SpelDocumentationTests.java +++ b/org.springframework.expression/src/test/java/org/springframework/expression/spel/SpelDocumentationTests.java @@ -70,7 +70,7 @@ public class SpelDocumentationTests extends ExpressionTestCase { public List Members2 = new ArrayList(); public Map officers = new HashMap(); - public List reverse = new ArrayList>(); + public List> reverse = new ArrayList>(); @SuppressWarnings("unchecked") IEEE() {