formatters now plugged into unified type conversion api with formatter specific registry spi

This commit is contained in:
Keith Donald
2009-11-04 00:08:47 +00:00
parent 2e4fa28ca6
commit 0a78287a8f
19 changed files with 449 additions and 482 deletions

View File

@@ -26,6 +26,7 @@ package org.springframework.core.convert;
public interface ConversionService {
/**
* TODO - do we really need to support this?
* Returns true if objects of sourceType can be converted to targetType.
* @param sourceType the source type to convert from (required)
* @param targetType the target type to convert to (required)
@@ -34,6 +35,7 @@ public interface ConversionService {
boolean canConvert(Class<?> sourceType, Class<?> targetType);
/**
* TODO - do we really need to support this?
* Convert the source to targetType.
* @param source the source object to convert (may be null)
* @param targetType the target type to convert to (required)

View File

@@ -283,6 +283,18 @@ public class TypeDescriptor {
}
}
/**
* Obtain the annotation associated with the wrapped parameter/field, if any.
*/
public Annotation getAnnotation(Class<? extends Annotation> annotationType) {
for (Annotation annotation : getAnnotations()) {
if (annotation.annotationType().equals(annotationType)) {
return annotation;
}
}
return null;
}
/**
* Returns true if this type is an abstract class.
*/
@@ -402,5 +414,5 @@ public class TypeDescriptor {
return "[TypeDescriptor type=" + getType().getName() + "]";
}
}
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright 2002-2009 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.core.convert.support;
import org.springframework.core.convert.TypeDescriptor;
/**
* A generic converter that conditionally executes.
* Often used when selectively matching custom conversion logic based on the presence of a field or class-level annotation.
* For example, when converting from a String to a Date field, an implementation might return true if the target field has also been annotated with <code>@DateTimeFormat</code>.
* @author Keith Donald
* @since 3.0
*/
public interface ConditionalGenericConverter extends GenericConverter {
/**
* Should the conversion between <code>sourceFieldType</code> and <code>targetFieldType</code> be performed?
* @param sourceFieldType the type descriptor of the field we are converting from
* @param targetFieldType the type descriptor of the field we are converting to
* @return true if conversion should be performed, false otherwise
*/
boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType);
}

View File

@@ -51,7 +51,7 @@ public class GenericConversionService implements ConversionService, ConverterReg
}
};
private final Map<Class<?>, Map<Class<?>, GenericConverter>> sourceTypeConverters = new HashMap<Class<?>, Map<Class<?>, GenericConverter>>(36);
private final Map<Class<?>, Map<Class<?>, MatchableConverters>> converters = new HashMap<Class<?>, Map<Class<?>, MatchableConverters>>(36);
private ConversionService parent;
@@ -144,7 +144,7 @@ public class GenericConversionService implements ConversionService, ConverterReg
}
public void removeConvertible(Class<?> sourceType, Class<?> targetType) {
getSourceMap(sourceType).remove(targetType);
getSourceConverterMap(sourceType).remove(targetType);
}
// implementing ConversionService
@@ -183,13 +183,13 @@ public class GenericConversionService implements ConversionService, ConverterReg
}
/**
* Registers a GenericConverter.
* Registers a GenericConverter for the source/target type pair.
* @param sourceType the source type to convert from
* @param targetType the target type to convert to
* @param converter the generic converter.
*/
public void addGenericConverter(Class<?> sourceType, Class<?> targetType, GenericConverter converter) {
getSourceMap(sourceType).put(targetType, converter);
getMatchableConvertersList(sourceType, targetType).add(converter);
}
/**
@@ -244,7 +244,8 @@ public class GenericConversionService implements ConversionService, ConverterReg
* @return the generic converter that will perform the conversion, or <code>null</code> if no suitable converter was found
*/
protected GenericConverter getConverter(TypeDescriptor sourceType, TypeDescriptor targetType) {
GenericConverter converter = findConverterByClassPair(sourceType.getObjectType(), targetType.getObjectType());
MatchableConverters matchable = findMatchableConvertersForClassPair(sourceType.getObjectType(), targetType.getObjectType());
GenericConverter converter = matchConverter(matchable, sourceType, targetType);
if (converter != null) {
return converter;
} else if (this.parent != null && this.parent.canConvert(sourceType, targetType)) {
@@ -273,42 +274,61 @@ public class GenericConversionService implements ConversionService, ConverterReg
// internal helpers
private Class<?>[] getRequiredTypeInfo(Object converter, Class<?> genericIfc) {
return GenericTypeResolver.resolveTypeArguments(converter.getClass(), genericIfc);
}
private MatchableConverters getMatchableConvertersList(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 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 void assertNotNull(TypeDescriptor sourceType, TypeDescriptor targetType) {
Assert.notNull(sourceType, "The sourceType to convert to is required");
Assert.notNull(targetType, "The targetType to convert to is required");
}
private Class<?>[] getRequiredTypeInfo(Object converter, Class<?> genericIfc) {
return GenericTypeResolver.resolveTypeArguments(converter.getClass(), genericIfc);
}
private GenericConverter findConverterByClassPair(Class<?> sourceType, Class<?> targetType) {
private MatchableConverters findMatchableConvertersForClassPair(Class<?> sourceType, Class<?> targetType) {
if (sourceType.isInterface()) {
LinkedList<Class<?>> classQueue = new LinkedList<Class<?>>();
classQueue.addFirst(sourceType);
while (!classQueue.isEmpty()) {
Class<?> currentClass = classQueue.removeLast();
Map<Class<?>, GenericConverter> converters = getConvertersForSource(currentClass);
GenericConverter converter = getConverter(converters, targetType);
if (converter != null) {
return converter;
Map<Class<?>, MatchableConverters> converters = getTargetConvertersForSource(currentClass);
MatchableConverters matchable = getMatchableConvertersForTarget(converters, targetType);
if (matchable != null) {
return matchable;
}
Class<?>[] interfaces = currentClass.getInterfaces();
for (Class<?> ifc : interfaces) {
classQueue.addFirst(ifc);
}
}
Map<Class<?>, GenericConverter> objectConverters = getConvertersForSource(Object.class);
return getConverter(objectConverters, targetType);
Map<Class<?>, MatchableConverters> objectConverters = getTargetConvertersForSource(Object.class);
return getMatchableConvertersForTarget(objectConverters, targetType);
} else {
LinkedList<Class<?>> classQueue = new LinkedList<Class<?>>();
classQueue.addFirst(sourceType);
while (!classQueue.isEmpty()) {
Class<?> currentClass = classQueue.removeLast();
Map<Class<?>, GenericConverter> converters = getConvertersForSource(currentClass);
GenericConverter converter = getConverter(converters, targetType);
if (converter != null) {
return converter;
Map<Class<?>, MatchableConverters> converters = getTargetConvertersForSource(currentClass);
MatchableConverters matchable = getMatchableConvertersForTarget(converters, targetType);
if (matchable != null) {
return matchable;
}
if (currentClass.isArray()) {
Class<?> componentType = ClassUtils.resolvePrimitiveIfNecessary(currentClass.getComponentType());
@@ -329,30 +349,22 @@ public class GenericConversionService implements ConversionService, ConverterReg
}
}
private Map<Class<?>, GenericConverter> getSourceMap(Class<?> sourceType) {
Map<Class<?>, GenericConverter> sourceMap = sourceTypeConverters.get(sourceType);
if (sourceMap == null) {
sourceMap = new HashMap<Class<?>, GenericConverter>();
this.sourceTypeConverters.put(sourceType, sourceMap);
}
return sourceMap;
}
private Map<Class<?>, GenericConverter> getConvertersForSource(Class<?> sourceType) {
Map<Class<?>, GenericConverter> converters = this.sourceTypeConverters.get(sourceType);
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 getConverter(Map<Class<?>, GenericConverter> converters, Class<?> targetType) {
private MatchableConverters getMatchableConvertersForTarget(Map<Class<?>, MatchableConverters> converters,
Class<?> targetType) {
if (targetType.isInterface()) {
LinkedList<Class<?>> classQueue = new LinkedList<Class<?>>();
classQueue.addFirst(targetType);
while (!classQueue.isEmpty()) {
Class<?> currentClass = classQueue.removeLast();
GenericConverter converter = converters.get(currentClass);
MatchableConverters converter = converters.get(currentClass);
if (converter != null) {
return converter;
}
@@ -367,7 +379,7 @@ public class GenericConversionService implements ConversionService, ConverterReg
classQueue.addFirst(targetType);
while (!classQueue.isEmpty()) {
Class<?> currentClass = classQueue.removeLast();
GenericConverter converter = converters.get(currentClass);
MatchableConverters converter = converters.get(currentClass);
if (converter != null) {
return converter;
}
@@ -390,6 +402,10 @@ public class GenericConversionService implements ConversionService, ConverterReg
}
}
private GenericConverter matchConverter(MatchableConverters matchable, TypeDescriptor sourceFieldType, TypeDescriptor targetFieldType) {
return matchable != null ? matchable.matchConverter(sourceFieldType, targetFieldType) : null;
}
@SuppressWarnings("unchecked")
private final class ConverterAdapter implements GenericConverter {
@@ -424,4 +440,27 @@ public class GenericConversionService implements ConversionService, ConverterReg
}
}
}
private static class MatchableConverters {
private LinkedList<GenericConverter> matchableConverters = new LinkedList<GenericConverter>();
public void add(GenericConverter converter) {
this.matchableConverters.addFirst(converter);
}
public GenericConverter matchConverter(TypeDescriptor sourceType, TypeDescriptor targetType) {
for (GenericConverter matchable : this.matchableConverters) {
if (!(matchable instanceof ConditionalGenericConverter)) {
return matchable;
}
ConditionalGenericConverter conditional = (ConditionalGenericConverter) matchable;
if (conditional.matches(sourceType, targetType)) {
return matchable;
}
}
return null;
}
}
}