Skip to content

Commit d0e6f0f

Browse files
dharaburdasnicoll
authored andcommitted
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
1 parent ebc5fea commit d0e6f0f

File tree

2 files changed

+107
-11
lines changed

2 files changed

+107
-11
lines changed

spring-core/src/main/java/org/springframework/core/convert/support/GenericConversionService.java

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
* @author Juergen Hoeller
5656
* @author Chris Beams
5757
* @author Phillip Webb
58+
* @author David Haraburda
5859
* @since 3.0
5960
*/
6061
public class GenericConversionService implements ConfigurableConversionService {
@@ -567,19 +568,31 @@ private List<Class<?>> getClassHierarchy(Class<?> type) {
567568
Class<?> candidate = hierarchy.get(i);
568569
candidate = (array ? candidate.getComponentType() : ClassUtils.resolvePrimitiveIfNecessary(candidate));
569570
Class<?> superclass = candidate.getSuperclass();
570-
if (candidate.getSuperclass() != null && superclass != Object.class) {
571+
if (candidate.getSuperclass() != null && superclass != Object.class && superclass != Enum.class) {
571572
addToClassHierarchy(i + 1, candidate.getSuperclass(), array, hierarchy, visited);
572573
}
573-
for (Class<?> implementedInterface : candidate.getInterfaces()) {
574-
addToClassHierarchy(hierarchy.size(), implementedInterface, array, hierarchy, visited);
575-
}
574+
addInterfacesToClassHierarchy(candidate, array, hierarchy, visited);
576575
i++;
577576
}
577+
578+
if (type.isEnum()) {
579+
addToClassHierarchy(hierarchy.size(), Enum.class, array, hierarchy, visited);
580+
addToClassHierarchy(hierarchy.size(), Enum.class, false, hierarchy, visited);
581+
addInterfacesToClassHierarchy(Enum.class, array, hierarchy, visited);
582+
}
583+
578584
addToClassHierarchy(hierarchy.size(), Object.class, array, hierarchy, visited);
579585
addToClassHierarchy(hierarchy.size(), Object.class, false, hierarchy, visited);
580586
return hierarchy;
581587
}
582588

589+
private void addInterfacesToClassHierarchy(Class<?> type, boolean asArray,
590+
List<Class<?>> hierarchy, Set<Class<?>> visited) {
591+
for (Class<?> implementedInterface : type.getInterfaces()) {
592+
addToClassHierarchy(hierarchy.size(), implementedInterface, asArray, hierarchy, visited);
593+
}
594+
}
595+
583596
private void addToClassHierarchy(int index, Class<?> type, boolean asArray,
584597
List<Class<?>> hierarchy, Set<Class<?>> visited) {
585598
if (asArray) {

spring-core/src/test/java/org/springframework/core/convert/support/GenericConversionServiceTests.java

Lines changed: 90 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
* @author Keith Donald
5858
* @author Juergen Hoeller
5959
* @author Phillip Webb
60+
* @author David Haraburda
6061
*/
6162
public class GenericConversionServiceTests {
6263

@@ -758,6 +759,20 @@ public void testEnumWithInterfaceToStringConversion() {
758759
assertEquals("1", result);
759760
}
760761

762+
@Test
763+
public void testStringToEnumWithInterfaceConversion() {
764+
conversionService.addConverterFactory(new StringToEnumConverterFactory());
765+
conversionService.addConverterFactory(new StringToMyEnumInterfaceConverterFactory());
766+
assertEquals(MyEnum.A, conversionService.convert("1", MyEnum.class));
767+
}
768+
769+
@Test
770+
public void testStringToEnumWithBaseInterfaceConversion() {
771+
conversionService.addConverterFactory(new StringToEnumConverterFactory());
772+
conversionService.addConverterFactory(new StringToMyEnumBaseInterfaceConverterFactory());
773+
assertEquals(MyEnum.A, conversionService.convert("base1", MyEnum.class));
774+
}
775+
761776
@Test
762777
public void convertNullAnnotatedStringToString() throws Exception {
763778
DefaultConversionService.addDefaultConverters(conversionService);
@@ -930,19 +945,34 @@ public int getNestedMatchAttempts() {
930945
}
931946
}
932947

948+
interface MyEnumBaseInterface {
949+
String getBaseCode();
950+
}
933951

934-
interface MyEnumInterface {
935-
952+
interface MyEnumInterface extends MyEnumBaseInterface {
936953
String getCode();
937954
}
938955

939956
public static enum MyEnum implements MyEnumInterface {
940957

941-
A {
942-
@Override
943-
public String getCode() {
944-
return "1";
945-
}
958+
A("1"),
959+
B("2"),
960+
C("3");
961+
962+
private String code;
963+
964+
MyEnum(String code) {
965+
this.code = code;
966+
}
967+
968+
@Override
969+
public String getCode() {
970+
return code;
971+
}
972+
973+
@Override
974+
public String getBaseCode() {
975+
return "base" + code;
946976
}
947977
}
948978

@@ -971,6 +1001,59 @@ public String convert(T source) {
9711001
}
9721002
}
9731003

1004+
private static class StringToMyEnumInterfaceConverterFactory implements ConverterFactory<String, MyEnumInterface> {
1005+
1006+
@SuppressWarnings("unchecked")
1007+
public <T extends MyEnumInterface> Converter<String, T> getConverter(Class<T> targetType) {
1008+
return new StringToMyEnumInterfaceConverter(targetType);
1009+
}
1010+
1011+
private static class StringToMyEnumInterfaceConverter<T extends Enum<?> & MyEnumInterface> implements Converter<String, T> {
1012+
private final Class<T> enumType;
1013+
1014+
public StringToMyEnumInterfaceConverter(Class<T> enumType) {
1015+
this.enumType = enumType;
1016+
}
1017+
1018+
public T convert(String source) {
1019+
for (T value : enumType.getEnumConstants()) {
1020+
if (value.getCode().equals(source)) {
1021+
return value;
1022+
}
1023+
}
1024+
return null;
1025+
}
1026+
}
1027+
1028+
}
1029+
1030+
private static class StringToMyEnumBaseInterfaceConverterFactory implements ConverterFactory<String, MyEnumBaseInterface> {
1031+
1032+
@SuppressWarnings("unchecked")
1033+
public <T extends MyEnumBaseInterface> Converter<String, T> getConverter(Class<T> targetType) {
1034+
return new StringToMyEnumBaseInterfaceConverter(targetType);
1035+
}
1036+
1037+
private static class StringToMyEnumBaseInterfaceConverter<T extends Enum<?> & MyEnumBaseInterface> implements Converter<String, T> {
1038+
private final Class<T> enumType;
1039+
1040+
public StringToMyEnumBaseInterfaceConverter(Class<T> enumType) {
1041+
this.enumType = enumType;
1042+
}
1043+
1044+
public T convert(String source) {
1045+
for (T value : enumType.getEnumConstants()) {
1046+
if (value.getBaseCode().equals(source)) {
1047+
return value;
1048+
}
1049+
}
1050+
return null;
1051+
}
1052+
}
1053+
1054+
}
1055+
1056+
9741057
public static class MyStringToStringCollectionConverter implements Converter<String, Collection<String>> {
9751058

9761059
@Override

0 commit comments

Comments
 (0)