revised findCommonElement handling within TypeDescriptor.forObject(Object); we now fully introspect the collection elements to resolve the common type. We also support nested introspection e.g. collections of collections. Object.class is used to indicate no common type, and TypeDescriptor.NULL is used to indicate a null element value

This commit is contained in:
Keith Donald
2011-05-23 05:21:02 +00:00
parent 79f9d1cfc6
commit 5c67dbf424
10 changed files with 231 additions and 50 deletions

View File

@@ -43,8 +43,7 @@ public final class ConversionFailedException extends ConversionException {
* @param cause the cause of the conversion failure
*/
public ConversionFailedException(TypeDescriptor sourceType, TypeDescriptor targetType, Object value, Throwable cause) {
super("Unable to convert value \"" + ObjectUtils.nullSafeToString(value) + "\" from type '" +
sourceType.getName() + "' to type '" + targetType.getName() + "'", cause);
super("Failed to convert from type " + sourceType + " to type " + targetType + " for value '" + ObjectUtils.nullSafeToString(value) + "'", cause);
this.sourceType = sourceType;
this.targetType = targetType;
this.value = value;

View File

@@ -37,8 +37,7 @@ public final class ConverterNotFoundException extends ConversionException {
* @param message a descriptive message
*/
public ConverterNotFoundException(TypeDescriptor sourceType, TypeDescriptor targetType) {
super("No converter found capable of converting from '" + sourceType.getName() +
"' to '" + targetType.getName() + "'");
super("No converter found capable of converting from type " + sourceType + " to type " + targetType);
this.sourceType = sourceType;
this.targetType = targetType;
}

View File

@@ -20,6 +20,7 @@ import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import org.springframework.core.GenericCollectionTypeResolver;
@@ -515,20 +516,51 @@ public class TypeDescriptor {
return methodParameter;
}
private static Object findCommonElement(Collection<?> values) {
private static CommonElement findCommonElement(Collection<?> values) {
Class<?> commonType = null;
Object candidate = null;
for (Object value : values) {
if (value != null) {
if (candidate == null) {
commonType = value.getClass();
candidate = value;
} else if (candidate.getClass() != value.getClass()) {
return null;
} else {
commonType = commonType(commonType, value.getClass());
if (commonType == Object.class) {
return null;
}
}
}
}
return candidate;
return new CommonElement(commonType, candidate);
}
private static Class<?> commonType(Class<?> commonType, Class<?> valueClass) {
LinkedList<Class<?>> classQueue = new LinkedList<Class<?>>();
classQueue.addFirst(commonType);
while (!classQueue.isEmpty()) {
Class<?> currentClass = classQueue.removeLast();
if (currentClass.isAssignableFrom(valueClass)) {
return currentClass;
}
Class<?>[] interfaces = currentClass.getInterfaces();
for (Class<?> ifc : interfaces) {
addInterfaceHierarchy(ifc, classQueue);
}
if (currentClass.getSuperclass() != null) {
classQueue.addFirst(currentClass.getSuperclass());
}
}
throw new IllegalStateException("Should never be invoked");
}
private static void addInterfaceHierarchy(Class<?> ifc, LinkedList<Class<?>> classQueue) {
classQueue.addFirst(ifc);
for (Class<?> inheritedIfc : ifc.getInterfaces()) {
addInterfaceHierarchy(inheritedIfc, classQueue);
}
}
// internal constructors
private TypeDescriptor() {
@@ -545,38 +577,61 @@ public class TypeDescriptor {
this.fieldNestingLevel = nestingLevel;
}
public TypeDescriptor(Class<?> mapType, Object commonKey, Object commonValue) {
public TypeDescriptor(Class<?> mapType, CommonElement commonKey, CommonElement commonValue) {
this.type = mapType;
this.mapKeyType = applyIndexedObject(commonKey);
this.mapValueType = applyIndexedObject(commonValue);
this.mapKeyType = applyCommonElement(commonKey);
this.mapValueType = applyCommonElement(commonValue);
}
public TypeDescriptor(Class<?> collectionType, Object commonElement) {
public TypeDescriptor(Class<?> collectionType, CommonElement commonElement) {
this.type = collectionType;
this.elementType = applyIndexedObject(commonElement);
this.elementType = applyCommonElement(commonElement);
}
private TypeDescriptor applyIndexedObject(Object object) {
if (object == null) {
private TypeDescriptor applyCommonElement(CommonElement commonElement) {
if (commonElement == null) {
return TypeDescriptor.valueOf(Object.class);
}
if (object instanceof Collection<?>) {
Collection<?> collection = (Collection<?>) object;
if (commonElement.getValue() instanceof Collection<?>) {
Collection<?> collection = (Collection<?>) commonElement.getValue();
if (collection.size() == 0) {
return TypeDescriptor.valueOf(Object.class);
}
return new TypeDescriptor(object.getClass(), findCommonElement((Collection<?>) object));
return new TypeDescriptor(commonElement.getType(), findCommonElement(collection));
}
else if (object instanceof Map<?, ?>) {
Map<?, ?> map = (Map<?, ?>) object;
else if (commonElement.getValue() instanceof Map<?, ?>) {
Map<?, ?> map = (Map<?, ?>) commonElement.getValue();
if (map.size() == 0) {
return TypeDescriptor.valueOf(Object.class);
}
return new TypeDescriptor(object.getClass(), findCommonElement(map.keySet()), findCommonElement(map.values()));
return new TypeDescriptor(commonElement.getType(), findCommonElement(map.keySet()), findCommonElement(map.values()));
}
else {
return TypeDescriptor.valueOf(object.getClass());
return TypeDescriptor.valueOf(commonElement.getType());
}
}
// inner classes
private static class CommonElement {
private Class<?> type;
private Object value;
public CommonElement(Class<?> type, Object value) {
this.type = type;
this.value = value;
}
public Class<?> getType() {
return type;
}
public Object getValue() {
return value;
}
}
}

View File

@@ -49,11 +49,7 @@ final class CollectionToArrayConverter implements ConditionalGenericConverter {
}
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
TypeDescriptor sourceElementType = sourceType.getElementTypeDescriptor();
if (Object.class.equals(sourceElementType.getType())) {
return true;
}
return this.conversionService.canConvert(sourceElementType, targetType.getElementTypeDescriptor());
return this.conversionService.canConvert(sourceType.getElementTypeDescriptor(), targetType.getElementTypeDescriptor());
}
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {

View File

@@ -51,9 +51,6 @@ final class CollectionToCollectionConverter implements ConditionalGenericConvert
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
TypeDescriptor sourceElementType = sourceType.getElementTypeDescriptor();
TypeDescriptor targetElementType = targetType.getElementTypeDescriptor();
if (Object.class.equals(sourceElementType.getType()) || Object.class.equals(targetElementType.getType())) {
return true;
}
return this.conversionService.canConvert(sourceElementType, targetElementType);
}

View File

@@ -43,11 +43,7 @@ final class CollectionToObjectConverter implements ConditionalGenericConverter {
}
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
TypeDescriptor sourceElementType = sourceType.getElementTypeDescriptor();
if (Object.class.equals(sourceElementType.getType())) {
return true;
}
return this.conversionService.canConvert(sourceElementType, targetType);
return this.conversionService.canConvert(sourceType.getElementTypeDescriptor(), targetType);
}
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {

View File

@@ -49,15 +49,6 @@ final class MapToMapConverter implements ConditionalGenericConverter {
}
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
TypeDescriptor sourceKeyType = sourceType.getMapKeyTypeDescriptor();
TypeDescriptor targetKeyType = targetType.getMapKeyTypeDescriptor();
TypeDescriptor sourceValueType = sourceType.getMapValueTypeDescriptor();
TypeDescriptor targetValueType = targetType.getMapValueTypeDescriptor();
if (Object.class.equals(sourceKeyType.getType()) && Object.class.equals(sourceValueType.getType())
|| Object.class.equals(targetKeyType.getType()) && Object.class.equals(targetValueType.getType())) {
// catches the empty map case
return true;
}
return this.conversionService.canConvert(sourceType.getMapKeyTypeDescriptor(), targetType.getMapKeyTypeDescriptor()) &&
this.conversionService.canConvert(sourceType.getMapValueTypeDescriptor(), targetType.getMapValueTypeDescriptor());
}