diff --git a/spring-core/src/main/java/org/springframework/core/convert/TypeDescriptor.java b/spring-core/src/main/java/org/springframework/core/convert/TypeDescriptor.java index 58585070a8..7e119c3454 100644 --- a/spring-core/src/main/java/org/springframework/core/convert/TypeDescriptor.java +++ b/spring-core/src/main/java/org/springframework/core/convert/TypeDescriptor.java @@ -23,6 +23,7 @@ import java.util.HashMap; import java.util.Map; import org.springframework.core.MethodParameter; +import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.ObjectUtils; @@ -249,6 +250,23 @@ public class TypeDescriptor { this.mapKeyTypeDescriptor, this.mapValueTypeDescriptor, this.annotations); } + /** + * Cast this {@link TypeDescriptor} to a superclass or implemented interface + * preserving annotations and nested type context. + * + * @param superType the super type to cast to (can be {@code null} + * @return a new TypeDescriptor for the up-cast type + * @throws IllegalArgumentException if this type is not assignable to the super-type + */ + public TypeDescriptor upcast(Class superType) { + if (superType == null) { + return null; + } + Assert.isAssignable(superType, getType()); + return new TypeDescriptor(superType, this.elementTypeDescriptor, + this.mapKeyTypeDescriptor, this.mapValueTypeDescriptor, this.annotations); + } + /** * Returns the name of this type: the fully qualified class name. */ diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/GenericConversionService.java b/spring-core/src/main/java/org/springframework/core/convert/support/GenericConversionService.java index 5856489988..fa9383e2f2 100644 --- a/spring-core/src/main/java/org/springframework/core/convert/support/GenericConversionService.java +++ b/spring-core/src/main/java/org/springframework/core/convert/support/GenericConversionService.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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. @@ -19,9 +19,8 @@ package org.springframework.core.convert.support; import java.lang.reflect.Array; import java.util.ArrayList; import java.util.Collections; -import java.util.HashMap; import java.util.HashSet; -import java.util.Iterator; +import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; @@ -40,8 +39,11 @@ import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.converter.ConverterFactory; import org.springframework.core.convert.converter.ConverterRegistry; import org.springframework.core.convert.converter.GenericConverter; +import org.springframework.core.convert.converter.GenericConverter.ConvertiblePair; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; +import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; /** * Base {@link ConversionService} implementation suitable for use in most environments. @@ -51,37 +53,24 @@ import org.springframework.util.ClassUtils; * @author Keith Donald * @author Juergen Hoeller * @author Chris Beams + * @author Phillip Webb * @since 3.0 */ public class GenericConversionService implements ConfigurableConversionService { - private static final GenericConverter NO_OP_CONVERTER = new GenericConverter() { - public Set getConvertibleTypes() { - return null; - } - public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { - return source; - } - public String toString() { - return "NO_OP"; - } - }; + /** + * General NO-OP converter used when conversion is not required. + */ + private static final GenericConverter NO_OP_CONVERTER = new NoOpConverter("NO_OP"); - private static final GenericConverter NO_MATCH = new GenericConverter() { - public Set getConvertibleTypes() { - throw new UnsupportedOperationException(); - } - public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { - throw new UnsupportedOperationException(); - } - public String toString() { - return "NO_MATCH"; - } - }; + /** + * Used as a cache entry when no converter is available. This converter is never + * returned. + */ + private static final GenericConverter NO_MATCH = new NoOpConverter("NO_MATCH"); - private final Map, Map, MatchableConverters>> converters = - new HashMap, Map, MatchableConverters>>(36); + private final Converters converters = new Converters(); private final Map converterCache = new ConcurrentHashMap(); @@ -91,10 +80,8 @@ public class GenericConversionService implements ConfigurableConversionService { public void addConverter(Converter converter) { GenericConverter.ConvertiblePair typeInfo = getRequiredTypeInfo(converter, Converter.class); - if (typeInfo == null) { - throw new IllegalArgumentException("Unable to the determine sourceType and targetType which " + + Assert.notNull(typeInfo, "Unable to the determine sourceType and targetType which " + "your Converter converts between; declare these generic types."); - } addConverter(new ConverterAdapter(typeInfo, converter)); } @@ -104,10 +91,7 @@ public class GenericConversionService implements ConfigurableConversionService { } public void addConverter(GenericConverter converter) { - Set convertibleTypes = converter.getConvertibleTypes(); - for (GenericConverter.ConvertiblePair convertibleType : convertibleTypes) { - getMatchableConverters(convertibleType.getSourceType(), convertibleType.getTargetType()).add(converter); - } + this.converters.add(converter); invalidateCache(); } @@ -121,24 +105,19 @@ public class GenericConversionService implements ConfigurableConversionService { } public void removeConvertible(Class sourceType, Class targetType) { - getSourceConverterMap(sourceType).remove(targetType); + this.converters.remove(sourceType, targetType); invalidateCache(); } - // implementing ConversionService public boolean canConvert(Class sourceType, Class targetType) { - if (targetType == null) { - throw new IllegalArgumentException("The targetType to convert to cannot be null"); - } + Assert.notNull(targetType, "The targetType to convert to cannot be null"); return canConvert(sourceType != null ? TypeDescriptor.valueOf(sourceType) : null, TypeDescriptor.valueOf(targetType)); } public boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType) { - if (targetType == null) { - throw new IllegalArgumentException("The targetType to convert to cannot be null"); - } + Assert.notNull(targetType,"The targetType to convert to cannot be null"); if (sourceType == null) { return true; } @@ -148,16 +127,12 @@ public class GenericConversionService implements ConfigurableConversionService { @SuppressWarnings("unchecked") public T convert(Object source, Class targetType) { - if (targetType == null) { - throw new IllegalArgumentException("The targetType to convert to cannot be null"); - } + Assert.notNull(targetType,"The targetType to convert to cannot be null"); return (T) convert(source, TypeDescriptor.forObject(source), TypeDescriptor.valueOf(targetType)); } public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { - if (targetType == null) { - throw new IllegalArgumentException("The targetType to convert to cannot be null"); - } + Assert.notNull(targetType,"The targetType to convert to cannot be null"); if (sourceType == null) { Assert.isTrue(source == null, "The source must be [null] if sourceType == [null]"); return handleResult(sourceType, targetType, convertNullSource(sourceType, targetType)); @@ -171,9 +146,7 @@ public class GenericConversionService implements ConfigurableConversionService { Object result = ConversionUtils.invokeConverter(converter, source, sourceType, targetType); return handleResult(sourceType, targetType, result); } - else { - return handleConverterNotFound(source, sourceType, targetType); - } + return handleConverterNotFound(source, sourceType, targetType); } /** @@ -191,21 +164,7 @@ public class GenericConversionService implements ConfigurableConversionService { } public String toString() { - List converterStrings = new ArrayList(); - for (Map, MatchableConverters> targetConverters : this.converters.values()) { - for (MatchableConverters matchable : targetConverters.values()) { - converterStrings.add(matchable.toString()); - } - } - Collections.sort(converterStrings); - StringBuilder builder = new StringBuilder(); - builder.append("ConversionService converters = ").append("\n"); - for (String converterString : converterStrings) { - builder.append("\t"); - builder.append(converterString); - builder.append("\n"); - } - return builder.toString(); + return this.converters.toString(); } @@ -231,7 +190,7 @@ public class GenericConversionService implements ConfigurableConversionService { * Subclasses may override. * @param sourceType the source type to convert from * @param targetType the target type to convert to - * @return the generic converter that will perform the conversion, or null if no suitable converter was found + * @return the generic converter that will perform the conversion, or {@code null} if no suitable converter was found * @see #getDefaultConverter(TypeDescriptor, TypeDescriptor) */ protected GenericConverter getConverter(TypeDescriptor sourceType, TypeDescriptor targetType) { @@ -240,20 +199,19 @@ public class GenericConversionService implements ConfigurableConversionService { if (converter != null) { return (converter != NO_MATCH ? converter : null); } - else { - converter = findConverterForClassPair(sourceType, targetType); - if (converter == null) { - converter = getDefaultConverter(sourceType, targetType); - } - if (converter != null) { - this.converterCache.put(key, converter); - return converter; - } - else { - this.converterCache.put(key, NO_MATCH); - return null; - } + + converter = this.converters.find(sourceType, targetType); + if (converter == null) { + converter = getDefaultConverter(sourceType, targetType); } + + if (converter != null) { + this.converterCache.put(key, converter); + return converter; + } + + this.converterCache.put(key, NO_MATCH); + return null; } /** @@ -276,204 +234,19 @@ public class GenericConversionService implements ConfigurableConversionService { return (args != null ? new GenericConverter.ConvertiblePair(args[0], args[1]) : null); } - private MatchableConverters getMatchableConverters(Class sourceType, Class targetType) { - Map, MatchableConverters> sourceMap = getSourceConverterMap(sourceType); - MatchableConverters matchable = sourceMap.get(targetType); - if (matchable == null) { - matchable = new MatchableConverters(); - sourceMap.put(targetType, matchable); - } - return matchable; - } - private void invalidateCache() { this.converterCache.clear(); } - private Map, MatchableConverters> getSourceConverterMap(Class sourceType) { - Map, MatchableConverters> sourceMap = converters.get(sourceType); - if (sourceMap == null) { - sourceMap = new HashMap, MatchableConverters>(); - this.converters.put(sourceType, sourceMap); - } - return sourceMap; - } - - private GenericConverter findConverterForClassPair(TypeDescriptor sourceType, TypeDescriptor targetType) { - Class sourceObjectType = sourceType.getObjectType(); - if (sourceObjectType.isInterface()) { - LinkedList> classQueue = new LinkedList>(); - classQueue.addFirst(sourceObjectType); - while (!classQueue.isEmpty()) { - Class currentClass = classQueue.removeLast(); - Map, MatchableConverters> converters = getTargetConvertersForSource(currentClass); - GenericConverter converter = getMatchingConverterForTarget(sourceType, targetType, converters); - if (converter != null) { - return converter; - } - Class[] interfaces = currentClass.getInterfaces(); - for (Class ifc : interfaces) { - classQueue.addFirst(ifc); - } - } - Map, MatchableConverters> objectConverters = getTargetConvertersForSource(Object.class); - return getMatchingConverterForTarget(sourceType, targetType, objectConverters); - } - else if (sourceObjectType.isArray()) { - LinkedList> classQueue = new LinkedList>(); - classQueue.addFirst(sourceObjectType); - while (!classQueue.isEmpty()) { - Class currentClass = classQueue.removeLast(); - Map, MatchableConverters> converters = getTargetConvertersForSource(currentClass); - GenericConverter converter = getMatchingConverterForTarget(sourceType, targetType, converters); - if (converter != null) { - return converter; - } - Class componentType = ClassUtils.resolvePrimitiveIfNecessary(currentClass.getComponentType()); - if (componentType.getSuperclass() != null) { - classQueue.addFirst(Array.newInstance(componentType.getSuperclass(), 0).getClass()); - } - else if (componentType.isInterface()) { - classQueue.addFirst(Object[].class); - } - } - return null; - } - else { - HashSet> interfaces = new LinkedHashSet>(); - LinkedList> classQueue = new LinkedList>(); - classQueue.addFirst(sourceObjectType); - while (!classQueue.isEmpty()) { - Class currentClass = classQueue.removeLast(); - Map, 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) { - Map, MatchableConverters> converters = getTargetConvertersForSource(interfaceType); - GenericConverter converter = getMatchingConverterForTarget(sourceType, targetType, converters); - if (converter != null) { - return converter; - } - } - Map, MatchableConverters> objectConverters = getTargetConvertersForSource(Object.class); - return getMatchingConverterForTarget(sourceType, targetType, objectConverters); - } - } - - private Map, MatchableConverters> getTargetConvertersForSource(Class sourceType) { - Map, MatchableConverters> converters = this.converters.get(sourceType); - if (converters == null) { - converters = Collections.emptyMap(); - } - return converters; - } - - private GenericConverter getMatchingConverterForTarget(TypeDescriptor sourceType, TypeDescriptor targetType, - Map, MatchableConverters> converters) { - Class targetObjectType = targetType.getObjectType(); - if (targetObjectType.isInterface()) { - LinkedList> classQueue = new LinkedList>(); - classQueue.addFirst(targetObjectType); - while (!classQueue.isEmpty()) { - Class currentClass = classQueue.removeLast(); - MatchableConverters matchable = converters.get(currentClass); - GenericConverter converter = matchConverter(matchable, sourceType, targetType); - if (converter != null) { - return converter; - } - Class[] interfaces = currentClass.getInterfaces(); - for (Class ifc : interfaces) { - classQueue.addFirst(ifc); - } - } - return matchConverter(converters.get(Object.class), sourceType, targetType); - } - else if (targetObjectType.isArray()) { - LinkedList> classQueue = new LinkedList>(); - classQueue.addFirst(targetObjectType); - while (!classQueue.isEmpty()) { - Class currentClass = classQueue.removeLast(); - MatchableConverters matchable = converters.get(currentClass); - GenericConverter converter = matchConverter(matchable, sourceType, targetType); - if (converter != null) { - return converter; - } - Class componentType = ClassUtils.resolvePrimitiveIfNecessary(currentClass.getComponentType()); - if (componentType.getSuperclass() != null) { - classQueue.addFirst(Array.newInstance(componentType.getSuperclass(), 0).getClass()); - } - else if (componentType.isInterface()) { - classQueue.addFirst(Object[].class); - } - } - return null; - } - else { - Set> interfaces = new LinkedHashSet>(); - LinkedList> classQueue = new LinkedList>(); - classQueue.addFirst(targetObjectType); - while (!classQueue.isEmpty()) { - Class currentClass = classQueue.removeLast(); - 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) { - MatchableConverters matchable = converters.get(interfaceType); - GenericConverter converter = matchConverter(matchable, sourceType, targetType); - if (converter != null) { - return converter; - } - } - return matchConverter(converters.get(Object.class), sourceType, targetType); - } - } - - private void addInterfaceHierarchy(Class interfaceType, Set> interfaces) { - interfaces.add(interfaceType); - for (Class inheritedInterface : interfaceType.getInterfaces()) { - addInterfaceHierarchy(inheritedInterface, interfaces); - } - } - - private GenericConverter matchConverter( - MatchableConverters matchable, TypeDescriptor sourceFieldType, TypeDescriptor targetFieldType) { - if (matchable == null) { - return null; - } - return matchable.matchConverter(sourceFieldType, targetFieldType); - } - private Object handleConverterNotFound(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { if (source == null) { assertNotPrimitiveTargetType(sourceType, targetType); return source; } - else if (sourceType.isAssignableTo(targetType) && targetType.getObjectType().isInstance(source)) { + if (sourceType.isAssignableTo(targetType) && targetType.getObjectType().isInstance(source)) { return source; } - else { - throw new ConverterNotFoundException(sourceType, targetType); - } + throw new ConverterNotFoundException(sourceType, targetType); } private Object handleResult(TypeDescriptor sourceType, TypeDescriptor targetType, Object result) { @@ -482,6 +255,7 @@ public class GenericConversionService implements ConfigurableConversionService { } return result; } + private void assertNotPrimitiveTargetType(TypeDescriptor sourceType, TypeDescriptor targetType) { if (targetType.isPrimitive()) { throw new ConversionFailedException(sourceType, targetType, null, @@ -490,24 +264,32 @@ public class GenericConversionService implements ConfigurableConversionService { } + /** + * Adapts a {@link Converter} to a {@link GenericConverter}. + */ @SuppressWarnings("unchecked") - private final class ConverterAdapter implements GenericConverter { + private final class ConverterAdapter implements ConditionalGenericConverter { private final ConvertiblePair typeInfo; private final Converter converter; + public ConverterAdapter(ConvertiblePair typeInfo, Converter converter) { this.converter = (Converter) converter; this.typeInfo = typeInfo; } + public Set getConvertibleTypes() { return Collections.singleton(this.typeInfo); } - public boolean matchesTargetType(TypeDescriptor targetType) { - return this.typeInfo.getTargetType().equals(targetType.getObjectType()); + public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) { + if(!this.typeInfo.getTargetType().equals(targetType.getObjectType())) { + return false; + } + return true; } public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { @@ -524,6 +306,9 @@ public class GenericConversionService implements ConfigurableConversionService { } + /** + * Adapts a {@link ConverterFactory} to a {@link GenericConverter}. + */ @SuppressWarnings("unchecked") private final class ConverterFactoryAdapter implements GenericConverter { @@ -531,11 +316,13 @@ public class GenericConversionService implements ConfigurableConversionService { private final ConverterFactory converterFactory; + public ConverterFactoryAdapter(ConvertiblePair typeInfo, ConverterFactory converterFactory) { this.converterFactory = (ConverterFactory) converterFactory; this.typeInfo = typeInfo; } + public Set getConvertibleTypes() { return Collections.singleton(this.typeInfo); } @@ -554,73 +341,22 @@ public class GenericConversionService implements ConfigurableConversionService { } - private static class MatchableConverters { - - private LinkedList conditionalConverters; - - private GenericConverter defaultConverter; - - public void add(GenericConverter converter) { - if (converter instanceof ConditionalGenericConverter) { - if (this.conditionalConverters == null) { - this.conditionalConverters = new LinkedList(); - } - this.conditionalConverters.addFirst((ConditionalGenericConverter) converter); - } - else { - this.defaultConverter = converter; - } - } - - public GenericConverter matchConverter(TypeDescriptor sourceType, TypeDescriptor targetType) { - if (this.conditionalConverters != null) { - for (ConditionalGenericConverter conditional : this.conditionalConverters) { - if (conditional.matches(sourceType, targetType)) { - return conditional; - } - } - } - if (this.defaultConverter instanceof ConverterAdapter) { - ConverterAdapter adapter = (ConverterAdapter) this.defaultConverter; - if (!adapter.matchesTargetType(targetType)) { - return null; - } - } - return this.defaultConverter; - } - - public String toString() { - if (this.conditionalConverters != null) { - StringBuilder builder = new StringBuilder(); - for (Iterator it = this.conditionalConverters.iterator(); it.hasNext();) { - builder.append(it.next()); - if (it.hasNext()) { - builder.append(", "); - } - } - if (this.defaultConverter != null) { - builder.append(", ").append(this.defaultConverter); - } - return builder.toString(); - } - else { - return this.defaultConverter.toString(); - } - } - } - - + /** + * Key for use with the converter cache. + */ private static final class ConverterCacheKey { private final TypeDescriptor sourceType; private final TypeDescriptor targetType; + public ConverterCacheKey(TypeDescriptor sourceType, TypeDescriptor targetType) { this.sourceType = sourceType; this.targetType = targetType; } + public boolean equals(Object other) { if (this == other) { return true; @@ -629,16 +365,221 @@ public class GenericConversionService implements ConfigurableConversionService { return false; } ConverterCacheKey otherKey = (ConverterCacheKey) other; - return this.sourceType.equals(otherKey.sourceType) && this.targetType.equals(otherKey.targetType); + return ObjectUtils.nullSafeEquals(this.sourceType, otherKey.sourceType) + && ObjectUtils.nullSafeEquals(this.targetType, otherKey.targetType); } public int hashCode() { - return this.sourceType.hashCode() * 29 + this.targetType.hashCode(); + return ObjectUtils.nullSafeHashCode(this.sourceType) * 29 + + ObjectUtils.nullSafeHashCode(this.targetType); } public String toString() { - return "ConverterCacheKey [sourceType = " + this.sourceType + ", targetType = " + this.targetType + "]"; + return "ConverterCacheKey [sourceType = " + this.sourceType + + ", targetType = " + this.targetType + "]"; } } + /** + * Manages all converters registered with the service. + */ + private static class Converters { + + private static final Set> IGNORED_CLASSES; + static { + Set> ignored = new HashSet>(); + ignored.add(Object.class); + ignored.add(Object[].class); + IGNORED_CLASSES = Collections.unmodifiableSet(ignored); + } + + private final Map converters = + new LinkedHashMap(36); + + public void add(GenericConverter converter) { + Set convertibleTypes = converter.getConvertibleTypes(); + Assert.state(converter.getConvertibleTypes() != null, "Converter does not specifiy ConvertibleTypes"); + for (ConvertiblePair convertiblePair : convertibleTypes) { + ConvertersForPair convertersForPair = getMatchableConverters(convertiblePair); + convertersForPair.add(converter); + } + } + + private ConvertersForPair getMatchableConverters(ConvertiblePair convertiblePair) { + ConvertersForPair convertersForPair = this.converters.get(convertiblePair); + if (convertersForPair == null) { + convertersForPair = new ConvertersForPair(); + this.converters.put(convertiblePair, convertersForPair); + } + return convertersForPair; + } + + public void remove(Class sourceType, Class targetType) { + converters.remove(new ConvertiblePair(sourceType, targetType)); + } + + /** + * Find a {@link GenericConverter} given a source and target type. This method will + * attempt to match all possible converters by working though the class and interface + * hierarchy of the types. + * @param sourceType the source type + * @param targetType the target type + * @return a {@link GenericConverter} or null + * @see #getTypeHierarchy(Class) + */ + public GenericConverter find(TypeDescriptor sourceType, TypeDescriptor targetType) { + // Search the full type hierarchy + List sourceCandidates = getTypeHierarchy(sourceType); + List targetCandidates = getTypeHierarchy(targetType); + for (TypeDescriptor sourceCandidate : sourceCandidates) { + for (TypeDescriptor targetCandidate : targetCandidates) { + GenericConverter converter = getRegisteredConverter(sourceType, targetType, sourceCandidate, targetCandidate); + if(converter != null) { + return converter; + } + } + } + return null; + } + + private GenericConverter getRegisteredConverter(TypeDescriptor sourceType, TypeDescriptor targetType, + TypeDescriptor sourceCandidate, TypeDescriptor targetCandidate) { + + // Check specifically registered converters + ConvertersForPair convertersForPair = converters.get(new ConvertiblePair( + sourceCandidate.getType(), targetCandidate.getType())); + GenericConverter converter = convertersForPair == null ? null + : convertersForPair.getConverter(sourceType, targetType); + if (converter != null) { + return converter; + } + + return null; + } + + /** + * Returns an ordered class hierarchy for the given type. + * @param type the type + * @return an ordered list of all classes that the given type extends or + * implements. + */ + private List getTypeHierarchy(TypeDescriptor type) { + if(type.isPrimitive()) { + type = TypeDescriptor.valueOf(type.getObjectType()); + } + Set typeHierarchy = new LinkedHashSet(); + collectTypeHierarchy(typeHierarchy, type); + if(type.isArray()) { + typeHierarchy.add(TypeDescriptor.valueOf(Object[].class)); + } + typeHierarchy.add(TypeDescriptor.valueOf(Object.class)); + return new ArrayList(typeHierarchy); + } + + private void collectTypeHierarchy(Set typeHierarchy, + TypeDescriptor type) { + if(type != null && !IGNORED_CLASSES.contains(type.getType())) { + if(typeHierarchy.add(type)) { + Class superclass = type.getType().getSuperclass(); + if (type.isArray()) { + superclass = ClassUtils.resolvePrimitiveIfNecessary(superclass); + } + collectTypeHierarchy(typeHierarchy, createRelated(type, superclass)); + + for (Class implementsInterface : type.getType().getInterfaces()) { + collectTypeHierarchy(typeHierarchy, createRelated(type, implementsInterface)); + } + } + } + } + + private TypeDescriptor createRelated(TypeDescriptor type, Class relatedType) { + if (relatedType == null && type.isArray()) { + relatedType = Array.newInstance(relatedType, 0).getClass(); + } + if(!type.getType().equals(relatedType)) { + return type.upcast(relatedType); + } + return null; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("ConversionService converters = ").append("\n"); + for (String converterString : getConverterStrings()) { + builder.append("\t"); + builder.append(converterString); + builder.append("\n"); + } + return builder.toString(); + } + + private List getConverterStrings() { + List converterStrings = new ArrayList(); + for (ConvertersForPair convertersForPair : converters.values()) { + converterStrings.add(convertersForPair.toString()); + } + Collections.sort(converterStrings); + return converterStrings; + } + } + + + /** + * Manages converters registered with a specific {@link ConvertiblePair}. + */ + private static class ConvertersForPair { + + private final LinkedList converters = new LinkedList(); + + public void add(GenericConverter converter) { + this.converters.addFirst(converter); + } + + public GenericConverter getConverter(TypeDescriptor sourceType, + TypeDescriptor targetType) { + for (GenericConverter converter : this.converters) { + if (!(converter instanceof ConditionalGenericConverter) + || ((ConditionalGenericConverter) converter).matches(sourceType, + targetType)) { + return converter; + } + } + return null; + } + + public String toString() { + return StringUtils.collectionToCommaDelimitedString(this.converters); + } + } + + + /** + * Internal converter that performs no operation. + */ + private static class NoOpConverter implements GenericConverter { + + private String name; + + + public NoOpConverter(String name) { + this.name = name; + } + + + public Set getConvertibleTypes() { + return null; + } + + public Object convert(Object source, TypeDescriptor sourceType, + TypeDescriptor targetType) { + return source; + } + + @Override + public String toString() { + return name; + } + } } diff --git a/spring-core/src/test/java/org/springframework/core/convert/TypeDescriptorTests.java b/spring-core/src/test/java/org/springframework/core/convert/TypeDescriptorTests.java index 20cefeeec3..e16a332426 100644 --- a/spring-core/src/test/java/org/springframework/core/convert/TypeDescriptorTests.java +++ b/spring-core/src/test/java/org/springframework/core/convert/TypeDescriptorTests.java @@ -16,6 +16,13 @@ package org.springframework.core.convert; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -28,15 +35,8 @@ import java.util.List; import java.util.Map; import org.junit.Test; - import org.springframework.core.MethodParameter; -import static junit.framework.Assert.assertEquals; -import static junit.framework.Assert.assertTrue; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; - /** * @author Keith Donald * @author Andy Clement @@ -801,4 +801,26 @@ public class TypeDescriptorTests { public Map isAssignableMapKeyValueTypes; + @Test + public void testUpCast() throws Exception { + Property property = new Property(getClass(), getClass().getMethod("getProperty"), + getClass().getMethod("setProperty", Map.class)); + TypeDescriptor typeDescriptor = new TypeDescriptor(property); + TypeDescriptor upCast = typeDescriptor.upcast(Object.class); + assertTrue(upCast.getAnnotation(MethodAnnotation1.class) != null); + } + + @Test + public void testUpCastNotSuper() throws Exception { + Property property = new Property(getClass(), getClass().getMethod("getProperty"), + getClass().getMethod("setProperty", Map.class)); + TypeDescriptor typeDescriptor = new TypeDescriptor(property); + try { + typeDescriptor.upcast(Collection.class); + fail("Did not throw"); + } catch(IllegalArgumentException e) { + assertEquals("interface java.util.Map is not assignable to interface java.util.Collection", e.getMessage()); + } + } + }