Change GenericConversionService to better handle enum

Prior to this commit, given an enum which implements some interface,
GenericConversionService would select the String -> Enum converter even
if a converter for String -> SomeInterface was registered.  This also
affected converters that were registered for String ->
SomeBaseInterface, when SomeInterface extended SomeBaseInterface.

This change modifies the behavior of the private method
getClassHierarchy() by placing Enum.class as late as possible, pretty
much the same way as Object.class is handled.

Issue: SPR-12050
master
David Haraburda 10 years ago committed by Stephane Nicoll
parent ebc5fea77b
commit d0e6f0f73f
  1. 21
      spring-core/src/main/java/org/springframework/core/convert/support/GenericConversionService.java
  2. 97
      spring-core/src/test/java/org/springframework/core/convert/support/GenericConversionServiceTests.java

@ -55,6 +55,7 @@ import org.springframework.util.StringUtils;
* @author Juergen Hoeller
* @author Chris Beams
* @author Phillip Webb
* @author David Haraburda
* @since 3.0
*/
public class GenericConversionService implements ConfigurableConversionService {
@ -567,19 +568,31 @@ public class GenericConversionService implements ConfigurableConversionService {
Class<?> candidate = hierarchy.get(i);
candidate = (array ? candidate.getComponentType() : ClassUtils.resolvePrimitiveIfNecessary(candidate));
Class<?> superclass = candidate.getSuperclass();
if (candidate.getSuperclass() != null && superclass != Object.class) {
if (candidate.getSuperclass() != null && superclass != Object.class && superclass != Enum.class) {
addToClassHierarchy(i + 1, candidate.getSuperclass(), array, hierarchy, visited);
}
for (Class<?> implementedInterface : candidate.getInterfaces()) {
addToClassHierarchy(hierarchy.size(), implementedInterface, array, hierarchy, visited);
}
addInterfacesToClassHierarchy(candidate, array, hierarchy, visited);
i++;
}
if (type.isEnum()) {
addToClassHierarchy(hierarchy.size(), Enum.class, array, hierarchy, visited);
addToClassHierarchy(hierarchy.size(), Enum.class, false, hierarchy, visited);
addInterfacesToClassHierarchy(Enum.class, array, hierarchy, visited);
}
addToClassHierarchy(hierarchy.size(), Object.class, array, hierarchy, visited);
addToClassHierarchy(hierarchy.size(), Object.class, false, hierarchy, visited);
return hierarchy;
}
private void addInterfacesToClassHierarchy(Class<?> type, boolean asArray,
List<Class<?>> hierarchy, Set<Class<?>> visited) {
for (Class<?> implementedInterface : type.getInterfaces()) {
addToClassHierarchy(hierarchy.size(), implementedInterface, asArray, hierarchy, visited);
}
}
private void addToClassHierarchy(int index, Class<?> type, boolean asArray,
List<Class<?>> hierarchy, Set<Class<?>> visited) {
if (asArray) {

@ -57,6 +57,7 @@ import static org.junit.Assert.*;
* @author Keith Donald
* @author Juergen Hoeller
* @author Phillip Webb
* @author David Haraburda
*/
public class GenericConversionServiceTests {
@ -758,6 +759,20 @@ public class GenericConversionServiceTests {
assertEquals("1", result);
}
@Test
public void testStringToEnumWithInterfaceConversion() {
conversionService.addConverterFactory(new StringToEnumConverterFactory());
conversionService.addConverterFactory(new StringToMyEnumInterfaceConverterFactory());
assertEquals(MyEnum.A, conversionService.convert("1", MyEnum.class));
}
@Test
public void testStringToEnumWithBaseInterfaceConversion() {
conversionService.addConverterFactory(new StringToEnumConverterFactory());
conversionService.addConverterFactory(new StringToMyEnumBaseInterfaceConverterFactory());
assertEquals(MyEnum.A, conversionService.convert("base1", MyEnum.class));
}
@Test
public void convertNullAnnotatedStringToString() throws Exception {
DefaultConversionService.addDefaultConverters(conversionService);
@ -930,19 +945,34 @@ public class GenericConversionServiceTests {
}
}
interface MyEnumBaseInterface {
String getBaseCode();
}
interface MyEnumInterface {
interface MyEnumInterface extends MyEnumBaseInterface {
String getCode();
}
public static enum MyEnum implements MyEnumInterface {
A {
@Override
public String getCode() {
return "1";
}
A("1"),
B("2"),
C("3");
private String code;
MyEnum(String code) {
this.code = code;
}
@Override
public String getCode() {
return code;
}
@Override
public String getBaseCode() {
return "base" + code;
}
}
@ -971,6 +1001,59 @@ public class GenericConversionServiceTests {
}
}
private static class StringToMyEnumInterfaceConverterFactory implements ConverterFactory<String, MyEnumInterface> {
@SuppressWarnings("unchecked")
public <T extends MyEnumInterface> Converter<String, T> getConverter(Class<T> targetType) {
return new StringToMyEnumInterfaceConverter(targetType);
}
private static class StringToMyEnumInterfaceConverter<T extends Enum<?> & MyEnumInterface> implements Converter<String, T> {
private final Class<T> enumType;
public StringToMyEnumInterfaceConverter(Class<T> enumType) {
this.enumType = enumType;
}
public T convert(String source) {
for (T value : enumType.getEnumConstants()) {
if (value.getCode().equals(source)) {
return value;
}
}
return null;
}
}
}
private static class StringToMyEnumBaseInterfaceConverterFactory implements ConverterFactory<String, MyEnumBaseInterface> {
@SuppressWarnings("unchecked")
public <T extends MyEnumBaseInterface> Converter<String, T> getConverter(Class<T> targetType) {
return new StringToMyEnumBaseInterfaceConverter(targetType);
}
private static class StringToMyEnumBaseInterfaceConverter<T extends Enum<?> & MyEnumBaseInterface> implements Converter<String, T> {
private final Class<T> enumType;
public StringToMyEnumBaseInterfaceConverter(Class<T> enumType) {
this.enumType = enumType;
}
public T convert(String source) {
for (T value : enumType.getEnumConstants()) {
if (value.getBaseCode().equals(source)) {
return value;
}
}
return null;
}
}
}
public static class MyStringToStringCollectionConverter implements Converter<String, Collection<String>> {
@Override

Loading…
Cancel
Save