Refactor GenericConversionService

Refactor internal workings of GenericConversionService in order to
better support future enhancements. This commit should not affect
existing behavior.

Issue: SPR-9927
This commit is contained in:
Phillip Webb
2012-10-26 17:10:42 -07:00
committed by Chris Beams
parent 9bfe675761
commit 4dc289592d
3 changed files with 317 additions and 336 deletions

View File

@@ -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.
*/

View File

@@ -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<ConvertiblePair> 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<ConvertiblePair> 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<Class<?>, Map<Class<?>, MatchableConverters>> converters =
new HashMap<Class<?>, Map<Class<?>, MatchableConverters>>(36);
private final Converters converters = new Converters();
private final Map<ConverterCacheKey, GenericConverter> converterCache =
new ConcurrentHashMap<ConverterCacheKey, GenericConverter>();
@@ -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 <S> and targetType <T> which " +
Assert.notNull(typeInfo, "Unable to the determine sourceType <S> and targetType <T> which " +
"your Converter<S, T> 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<GenericConverter.ConvertiblePair> 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> T convert(Object source, Class<T> 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<String> converterStrings = new ArrayList<String>();
for (Map<Class<?>, 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 <code>null</code> 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<Class<?>, 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<Class<?>, MatchableConverters> getSourceConverterMap(Class<?> sourceType) {
Map<Class<?>, MatchableConverters> sourceMap = converters.get(sourceType);
if (sourceMap == null) {
sourceMap = new HashMap<Class<?>, MatchableConverters>();
this.converters.put(sourceType, sourceMap);
}
return sourceMap;
}
private GenericConverter findConverterForClassPair(TypeDescriptor sourceType, TypeDescriptor targetType) {
Class<?> sourceObjectType = sourceType.getObjectType();
if (sourceObjectType.isInterface()) {
LinkedList<Class<?>> classQueue = new LinkedList<Class<?>>();
classQueue.addFirst(sourceObjectType);
while (!classQueue.isEmpty()) {
Class<?> currentClass = classQueue.removeLast();
Map<Class<?>, 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<Class<?>, MatchableConverters> objectConverters = getTargetConvertersForSource(Object.class);
return getMatchingConverterForTarget(sourceType, targetType, objectConverters);
}
else if (sourceObjectType.isArray()) {
LinkedList<Class<?>> classQueue = new LinkedList<Class<?>>();
classQueue.addFirst(sourceObjectType);
while (!classQueue.isEmpty()) {
Class<?> currentClass = classQueue.removeLast();
Map<Class<?>, 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<Class<?>> interfaces = new LinkedHashSet<Class<?>>();
LinkedList<Class<?>> classQueue = new LinkedList<Class<?>>();
classQueue.addFirst(sourceObjectType);
while (!classQueue.isEmpty()) {
Class<?> currentClass = classQueue.removeLast();
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) {
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);
}
}
private Map<Class<?>, MatchableConverters> getTargetConvertersForSource(Class<?> sourceType) {
Map<Class<?>, MatchableConverters> converters = this.converters.get(sourceType);
if (converters == null) {
converters = Collections.emptyMap();
}
return converters;
}
private GenericConverter getMatchingConverterForTarget(TypeDescriptor sourceType, TypeDescriptor targetType,
Map<Class<?>, MatchableConverters> converters) {
Class<?> targetObjectType = targetType.getObjectType();
if (targetObjectType.isInterface()) {
LinkedList<Class<?>> classQueue = new LinkedList<Class<?>>();
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<Class<?>> classQueue = new LinkedList<Class<?>>();
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<Class<?>> interfaces = new LinkedHashSet<Class<?>>();
LinkedList<Class<?>> classQueue = new LinkedList<Class<?>>();
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<Class<?>> 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<Object, Object> converter;
public ConverterAdapter(ConvertiblePair typeInfo, Converter<?, ?> converter) {
this.converter = (Converter<Object, Object>) converter;
this.typeInfo = typeInfo;
}
public Set<ConvertiblePair> 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<Object, Object> converterFactory;
public ConverterFactoryAdapter(ConvertiblePair typeInfo, ConverterFactory<?, ?> converterFactory) {
this.converterFactory = (ConverterFactory<Object, Object>) converterFactory;
this.typeInfo = typeInfo;
}
public Set<ConvertiblePair> getConvertibleTypes() {
return Collections.singleton(this.typeInfo);
}
@@ -554,73 +341,22 @@ public class GenericConversionService implements ConfigurableConversionService {
}
private static class MatchableConverters {
private LinkedList<ConditionalGenericConverter> conditionalConverters;
private GenericConverter defaultConverter;
public void add(GenericConverter converter) {
if (converter instanceof ConditionalGenericConverter) {
if (this.conditionalConverters == null) {
this.conditionalConverters = new LinkedList<ConditionalGenericConverter>();
}
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<ConditionalGenericConverter> 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<Class<?>> IGNORED_CLASSES;
static {
Set<Class<?>> ignored = new HashSet<Class<?>>();
ignored.add(Object.class);
ignored.add(Object[].class);
IGNORED_CLASSES = Collections.unmodifiableSet(ignored);
}
private final Map<ConvertiblePair, ConvertersForPair> converters =
new LinkedHashMap<ConvertiblePair, ConvertersForPair>(36);
public void add(GenericConverter converter) {
Set<ConvertiblePair> 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 <tt>null</tt>
* @see #getTypeHierarchy(Class)
*/
public GenericConverter find(TypeDescriptor sourceType, TypeDescriptor targetType) {
// Search the full type hierarchy
List<TypeDescriptor> sourceCandidates = getTypeHierarchy(sourceType);
List<TypeDescriptor> 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<TypeDescriptor> getTypeHierarchy(TypeDescriptor type) {
if(type.isPrimitive()) {
type = TypeDescriptor.valueOf(type.getObjectType());
}
Set<TypeDescriptor> typeHierarchy = new LinkedHashSet<TypeDescriptor>();
collectTypeHierarchy(typeHierarchy, type);
if(type.isArray()) {
typeHierarchy.add(TypeDescriptor.valueOf(Object[].class));
}
typeHierarchy.add(TypeDescriptor.valueOf(Object.class));
return new ArrayList<TypeDescriptor>(typeHierarchy);
}
private void collectTypeHierarchy(Set<TypeDescriptor> 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<String> getConverterStrings() {
List<String> converterStrings = new ArrayList<String>();
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<GenericConverter> converters = new LinkedList<GenericConverter>();
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<ConvertiblePair> getConvertibleTypes() {
return null;
}
public Object convert(Object source, TypeDescriptor sourceType,
TypeDescriptor targetType) {
return source;
}
@Override
public String toString() {
return name;
}
}
}