StringToEnumConverter.java
package fr.sii.ogham.core.convert;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Documented;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import fr.sii.ogham.core.exception.convert.ConversionException;
/**
* Converts a string to a {@link Enum} value. It uses
* {@link Enum#valueOf(Class, String)} to get the enum value by default.
*
* <p>
* If the enum is annotated with {@link FactoryMethod}, the name of the static
* method is used to create the enum instance:
*
* <pre>
* {@code
* {@literal @}FactoryMethod(name="fromNameOrValue")
* enum MyEnum {
* A("1"),
* B("2");
*
* private final String value;
* MyEnum(String value) {
* this.value = value;
* }
*
* public String value() {
* return value;
* }
*
* public static MyEnum fromNameOrValue(String nameOrValue) {
* for (MyEnum e : values()) {
* if (e.value().equals(nameOrValue))
* return e;
* if (e.name().equals(nameOrValue))
* return e;
* throw new IllegalArgumentException("Unknown name or value: "+nameOrValue);
* }
* }
* }
* }
* </pre>
*
*
* @author Aurélien Baudet
*
*/
@SuppressWarnings("squid:S1192")
public class StringToEnumConverter implements SupportingConverter {
/**
* Idicates which method to use to instantiate the {@link Enum} instead of
* using {@link Enum#valueOf(Class, String)}.
*
* @author Aurélien Baudet
*/
@Target(TYPE)
@Retention(RUNTIME)
@Documented
@Inherited
public static @interface FactoryMethod {
/**
* The name of the factory method to use for instantiating the
* {@link Enum} instead of {@link Enum#valueOf(Class, String)}.
*
* @return the name of the factory method
*/
String name();
}
@Override
public <T> T convert(Object source, Class<T> targetType) {
String name = (String) source;
if (name == null || name.isEmpty()) {
return null;
}
FactoryMethod annotation = targetType.getAnnotation(FactoryMethod.class);
if (annotation != null) {
return create(targetType, annotation.name(), name);
}
return valueOf(targetType, name);
}
@Override
public boolean supports(Class<?> sourceType, Class<?> targetType) {
return String.class.isAssignableFrom(sourceType) && Enum.class.isAssignableFrom(targetType);
}
@SuppressWarnings("unchecked")
private static <T> T create(Class<T> targetType, String methodName, String value) {
try {
Method method = targetType.getMethod(methodName, String.class);
return (T) method.invoke(null, value);
} catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
throw new ConversionException("Failed to convert " + value + " into Enum using custom factory method", e);
}
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private static <T> T valueOf(Class<T> targetType, String name) {
try {
return (T) Enum.valueOf((Class<Enum>) targetType, name.trim());
} catch (IllegalArgumentException e) {
throw new ConversionException("Failed to convert " + name + " into Enum", e);
}
}
}