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:
committed by
Chris Beams
parent
9bfe675761
commit
4dc289592d
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user