GenericConversionService prefers matches against inherited interfaces over superclasses (SPR-6297)

This commit is contained in:
Juergen Hoeller 2009-12-09 16:16:55 +00:00
parent ad29a2376d
commit 010e72c35a
2 changed files with 124 additions and 37 deletions

View File

@ -16,8 +16,6 @@
package org.springframework.core.convert.support; package org.springframework.core.convert.support;
import static org.springframework.core.convert.support.ConversionUtils.invokeConverter;
import java.lang.reflect.Array; import java.lang.reflect.Array;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
@ -29,6 +27,7 @@ import java.util.Map;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.core.GenericTypeResolver; import org.springframework.core.GenericTypeResolver;
import org.springframework.core.convert.ConversionFailedException; import org.springframework.core.convert.ConversionFailedException;
import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.ConversionService;
@ -39,11 +38,14 @@ import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.converter.ConverterFactory; import org.springframework.core.convert.converter.ConverterFactory;
import org.springframework.core.convert.converter.ConverterRegistry; import org.springframework.core.convert.converter.ConverterRegistry;
import org.springframework.core.convert.converter.GenericConverter; import org.springframework.core.convert.converter.GenericConverter;
import static org.springframework.core.convert.support.ConversionUtils.*;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.ClassUtils; import org.springframework.util.ClassUtils;
/** /**
* Base ConversionService implementation suitable for use in most environments. * Base {@link ConversionService} implementation suitable for use in most environments.
* Implements {@link ConverterRegistry} as registration API.
*
* @author Keith Donald * @author Keith Donald
* @author Juergen Hoeller * @author Juergen Hoeller
* @since 3.0 * @since 3.0
@ -56,21 +58,23 @@ public class GenericConversionService implements ConversionService, ConverterReg
public Class<?>[][] getConvertibleTypes() { public Class<?>[][] getConvertibleTypes() {
return null; return null;
} }
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
return source; return source;
} }
}; };
private final Map<Class<?>, Map<Class<?>, MatchableConverters>> converters = new HashMap<Class<?>, Map<Class<?>, MatchableConverters>>(36);
private final Map<Class<?>, Map<Class<?>, MatchableConverters>> converters =
new HashMap<Class<?>, Map<Class<?>, MatchableConverters>>(36);
// implementing ConverterRegistry // implementing ConverterRegistry
public void addConverter(Converter<?, ?> converter) { public void addConverter(Converter<?, ?> converter) {
Class<?>[] typeInfo = getRequiredTypeInfo(converter, Converter.class); Class<?>[] typeInfo = getRequiredTypeInfo(converter, Converter.class);
if (typeInfo == null) { if (typeInfo == null) {
throw new IllegalArgumentException( throw new IllegalArgumentException("Unable to the determine sourceType <S> and targetType <T> which " +
"Unable to the determine sourceType <S> and targetType <T> your Converter<S, T> converts between; declare these types or implement ConverterInfo"); "your Converter<S, T> converts between; declare these generic types.");
} }
addGenericConverter(new ConverterAdapter(typeInfo, converter)); addGenericConverter(new ConverterAdapter(typeInfo, converter));
} }
@ -78,8 +82,8 @@ public class GenericConversionService implements ConversionService, ConverterReg
public void addConverterFactory(ConverterFactory<?, ?> converterFactory) { public void addConverterFactory(ConverterFactory<?, ?> converterFactory) {
Class<?>[] typeInfo = getRequiredTypeInfo(converterFactory, ConverterFactory.class); Class<?>[] typeInfo = getRequiredTypeInfo(converterFactory, ConverterFactory.class);
if (typeInfo == null) { if (typeInfo == null) {
throw new IllegalArgumentException( throw new IllegalArgumentException("Unable to the determine sourceType <S> and targetRangeType R which " +
"Unable to the determine sourceType <S> and targetRangeType R your ConverterFactory<S, R> converts between; declare these types or implement ConverterInfo"); "your ConverterFactory<S, R> converts between; declare these generic types.");
} }
addGenericConverter(new ConverterFactoryAdapter(typeInfo, converterFactory)); addGenericConverter(new ConverterFactoryAdapter(typeInfo, converterFactory));
} }
@ -95,6 +99,7 @@ public class GenericConversionService implements ConversionService, ConverterReg
getSourceConverterMap(sourceType).remove(targetType); getSourceConverterMap(sourceType).remove(targetType);
} }
// implementing ConversionService // implementing ConversionService
public boolean canConvert(Class<?> sourceType, Class<?> targetType) { public boolean canConvert(Class<?> sourceType, Class<?> targetType) {
@ -148,6 +153,7 @@ public class GenericConversionService implements ConversionService, ConverterReg
return builder.toString(); return builder.toString();
} }
// subclassing hooks // subclassing hooks
/** /**
@ -161,8 +167,8 @@ public class GenericConversionService implements ConversionService, ConverterReg
*/ */
protected Object convertNullSource(TypeDescriptor sourceType, TypeDescriptor targetType) { protected Object convertNullSource(TypeDescriptor sourceType, TypeDescriptor targetType) {
if (targetType.isPrimitive()) { if (targetType.isPrimitive()) {
throw new ConversionFailedException(sourceType, targetType, null, new IllegalArgumentException( throw new ConversionFailedException(sourceType, targetType, null,
"A null value cannot be assigned to a primitive type")); new IllegalArgumentException("A null value cannot be assigned to a primitive type"));
} }
return null; return null;
} }
@ -170,9 +176,8 @@ public class GenericConversionService implements ConversionService, ConverterReg
/** /**
* Hook method to lookup the converter for a given sourceType/targetType pair. * Hook method to lookup the converter for a given sourceType/targetType pair.
* First queries this ConversionService's converter map. * First queries this ConversionService's converter map.
* If no suitable Converter is found, and a {@link #setParent parent} is set, then queries the parent. * <p>Returns <code>null</code> if this ConversionService simply cannot convert
* Returns <code>null</code> if this ConversionService simply cannot convert between sourceType and targetType. * between sourceType and targetType. Subclasses may override.
* Subclasses may override.
* @param sourceType the source type to convert from * @param sourceType the source type to convert from
* @param targetType the target type to convert to * @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</code> if no suitable converter was found
@ -278,7 +283,7 @@ public class GenericConversionService implements ConversionService, ConverterReg
else { else {
Class<?>[] interfaces = currentClass.getInterfaces(); Class<?>[] interfaces = currentClass.getInterfaces();
for (Class<?> ifc : interfaces) { for (Class<?> ifc : interfaces) {
classQueue.addFirst(ifc); addInterfaceHierarchy(ifc, classQueue);
} }
if (currentClass.getSuperclass() != null) { if (currentClass.getSuperclass() != null) {
classQueue.addFirst(currentClass.getSuperclass()); classQueue.addFirst(currentClass.getSuperclass());
@ -333,10 +338,11 @@ public class GenericConversionService implements ConversionService, ConverterReg
if (componentType.getSuperclass() != null) { if (componentType.getSuperclass() != null) {
classQueue.addFirst(Array.newInstance(componentType.getSuperclass(), 0).getClass()); classQueue.addFirst(Array.newInstance(componentType.getSuperclass(), 0).getClass());
} }
} else { }
else {
Class<?>[] interfaces = currentClass.getInterfaces(); Class<?>[] interfaces = currentClass.getInterfaces();
for (Class<?> ifc : interfaces) { for (Class<?> ifc : interfaces) {
classQueue.addFirst(ifc); addInterfaceHierarchy(ifc, classQueue);
} }
if (currentClass.getSuperclass() != null) { if (currentClass.getSuperclass() != null) {
classQueue.addFirst(currentClass.getSuperclass()); classQueue.addFirst(currentClass.getSuperclass());
@ -347,10 +353,17 @@ public class GenericConversionService implements ConversionService, ConverterReg
} }
} }
private GenericConverter matchConverter(MatchableConverters matchable, TypeDescriptor sourceFieldType, private void addInterfaceHierarchy(Class<?> ifc, LinkedList<Class<?>> classQueue) {
TypeDescriptor targetFieldType) { classQueue.addFirst(ifc);
for (Class<?> inheritedIfc : ifc.getInterfaces()) {
addInterfaceHierarchy(inheritedIfc, classQueue);
}
}
return matchable != null ? matchable.matchConverter(sourceFieldType, targetFieldType) : null; private GenericConverter matchConverter(
MatchableConverters matchable, TypeDescriptor sourceFieldType, TypeDescriptor targetFieldType) {
return (matchable != null ? matchable.matchConverter(sourceFieldType, targetFieldType) : null);
} }
@ -407,7 +420,8 @@ public class GenericConversionService implements ConversionService, ConverterReg
} }
public String toString() { public String toString() {
return this.typeInfo[0].getName() + " -> " + this.typeInfo[1].getName() + " : " + this.converterFactory.toString(); return this.typeInfo[0].getName() + " -> " + this.typeInfo[1].getName() + " : " +
this.converterFactory.toString();
} }
} }
@ -424,7 +438,8 @@ public class GenericConversionService implements ConversionService, ConverterReg
this.conditionalConverters = new LinkedList<ConditionalGenericConverter>(); this.conditionalConverters = new LinkedList<ConditionalGenericConverter>();
} }
this.conditionalConverters.addFirst((ConditionalGenericConverter) converter); this.conditionalConverters.addFirst((ConditionalGenericConverter) converter);
} else { }
else {
this.defaultConverter = converter; this.defaultConverter = converter;
} }
} }
@ -434,18 +449,19 @@ public class GenericConversionService implements ConversionService, ConverterReg
for (ConditionalGenericConverter conditional : this.conditionalConverters) { for (ConditionalGenericConverter conditional : this.conditionalConverters) {
if (conditional.matches(sourceType, targetType)) { if (conditional.matches(sourceType, targetType)) {
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("Converter Lookup [MATCHED] " + conditional); logger.debug("Converter lookup [MATCHED] " + conditional);
} }
return conditional; return conditional;
} else { }
else {
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("Converter Lookup [DID NOT MATCH] " + conditional); logger.debug("Converter lookup [DID NOT MATCH] " + conditional);
} }
} }
} }
} }
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("Converter Lookup [MATCHED] " + this.defaultConverter); logger.debug("Converter lookup [MATCHED] " + this.defaultConverter);
} }
return this.defaultConverter; return this.defaultConverter;
} }
@ -463,7 +479,8 @@ public class GenericConversionService implements ConversionService, ConverterReg
builder.append(", ").append(this.defaultConverter); builder.append(", ").append(this.defaultConverter);
} }
return builder.toString(); return builder.toString();
} else { }
else {
return this.defaultConverter.toString(); return this.defaultConverter.toString();
} }
} }

View File

@ -16,18 +16,23 @@
package org.springframework.core.convert.support; package org.springframework.core.convert.support;
import static junit.framework.Assert.assertEquals; import java.util.ArrayList;
import static junit.framework.Assert.assertNull; import java.util.HashMap;
import static junit.framework.Assert.fail; import java.util.List;
import static org.junit.Assert.assertFalse; import java.util.Map;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.*;
import org.junit.Test; import org.junit.Test;
import org.springframework.core.convert.ConversionFailedException; import org.springframework.core.convert.ConversionFailedException;
import org.springframework.core.convert.ConverterNotFoundException; import org.springframework.core.convert.ConverterNotFoundException;
import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.converter.Converter;
/**
* @author Keith Donald
* @author Juergen Hoeller
*/
public class GenericConversionServiceTests { public class GenericConversionServiceTests {
private GenericConversionService conversionService = new GenericConversionService(); private GenericConversionService conversionService = new GenericConversionService();
@ -53,7 +58,8 @@ public class GenericConversionServiceTests {
try { try {
conversionService.convert("3", Integer.class); conversionService.convert("3", Integer.class);
fail("Should have thrown an exception"); fail("Should have thrown an exception");
} catch (ConverterNotFoundException e) { }
catch (ConverterNotFoundException e) {
} }
} }
@ -91,7 +97,8 @@ public class GenericConversionServiceTests {
try { try {
conversionService.convert("BOGUS", Integer.class); conversionService.convert("BOGUS", Integer.class);
fail("Should have failed"); fail("Should have failed");
} catch (ConversionFailedException e) { }
catch (ConversionFailedException e) {
} }
} }
@ -136,9 +143,72 @@ public class GenericConversionServiceTests {
conversionService.addGenericConverter(new ObjectToArrayConverter(conversionService)); conversionService.addGenericConverter(new ObjectToArrayConverter(conversionService));
conversionService.convert("1", Integer[].class); conversionService.convert("1", Integer[].class);
fail("Should hace failed"); fail("Should hace failed");
} catch (ConversionFailedException e) { }
catch (ConversionFailedException e) {
assertTrue(e.getCause() instanceof ConverterNotFoundException); assertTrue(e.getCause() instanceof ConverterNotFoundException);
} }
} }
@Test
public void testListToIterableConversion() {
GenericConversionService conversionService = new GenericConversionService();
List<Object> raw = new ArrayList<Object>();
raw.add("one");
raw.add("two");
Object converted = conversionService.convert(raw, Iterable.class);
assertSame(raw, converted);
}
@Test
public void testListToObjectConversion() {
GenericConversionService conversionService = new GenericConversionService();
List<Object> raw = new ArrayList<Object>();
raw.add("one");
raw.add("two");
Object converted = conversionService.convert(raw, Object.class);
assertSame(raw, converted);
}
@Test
public void testMapToObjectConversionBug() {
GenericConversionService conversionService = new GenericConversionService();
Map<Object, Object> raw = new HashMap<Object, Object>();
raw.put("key", "value");
Object converted = conversionService.convert(raw, Object.class);
assertSame(raw, converted);
}
@Test
public void testInterfaceToString() {
GenericConversionService conversionService = new GenericConversionService();
conversionService.addConverter(new MyBaseInterfaceConverter());
conversionService.addConverter(new ObjectToStringConverter());
Object converted = conversionService.convert(new MyInterfaceImplementer(), String.class);
assertEquals("RESULT", converted);
}
private interface MyBaseInterface {
}
private interface MyInterface extends MyBaseInterface {
}
private static class MyInterfaceImplementer implements MyInterface {
}
private class MyBaseInterfaceConverter implements Converter<MyBaseInterface, String> {
public String convert(MyBaseInterface source) {
return "RESULT";
}
}
} }