From 6270f5008d9187fdeb0bc1b5a4c7c2c89793c6a0 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Thu, 22 Aug 2019 14:42:07 -0700 Subject: [PATCH] Polish lenient enum converter See gh-17798 --- .../convert/ApplicationConversionService.java | 2 +- ...LenientBooleanToEnumConverterFactory.java} | 25 +------- .../LenientStringToEnumConverterFactory.java | 34 +--------- ...ava => LenientToEnumConverterFactory.java} | 64 +++++++++++-------- ...ntBooleanToEnumConverterFactoryTests.java} | 6 +- 5 files changed, 45 insertions(+), 86 deletions(-) rename spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/{BooleanToEnumConverterFactory.java => LenientBooleanToEnumConverterFactory.java} (56%) rename spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/{AbstractTypeToEnumConverterFactory.java => LenientToEnumConverterFactory.java} (57%) rename spring-boot-project/spring-boot/src/test/java/org/springframework/boot/convert/{BooleanToEnumConverterFactoryTests.java => LenientBooleanToEnumConverterFactoryTests.java} (88%) diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/ApplicationConversionService.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/ApplicationConversionService.java index 0b381d2efe..bce598e43b 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/ApplicationConversionService.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/ApplicationConversionService.java @@ -116,7 +116,7 @@ public class ApplicationConversionService extends FormattingConversionService { registry.addConverter(new StringToDataSizeConverter()); registry.addConverter(new NumberToDataSizeConverter()); registry.addConverterFactory(new LenientStringToEnumConverterFactory()); - registry.addConverterFactory(new BooleanToEnumConverterFactory()); + registry.addConverterFactory(new LenientBooleanToEnumConverterFactory()); } /** diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/BooleanToEnumConverterFactory.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/LenientBooleanToEnumConverterFactory.java similarity index 56% rename from spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/BooleanToEnumConverterFactory.java rename to spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/LenientBooleanToEnumConverterFactory.java index 4ce9486e9a..6053a50a8a 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/BooleanToEnumConverterFactory.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/LenientBooleanToEnumConverterFactory.java @@ -16,35 +16,12 @@ package org.springframework.boot.convert; -import org.springframework.core.convert.converter.Converter; - /** * Converter to support mapping of YAML style {@code "false"} and {@code "true"} to enums * {@code ON} and {@code OFF}. * * @author Madhura Bhave */ -@SuppressWarnings("rawtypes") -final class BooleanToEnumConverterFactory extends AbstractTypeToEnumConverterFactory { - - @Override - Converter getTypeToEnumConverter(Class targetType) { - return new BooleanToEnum<>(targetType); - } - - private class BooleanToEnum implements Converter { - - private final Class enumType; - - BooleanToEnum(Class enumType) { - this.enumType = enumType; - } - - @Override - public T convert(Boolean source) { - return findEnum(Boolean.toString(source), this.enumType); - } - - } +final class LenientBooleanToEnumConverterFactory extends LenientToEnumConverterFactory { } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/LenientStringToEnumConverterFactory.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/LenientStringToEnumConverterFactory.java index 145d7afa90..ce433fc764 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/LenientStringToEnumConverterFactory.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/LenientStringToEnumConverterFactory.java @@ -16,8 +16,6 @@ package org.springframework.boot.convert; -import org.springframework.core.convert.converter.Converter; - /** * Converts from a String to a {@link java.lang.Enum} with lenient conversion rules. * Specifically: @@ -30,36 +28,6 @@ import org.springframework.core.convert.converter.Converter; * * @author Phillip Webb */ -@SuppressWarnings({ "unchecked", "rawtypes" }) -final class LenientStringToEnumConverterFactory extends AbstractTypeToEnumConverterFactory { - - @Override - Converter getTypeToEnumConverter(Class targetType) { - return new StringToEnum<>(targetType); - } - - private class StringToEnum implements Converter { - - private final Class enumType; - - StringToEnum(Class enumType) { - this.enumType = enumType; - } - - @Override - public T convert(String source) { - if (source.isEmpty()) { - return null; - } - source = source.trim(); - try { - return (T) Enum.valueOf(this.enumType, source); - } - catch (Exception ex) { - return findEnum(source, this.enumType); - } - } - - } +final class LenientStringToEnumConverterFactory extends LenientToEnumConverterFactory { } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/AbstractTypeToEnumConverterFactory.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/LenientToEnumConverterFactory.java similarity index 57% rename from spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/AbstractTypeToEnumConverterFactory.java rename to spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/LenientToEnumConverterFactory.java index ede8b3f134..600e8657e8 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/AbstractTypeToEnumConverterFactory.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/LenientToEnumConverterFactory.java @@ -18,7 +18,6 @@ package org.springframework.boot.convert; import java.util.Collections; import java.util.EnumSet; -import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; @@ -37,7 +36,7 @@ import org.springframework.util.MultiValueMap; * @author Madhura Bhave */ @SuppressWarnings("rawtypes") -abstract class AbstractTypeToEnumConverterFactory implements ConverterFactory> { +abstract class LenientToEnumConverterFactory implements ConverterFactory> { private static Map> ALIASES; @@ -49,43 +48,58 @@ abstract class AbstractTypeToEnumConverterFactory implements ConverterFactory } @Override + @SuppressWarnings("unchecked") public > Converter getConverter(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 getTypeToEnumConverter(targetType); + return new LenientToEnumConverter((Class) enumType); } - abstract Converter getTypeToEnumConverter(Class targetType); - @SuppressWarnings("unchecked") - E findEnum(String source, Class enumType) { - Map candidates = new LinkedHashMap<>(); - for (E candidate : (Set) EnumSet.allOf(enumType)) { - candidates.put(getCanonicalName(candidate.name()), candidate); + private class LenientToEnumConverter implements Converter { + + private final Class enumType; + + LenientToEnumConverter(Class enumType) { + this.enumType = enumType; } - String name = getCanonicalName(source); - E result = candidates.get(name); - if (result != null) { - return result; - } - for (String alias : ALIASES.getOrDefault(name, Collections.emptyList())) { - result = candidates.get(alias); - if (result != null) { - return result; + + @Override + public E convert(T source) { + String value = source.toString().trim(); + if (value.isEmpty()) { + return null; + } + try { + return (E) Enum.valueOf(this.enumType, value); + } + catch (Exception ex) { + return findEnum(value); } } - throw new IllegalArgumentException("No enum constant " + enumType.getCanonicalName() + "." + source); - } + private E findEnum(String value) { + String name = getCanonicalName(value); + List aliases = ALIASES.getOrDefault(name, Collections.emptyList()); + for (E candidate : (Set) EnumSet.allOf(this.enumType)) { + String candidateName = getCanonicalName(candidate.name()); + if (name.equals(candidateName) || aliases.contains(candidateName)) { + return candidate; + } + } + throw new IllegalArgumentException("No enum constant " + this.enumType.getCanonicalName() + "." + value); + } + + private String getCanonicalName(String name) { + StringBuilder canonicalName = new StringBuilder(name.length()); + name.chars().filter(Character::isLetterOrDigit).map(Character::toLowerCase) + .forEach((c) -> canonicalName.append((char) c)); + return canonicalName.toString(); + } - private String getCanonicalName(String name) { - StringBuilder canonicalName = new StringBuilder(name.length()); - name.chars().filter(Character::isLetterOrDigit).map(Character::toLowerCase) - .forEach((c) -> canonicalName.append((char) c)); - return canonicalName.toString(); } } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/convert/BooleanToEnumConverterFactoryTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/convert/LenientBooleanToEnumConverterFactoryTests.java similarity index 88% rename from spring-boot-project/spring-boot/src/test/java/org/springframework/boot/convert/BooleanToEnumConverterFactoryTests.java rename to spring-boot-project/spring-boot/src/test/java/org/springframework/boot/convert/LenientBooleanToEnumConverterFactoryTests.java index 7fc3d81070..fadac100fd 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/convert/BooleanToEnumConverterFactoryTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/convert/LenientBooleanToEnumConverterFactoryTests.java @@ -25,11 +25,11 @@ import org.springframework.core.convert.ConversionService; import static org.assertj.core.api.Assertions.assertThat; /** - * Tests for {@link BooleanToEnumConverterFactory}. + * Tests for {@link LenientBooleanToEnumConverterFactory}. * * @author Madhura Bhave */ -class BooleanToEnumConverterFactoryTests { +class LenientBooleanToEnumConverterFactoryTests { @ConversionServiceTest void convertFromBooleanToEnumWhenShouldConvertValue(ConversionService conversionService) { @@ -41,7 +41,7 @@ class BooleanToEnumConverterFactoryTests { static Stream conversionServices() { return ConversionServiceArguments - .with((service) -> service.addConverterFactory(new BooleanToEnumConverterFactory())); + .with((service) -> service.addConverterFactory(new LenientBooleanToEnumConverterFactory())); } enum TestOnOffEnum {