added TypeDescriptor resolveCollectionElement and Map key/value types
This commit is contained in:
@@ -36,13 +36,13 @@ import org.springframework.util.ObjectUtils;
|
||||
*/
|
||||
public class TypeDescriptor {
|
||||
|
||||
static final Annotation[] EMPTY_ANNOTATION_ARRAY = new Annotation[0];
|
||||
|
||||
/** Constant defining a TypeDescriptor for a <code>null</code> value */
|
||||
public static final TypeDescriptor NULL = new TypeDescriptor();
|
||||
|
||||
private static final Map<Class<?>, TypeDescriptor> typeDescriptorCache = new HashMap<Class<?>, TypeDescriptor>();
|
||||
|
||||
static final Annotation[] EMPTY_ANNOTATION_ARRAY = new Annotation[0];
|
||||
|
||||
static {
|
||||
typeDescriptorCache.put(boolean.class, new TypeDescriptor(boolean.class));
|
||||
typeDescriptorCache.put(Boolean.class, new TypeDescriptor(Boolean.class));
|
||||
@@ -237,21 +237,21 @@ public class TypeDescriptor {
|
||||
* Returns the Object wrapper type if the underlying type is a primitive.
|
||||
*/
|
||||
public Class<?> getObjectType() {
|
||||
return ClassUtils.resolvePrimitiveIfNecessary(getType());
|
||||
return getType() != null ? ClassUtils.resolvePrimitiveIfNecessary(getType()) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of this type: the fully qualified class name.
|
||||
*/
|
||||
public String getName() {
|
||||
return ClassUtils.getQualifiedName(getType());
|
||||
return getType() != null ? ClassUtils.getQualifiedName(getType()) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this type a primitive type?
|
||||
*/
|
||||
public boolean isPrimitive() {
|
||||
return getType().isPrimitive();
|
||||
return getType() != null && getType().isPrimitive();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -304,21 +304,21 @@ public class TypeDescriptor {
|
||||
* Is this type a {@link Collection} type?
|
||||
*/
|
||||
public boolean isCollection() {
|
||||
return Collection.class.isAssignableFrom(getType());
|
||||
return getType() != null && Collection.class.isAssignableFrom(getType());
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this type an array type?
|
||||
*/
|
||||
public boolean isArray() {
|
||||
return getType().isArray();
|
||||
return getType() != null && getType().isArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* If this type is a {@link Collection} or array, returns the underlying element type.
|
||||
* Returns <code>null</code> if this type is neither an array or collection.
|
||||
* Returns Object.class if this type is a collection and the element type was not explicitly declared.
|
||||
* @return the map element type, or <code>null</code> if not a collection or array.
|
||||
* @throws IllegalStateException if this descriptor is not for a java.util.Collection or Array
|
||||
*/
|
||||
public Class<?> getElementType() {
|
||||
return getElementTypeDescriptor().getType();
|
||||
@@ -326,27 +326,47 @@ public class TypeDescriptor {
|
||||
|
||||
/**
|
||||
* The collection or array element type as a type descriptor.
|
||||
* Returns {@link TypeDescriptor#NULL} if this type is not a collection or an array.
|
||||
* Returns TypeDescriptor.valueOf(Object.class) if this type is a collection and the element type is not explicitly declared.
|
||||
* @throws IllegalStateException if this descriptor is not for a java.util.Collection or Array
|
||||
*/
|
||||
public TypeDescriptor getElementTypeDescriptor() {
|
||||
if (!isCollection() && !isArray()) {
|
||||
throw new IllegalStateException("Not a java.util.Collection or Array");
|
||||
}
|
||||
return this.elementType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of this type descriptor that has its elementType populated from the specified Collection.
|
||||
* This property will be set by calculating the "common element type" of the specified Collection.
|
||||
* For example, if the collection contains String elements, the returned TypeDescriptor will have its elementType set to String.
|
||||
* This method is designed to be used when converting values read from Collection fields or method return values that are not parameterized e.g. Collection vs. Collection<String>
|
||||
* In this scenario the elementType will be Object.class before invoking this method.
|
||||
* @param colection the collection to derive the elementType from
|
||||
* @return a new TypeDescriptor with the resolved elementType property
|
||||
* @throws IllegalArgumentException if this is not a type descriptor for a java.util.Collection.
|
||||
*/
|
||||
public TypeDescriptor resolveCollectionElementType(Collection<?> collection) {
|
||||
if (!isCollection()) {
|
||||
throw new IllegalStateException("Not a java.util.Collection");
|
||||
}
|
||||
return new TypeDescriptor(type, CommonElement.typeDescriptor(collection), mapKeyType, mapValueType, annotations);
|
||||
}
|
||||
|
||||
// map type descriptor operations
|
||||
|
||||
/**
|
||||
* Is this type a {@link Map} type?
|
||||
*/
|
||||
public boolean isMap() {
|
||||
return Map.class.isAssignableFrom(getType());
|
||||
return getType() != null && Map.class.isAssignableFrom(getType());
|
||||
}
|
||||
|
||||
/**
|
||||
* If this type is a {@link Map}, returns the underlying key type.
|
||||
* Returns <code>null</code> if this type is not map.
|
||||
* Returns Object.class if this type is a map and its key type was not explicitly declared.
|
||||
* @return the map key type, or <code>null</code> if not a map.
|
||||
* @throws IllegalStateException if this descriptor is not for a java.util.Map
|
||||
*/
|
||||
public Class<?> getMapKeyType() {
|
||||
return getMapKeyTypeDescriptor().getType();
|
||||
@@ -354,10 +374,13 @@ public class TypeDescriptor {
|
||||
|
||||
/**
|
||||
* The map key type as a type descriptor.
|
||||
* Returns {@link TypeDescriptor#NULL} if this type is not a map.
|
||||
* Returns TypeDescriptor.valueOf(Object.class) if this type is a map and the key type is not explicitly declared.
|
||||
* @throws IllegalStateException if this descriptor is not for a java.util.Map
|
||||
*/
|
||||
public TypeDescriptor getMapKeyTypeDescriptor() {
|
||||
if (!isMap()) {
|
||||
throw new IllegalStateException("Not a map");
|
||||
}
|
||||
return this.mapKeyType;
|
||||
}
|
||||
|
||||
@@ -366,6 +389,7 @@ public class TypeDescriptor {
|
||||
* Returns <code>null</code> if this type is not map.
|
||||
* Returns Object.class if this type is a map and its value type was not explicitly declared.
|
||||
* @return the map value type, or <code>null</code> if not a map.
|
||||
* @throws IllegalStateException if this descriptor is not for a java.util.Map
|
||||
*/
|
||||
public Class<?> getMapValueType() {
|
||||
return getMapValueTypeDescriptor().getType();
|
||||
@@ -373,12 +397,32 @@ public class TypeDescriptor {
|
||||
|
||||
/**
|
||||
* The map value type as a type descriptor.
|
||||
* Returns {@link TypeDescriptor#NULL} if this type is not a map.
|
||||
* Returns TypeDescriptor.valueOf(Object.class) if this type is a map and the value type is not explicitly declared.
|
||||
* @throws IllegalStateException if this descriptor is not for a java.util.Map
|
||||
*/
|
||||
public TypeDescriptor getMapValueTypeDescriptor() {
|
||||
if (!isMap()) {
|
||||
throw new IllegalStateException("Not a map");
|
||||
}
|
||||
return this.mapValueType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of this type descriptor that has its mapKeyType and mapValueType properties populated from the specified Map.
|
||||
* These properties will be set by calculating the "common element type" of the specified Map's keySet and values collection.
|
||||
* For example, if the Map contains String keys and Integer values, the returned TypeDescriptor will have its mapKeyType set to String and its mapValueType to Integer.
|
||||
* This method is designed to be used when converting values read from Map fields or method return values that are not parameterized e.g. Map vs. Map<String, Integer>.
|
||||
* In this scenario the key and value types will be Object.class before invoking this method.
|
||||
* @param map the map to derive key and value types from
|
||||
* @return a new TypeDescriptor with the resolved mapKeyType and mapValueType properties
|
||||
* @throws IllegalArgumentException if this is not a type descriptor for a java.util.Map.
|
||||
*/
|
||||
public TypeDescriptor resolveMapKeyValueTypes(Map<?, ?> map) {
|
||||
if (!isMap()) {
|
||||
throw new IllegalStateException("Not a java.util.Map");
|
||||
}
|
||||
return new TypeDescriptor(type, elementType, CommonElement.typeDescriptor(map.keySet()), CommonElement.typeDescriptor(map.values()), annotations);
|
||||
}
|
||||
|
||||
// extending Object
|
||||
|
||||
@@ -386,20 +430,22 @@ public class TypeDescriptor {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (!(obj instanceof TypeDescriptor) || obj == TypeDescriptor.NULL) {
|
||||
if (!(obj instanceof TypeDescriptor)) {
|
||||
return false;
|
||||
}
|
||||
TypeDescriptor other = (TypeDescriptor) obj;
|
||||
boolean annotatedTypeEquals = getType().equals(other.getType()) && ObjectUtils.nullSafeEquals(getAnnotations(), other.getAnnotations());
|
||||
if (isCollection()) {
|
||||
return annotatedTypeEquals && ObjectUtils.nullSafeEquals(getElementType(), other.getElementType());
|
||||
boolean annotatedTypeEquals = ObjectUtils.nullSafeEquals(getType(), other.getType()) && ObjectUtils.nullSafeEquals(getAnnotations(), other.getAnnotations());
|
||||
if (!annotatedTypeEquals) {
|
||||
return false;
|
||||
}
|
||||
if (isCollection() || isArray()) {
|
||||
return ObjectUtils.nullSafeEquals(getElementType(), other.getElementType());
|
||||
}
|
||||
else if (isMap()) {
|
||||
return annotatedTypeEquals && ObjectUtils.nullSafeEquals(getMapKeyType(), other.getMapKeyType()) &&
|
||||
ObjectUtils.nullSafeEquals(getMapValueType(), other.getMapValueType());
|
||||
return ObjectUtils.nullSafeEquals(getMapKeyType(), other.getMapKeyType()) && ObjectUtils.nullSafeEquals(getMapValueType(), other.getMapValueType());
|
||||
}
|
||||
else {
|
||||
return annotatedTypeEquals;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -440,11 +486,11 @@ public class TypeDescriptor {
|
||||
}
|
||||
|
||||
TypeDescriptor(Class<?> collectionType, TypeDescriptor elementType) {
|
||||
this(collectionType, elementType, TypeDescriptor.NULL, TypeDescriptor.NULL);
|
||||
this(collectionType, elementType, TypeDescriptor.NULL, TypeDescriptor.NULL, EMPTY_ANNOTATION_ARRAY);
|
||||
}
|
||||
|
||||
TypeDescriptor(Class<?> mapType, TypeDescriptor keyType, TypeDescriptor valueType) {
|
||||
this(mapType, TypeDescriptor.NULL, keyType, valueType);
|
||||
this(mapType, TypeDescriptor.NULL, keyType, valueType, EMPTY_ANNOTATION_ARRAY);
|
||||
}
|
||||
|
||||
static Annotation[] nullSafeAnnotations(Annotation[] annotations) {
|
||||
@@ -458,15 +504,15 @@ public class TypeDescriptor {
|
||||
}
|
||||
|
||||
private TypeDescriptor() {
|
||||
this(null, TypeDescriptor.NULL, TypeDescriptor.NULL, TypeDescriptor.NULL);
|
||||
this(null, TypeDescriptor.NULL, TypeDescriptor.NULL, TypeDescriptor.NULL, EMPTY_ANNOTATION_ARRAY);
|
||||
}
|
||||
|
||||
private TypeDescriptor(Class<?> type, TypeDescriptor elementType, TypeDescriptor mapKeyType, TypeDescriptor mapValueType) {
|
||||
private TypeDescriptor(Class<?> type, TypeDescriptor elementType, TypeDescriptor mapKeyType, TypeDescriptor mapValueType, Annotation[] annotations) {
|
||||
this.type = type;
|
||||
this.elementType = elementType;
|
||||
this.mapKeyType = mapKeyType;
|
||||
this.mapValueType = mapValueType;
|
||||
this.annotations = EMPTY_ANNOTATION_ARRAY;
|
||||
this.annotations = annotations;
|
||||
}
|
||||
|
||||
// internal helpers
|
||||
@@ -477,5 +523,5 @@ public class TypeDescriptor {
|
||||
}
|
||||
return new TypeDescriptor(descriptor);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -57,10 +57,11 @@ final class CollectionToArrayConverter implements ConditionalGenericConverter {
|
||||
return null;
|
||||
}
|
||||
Collection<?> sourceCollection = (Collection<?>) source;
|
||||
Object array = Array.newInstance(targetType.getElementType(), sourceCollection.size());
|
||||
TypeDescriptor targetElementType = targetType.getElementTypeDescriptor();
|
||||
Object array = Array.newInstance(targetElementType.getType(), sourceCollection.size());
|
||||
int i = 0;
|
||||
for (Object sourceElement : sourceCollection) {
|
||||
Object targetElement = this.conversionService.convert(sourceElement, sourceType.getElementTypeDescriptor(), targetType.getElementTypeDescriptor());
|
||||
Object targetElement = this.conversionService.convert(sourceElement, sourceType.getElementTypeDescriptor(), targetElementType);
|
||||
Array.set(array, i++, targetElement);
|
||||
}
|
||||
return array;
|
||||
|
||||
@@ -167,36 +167,24 @@ public class GenericConversionService implements ConfigurableConversionService {
|
||||
logger.debug("Converting value " + StylerUtils.style(source) + " of " + sourceType + " to " + targetType);
|
||||
}
|
||||
if (sourceType == TypeDescriptor.NULL) {
|
||||
Assert.isTrue(source == null, "The value must be null if sourceType == TypeDescriptor.NULL");
|
||||
Object result = convertNullSource(sourceType, targetType);
|
||||
if (result == null) {
|
||||
assertNotPrimitiveTargetType(sourceType, targetType);
|
||||
}
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Converted to " + StylerUtils.style(result));
|
||||
}
|
||||
return result;
|
||||
Assert.isTrue(source == null, "The source must be [null] if sourceType == [null]");
|
||||
return handleResult(sourceType, targetType, convertNullSource(sourceType, targetType));
|
||||
}
|
||||
if (targetType == TypeDescriptor.NULL) {
|
||||
logger.debug("Converted to null");
|
||||
return null;
|
||||
}
|
||||
Assert.isTrue(source == null || sourceType.getObjectType().isInstance(source));
|
||||
if (source != null && !sourceType.getObjectType().isInstance(source)) {
|
||||
throw new IllegalArgumentException("The source to convert from must be an instance of " + sourceType + "; instead it was a " + source.getClass().getName());
|
||||
}
|
||||
GenericConverter converter = getConverter(sourceType, targetType);
|
||||
if (converter == null) {
|
||||
return handleConverterNotFound(source, sourceType, targetType);
|
||||
if (converter != null) {
|
||||
return handleResult(sourceType, targetType, ConversionUtils.invokeConverter(converter, source, sourceType, targetType));
|
||||
} else {
|
||||
return handleConverterNotFound(source, sourceType, targetType);
|
||||
}
|
||||
Object result = ConversionUtils.invokeConverter(converter, source, sourceType, targetType);
|
||||
if (result == null) {
|
||||
assertNotPrimitiveTargetType(sourceType, targetType);
|
||||
}
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Converted to " + StylerUtils.style(result));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
public String toString() {
|
||||
List<String> converterStrings = new ArrayList<String>();
|
||||
for (Map<Class<?>, MatchableConverters> targetConverters : this.converters.values()) {
|
||||
@@ -325,7 +313,7 @@ public class GenericConversionService implements ConfigurableConversionService {
|
||||
}
|
||||
|
||||
private void assertNotNull(TypeDescriptor sourceType, TypeDescriptor targetType) {
|
||||
Assert.notNull(sourceType, "The sourceType to convert to is required");
|
||||
Assert.notNull(sourceType, "The sourceType to convert from is required");
|
||||
Assert.notNull(targetType, "The targetType to convert to is required");
|
||||
}
|
||||
|
||||
@@ -537,6 +525,15 @@ public class GenericConversionService implements ConfigurableConversionService {
|
||||
}
|
||||
}
|
||||
|
||||
private Object handleResult(TypeDescriptor sourceType, TypeDescriptor targetType, Object result) {
|
||||
if (result == null) {
|
||||
assertNotPrimitiveTargetType(sourceType, targetType);
|
||||
}
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Converted to " + StylerUtils.style(result));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
private void assertNotPrimitiveTargetType(TypeDescriptor sourceType, TypeDescriptor targetType) {
|
||||
if (targetType.isPrimitive()) {
|
||||
throw new ConversionFailedException(sourceType, targetType, null,
|
||||
|
||||
@@ -48,14 +48,14 @@ final class ObjectToObjectConverter implements ConditionalGenericConverter {
|
||||
}
|
||||
|
||||
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
|
||||
Class<?> source = sourceType.getObjectType();
|
||||
Class<?> target = targetType.getObjectType();
|
||||
return (!source.equals(target) && hasValueOfMethodOrConstructor(target, source));
|
||||
Class<?> source = sourceType.getType();
|
||||
Class<?> target = targetType.getType();
|
||||
return !source.equals(target) && hasValueOfMethodOrConstructor(target, source);
|
||||
}
|
||||
|
||||
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
|
||||
Class<?> sourceClass = sourceType.getObjectType();
|
||||
Class<?> targetClass = targetType.getObjectType();
|
||||
Class<?> sourceClass = sourceType.getType();
|
||||
Class<?> targetClass = targetType.getType();
|
||||
Method method = getValueOfMethodOn(targetClass, sourceClass);
|
||||
try {
|
||||
if (method != null) {
|
||||
@@ -79,9 +79,8 @@ final class ObjectToObjectConverter implements ConditionalGenericConverter {
|
||||
") method or Constructor(" + sourceClass.getName() + ") exists on " + targetClass.getName());
|
||||
}
|
||||
|
||||
|
||||
public static boolean hasValueOfMethodOrConstructor(Class<?> targetClass, Class<?> sourceClass) {
|
||||
return (getValueOfMethodOn(targetClass, sourceClass) != null || getConstructor(targetClass, sourceClass) != null);
|
||||
return getValueOfMethodOn(targetClass, sourceClass) != null || getConstructor(targetClass, sourceClass) != null;
|
||||
}
|
||||
|
||||
private static Method getValueOfMethodOn(Class<?> targetClass, Class<?> sourceClass) {
|
||||
|
||||
@@ -56,8 +56,7 @@ final class StringToArrayConverter implements ConditionalGenericConverter {
|
||||
Object target = Array.newInstance(targetType.getElementType(), fields.length);
|
||||
for (int i = 0; i < fields.length; i++) {
|
||||
String sourceElement = fields[i];
|
||||
Object targetElement = this.conversionService.convert(sourceElement.trim(),
|
||||
sourceType, targetType.getElementTypeDescriptor());
|
||||
Object targetElement = this.conversionService.convert(sourceElement.trim(), sourceType, targetType.getElementTypeDescriptor());
|
||||
Array.set(target, i, targetElement);
|
||||
}
|
||||
return target;
|
||||
|
||||
@@ -32,8 +32,7 @@ final class StringToCharacterConverter implements Converter<String, Character> {
|
||||
}
|
||||
if (source.length() > 1) {
|
||||
throw new IllegalArgumentException(
|
||||
"Can only convert a [String] with length of 1 to a [Character]; string value '" + source
|
||||
+ "' has length of " + source.length());
|
||||
"Can only convert a [String] with length of 1 to a [Character]; string value '" + source + "' has length of " + source.length());
|
||||
}
|
||||
return source.charAt(0);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user