@ -1,5 +1,5 @@
@@ -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;
@@ -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 {
@@ -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 Super 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 - Super Converter 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 {
@@ -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 = getRequired TypeInfo ( converter ) ;
Class sourceClass = ( Class ) typeInfo . get ( 0 ) ;
Class targetClass = ( Class ) typeInfo . get ( 1 ) ;
// index forward
@ -104,7 +110,7 @@ public class GenericConversionService implements ConversionService {
@@ -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 = getRequired TypeInfo ( converter ) ;
Class sourceClass = ( Class ) typeInfo . get ( 0 ) ;
Class targetClass = ( Class ) typeInfo . get ( 1 ) ;
// index forward
@ -117,6 +123,31 @@ public class GenericConversionService implements ConversionService {
@@ -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 {
@@ -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 {
@@ -188,9 +219,156 @@ public class GenericConversionService implements ConversionService {
}
}
public ConversionExecutor getConversionExecutor ( String converterI d, Class sourceClass , Class targetClass )
public ConversionExecutor getConversionExecutor ( String i d, 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 {
@@ -208,25 +386,32 @@ public class GenericConversionService implements ConversionService {
// internal helpers
private List getTypeInfo ( Object converter ) {
private List getRequired TypeInfo ( 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 {
@@ -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 ) {