custom converters

This commit is contained in:
Keith Donald
2009-03-09 02:28:22 +00:00
parent 0ef0ff60f0
commit d9f5a7a6c8
3 changed files with 480 additions and 23 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2004-2009 the original author or authors.
* Copyright 2004-2008 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.
@@ -40,7 +40,7 @@ import org.springframework.util.Assert;
/**
* Base implementation of a conversion service. Initially empty, e.g. no converters are registered by default.
*
* TODO custom converters TODO handle case where no Converter/SuperConverter generic param info is available
* TODO auto-conversion of generic collection elements
*
* @author Keith Donald
*/
@@ -54,11 +54,17 @@ public class GenericConversionService implements ConversionService {
private final Map sourceClassConverters = new HashMap();
/**
* An indexed map of Converters. Each Map.Entry key is a source class (S) that can be converted from. Each Map.Entry
* value is a Map that defines the targetClass-to-Converter mappings for that source.
* An indexed map of SuperConverters. Each Map.Entry key is a source class (S) that can be converted from. Each
* Map.Entry value is a Map that defines the targetClass-to-SuperConverter mappings for that source.
*/
private final Map sourceClassSuperConverters = new HashMap();
/**
* A map of custom converters. Custom converters are assigned a unique identifier that can be used to lookup the
* converter. This allows multiple converters for the same source->target class to be registered.
*/
private final Map customConverters = new HashMap();
/**
* Indexes classes by well-known aliases.
*/
@@ -88,7 +94,7 @@ public class GenericConversionService implements ConversionService {
* @param converter the converter to register
*/
public void addConverter(Converter converter) {
List typeInfo = getTypeInfo(converter);
List typeInfo = getRequiredTypeInfo(converter);
Class sourceClass = (Class) typeInfo.get(0);
Class targetClass = (Class) typeInfo.get(1);
// index forward
@@ -104,7 +110,7 @@ public class GenericConversionService implements ConversionService {
* @param converter the super converter to register
*/
public void addConverter(SuperConverter converter) {
List typeInfo = getTypeInfo(converter);
List typeInfo = getRequiredTypeInfo(converter);
Class sourceClass = (Class) typeInfo.get(0);
Class targetClass = (Class) typeInfo.get(1);
// index forward
@@ -117,6 +123,31 @@ public class GenericConversionService implements ConversionService {
}
}
/**
* Register the converter as a custom converter with this conversion service.
* @param id the id to assign the converter
* @param converter the converter to use a custom converter
*/
public void addConverter(String id, Converter converter) {
customConverters.put(id, converter);
}
/**
* Adapts a {@link SuperTwoWayConverter} that converts between BS and BT class hierarchies to a {@link Converter}
* that converts between the specific BS/BT sub types S and T.
*
* TODO - I think this is going to force indexing on a getSourceClass/getTargetclass prop instead generic args
*
* @param sourceClass the source class S to convert from, which must be equal or extend BS
* @param targetClass the target type T to convert to, which must equal or extend BT
* @param converter the super two way converter
* @return a converter that converts from S to T by delegating to the super converter
*/
public static <S, T> Converter<S, T> converterFor(Class<S> sourceClass, Class<T> targetClass,
SuperTwoWayConverter converter) {
return new SuperTwoWayConverterConverter<S, T>(converter, sourceClass, targetClass);
}
/**
* Add a convenient alias for the target type. {@link #getClassForAlias(String)} can then be used to lookup the type
* given the alias.
@@ -126,16 +157,16 @@ public class GenericConversionService implements ConversionService {
aliasMap.put(alias, targetType);
}
// implementing ConversionService
public Object executeConversion(Object source, Class targetClass) throws ConversionExecutorNotFoundException,
ConversionException {
ConversionExecutor conversionExecutor = getConversionExecutor(source.getClass(), targetClass);
return conversionExecutor.execute(source);
return getConversionExecutor(source.getClass(), targetClass).execute(source);
}
public Object executeConversion(String converterId, Object source, Class targetClass)
throws ConversionExecutorNotFoundException, ConversionException {
ConversionExecutor conversionExecutor = getConversionExecutor(converterId, source.getClass(), targetClass);
return conversionExecutor.execute(source);
return getConversionExecutor(converterId, source.getClass(), targetClass).execute(source);
}
public ConversionExecutor getConversionExecutor(Class sourceClass, Class targetClass)
@@ -188,9 +219,156 @@ public class GenericConversionService implements ConversionService {
}
}
public ConversionExecutor getConversionExecutor(String converterId, Class sourceClass, Class targetClass)
public ConversionExecutor getConversionExecutor(String id, Class sourceClass, Class targetClass)
throws ConversionExecutorNotFoundException {
throw new UnsupportedOperationException("Not yet implemented");
Assert.hasText(id, "The id of the custom converter is required");
Assert.notNull(sourceClass, "The source class to convert from is required");
Assert.notNull(targetClass, "The target class to convert to is required");
Converter converter = (Converter) customConverters.get(id);
if (converter == null) {
if (parent != null) {
return parent.getConversionExecutor(id, sourceClass, targetClass);
} else {
throw new ConversionExecutorNotFoundException(sourceClass, targetClass,
"No custom Converter found with id '" + id + "' for converting from sourceClass ["
+ sourceClass.getName() + "] to targetClass [" + targetClass.getName() + "]");
}
}
sourceClass = convertToWrapperClassIfNecessary(sourceClass);
targetClass = convertToWrapperClassIfNecessary(targetClass);
// TODO Not optimal getting this each time
List typeInfo = getRequiredTypeInfo(converter);
Class converterSourceClass = (Class) typeInfo.get(0);
Class converterTargetClass = (Class) typeInfo.get(1);
if (sourceClass.isArray()) {
Class sourceComponentType = sourceClass.getComponentType();
if (targetClass.isArray()) {
Class targetComponentType = targetClass.getComponentType();
if (converterSourceClass.isAssignableFrom(sourceComponentType)) {
if (!converterTargetClass.equals(targetComponentType)) {
throw new ConversionExecutorNotFoundException(sourceClass, targetClass,
"Custom Converter with id '" + id
+ "' cannot convert from an array storing elements of type ["
+ sourceComponentType.getName() + "] to an array of storing elements of type ["
+ targetComponentType.getName() + "]");
}
ConversionExecutor elementConverter = new StaticConversionExecutor(sourceComponentType,
targetComponentType, converter);
return new StaticSuperConversionExecutor(sourceClass, targetClass, new ArrayToArray(
elementConverter));
} else if (converterTargetClass.isAssignableFrom(sourceComponentType)) {
if (!converterSourceClass.equals(targetComponentType)) {
throw new ConversionExecutorNotFoundException(sourceClass, targetClass,
"Custom Converter with id '" + id
+ "' cannot convert from an array storing elements of type ["
+ sourceComponentType.getName() + "] to an array of storing elements of type ["
+ targetComponentType.getName() + "]");
}
ConversionExecutor elementConverter = new StaticConversionExecutor(sourceComponentType,
targetComponentType, new ReverseConverter(converter));
return new StaticSuperConversionExecutor(sourceClass, targetClass, new ArrayToArray(
elementConverter));
} else {
throw new ConversionExecutorNotFoundException(sourceClass, targetClass,
"Custom Converter with id '" + id
+ "' cannot convert from an array storing elements of type ["
+ sourceComponentType.getName() + "] to an array storing elements of type ["
+ targetComponentType.getName() + "]");
}
} else if (Collection.class.isAssignableFrom(targetClass)) {
if (!targetClass.isInterface() && Modifier.isAbstract(targetClass.getModifiers())) {
throw new IllegalArgumentException("Conversion target class [" + targetClass.getName()
+ "] is invalid; cannot convert to abstract collection types--"
+ "request an interface or concrete implementation instead");
}
if (converterSourceClass.isAssignableFrom(sourceComponentType)) {
// type erasure has prevented us from getting the concrete type, this is best we can do for now
ConversionExecutor elementConverter = new StaticConversionExecutor(sourceComponentType,
converterTargetClass, converter);
return new StaticSuperConversionExecutor(sourceClass, targetClass, new ArrayToCollection(
elementConverter));
} else if (converterTargetClass.isAssignableFrom(sourceComponentType)) {
ConversionExecutor elementConverter = new StaticConversionExecutor(sourceComponentType,
converterSourceClass, new ReverseConverter(converter));
return new StaticSuperConversionExecutor(sourceClass, targetClass, new ArrayToCollection(
elementConverter));
} else {
throw new ConversionExecutorNotFoundException(sourceClass, targetClass,
"Custom Converter with id '" + id
+ "' cannot convert from array an storing elements type ["
+ sourceComponentType.getName() + "] to a collection of type ["
+ targetClass.getName() + "]");
}
}
}
if (targetClass.isArray()) {
Class targetComponentType = targetClass.getComponentType();
if (Collection.class.isAssignableFrom(sourceClass)) {
// type erasure limits us here as well
if (converterTargetClass.equals(targetComponentType)) {
ConversionExecutor elementConverter = new StaticConversionExecutor(converterSourceClass,
targetComponentType, converter);
SuperConverter collectionToArray = new ReverseSuperConverter(
new ArrayToCollection(elementConverter));
return new StaticSuperConversionExecutor(sourceClass, targetClass, collectionToArray);
} else if (converterSourceClass.equals(targetComponentType)) {
ConversionExecutor elementConverter = new StaticConversionExecutor(converterTargetClass,
targetComponentType, new ReverseConverter(converter));
SuperConverter collectionToArray = new ReverseSuperConverter(
new ArrayToCollection(elementConverter));
return new StaticSuperConversionExecutor(sourceClass, targetClass, collectionToArray);
} else {
throw new ConversionExecutorNotFoundException(sourceClass, targetClass,
"Custom Converter with id '" + id + "' cannot convert from collection of type ["
+ sourceClass.getName() + "] to an array storing elements of type ["
+ targetComponentType.getName() + "]");
}
} else {
if (converterSourceClass.isAssignableFrom(sourceClass)) {
if (!converterTargetClass.equals(targetComponentType)) {
throw new ConversionExecutorNotFoundException(sourceClass, targetClass,
"Custom Converter with id '" + id + "' cannot convert from sourceClass ["
+ sourceClass.getName() + "] to array holding elements of type ["
+ targetComponentType.getName() + "]");
}
ConversionExecutor elementConverter = new StaticConversionExecutor(sourceClass,
targetComponentType, converter);
return new StaticSuperConversionExecutor(sourceClass, targetClass, new ObjectToArray(
elementConverter));
} else if (converterTargetClass.isAssignableFrom(sourceClass)) {
if (!converterSourceClass.equals(targetComponentType)) {
throw new ConversionExecutorNotFoundException(sourceClass, targetClass,
"Custom Converter with id '" + id + "' cannot convert from sourceClass ["
+ sourceClass.getName() + "] to array holding elements of type ["
+ targetComponentType.getName() + "]");
}
ConversionExecutor elementConverter = new StaticConversionExecutor(sourceClass,
targetComponentType, new ReverseConverter(converter));
return new StaticSuperConversionExecutor(sourceClass, targetClass, new ObjectToArray(
elementConverter));
}
}
}
// TODO look to factor some of this duplicated code here and above out a bit
if (converterSourceClass.isAssignableFrom(sourceClass)) {
if (!converterTargetClass.equals(targetClass)) {
throw new ConversionExecutorNotFoundException(sourceClass, targetClass, "Custom Converter with id '"
+ id + "' cannot convert from sourceClass [" + sourceClass.getName() + "] to targetClass ["
+ targetClass.getName() + "]");
}
return new StaticConversionExecutor(sourceClass, targetClass, converter);
} else if (converterTargetClass.isAssignableFrom(sourceClass)) {
if (!converterSourceClass.equals(targetClass)) {
throw new ConversionExecutorNotFoundException(sourceClass, targetClass, "Custom Converter with id '"
+ id + "' cannot convert from sourceClass [" + sourceClass.getName() + "] to targetClass ["
+ targetClass.getName() + "]");
}
return new StaticConversionExecutor(sourceClass, targetClass, new ReverseConverter(converter));
} else {
throw new ConversionExecutorNotFoundException(sourceClass, targetClass, "Custom Converter with id '" + id
+ "' cannot convert from sourceClass [" + sourceClass.getName() + "] to targetClass ["
+ targetClass.getName() + "]");
}
}
public Class getClassForAlias(String name) throws IllegalArgumentException {
@@ -208,25 +386,32 @@ public class GenericConversionService implements ConversionService {
// internal helpers
private List getTypeInfo(Object converter) {
private List getRequiredTypeInfo(Object converter) {
List typeInfo = new ArrayList(2);
Class classToIntrospect = converter.getClass();
while (classToIntrospect != null) {
Type[] genericInterfaces = converter.getClass().getGenericInterfaces();
Type[] genericInterfaces = classToIntrospect.getGenericInterfaces();
for (Type genericInterface : genericInterfaces) {
if (genericInterface instanceof ParameterizedType) {
ParameterizedType parameterizedInterface = (ParameterizedType) genericInterface;
if (Converter.class.equals(parameterizedInterface.getRawType())
|| SuperConverter.class.isAssignableFrom((Class) parameterizedInterface.getRawType())) {
Type s = parameterizedInterface.getActualTypeArguments()[0];
Type t = parameterizedInterface.getActualTypeArguments()[1];
Class s = getParameterClass(parameterizedInterface.getActualTypeArguments()[0], converter
.getClass());
Class t = getParameterClass(parameterizedInterface.getActualTypeArguments()[1], converter
.getClass());
typeInfo.add(getParameterClass(s, converter.getClass()));
typeInfo.add(getParameterClass(t, converter.getClass()));
break;
}
}
}
classToIntrospect = classToIntrospect.getSuperclass();
}
if (typeInfo.size() != 2) {
throw new IllegalArgumentException("Unable to extract source and target class arguments from Converter ["
+ converter.getClass().getName() + "]; does the Converter specify the <S, T> generic types?");
}
return typeInfo;
}
@@ -237,8 +422,8 @@ public class GenericConversionService implements ConversionService {
if (parameterType instanceof Class) {
return (Class) parameterType;
}
// when would this happen?
return null;
throw new IllegalArgumentException("Unable to obtain the java.lang.Class for parameterType [" + parameterType
+ "] on Converter [" + converterClass.getName() + "]");
}
private Map getSourceMap(Class sourceClass) {

View File

@@ -1,11 +1,15 @@
package org.springframework.core.convert.converter;
package org.springframework.core.convert.service;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.converter.SuperConverter;
import org.springframework.core.convert.converter.SuperTwoWayConverter;
/**
* Adapts a {@link SuperTwoWayConverter} to the {@link Converter} interface in a type safe way. This adapter is useful
* for applying more general {@link SuperConverter} logic to a specific source/target class pair.
*/
@SuppressWarnings("unchecked")
public class SuperTwoWayConverterConverter<S, T> implements Converter<S, T> {
class SuperTwoWayConverterConverter<S, T> implements Converter<S, T> {
private SuperTwoWayConverter superConverter;
@@ -13,8 +17,7 @@ public class SuperTwoWayConverterConverter<S, T> implements Converter<S, T> {
private Class targetClass;
public <SCS, SCT> SuperTwoWayConverterConverter(SuperTwoWayConverter<SCS, SCT> superConverter,
Class<? extends SCS> sourceClass, Class<? extends SCT> targetClass) {
public SuperTwoWayConverterConverter(SuperTwoWayConverter superConverter, Class sourceClass, Class targetClass) {
this.superConverter = superConverter;
this.sourceClass = sourceClass;
this.targetClass = targetClass;