From 643a9a047d6bf807b3b682565a2b54a378c55e5c Mon Sep 17 00:00:00 2001 From: MoonFruit Date: Tue, 4 Mar 2025 17:36:49 +0800 Subject: [PATCH] Ensure ConversionService.canConvert(Enum) no longer throws an exception Signed-off-by: MoonFruit --- .../core/convert/support/ConversionUtils.java | 10 +++++++--- .../support/IntegerToEnumConverterFactory.java | 14 ++++++++++++-- .../support/StringToEnumConverterFactory.java | 14 ++++++++++++-- .../support/GenericConversionServiceTests.java | 14 ++++++++++++++ 4 files changed, 45 insertions(+), 7 deletions(-) diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/ConversionUtils.java b/spring-core/src/main/java/org/springframework/core/convert/support/ConversionUtils.java index e6e0a7c2a29e..91da300fdd68 100644 --- a/spring-core/src/main/java/org/springframework/core/convert/support/ConversionUtils.java +++ b/spring-core/src/main/java/org/springframework/core/convert/support/ConversionUtils.java @@ -22,7 +22,6 @@ import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.converter.GenericConverter; -import org.springframework.util.Assert; import org.springframework.util.ClassUtils; /** @@ -71,12 +70,17 @@ public static boolean canConvertElements(@Nullable TypeDescriptor sourceElementT return false; } - public static Class getEnumType(Class targetType) { + /** + * Resolve the enum type for the supplied target type. + * @param targetType the target type for which to resolve the enum type + * @return the resolved enum type, or {@code null} if the supplied target type + * does not refer to an enum + */ + public static @Nullable Class resolveEnumType(Class targetType) { Class enumType = targetType; while (enumType != null && !enumType.isEnum()) { enumType = enumType.getSuperclass(); } - Assert.notNull(enumType, () -> "The target type " + targetType.getName() + " does not refer to an enum"); return enumType; } diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/IntegerToEnumConverterFactory.java b/spring-core/src/main/java/org/springframework/core/convert/support/IntegerToEnumConverterFactory.java index 306400cdc36d..aafde995ca14 100644 --- a/spring-core/src/main/java/org/springframework/core/convert/support/IntegerToEnumConverterFactory.java +++ b/spring-core/src/main/java/org/springframework/core/convert/support/IntegerToEnumConverterFactory.java @@ -18,8 +18,11 @@ import org.jspecify.annotations.Nullable; +import org.springframework.core.convert.TypeDescriptor; +import org.springframework.core.convert.converter.ConditionalConverter; import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.converter.ConverterFactory; +import org.springframework.util.Assert; /** * Converts from an Integer to a {@link java.lang.Enum} by calling {@link Class#getEnumConstants()}. @@ -29,11 +32,18 @@ * @since 4.3 */ @SuppressWarnings({"rawtypes", "unchecked"}) -final class IntegerToEnumConverterFactory implements ConverterFactory { +final class IntegerToEnumConverterFactory implements ConverterFactory, ConditionalConverter { @Override public Converter getConverter(Class targetType) { - return new IntegerToEnum(ConversionUtils.getEnumType(targetType)); + Class enumType = ConversionUtils.resolveEnumType(targetType); + Assert.notNull(enumType, () -> "The target type " + targetType.getName() + " does not refer to an enum"); + return new IntegerToEnum(enumType); + } + + @Override + public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) { + return (ConversionUtils.resolveEnumType(targetType.getType()) != null); } diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/StringToEnumConverterFactory.java b/spring-core/src/main/java/org/springframework/core/convert/support/StringToEnumConverterFactory.java index 93c32d6d7005..917750ef881f 100644 --- a/spring-core/src/main/java/org/springframework/core/convert/support/StringToEnumConverterFactory.java +++ b/spring-core/src/main/java/org/springframework/core/convert/support/StringToEnumConverterFactory.java @@ -18,8 +18,11 @@ import org.jspecify.annotations.Nullable; +import org.springframework.core.convert.TypeDescriptor; +import org.springframework.core.convert.converter.ConditionalConverter; import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.converter.ConverterFactory; +import org.springframework.util.Assert; /** * Converts from a String to a {@link java.lang.Enum} by calling {@link Enum#valueOf(Class, String)}. @@ -29,11 +32,18 @@ * @since 3.0 */ @SuppressWarnings({"rawtypes", "unchecked"}) -final class StringToEnumConverterFactory implements ConverterFactory { +final class StringToEnumConverterFactory implements ConverterFactory, ConditionalConverter { @Override public Converter getConverter(Class targetType) { - return new StringToEnum(ConversionUtils.getEnumType(targetType)); + Class enumType = ConversionUtils.resolveEnumType(targetType); + Assert.notNull(enumType, () -> "The target type " + targetType.getName() + " does not refer to an enum"); + return new StringToEnum(enumType); + } + + @Override + public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) { + return (ConversionUtils.resolveEnumType(targetType.getType()) != null); } diff --git a/spring-core/src/test/java/org/springframework/core/convert/support/GenericConversionServiceTests.java b/spring-core/src/test/java/org/springframework/core/convert/support/GenericConversionServiceTests.java index 4d8a21bdf5d9..6150edb2c3aa 100644 --- a/spring-core/src/test/java/org/springframework/core/convert/support/GenericConversionServiceTests.java +++ b/spring-core/src/test/java/org/springframework/core/convert/support/GenericConversionServiceTests.java @@ -503,6 +503,20 @@ void stringToEnumWithBaseInterfaceConversion() { assertThat(conversionService.convert("base1", MyEnum.class)).isEqualTo(MyEnum.A); } + @Test // gh-34532 + void canConvertToEnumDoesNotThrowForNonEnumTargetType() { + conversionService.addConverterFactory(new StringToEnumConverterFactory()); + conversionService.addConverterFactory(new IntegerToEnumConverterFactory()); + + assertThat(conversionService.canConvert(String.class, Enum.class)).isFalse(); + assertThat(conversionService.canConvert(Integer.class, Enum.class)).isFalse(); + + assertThat(conversionService.canConvert(String.class, MyEnum.class)).isTrue(); + assertThat(conversionService.canConvert(Integer.class, MyEnum.class)).isTrue(); + assertThat(conversionService.convert("A", MyEnum.class)).isEqualTo(MyEnum.A); + assertThat(conversionService.convert(0, MyEnum.class)).isEqualTo(MyEnum.A); + } + @Test void convertNullAnnotatedStringToString() throws Exception { String source = null;