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:
Keith Donald 2011-06-02 23:37:19 +00:00
parent 5d69429178
commit c7cae10364
12 changed files with 930 additions and 449 deletions

View File

@ -43,7 +43,6 @@ import org.springframework.core.MethodParameter;
import org.springframework.core.convert.ConversionException; import org.springframework.core.convert.ConversionException;
import org.springframework.core.convert.ConverterNotFoundException; import org.springframework.core.convert.ConverterNotFoundException;
import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.support.PropertyTypeDescriptor;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils; import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
@ -369,23 +368,13 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra
PropertyDescriptor pd = nestedBw.getCachedIntrospectionResults().getPropertyDescriptor(tokens.actualName); PropertyDescriptor pd = nestedBw.getCachedIntrospectionResults().getPropertyDescriptor(tokens.actualName);
if (pd != null) { if (pd != null) {
if (tokens.keys != null) { if (tokens.keys != null) {
if (pd.getReadMethod() != null) { if (pd.getReadMethod() != null || pd.getWriteMethod() != null) {
return PropertyTypeDescriptor.forNestedType(new MethodParameter(pd.getReadMethod(), -1, tokens.keys.length), pd); 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 { } else {
if (pd.getReadMethod() != null) { if (pd.getReadMethod() != null || pd.getWriteMethod() != null) {
return new PropertyTypeDescriptor(new MethodParameter(pd.getReadMethod(), -1), pd); 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) private Object convertForProperty(String propertyName, Object oldValue, Object newValue, PropertyDescriptor pd)
throws TypeMismatchException { throws TypeMismatchException {
GenericTypeAwarePropertyDescriptor gpd = (GenericTypeAwarePropertyDescriptor) pd;
return convertIfNecessary(propertyName, oldValue, newValue, pd.getPropertyType(), Class<?> beanClass = gpd.getBeanClass();
new PropertyTypeDescriptor(BeanUtils.getWriteMethodParameter(pd), pd)); 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)) { if (isExtractOldValueForEditor() && arrayIndex < Array.getLength(propValue)) {
oldValue = Array.get(propValue, arrayIndex); oldValue = Array.get(propValue, arrayIndex);
} }
Object convertedValue = convertIfNecessary(propertyName, oldValue, pv.getValue(), requiredType, Object convertedValue = convertIfNecessary(propertyName, oldValue, pv.getValue(), requiredType, TypeDescriptor.nested(getWrappedClass(), pd, tokens.keys.length));
PropertyTypeDescriptor.forNestedType(requiredType, new MethodParameter(pd.getReadMethod(), -1, tokens.keys.length), pd));
// TODO review this grow algorithm along side the null gap algorithm for setting lists below ... the two are inconsistent // TODO review this grow algorithm along side the null gap algorithm for setting lists below ... the two are inconsistent
propValue = growArrayIfNecessary(propValue, arrayIndex, actualName); propValue = growArrayIfNecessary(propValue, arrayIndex, actualName);
Array.set(propValue, arrayIndex, convertedValue); Array.set(propValue, arrayIndex, convertedValue);
@ -980,8 +968,7 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra
if (isExtractOldValueForEditor() && index < list.size()) { if (isExtractOldValueForEditor() && index < list.size()) {
oldValue = list.get(index); oldValue = list.get(index);
} }
Object convertedValue = convertIfNecessary(propertyName, oldValue, pv.getValue(), requiredType, Object convertedValue = convertIfNecessary(propertyName, oldValue, pv.getValue(), requiredType, TypeDescriptor.nested(getWrappedClass(), pd, tokens.keys.length));
PropertyTypeDescriptor.forNestedType(requiredType, new MethodParameter(pd.getReadMethod(), -1, tokens.keys.length), pd));
if (index < list.size()) { if (index < list.size()) {
list.set(index, convertedValue); 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 // Pass full property name and old value in here, since we want full
// conversion ability for map values. // conversion ability for map values.
Object convertedMapValue = convertIfNecessary( Object convertedMapValue = convertIfNecessary(
propertyName, oldValue, pv.getValue(), mapValueType, propertyName, oldValue, pv.getValue(), mapValueType, TypeDescriptor.nested(getWrappedClass(), pd, tokens.keys.length));
PropertyTypeDescriptor.forNestedType(mapValueType, new MethodParameter(pd.getReadMethod(), -1, tokens.keys.length), pd));
map.put(convertedMapKey, convertedMapValue); map.put(convertedMapKey, convertedMapValue);
} }
else { else {

View File

@ -92,7 +92,10 @@ class GenericTypeAwarePropertyDescriptor extends PropertyDescriptor {
} }
} }
public Class<?> getBeanClass() {
return beanClass;
}
@Override @Override
public Method getReadMethod() { public Method getReadMethod() {
return this.readMethod; return this.readMethod;

View File

@ -16,7 +16,6 @@
package org.springframework.beans; package org.springframework.beans;
import java.beans.PropertyDescriptor;
import java.beans.PropertyEditor; import java.beans.PropertyEditor;
import java.lang.reflect.Array; import java.lang.reflect.Array;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
@ -27,13 +26,10 @@ import java.util.Map;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.core.CollectionFactory; import org.springframework.core.CollectionFactory;
import org.springframework.core.GenericCollectionTypeResolver;
import org.springframework.core.MethodParameter; import org.springframework.core.MethodParameter;
import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.support.PropertyTypeDescriptor;
import org.springframework.util.ClassUtils; import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
@ -144,9 +140,8 @@ class TypeConverterDelegate {
// Value not of required type? // Value not of required type?
if (editor != null || (requiredType != null && !ClassUtils.isAssignableValue(requiredType, convertedValue))) { if (editor != null || (requiredType != null && !ClassUtils.isAssignableValue(requiredType, convertedValue))) {
if (requiredType != null && Collection.class.isAssignableFrom(requiredType) && if (requiredType != null && Collection.class.isAssignableFrom(requiredType) && convertedValue instanceof String) {
convertedValue instanceof String && typeDescriptor.getMethodParameter() != null) { Class elemType = typeDescriptor.getElementType();
Class elemType = GenericCollectionTypeResolver.getCollectionParameterType(typeDescriptor.getMethodParameter());
if (elemType != null && Enum.class.isAssignableFrom(elemType)) { if (elemType != null && Enum.class.isAssignableFrom(elemType)) {
convertedValue = StringUtils.commaDelimitedListToStringArray((String) convertedValue); convertedValue = StringUtils.commaDelimitedListToStringArray((String) convertedValue);
} }
@ -290,10 +285,10 @@ class TypeConverterDelegate {
*/ */
protected PropertyEditor findDefaultEditor(Class requiredType, TypeDescriptor typeDescriptor) { protected PropertyEditor findDefaultEditor(Class requiredType, TypeDescriptor typeDescriptor) {
PropertyEditor editor = null; PropertyEditor editor = null;
if (typeDescriptor instanceof PropertyTypeDescriptor) { //if (typeDescriptor instanceof PropertyTypeDescriptor) {
PropertyDescriptor pd = ((PropertyTypeDescriptor) typeDescriptor).getPropertyDescriptor(); //PropertyDescriptor pd = ((PropertyTypeDescriptor) typeDescriptor).getPropertyDescriptor();
editor = pd.createPropertyEditor(this.targetObject); //editor = pd.createPropertyEditor(this.targetObject);
} //}
if (editor == null && requiredType != null) { if (editor == null && requiredType != null) {
// No custom editor -> check BeanWrapperImpl's default editors. // No custom editor -> check BeanWrapperImpl's default editors.
editor = this.propertyEditorRegistry.getDefaultEditor(requiredType); editor = this.propertyEditorRegistry.getDefaultEditor(requiredType);
@ -464,12 +459,8 @@ class TypeConverterDelegate {
return original; return original;
} }
MethodParameter methodParam = typeDescriptor.getMethodParameter(); Class elementType = typeDescriptor.getElementType();
Class elementType = null; if (elementType == Object.class && originalAllowed &&
if (methodParam != null) {
elementType = GenericCollectionTypeResolver.getCollectionParameterType(methodParam);
}
if (elementType == null && originalAllowed &&
!this.propertyEditorRegistry.hasCustomEditorForElement(null, propertyName)) { !this.propertyEditorRegistry.hasCustomEditorForElement(null, propertyName)) {
return original; return original;
} }
@ -514,14 +505,8 @@ class TypeConverterDelegate {
for (; it.hasNext(); i++) { for (; it.hasNext(); i++) {
Object element = it.next(); Object element = it.next();
String indexedPropertyName = buildIndexedPropertyName(propertyName, i); String indexedPropertyName = buildIndexedPropertyName(propertyName, i);
if (methodParam != null) {
methodParam.increaseNestingLevel();
}
Object convertedElement = convertIfNecessary( Object convertedElement = convertIfNecessary(
indexedPropertyName, null, element, elementType, typeDescriptor); indexedPropertyName, null, element, elementType, typeDescriptor.getElementTypeDescriptor());
if (methodParam != null) {
methodParam.decreaseNestingLevel();
}
try { try {
convertedCopy.add(convertedElement); convertedCopy.add(convertedElement);
} }
@ -546,14 +531,9 @@ class TypeConverterDelegate {
return original; return original;
} }
Class keyType = null; Class keyType = typeDescriptor.getMapKeyType();
Class valueType = null; Class valueType = typeDescriptor.getMapValueType();
MethodParameter methodParam = typeDescriptor.getMethodParameter(); if (keyType == Object.class && valueType == Object.class && originalAllowed &&
if (methodParam != null) {
keyType = GenericCollectionTypeResolver.getMapKeyParameterType(methodParam);
valueType = GenericCollectionTypeResolver.getMapValueParameterType(methodParam);
}
if (keyType == null && valueType == null && originalAllowed &&
!this.propertyEditorRegistry.hasCustomEditorForElement(null, propertyName)) { !this.propertyEditorRegistry.hasCustomEditorForElement(null, propertyName)) {
return original; return original;
} }
@ -599,18 +579,8 @@ class TypeConverterDelegate {
Object key = entry.getKey(); Object key = entry.getKey();
Object value = entry.getValue(); Object value = entry.getValue();
String keyedPropertyName = buildKeyedPropertyName(propertyName, key); String keyedPropertyName = buildKeyedPropertyName(propertyName, key);
if (methodParam != null) { Object convertedKey = convertIfNecessary(keyedPropertyName, null, key, keyType, typeDescriptor.getMapKeyTypeDescriptor());
methodParam.increaseNestingLevel(); Object convertedValue = convertIfNecessary(keyedPropertyName, null, value, valueType, typeDescriptor.getMapValueTypeDescriptor());
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();
}
try { try {
convertedCopy.put(convertedKey, convertedValue); convertedCopy.put(convertedKey, convertedValue);
} }

View File

@ -131,8 +131,14 @@ public class MethodParameter {
this.constructor = original.constructor; this.constructor = original.constructor;
this.parameterIndex = original.parameterIndex; this.parameterIndex = original.parameterIndex;
this.parameterType = original.parameterType; this.parameterType = original.parameterType;
this.genericParameterType = original.genericParameterType;
this.parameterAnnotations = original.parameterAnnotations; 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.typeVariableMap = original.typeVariableMap;
this.hash = original.hash;
} }

View File

@ -16,21 +16,26 @@
package org.springframework.core.convert; package org.springframework.core.convert;
import java.beans.PropertyDescriptor;
import java.lang.annotation.Annotation; import java.lang.annotation.Annotation;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import org.springframework.core.GenericCollectionTypeResolver; import org.springframework.core.GenericCollectionTypeResolver;
import org.springframework.core.GenericTypeResolver;
import org.springframework.core.MethodParameter; import org.springframework.core.MethodParameter;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils; import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils; import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
/** /**
* Context about a type to convert to. * 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 final TypeDescriptor mapValueType;
private TypeDescriptor elementType;
private TypeDescriptor mapKeyType;
private TypeDescriptor mapValueType;
private Annotation[] annotations;
private final Annotation[] annotations;
/** /**
* Create a new type descriptor from a method or constructor parameter. * 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. * Use this constructor when a target conversion point is a method parameter.
* @param methodParameter the MethodParameter to wrap * @param methodParameter the method parameter
*/ */
public TypeDescriptor(MethodParameter methodParameter) { public TypeDescriptor(MethodParameter methodParameter) {
Assert.notNull(methodParameter, "MethodParameter must not be null"); this(new ParameterDescriptor(methodParameter));
this.type = methodParameter.getParameterType();
this.methodParameter = methodParameter;
} }
/** /**
* Create a new type descriptor for a field. * Create a new type descriptor for a field.
* Use this constructor when a target conversion point originates from a field. * Use this constructor when a target conversion point is a field.
* @param field the field to wrap * @param field the field
*/ */
public TypeDescriptor(Field field) { public TypeDescriptor(Field field) {
Assert.notNull(field, "Field must not be null"); this(new FieldDescriptor(field));
this.type = field.getType(); }
this.field = 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); TypeDescriptor desc = typeDescriptorCache.get(type);
return (desc != null ? desc : new TypeDescriptor(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. * 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. * Creates a type descriptor for a nested type declared by the 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. * For example, if the methodParameter is a List&lt;String&gt; and the nestingLevel is 1, the nested type descriptor will be String.class.
* @param methodParameter the method parameter declaring the collection or map * 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 * @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) { public static TypeDescriptor nested(MethodParameter methodParameter, int nestingLevel) {
return new TypeDescriptor(resolveNestedType(methodParameter), methodParameter); 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. * Creates a type descriptor for a nested type declared by the field.
* 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. * For example, if the field is a List&lt;String&gt; and the nestingLevel is 1, the nested type descriptor will be String.class.
* @param nestedType the nested type * If the field is a List<List<String>> and the nestingLevel is 2, the nested type descriptor will also be a String.class.
* @param methodParameter the method parameter declaring the collection or map * 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 * @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) { public static TypeDescriptor nested(Field field, int nestingLevel) {
return new TypeDescriptor(nestedType, methodParameter); 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&lt;String&gt; and the nestingLevel is 1, the nested type descriptor will be String.class.
* If the property is a List<List<String>> and the nestingLevel is 2, the nested type descriptor will also be a String.class.
* If the field is a Map<Integer, String> and the nestingLevel is 1, the nested type descriptor will be String, derived from the map value.
* If the property is a List<Map<Integer, String>> and the nestingLevel is 2, the nested type descriptor will be String, derived from the map value.
* @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. * Obtain the annotations associated with the wrapped parameter/field, if any.
*/ */
public synchronized Annotation[] getAnnotations() { public Annotation[] getAnnotations() {
if (this.annotations == null) {
this.annotations = resolveAnnotations();
}
return this.annotations; 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 // 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. * 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() { public Class<?> getElementType() {
return getElementTypeDescriptor().getType(); return getElementTypeDescriptor().getType();
@ -282,10 +325,7 @@ public class TypeDescriptor {
/** /**
* Return the element type as a type descriptor. * Return the element type as a type descriptor.
*/ */
public synchronized TypeDescriptor getElementTypeDescriptor() { public TypeDescriptor getElementTypeDescriptor() {
if (this.elementType == null) {
this.elementType = resolveElementTypeDescriptor();
}
return this.elementType; return this.elementType;
} }
@ -300,7 +340,9 @@ public class TypeDescriptor {
/** /**
* Determine the generic key type of the wrapped Map parameter/field, if any. * 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() { public Class<?> getMapKeyType() {
return getMapKeyTypeDescriptor().getType(); return getMapKeyTypeDescriptor().getType();
@ -309,16 +351,15 @@ public class TypeDescriptor {
/** /**
* Returns map key type as a type descriptor. * Returns map key type as a type descriptor.
*/ */
public synchronized TypeDescriptor getMapKeyTypeDescriptor() { public TypeDescriptor getMapKeyTypeDescriptor() {
if (this.mapKeyType == null) {
this.mapKeyType = resolveMapKeyTypeDescriptor();
}
return this.mapKeyType; return this.mapKeyType;
} }
/** /**
* Determine the generic value type of the wrapped Map parameter/field, if any. * 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() { public Class<?> getMapValueType() {
return getMapValueTypeDescriptor().getType(); return getMapValueTypeDescriptor().getType();
@ -327,25 +368,10 @@ public class TypeDescriptor {
/** /**
* Returns map value type as a type descriptor. * Returns map value type as a type descriptor.
*/ */
public synchronized TypeDescriptor getMapValueTypeDescriptor() { public TypeDescriptor getMapValueTypeDescriptor() {
if (this.mapValueType == null) {
this.mapValueType = resolveMapValueTypeDescriptor();
}
return this.mapValueType; 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 // extending Object
public boolean equals(Object obj) { public boolean equals(Object obj) {
@ -395,128 +421,53 @@ public class TypeDescriptor {
} }
} }
// subclassing hooks // internal constructors
protected TypeDescriptor(Class<?> nestedType, MethodParameter methodParameter) { private TypeDescriptor(Class<?> type) {
this.type = handleUnknownNestedType(nestedType); this(new ClassDescriptor(type));
this.methodParameter = createNestedMethodParameter(methodParameter); }
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() { private TypeDescriptor(Class<?> type, TypeDescriptor elementType, TypeDescriptor mapKeyType, TypeDescriptor mapValueType) {
if (this.field != null) { this.type = type;
return this.field.getAnnotations(); this.elementType = elementType;
} this.mapKeyType = mapKeyType;
else if (this.methodParameter != null) { this.mapValueType = mapValueType;
if (this.methodParameter.getParameterIndex() < 0) { this.annotations = EMPTY_ANNOTATION_ARRAY;
return this.methodParameter.getMethodAnnotations();
}
else {
return this.methodParameter.getParameterAnnotations();
}
}
else {
return EMPTY_ANNOTATION_ARRAY;
}
} }
protected TypeDescriptor newNestedTypeDescriptor(Class<?> nestedType, MethodParameter nested) { private static Annotation[] nullSafeAnnotations(Annotation[] annotations) {
return new TypeDescriptor(nestedType, nested); return annotations != null ? annotations : EMPTY_ANNOTATION_ARRAY;
} }
protected static Class<?> resolveNestedType(MethodParameter methodParameter) { // forObject-related internal helpers
if (Collection.class.isAssignableFrom(methodParameter.getParameterType())) {
return GenericCollectionTypeResolver.getCollectionParameterType(methodParameter);
} else if (Map.class.isAssignableFrom(methodParameter.getParameterType())) {
return GenericCollectionTypeResolver.getMapValueParameterType(methodParameter);
} else if (methodParameter.getParameterType().isArray()) {
return methodParameter.getParameterType().getComponentType();
} else {
throw new IllegalStateException("Not a collection, map, or array method parameter type " + methodParameter.getParameterType());
}
}
// internal helpers
private TypeDescriptor resolveElementTypeDescriptor() {
if (isCollection()) {
return createNestedTypeDescriptor(resolveCollectionElementType());
}
else {
// TODO: GenericCollectionTypeResolver is not capable of applying nesting levels to array fields;
// this means generic info of nested lists or maps stored inside array method parameters or fields is not obtainable
return createNestedTypeDescriptor(getType().getComponentType());
}
}
private TypeDescriptor resolveMapKeyTypeDescriptor() {
return createNestedTypeDescriptor(resolveMapKeyType());
}
private TypeDescriptor resolveMapValueTypeDescriptor() {
return createNestedTypeDescriptor(resolveMapValueType());
}
private TypeDescriptor createNestedTypeDescriptor(Class<?> nestedType) {
nestedType = handleUnknownNestedType(nestedType);
if (this.methodParameter != null) {
return newNestedTypeDescriptor(nestedType, createNestedMethodParameter(this.methodParameter));
}
else if (this.field != null) {
return new TypeDescriptor(nestedType, this.field, this.fieldNestingLevel + 1);
}
else {
return TypeDescriptor.valueOf(nestedType);
}
}
@SuppressWarnings("unchecked")
private Class<?> resolveCollectionElementType() {
if (this.methodParameter != null) {
return GenericCollectionTypeResolver.getCollectionParameterType(this.methodParameter);
}
else if (this.field != null) {
return GenericCollectionTypeResolver.getCollectionFieldType(this.field, this.fieldNestingLevel);
}
else {
return GenericCollectionTypeResolver.getCollectionType((Class<? extends Collection<?>>) this.type);
}
}
@SuppressWarnings("unchecked")
private Class<?> resolveMapKeyType() {
if (this.methodParameter != null) {
return GenericCollectionTypeResolver.getMapKeyParameterType(this.methodParameter);
}
else if (this.field != null) {
return GenericCollectionTypeResolver.getMapKeyFieldType(this.field, this.fieldNestingLevel);
}
else {
return GenericCollectionTypeResolver.getMapKeyType((Class<? extends Map<?, ?>>) this.type);
}
}
@SuppressWarnings("unchecked")
private Class<?> resolveMapValueType() {
if (this.methodParameter != null) {
return GenericCollectionTypeResolver.getMapValueParameterType(this.methodParameter);
}
else if (this.field != null) {
return GenericCollectionTypeResolver.getMapValueFieldType(this.field, this.fieldNestingLevel);
}
else {
return GenericCollectionTypeResolver.getMapValueType((Class<? extends Map<?, ?>>) this.type);
}
}
private Class<?> handleUnknownNestedType(Class<?> nestedType) {
return nestedType != null ? nestedType : Object.class;
}
private MethodParameter createNestedMethodParameter(MethodParameter parentMethodParameter) {
MethodParameter methodParameter = new MethodParameter(parentMethodParameter);
methodParameter.increaseNestingLevel();
return methodParameter;
}
private static CommonElement findCommonElement(Collection<?> values) { private static CommonElement findCommonElement(Collection<?> values) {
Class<?> commonType = null; Class<?> commonType = null;
@ -569,34 +520,7 @@ public class TypeDescriptor {
} }
} }
// internal constructors private static TypeDescriptor fromCommonElement(CommonElement commonElement) {
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) {
if (commonElement == null) { if (commonElement == null) {
return TypeDescriptor.valueOf(Object.class); 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 // 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 static class CommonElement {

View File

@ -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;
}
}

View File

@ -19,17 +19,33 @@ package org.springframework.core.convert;
import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertTrue; import static junit.framework.Assert.assertTrue;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull; 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.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
import org.apache.commons.logging.LogFactory;
import org.junit.Ignore; import org.junit.Ignore;
import org.junit.Test; 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 * @author Andy Clement
@ -163,7 +179,7 @@ public class TypeDescriptorTests {
assertEquals(List.class, typeDescriptor.getType()); assertEquals(List.class, typeDescriptor.getType());
assertEquals(String.class, typeDescriptor.getElementType()); assertEquals(String.class, typeDescriptor.getElementType());
// TODO caught shorten these names but it is OK that they are fully qualified for now // 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 @Test
@ -173,7 +189,7 @@ public class TypeDescriptorTests {
assertEquals(List.class, typeDescriptor.getType()); assertEquals(List.class, typeDescriptor.getType());
assertEquals(List.class, typeDescriptor.getElementType()); assertEquals(List.class, typeDescriptor.getElementType());
assertEquals(String.class, typeDescriptor.getElementTypeDescriptor().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 @Test
@ -183,7 +199,7 @@ public class TypeDescriptorTests {
assertEquals(List.class, typeDescriptor.getType()); assertEquals(List.class, typeDescriptor.getType());
assertEquals(List.class, typeDescriptor.getElementType()); assertEquals(List.class, typeDescriptor.getElementType());
assertEquals(Object.class, typeDescriptor.getElementTypeDescriptor().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 @Test
@ -191,14 +207,14 @@ public class TypeDescriptorTests {
TypeDescriptor typeDescriptor = new TypeDescriptor(TypeDescriptorTests.class.getDeclaredField("intArray")); TypeDescriptor typeDescriptor = new TypeDescriptor(TypeDescriptorTests.class.getDeclaredField("intArray"));
assertTrue(typeDescriptor.isArray()); assertTrue(typeDescriptor.isArray());
assertEquals(Integer.TYPE,typeDescriptor.getElementType()); assertEquals(Integer.TYPE,typeDescriptor.getElementType());
assertEquals("int[]",typeDescriptor.asString()); assertEquals("int[]",typeDescriptor.toString());
} }
@Test @Test
public void buildingArrayTypeDescriptor() throws Exception { public void buildingArrayTypeDescriptor() throws Exception {
TypeDescriptor typeDescriptor = TypeDescriptor.valueOf(int[].class); TypeDescriptor typeDescriptor = TypeDescriptor.valueOf(int[].class);
assertTrue(typeDescriptor.isArray()); assertTrue(typeDescriptor.isArray());
assertEquals(Integer.TYPE ,typeDescriptor.getElementType()); assertEquals(Integer.TYPE, typeDescriptor.getElementType());
} }
@Test @Test
@ -208,7 +224,7 @@ public class TypeDescriptorTests {
assertTrue(typeDescriptor.isArray()); assertTrue(typeDescriptor.isArray());
assertEquals(List.class,typeDescriptor.getElementType()); assertEquals(List.class,typeDescriptor.getElementType());
assertEquals(String.class, typeDescriptor.getElementTypeDescriptor().getElementType()); assertEquals(String.class, typeDescriptor.getElementTypeDescriptor().getElementType());
assertEquals("java.util.List[]",typeDescriptor.asString()); assertEquals("java.util.List[]",typeDescriptor.toString());
} }
@Test @Test
@ -218,7 +234,7 @@ public class TypeDescriptorTests {
assertEquals(String.class,typeDescriptor.getMapKeyType()); assertEquals(String.class,typeDescriptor.getMapKeyType());
assertEquals(List.class, typeDescriptor.getMapValueType()); assertEquals(List.class, typeDescriptor.getMapValueType());
assertEquals(Integer.class, typeDescriptor.getMapValueTypeDescriptor().getElementType()); 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 @Test
@ -244,5 +260,373 @@ public class TypeDescriptorTests {
TypeDescriptor t12 = new TypeDescriptor(getClass().getField("mapField")); TypeDescriptor t12 = new TypeDescriptor(getClass().getField("mapField"));
assertEquals(t11, t12); 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;
}
}
} }

View File

@ -90,10 +90,13 @@ public class Indexer extends SpelNodeImpl {
// Indexing into a Map // Indexing into a Map
if (targetObject instanceof Map) { if (targetObject instanceof Map) {
Object possiblyConvertedKey = index; if (targetObjectTypeDescriptor.isMap()) {
possiblyConvertedKey = state.convertValue(index, targetObjectTypeDescriptor.getMapKeyTypeDescriptor()); Object possiblyConvertedKey = state.convertValue(index, targetObjectTypeDescriptor.getMapKeyTypeDescriptor());
Object o = ((Map<?, ?>) targetObject).get(possiblyConvertedKey); Object o = ((Map<?, ?>) targetObject).get(possiblyConvertedKey);
return new TypedValue(o, targetObjectTypeDescriptor.getMapValueTypeDescriptor()); return new TypedValue(o, targetObjectTypeDescriptor.getMapValueTypeDescriptor());
} else {
return new TypedValue(((Map<?, ?>) targetObject).get(index));
}
} }
if (targetObject == null) { if (targetObject == null) {
@ -158,11 +161,11 @@ public class Indexer extends SpelNodeImpl {
} }
} }
} catch (AccessException e) { } 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 @Override
@ -212,7 +215,7 @@ public class Indexer extends SpelNodeImpl {
return; return;
} }
else { 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());
} }
/** /**

View File

@ -29,6 +29,7 @@ import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.expression.spel.SpelMessage; import org.springframework.expression.spel.SpelMessage;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.ClassUtils; import org.springframework.util.ClassUtils;
import org.springframework.util.MethodInvoker;
/** /**
* Utility methods used by the reflection resolver code to discover the appropriate * 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) { public static int getTypeDifferenceWeight(List<TypeDescriptor> paramTypes, List<TypeDescriptor> argTypes) {
int result = 0; int result = 0;
@ -279,7 +280,7 @@ public class ReflectionHelper {
TypeDescriptor targetType; TypeDescriptor targetType;
if (varargsPosition != null && argPosition >= varargsPosition) { if (varargsPosition != null && argPosition >= varargsPosition) {
MethodParameter methodParam = MethodParameter.forMethodOrConstructor(methodOrCtor, varargsPosition); MethodParameter methodParam = MethodParameter.forMethodOrConstructor(methodOrCtor, varargsPosition);
targetType = TypeDescriptor.forNestedType(methodParam); targetType = TypeDescriptor.nested(methodParam, 1);
} }
else { else {
targetType = new TypeDescriptor(MethodParameter.forMethodOrConstructor(methodOrCtor, argPosition)); targetType = new TypeDescriptor(MethodParameter.forMethodOrConstructor(methodOrCtor, argPosition));
@ -311,7 +312,7 @@ public class ReflectionHelper {
TypeDescriptor targetType; TypeDescriptor targetType;
if (varargsPosition != null && argPosition >= varargsPosition) { if (varargsPosition != null && argPosition >= varargsPosition) {
MethodParameter methodParam = new MethodParameter(method, varargsPosition); MethodParameter methodParam = new MethodParameter(method, varargsPosition);
targetType = TypeDescriptor.forNestedType(methodParam); targetType = TypeDescriptor.nested(methodParam, 1);
} }
else { else {
targetType = new TypeDescriptor(new MethodParameter(method, argPosition)); targetType = new TypeDescriptor(new MethodParameter(method, argPosition));

View File

@ -28,7 +28,6 @@ import java.util.concurrent.ConcurrentHashMap;
import org.springframework.core.MethodParameter; import org.springframework.core.MethodParameter;
import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.support.PropertyTypeDescriptor;
import org.springframework.expression.AccessException; import org.springframework.expression.AccessException;
import org.springframework.expression.EvaluationContext; import org.springframework.expression.EvaluationContext;
import org.springframework.expression.EvaluationException; import org.springframework.expression.EvaluationException;
@ -79,8 +78,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
try { try {
// The readerCache will only contain gettable properties (let's not worry about setters for now) // The readerCache will only contain gettable properties (let's not worry about setters for now)
PropertyDescriptor propertyDescriptor = new PropertyDescriptor(name, method, null); PropertyDescriptor propertyDescriptor = new PropertyDescriptor(name, method, null);
TypeDescriptor typeDescriptor = TypeDescriptor typeDescriptor = new TypeDescriptor(type, propertyDescriptor);
new PropertyTypeDescriptor(new MethodParameter(method, -1), propertyDescriptor);
this.readerCache.put(cacheKey, new InvokerPair(method, typeDescriptor)); this.readerCache.put(cacheKey, new InvokerPair(method, typeDescriptor));
this.typeDescriptorCache.put(cacheKey, typeDescriptor); this.typeDescriptorCache.put(cacheKey, typeDescriptor);
return true; return true;
@ -127,8 +125,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
try { try {
// The readerCache will only contain gettable properties (let's not worry about setters for now) // The readerCache will only contain gettable properties (let's not worry about setters for now)
PropertyDescriptor propertyDescriptor = new PropertyDescriptor(name, method, null); PropertyDescriptor propertyDescriptor = new PropertyDescriptor(name, method, null);
TypeDescriptor typeDescriptor = TypeDescriptor typeDescriptor = new TypeDescriptor(type, propertyDescriptor);
new PropertyTypeDescriptor(new MethodParameter(method, -1), propertyDescriptor);
invoker = new InvokerPair(method, typeDescriptor); invoker = new InvokerPair(method, typeDescriptor);
this.readerCache.put(cacheKey, invoker); this.readerCache.put(cacheKey, invoker);
} }
@ -191,8 +188,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
catch (IntrospectionException ex) { catch (IntrospectionException ex) {
throw new AccessException("Unable to access property '" + name + "' through setter "+method, ex); throw new AccessException("Unable to access property '" + name + "' through setter "+method, ex);
} }
MethodParameter mp = new MethodParameter(method,0); TypeDescriptor typeDescriptor = new TypeDescriptor(type, propertyDescriptor);
TypeDescriptor typeDescriptor = new PropertyTypeDescriptor(mp, propertyDescriptor);
this.writerCache.put(cacheKey, method); this.writerCache.put(cacheKey, method);
this.typeDescriptorCache.put(cacheKey, typeDescriptor); this.typeDescriptorCache.put(cacheKey, typeDescriptor);
return true; return true;

View File

@ -66,12 +66,10 @@ public class StandardTypeConverter implements TypeConverter {
return this.conversionService.convert(value, sourceType, targetType); return this.conversionService.convert(value, sourceType, targetType);
} }
catch (ConverterNotFoundException cenfe) { catch (ConverterNotFoundException cenfe) {
throw new SpelEvaluationException(cenfe, SpelMessage.TYPE_CONVERSION_ERROR, throw new SpelEvaluationException(cenfe, SpelMessage.TYPE_CONVERSION_ERROR, sourceType.toString(), targetType.toString());
sourceType.toString(), targetType.asString());
} }
catch (ConversionException ce) { catch (ConversionException ce) {
throw new SpelEvaluationException(ce, SpelMessage.TYPE_CONVERSION_ERROR, throw new SpelEvaluationException(ce, SpelMessage.TYPE_CONVERSION_ERROR, sourceType.toString(), targetType.toString());
sourceType.toString(), targetType.asString());
} }
} }

View File

@ -70,7 +70,7 @@ public class SpelDocumentationTests extends ExpressionTestCase {
public List Members2 = new ArrayList(); public List Members2 = new ArrayList();
public Map<String,Object> officers = new HashMap<String,Object>(); 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") @SuppressWarnings("unchecked")
IEEE() { IEEE() {