Polish lenient enum converter

See gh-17798
pull/17947/head
Phillip Webb 5 years ago
parent b43827d626
commit 6270f5008d

@ -116,7 +116,7 @@ public class ApplicationConversionService extends FormattingConversionService {
registry.addConverter(new StringToDataSizeConverter()); registry.addConverter(new StringToDataSizeConverter());
registry.addConverter(new NumberToDataSizeConverter()); registry.addConverter(new NumberToDataSizeConverter());
registry.addConverterFactory(new LenientStringToEnumConverterFactory()); registry.addConverterFactory(new LenientStringToEnumConverterFactory());
registry.addConverterFactory(new BooleanToEnumConverterFactory()); registry.addConverterFactory(new LenientBooleanToEnumConverterFactory());
} }
/** /**

@ -16,35 +16,12 @@
package org.springframework.boot.convert; 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 * Converter to support mapping of YAML style {@code "false"} and {@code "true"} to enums
* {@code ON} and {@code OFF}. * {@code ON} and {@code OFF}.
* *
* @author Madhura Bhave * @author Madhura Bhave
*/ */
@SuppressWarnings("rawtypes") final class LenientBooleanToEnumConverterFactory extends LenientToEnumConverterFactory<Boolean> {
final class BooleanToEnumConverterFactory extends AbstractTypeToEnumConverterFactory<Boolean> {
@Override
<E extends Enum> Converter<Boolean, E> getTypeToEnumConverter(Class<E> targetType) {
return new BooleanToEnum<>(targetType);
}
private class BooleanToEnum<T extends Enum> implements Converter<Boolean, T> {
private final Class<T> enumType;
BooleanToEnum(Class<T> enumType) {
this.enumType = enumType;
}
@Override
public T convert(Boolean source) {
return findEnum(Boolean.toString(source), this.enumType);
}
}
} }

@ -16,8 +16,6 @@
package org.springframework.boot.convert; 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. * Converts from a String to a {@link java.lang.Enum} with lenient conversion rules.
* Specifically: * Specifically:
@ -30,36 +28,6 @@ import org.springframework.core.convert.converter.Converter;
* *
* @author Phillip Webb * @author Phillip Webb
*/ */
@SuppressWarnings({ "unchecked", "rawtypes" }) final class LenientStringToEnumConverterFactory extends LenientToEnumConverterFactory<String> {
final class LenientStringToEnumConverterFactory extends AbstractTypeToEnumConverterFactory<String> {
@Override
<E extends Enum> Converter<String, E> getTypeToEnumConverter(Class<E> targetType) {
return new StringToEnum<>(targetType);
}
private class StringToEnum<T extends Enum> implements Converter<String, T> {
private final Class<T> enumType;
StringToEnum(Class<T> 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);
}
}
}
} }

@ -18,7 +18,6 @@ package org.springframework.boot.convert;
import java.util.Collections; import java.util.Collections;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@ -37,7 +36,7 @@ import org.springframework.util.MultiValueMap;
* @author Madhura Bhave * @author Madhura Bhave
*/ */
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
abstract class AbstractTypeToEnumConverterFactory<T> implements ConverterFactory<T, Enum<?>> { abstract class LenientToEnumConverterFactory<T> implements ConverterFactory<T, Enum<?>> {
private static Map<String, List<String>> ALIASES; private static Map<String, List<String>> ALIASES;
@ -49,36 +48,49 @@ abstract class AbstractTypeToEnumConverterFactory<T> implements ConverterFactory
} }
@Override @Override
@SuppressWarnings("unchecked")
public <E extends Enum<?>> Converter<T, E> getConverter(Class<E> targetType) { public <E extends Enum<?>> Converter<T, E> getConverter(Class<E> targetType) {
Class<?> enumType = targetType; Class<?> enumType = targetType;
while (enumType != null && !enumType.isEnum()) { while (enumType != null && !enumType.isEnum()) {
enumType = enumType.getSuperclass(); enumType = enumType.getSuperclass();
} }
Assert.notNull(enumType, () -> "The target type " + targetType.getName() + " does not refer to an enum"); Assert.notNull(enumType, () -> "The target type " + targetType.getName() + " does not refer to an enum");
return getTypeToEnumConverter(targetType); return new LenientToEnumConverter<E>((Class<E>) enumType);
} }
abstract <E extends Enum> Converter<T, E> getTypeToEnumConverter(Class<E> targetType);
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
<E extends Enum> E findEnum(String source, Class<E> enumType) { private class LenientToEnumConverter<E extends Enum> implements Converter<T, E> {
Map<String, E> candidates = new LinkedHashMap<>();
for (E candidate : (Set<E>) EnumSet.allOf(enumType)) { private final Class<E> enumType;
candidates.put(getCanonicalName(candidate.name()), candidate);
LenientToEnumConverter(Class<E> enumType) {
this.enumType = enumType;
} }
String name = getCanonicalName(source);
E result = candidates.get(name); @Override
if (result != null) { public E convert(T source) {
return result; String value = source.toString().trim();
if (value.isEmpty()) {
return null;
} }
for (String alias : ALIASES.getOrDefault(name, Collections.emptyList())) { try {
result = candidates.get(alias); return (E) Enum.valueOf(this.enumType, value);
if (result != null) { }
return result; 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<String> aliases = ALIASES.getOrDefault(name, Collections.emptyList());
for (E candidate : (Set<E>) 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) { private String getCanonicalName(String name) {
@ -89,3 +101,5 @@ abstract class AbstractTypeToEnumConverterFactory<T> implements ConverterFactory
} }
} }
}

@ -25,11 +25,11 @@ import org.springframework.core.convert.ConversionService;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
/** /**
* Tests for {@link BooleanToEnumConverterFactory}. * Tests for {@link LenientBooleanToEnumConverterFactory}.
* *
* @author Madhura Bhave * @author Madhura Bhave
*/ */
class BooleanToEnumConverterFactoryTests { class LenientBooleanToEnumConverterFactoryTests {
@ConversionServiceTest @ConversionServiceTest
void convertFromBooleanToEnumWhenShouldConvertValue(ConversionService conversionService) { void convertFromBooleanToEnumWhenShouldConvertValue(ConversionService conversionService) {
@ -41,7 +41,7 @@ class BooleanToEnumConverterFactoryTests {
static Stream<? extends Arguments> conversionServices() { static Stream<? extends Arguments> conversionServices() {
return ConversionServiceArguments return ConversionServiceArguments
.with((service) -> service.addConverterFactory(new BooleanToEnumConverterFactory())); .with((service) -> service.addConverterFactory(new LenientBooleanToEnumConverterFactory()));
} }
enum TestOnOffEnum { enum TestOnOffEnum {
Loading…
Cancel
Save