simplified TypeDescriptor usage and updated use of the API across BeanWrapper and SpEL; collapsed PropertyTypeDescriptor into TypeDescriptor for simplicity and ease of use; improved docs
git-svn-id: https://src.springframework.org/svn/spring-framework/trunk@4424 50f2f4bb-b051-0410-bef5-90022cba6387
This commit is contained in:
parent
5d69429178
commit
c7cae10364
|
|
@ -43,7 +43,6 @@ import org.springframework.core.MethodParameter;
|
||||||
import org.springframework.core.convert.ConversionException;
|
import org.springframework.core.convert.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 {
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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<String> 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<String> 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<String> and the nestingLevel is 1, the nested type descriptor will be String.class.
|
||||||
|
* If the property is a List<List<String>> and the nestingLevel is 2, the nested type descriptor will also be a String.class.
|
||||||
|
* If the field is a Map<Integer, String> and the nestingLevel is 1, the nested type descriptor will be String, derived from the map value.
|
||||||
|
* If the property is a List<Map<Integer, String>> and the nestingLevel is 2, the nested type descriptor will be String, derived from the map value.
|
||||||
|
* @param property the property
|
||||||
|
* @param nestingLevel the nesting level
|
||||||
|
* @return the nested type descriptor
|
||||||
|
* @throws IllegalArgumentException if the property is not of a collection, array, or map type.
|
||||||
|
*/
|
||||||
|
public static TypeDescriptor nested(Class<?> beanClass, PropertyDescriptor property, int nestingLevel) {
|
||||||
|
return nested(new BeanPropertyDescriptor(beanClass, property), nestingLevel);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -206,10 +257,7 @@ public class TypeDescriptor {
|
||||||
/**
|
/**
|
||||||
* Obtain the annotations associated with the wrapped parameter/field, if any.
|
* 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 {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,135 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2002-2010 the original author or authors.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.springframework.core.convert.support;
|
|
||||||
|
|
||||||
import java.beans.PropertyDescriptor;
|
|
||||||
import java.lang.annotation.Annotation;
|
|
||||||
import java.lang.reflect.Field;
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import org.springframework.core.MethodParameter;
|
|
||||||
import org.springframework.core.convert.TypeDescriptor;
|
|
||||||
import org.springframework.util.ReflectionUtils;
|
|
||||||
import org.springframework.util.StringUtils;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@link TypeDescriptor} extension that exposes additional annotations
|
|
||||||
* as conversion metadata: namely, annotations on other accessor methods
|
|
||||||
* (getter/setter) and on the underlying field, if found.
|
|
||||||
*
|
|
||||||
* @author Juergen Hoeller
|
|
||||||
* @author Keith Donald
|
|
||||||
* @since 3.0.2
|
|
||||||
*/
|
|
||||||
public class PropertyTypeDescriptor extends TypeDescriptor {
|
|
||||||
|
|
||||||
private final PropertyDescriptor propertyDescriptor;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new type descriptor for the given bean property.
|
|
||||||
* @param methodParameter the target method parameter
|
|
||||||
* @param propertyDescriptor the corresponding JavaBean PropertyDescriptor
|
|
||||||
*/
|
|
||||||
public PropertyTypeDescriptor(MethodParameter methodParameter, PropertyDescriptor propertyDescriptor) {
|
|
||||||
super(methodParameter);
|
|
||||||
this.propertyDescriptor = propertyDescriptor;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new type descriptor for a nested type declared on an array, collection, or map-based property.
|
|
||||||
* Use this factory method when you've resolved a nested source object such as a collection element or map value and wish to have it converted.
|
|
||||||
* Builds in protection for increasing the nesting level of the provided MethodParameter if the nestedType is itself a collection.
|
|
||||||
* @param methodParameter the method parameter
|
|
||||||
* @return the property descriptor
|
|
||||||
*/
|
|
||||||
public static PropertyTypeDescriptor forNestedType(MethodParameter methodParameter, PropertyDescriptor propertyDescriptor) {
|
|
||||||
return forNestedType(resolveNestedType(methodParameter), methodParameter, propertyDescriptor);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new type descriptor for a nested type declared on an array, collection, or map-based property.
|
|
||||||
* Use this factory method when you've resolved a nested source object such as a collection element or map value and wish to have it converted.
|
|
||||||
* Builds in protection for increasing the nesting level of the provided MethodParameter if the nestedType is itself a collection.
|
|
||||||
* @param nestedType the nested type
|
|
||||||
* @param methodParameter the method parameter
|
|
||||||
* @return the property descriptor
|
|
||||||
*/
|
|
||||||
public static PropertyTypeDescriptor forNestedType(Class<?> nestedType, MethodParameter methodParameter, PropertyDescriptor propertyDescriptor) {
|
|
||||||
return new PropertyTypeDescriptor(nestedType, methodParameter, propertyDescriptor);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the underlying PropertyDescriptor.
|
|
||||||
*/
|
|
||||||
public PropertyDescriptor getPropertyDescriptor() {
|
|
||||||
return this.propertyDescriptor;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Annotation[] resolveAnnotations() {
|
|
||||||
Map<Class<?>, Annotation> annMap = new LinkedHashMap<Class<?>, Annotation>();
|
|
||||||
String name = this.propertyDescriptor.getName();
|
|
||||||
if (StringUtils.hasLength(name)) {
|
|
||||||
Class<?> clazz = getMethodParameter().getMethod().getDeclaringClass();
|
|
||||||
Field field = ReflectionUtils.findField(clazz, name);
|
|
||||||
if (field == null) {
|
|
||||||
// Same lenient fallback checking as in CachedIntrospectionResults...
|
|
||||||
field = ReflectionUtils.findField(clazz, name.substring(0, 1).toLowerCase() + name.substring(1));
|
|
||||||
if (field == null) {
|
|
||||||
field = ReflectionUtils.findField(clazz, name.substring(0, 1).toUpperCase() + name.substring(1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (field != null) {
|
|
||||||
for (Annotation ann : field.getAnnotations()) {
|
|
||||||
annMap.put(ann.annotationType(), ann);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Method writeMethod = this.propertyDescriptor.getWriteMethod();
|
|
||||||
Method readMethod = this.propertyDescriptor.getReadMethod();
|
|
||||||
if (writeMethod != null && writeMethod != getMethodParameter().getMethod()) {
|
|
||||||
for (Annotation ann : writeMethod.getAnnotations()) {
|
|
||||||
annMap.put(ann.annotationType(), ann);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (readMethod != null && readMethod != getMethodParameter().getMethod()) {
|
|
||||||
for (Annotation ann : readMethod.getAnnotations()) {
|
|
||||||
annMap.put(ann.annotationType(), ann);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (Annotation ann : getMethodParameter().getMethodAnnotations()) {
|
|
||||||
annMap.put(ann.annotationType(), ann);
|
|
||||||
}
|
|
||||||
for (Annotation ann : getMethodParameter().getParameterAnnotations()) {
|
|
||||||
annMap.put(ann.annotationType(), ann);
|
|
||||||
}
|
|
||||||
return annMap.values().toArray(new Annotation[annMap.size()]);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected TypeDescriptor newNestedTypeDescriptor(Class<?> nestedType, MethodParameter nested) {
|
|
||||||
return new PropertyTypeDescriptor(nestedType, nested, this.propertyDescriptor);
|
|
||||||
}
|
|
||||||
|
|
||||||
// internal constructors
|
|
||||||
|
|
||||||
private PropertyTypeDescriptor(Class<?> nestedType, MethodParameter methodParameter, PropertyDescriptor propertyDescriptor) {
|
|
||||||
super(nestedType, methodParameter);
|
|
||||||
this.propertyDescriptor = propertyDescriptor;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -19,17 +19,33 @@ package org.springframework.core.convert;
|
||||||
import static junit.framework.Assert.assertEquals;
|
import static junit.framework.Assert.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -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));
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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() {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue