Revised converter search algorithm to favor super classes before interface hierarchy

This commit is contained in:
Keith Donald 2011-05-24 22:20:54 +00:00
parent ad93d20a6c
commit f43d0e1003
4 changed files with 225 additions and 67 deletions

View File

@ -20,8 +20,10 @@ import java.lang.annotation.Annotation;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
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 org.springframework.core.GenericCollectionTypeResolver; import org.springframework.core.GenericCollectionTypeResolver;
import org.springframework.core.MethodParameter; import org.springframework.core.MethodParameter;
@ -127,8 +129,8 @@ public class TypeDescriptor {
* Use this factory method to introspect a source object's type before asking the conversion system to convert it to some another type. * Use this factory method to introspect a source object's type before asking the conversion system to convert it to some another type.
* Builds in population of nested type descriptors for collection and map objects through object introspection. * Builds in population of nested type descriptors for collection and map objects through object introspection.
* If the object is null, returns {@link TypeDescriptor#NULL}. * If the object is null, returns {@link TypeDescriptor#NULL}.
* If the object is not a collection, simply calls {@link #valueOf(Class)}. * If the object is not a collection or map, simply calls {@link #valueOf(Class)}.
* If the object is a collection, this factory method will derive the element type(s) by introspecting the collection. * If the object is a collection or map, this factory method will derive the element type(s) by introspecting the collection or map.
* @param object the source object * @param object the source object
* @return the type descriptor * @return the type descriptor
* @see ConversionService#convert(Object, Class) * @see ConversionService#convert(Object, Class)
@ -331,7 +333,7 @@ public class TypeDescriptor {
} }
return this.mapValueType; return this.mapValueType;
} }
// special case public operations // special case public operations
/** /**
@ -536,6 +538,7 @@ public class TypeDescriptor {
} }
private static Class<?> commonType(Class<?> commonType, Class<?> valueClass) { private static Class<?> commonType(Class<?> commonType, Class<?> valueClass) {
Set<Class<?>> interfaces = new LinkedHashSet<Class<?>>();
LinkedList<Class<?>> classQueue = new LinkedList<Class<?>>(); LinkedList<Class<?>> classQueue = new LinkedList<Class<?>>();
classQueue.addFirst(commonType); classQueue.addFirst(commonType);
while (!classQueue.isEmpty()) { while (!classQueue.isEmpty()) {
@ -543,21 +546,26 @@ public class TypeDescriptor {
if (currentClass.isAssignableFrom(valueClass)) { if (currentClass.isAssignableFrom(valueClass)) {
return currentClass; return currentClass;
} }
Class<?>[] interfaces = currentClass.getInterfaces(); Class<?> superClass = currentClass.getSuperclass();
for (Class<?> ifc : interfaces) { if (superClass != null && superClass != Object.class) {
addInterfaceHierarchy(ifc, classQueue);
}
if (currentClass.getSuperclass() != null) {
classQueue.addFirst(currentClass.getSuperclass()); classQueue.addFirst(currentClass.getSuperclass());
} }
for (Class<?> interfaceType : currentClass.getInterfaces()) {
addInterfaceHierarchy(interfaceType, interfaces);
}
} }
throw new IllegalStateException("Should never be invoked"); for (Class<?> interfaceType : interfaces) {
if (interfaceType.isAssignableFrom(valueClass)) {
return interfaceType;
}
}
return Object.class;
} }
private static void addInterfaceHierarchy(Class<?> ifc, LinkedList<Class<?>> classQueue) { private static void addInterfaceHierarchy(Class<?> interfaceType, Set<Class<?>> interfaces) {
classQueue.addFirst(ifc); interfaces.add(interfaceType);
for (Class<?> inheritedIfc : ifc.getInterfaces()) { for (Class<?> inheritedInterface : interfaceType.getInterfaces()) {
addInterfaceHierarchy(inheritedIfc, classQueue); addInterfaceHierarchy(inheritedInterface, interfaces);
} }
} }
@ -577,13 +585,13 @@ public class TypeDescriptor {
this.fieldNestingLevel = nestingLevel; this.fieldNestingLevel = nestingLevel;
} }
public TypeDescriptor(Class<?> mapType, CommonElement commonKey, CommonElement commonValue) { private TypeDescriptor(Class<?> mapType, CommonElement commonKey, CommonElement commonValue) {
this.type = mapType; this.type = mapType;
this.mapKeyType = applyCommonElement(commonKey); this.mapKeyType = applyCommonElement(commonKey);
this.mapValueType = applyCommonElement(commonValue); this.mapValueType = applyCommonElement(commonValue);
} }
public TypeDescriptor(Class<?> collectionType, CommonElement commonElement) { private TypeDescriptor(Class<?> collectionType, CommonElement commonElement) {
this.type = collectionType; this.type = collectionType;
this.elementType = applyCommonElement(commonElement); this.elementType = applyCommonElement(commonElement);
} }
@ -633,5 +641,5 @@ public class TypeDescriptor {
} }
} }
} }

View File

@ -20,7 +20,9 @@ import java.lang.reflect.Array;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -29,7 +31,6 @@ import java.util.concurrent.ConcurrentHashMap;
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.GenericTypeResolver; import org.springframework.core.GenericTypeResolver;
import org.springframework.core.convert.ConversionFailedException; import org.springframework.core.convert.ConversionFailedException;
import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.ConversionService;
@ -334,7 +335,7 @@ public class GenericConversionService implements ConversionService, ConverterReg
while (!classQueue.isEmpty()) { while (!classQueue.isEmpty()) {
Class<?> currentClass = classQueue.removeLast(); Class<?> currentClass = classQueue.removeLast();
if (logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
logger.trace("Searching for converters indexed by sourceType [" + currentClass.getName() + "]"); logger.trace("Searching for converters indexed by source interface [" + currentClass.getName() + "]");
} }
Map<Class<?>, MatchableConverters> converters = getTargetConvertersForSource(currentClass); Map<Class<?>, MatchableConverters> converters = getTargetConvertersForSource(currentClass);
GenericConverter converter = getMatchingConverterForTarget(sourceType, targetType, converters); GenericConverter converter = getMatchingConverterForTarget(sourceType, targetType, converters);
@ -349,40 +350,62 @@ public class GenericConversionService implements ConversionService, ConverterReg
Map<Class<?>, MatchableConverters> objectConverters = getTargetConvertersForSource(Object.class); Map<Class<?>, MatchableConverters> objectConverters = getTargetConvertersForSource(Object.class);
return getMatchingConverterForTarget(sourceType, targetType, objectConverters); return getMatchingConverterForTarget(sourceType, targetType, objectConverters);
} }
else { else if (sourceObjectType.isArray()) {
LinkedList<Class<?>> classQueue = new LinkedList<Class<?>>(); LinkedList<Class<?>> classQueue = new LinkedList<Class<?>>();
classQueue.addFirst(sourceObjectType); classQueue.addFirst(sourceObjectType);
LinkedList<Class<?>> attemptedInterfaces = new LinkedList<Class<?>>();
while (!classQueue.isEmpty()) { while (!classQueue.isEmpty()) {
Class<?> currentClass = classQueue.removeLast(); Class<?> currentClass = classQueue.removeLast();
if (logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
logger.trace("Searching for converters indexed by sourceType [" + currentClass.getName() + "]"); logger.trace("Searching for converters indexed by source array type [" + currentClass.getName() + "]");
} }
Map<Class<?>, MatchableConverters> converters = getTargetConvertersForSource(currentClass); Map<Class<?>, MatchableConverters> converters = getTargetConvertersForSource(currentClass);
GenericConverter converter = getMatchingConverterForTarget(sourceType, targetType, converters); GenericConverter converter = getMatchingConverterForTarget(sourceType, targetType, converters);
if (converter != null) { if (converter != null) {
return converter; return converter;
} }
if (currentClass.isArray()) { Class<?> componentType = ClassUtils.resolvePrimitiveIfNecessary(currentClass.getComponentType());
Class<?> componentType = ClassUtils.resolvePrimitiveIfNecessary(currentClass.getComponentType()); if (componentType.getSuperclass() != null) {
if (componentType.getSuperclass() != null) { classQueue.addFirst(Array.newInstance(componentType.getSuperclass(), 0).getClass());
classQueue.addFirst(Array.newInstance(componentType.getSuperclass(), 0).getClass());
}
else if (componentType.isInterface()) {
classQueue.addFirst(Object[].class);
}
} }
else { else if (componentType.isInterface()) {
Class<?>[] interfaces = currentClass.getInterfaces(); classQueue.addFirst(Object[].class);
for (Class<?> ifc : interfaces) {
addInterfaceHierarchy(ifc, classQueue, attemptedInterfaces);
}
if (currentClass.getSuperclass() != null) {
classQueue.addFirst(currentClass.getSuperclass());
}
} }
} }
return null; return null;
} else {
HashSet<Class<?>> interfaces = new LinkedHashSet<Class<?>>();
LinkedList<Class<?>> classQueue = new LinkedList<Class<?>>();
classQueue.addFirst(sourceObjectType);
while (!classQueue.isEmpty()) {
Class<?> currentClass = classQueue.removeLast();
if (logger.isTraceEnabled()) {
logger.trace("Searching for converters indexed by source class [" + currentClass.getName() + "]");
}
Map<Class<?>, MatchableConverters> converters = getTargetConvertersForSource(currentClass);
GenericConverter converter = getMatchingConverterForTarget(sourceType, targetType, converters);
if (converter != null) {
return converter;
}
Class<?> superClass = currentClass.getSuperclass();
if (superClass != null && superClass != Object.class) {
classQueue.addFirst(superClass);
}
for (Class<?> interfaceType : currentClass.getInterfaces()) {
addInterfaceHierarchy(interfaceType, interfaces);
}
}
for (Class<?> interfaceType : interfaces) {
if (logger.isTraceEnabled()) {
logger.trace("Searching for converters indexed by source interface [" + interfaceType.getName() + "]");
}
Map<Class<?>, MatchableConverters> converters = getTargetConvertersForSource(interfaceType);
GenericConverter converter = getMatchingConverterForTarget(sourceType, targetType, converters);
if (converter != null) {
return converter;
}
}
Map<Class<?>, MatchableConverters> objectConverters = getTargetConvertersForSource(Object.class);
return getMatchingConverterForTarget(sourceType, targetType, objectConverters);
} }
} }
@ -403,7 +426,7 @@ public class GenericConversionService implements ConversionService, ConverterReg
while (!classQueue.isEmpty()) { while (!classQueue.isEmpty()) {
Class<?> currentClass = classQueue.removeLast(); Class<?> currentClass = classQueue.removeLast();
if (logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
logger.trace("and indexed by targetType [" + currentClass.getName() + "]"); logger.trace("and indexed by target class [" + currentClass.getName() + "]");
} }
MatchableConverters matchable = converters.get(currentClass); MatchableConverters matchable = converters.get(currentClass);
GenericConverter converter = matchConverter(matchable, sourceType, targetType); GenericConverter converter = matchConverter(matchable, sourceType, targetType);
@ -419,51 +442,72 @@ public class GenericConversionService implements ConversionService, ConverterReg
logger.trace("and indexed by [java.lang.Object]"); logger.trace("and indexed by [java.lang.Object]");
} }
return matchConverter(converters.get(Object.class), sourceType, targetType); return matchConverter(converters.get(Object.class), sourceType, targetType);
} } else if (targetObjectType.isArray()) {
else {
LinkedList<Class<?>> classQueue = new LinkedList<Class<?>>(); LinkedList<Class<?>> classQueue = new LinkedList<Class<?>>();
classQueue.addFirst(targetObjectType); classQueue.addFirst(targetObjectType);
LinkedList<Class<?>> attemptedInterfaces = new LinkedList<Class<?>>();
while (!classQueue.isEmpty()) { while (!classQueue.isEmpty()) {
Class<?> currentClass = classQueue.removeLast(); Class<?> currentClass = classQueue.removeLast();
if (logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
logger.trace("and indexed by targetType [" + currentClass.getName() + "]"); logger.trace("and indexed by target array type [" + currentClass.getName() + "]");
} }
MatchableConverters matchable = converters.get(currentClass); MatchableConverters matchable = converters.get(currentClass);
GenericConverter converter = matchConverter(matchable, sourceType, targetType); GenericConverter converter = matchConverter(matchable, sourceType, targetType);
if (converter != null) { if (converter != null) {
return converter; return converter;
} }
if (currentClass.isArray()) { Class<?> componentType = ClassUtils.resolvePrimitiveIfNecessary(currentClass.getComponentType());
Class<?> componentType = ClassUtils.resolvePrimitiveIfNecessary(currentClass.getComponentType()); if (componentType.getSuperclass() != null) {
if (componentType.getSuperclass() != null) { classQueue.addFirst(Array.newInstance(componentType.getSuperclass(), 0).getClass());
classQueue.addFirst(Array.newInstance(componentType.getSuperclass(), 0).getClass());
}
else if (componentType.isInterface()) {
classQueue.addFirst(Object[].class);
}
} }
else { else if (componentType.isInterface()) {
Class<?>[] interfaces = currentClass.getInterfaces(); classQueue.addFirst(Object[].class);
for (Class<?> ifc : interfaces) {
addInterfaceHierarchy(ifc, classQueue, attemptedInterfaces);
}
if (currentClass.getSuperclass() != null) {
classQueue.addFirst(currentClass.getSuperclass());
}
} }
} }
return null; return null;
} }
else {
Set<Class<?>> interfaces = new LinkedHashSet<Class<?>>();
LinkedList<Class<?>> classQueue = new LinkedList<Class<?>>();
classQueue.addFirst(targetObjectType);
while (!classQueue.isEmpty()) {
Class<?> currentClass = classQueue.removeLast();
if (logger.isTraceEnabled()) {
logger.trace("and indexed by target class [" + currentClass.getName() + "]");
}
MatchableConverters matchable = converters.get(currentClass);
GenericConverter converter = matchConverter(matchable, sourceType, targetType);
if (converter != null) {
return converter;
}
Class<?> superClass = currentClass.getSuperclass();
if (superClass != null && superClass != Object.class) {
classQueue.addFirst(superClass);
}
for (Class<?> interfaceType : currentClass.getInterfaces()) {
addInterfaceHierarchy(interfaceType, interfaces);
}
}
for (Class<?> interfaceType : interfaces) {
if (logger.isTraceEnabled()) {
logger.trace("and indexed by target interface [" + interfaceType.getName() + "]");
}
MatchableConverters matchable = converters.get(interfaceType);
GenericConverter converter = matchConverter(matchable, sourceType, targetType);
if (converter != null) {
return converter;
}
}
if (logger.isTraceEnabled()) {
logger.trace("and indexed by [java.lang.Object]");
}
return matchConverter(converters.get(Object.class), sourceType, targetType);
}
} }
private void addInterfaceHierarchy(Class<?> interfaceType, LinkedList<Class<?>> classQueue, LinkedList<Class<?>> attemptedInterfaces) { private void addInterfaceHierarchy(Class<?> interfaceType, Set<Class<?>> interfaces) {
if (!attemptedInterfaces.contains(interfaceType)) { interfaces.add(interfaceType);
classQueue.addFirst(interfaceType); for (Class<?> inheritedInterface : interfaceType.getInterfaces()) {
attemptedInterfaces.add(interfaceType); addInterfaceHierarchy(inheritedInterface, interfaces);
for (Class<?> inheritedInterface : interfaceType.getInterfaces()) {
addInterfaceHierarchy(inheritedInterface, classQueue, attemptedInterfaces);
}
} }
} }

View File

@ -19,8 +19,10 @@ 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.assertNull;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@ -49,6 +51,110 @@ public class TypeDescriptorTests {
public Map<String, Integer> mapField = new HashMap<String, Integer>(); public Map<String, Integer> mapField = new HashMap<String, Integer>();
public Map<String, List<Integer>> nestedMapField = new HashMap<String, List<Integer>>(); public Map<String, List<Integer>> nestedMapField = new HashMap<String, List<Integer>>();
@Test
public void forCollection() {
List<String> list = new ArrayList<String>();
list.add("1");
TypeDescriptor desc = TypeDescriptor.forObject(list);
assertEquals(String.class, desc.getElementType());
}
@Test
public void forCollectionEmpty() {
List<String> list = new ArrayList<String>();
TypeDescriptor desc = TypeDescriptor.forObject(list);
assertNull(desc.getElementType());
}
@Test
public void forCollectionSuperClassCommonType() throws SecurityException, NoSuchFieldException {
List<Number> list = new ArrayList<Number>();
list.add(1);
list.add(2L);
TypeDescriptor desc = TypeDescriptor.forObject(list);
assertEquals(Number.class, desc.getElementType());
}
public List<Long> longs;
@Test
public void forCollectionNoObviousCommonType() {
List<Object> collection = new ArrayList<Object>();
List<String> list = new ArrayList<String>();
list.add("1");
collection.add(list);
Map<String, String> map = new HashMap<String, String>();
collection.add(map);
map.put("1", "2");
TypeDescriptor desc = TypeDescriptor.forObject(collection);
assertEquals(Cloneable.class, desc.getElementType());
}
@Test
public void forCollectionNoCommonType() {
List<Object> collection = new ArrayList<Object>();
collection.add(new Object());
collection.add("1");
TypeDescriptor desc = TypeDescriptor.forObject(collection);
assertEquals(Object.class, desc.getElementType());
}
@Test
public void forCollectionNested() {
List<Object> collection = new ArrayList<Object>();
collection.add(Arrays.asList("1", "2"));
collection.add(Arrays.asList("3", "4"));
TypeDescriptor desc = TypeDescriptor.forObject(collection);
assertEquals(Arrays.asList("foo").getClass(), desc.getElementType());
assertEquals(String.class, desc.getElementTypeDescriptor().getElementType());
}
@Test
public void forMap() {
Map<String, String> map = new HashMap<String, String>();
map.put("1", "2");
TypeDescriptor desc = TypeDescriptor.forObject(map);
assertEquals(String.class, desc.getMapKeyType());
assertEquals(String.class, desc.getMapValueType());
}
@Test
public void forMapEmpty() {
Map<String, String> map = new HashMap<String, String>();
TypeDescriptor desc = TypeDescriptor.forObject(map);
assertNull(desc.getMapKeyType());
assertNull(desc.getMapValueType());
}
@Test
public void forMapCommonSuperClass() {
Map<Number, Number> map = new HashMap<Number, Number>();
map.put(1, 2);
map.put(2L, 3L);
TypeDescriptor desc = TypeDescriptor.forObject(map);
assertEquals(Number.class, desc.getMapKeyType());
assertEquals(Number.class, desc.getMapValueType());
}
@Test
public void forMapNoObviousCommonType() {
Map<Object, Object> map = new HashMap<Object, Object>();
map.put("1", "2");
map.put(2, 2);
TypeDescriptor desc = TypeDescriptor.forObject(map);
assertEquals(Comparable.class, desc.getMapKeyType());
assertEquals(Comparable.class, desc.getMapValueType());
}
@Test
public void forMapNested() {
Map<Integer, List<String>> map = new HashMap<Integer, List<String>>();
map.put(1, Arrays.asList("1, 2"));
TypeDescriptor desc = TypeDescriptor.forObject(map);
assertEquals(Integer.class, desc.getMapKeyType());
assertEquals(String.class, desc.getMapValueTypeDescriptor().getElementType());
}
@Test @Test
public void listDescriptor() throws Exception { public void listDescriptor() throws Exception {

View File

@ -48,7 +48,7 @@ public class CollectionToCollectionConverterTests {
assertEquals((Integer) 37, result.get(1)); assertEquals((Integer) 37, result.get(1));
} }
public List<Integer> scalarListTarget; public ArrayList<Integer> scalarListTarget;
@Test @Test
public void emptyListToList() throws Exception { public void emptyListToList() throws Exception {