EnumFormatter.java

package com.gh.mygreen.xlsmapper.textformatter;

import java.lang.reflect.Method;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional;

import com.gh.mygreen.xlsmapper.util.ArgUtils;

/**
 * 列挙型のフォーマッタ。
 *
 * @since 2.0
 * @author T.TSUCHIE
 *
 * @param <T> 列挙型
 */
public class EnumFormatter<T extends Enum<T>> implements TextFormatter<T> {

    private final Class<? extends Enum<?>> type;

    private final boolean ignoreCase;

    private final Optional<Method> aliasMethod;

    /**
     * キーが列挙型、値が文字列のマップ。
     */
    private final Map<Enum<?>, String> toStringMap;

    /**
     * キーが文字列、値が列挙型のマップ
     */
    private final Map<String, Enum<?>> toObjectMap;

    public EnumFormatter(final Class<T> type, final boolean ignoreCase) {
        ArgUtils.notNull(type, "type");

        this.type = type;
        this.ignoreCase = ignoreCase;
        this.aliasMethod = Optional.empty();

        this.toStringMap = createToStringMap(type);
        this.toObjectMap = createToObjectMap(type, ignoreCase);
    }

    public EnumFormatter(final Class<T> type, final boolean ignoreCase, final String alias) {
        ArgUtils.notNull(type, "type");
        ArgUtils.notEmpty(alias, "alias");

        this.type = type;
        this.ignoreCase = ignoreCase;
        this.aliasMethod = Optional.of(getEnumAliasMethod(type, alias));

        this.toStringMap = createToStringMap(type, alias);
        this.toObjectMap = createToObjectMap(type, ignoreCase, alias);

    }

    public EnumFormatter(final Class<T> type) {
        this(type, false);
    }

    public EnumFormatter(final Class<T> type, final String alias) {
        this(type, false, alias);
    }

    private static <T extends Enum<T>> Method getEnumAliasMethod(final Class<T> enumClass, final String alias) {

        try {
            final Method method = enumClass.getMethod(alias);
            method.setAccessible(true);
            return method;

        } catch (ReflectiveOperationException e) {
            throw new IllegalArgumentException(String.format("not found method '%s'", alias), e);
        }

    }

    private static <T extends Enum<T>> Map<Enum<?>, String> createToStringMap(final Class<T> enumClass) {

        final EnumSet<T> set = EnumSet.allOf(enumClass);

        final Map<Enum<?>, String> map = new LinkedHashMap<>();
        for(T e : set) {
            map.put(e, e.name());

        }

        return Collections.unmodifiableMap(map);
    }

    private static <T extends Enum<T>> Map<Enum<?>, String> createToStringMap(final Class<T> enumClass, final String alias) {

        final Method method = getEnumAliasMethod(enumClass, alias);

        final Map<Enum<?>, String> map = new LinkedHashMap<>();
        try {
            final EnumSet<T> set = EnumSet.allOf(enumClass);
            for(T e : set) {
                Object returnValue = method.invoke(e);
                map.put(e, returnValue.toString());

            }

        } catch(ReflectiveOperationException e) {
            throw new RuntimeException("fail get enum value.", e);
        }

        return Collections.unmodifiableMap(map);
    }

    private static <T extends Enum<T>> Map<String, Enum<?>> createToObjectMap(final Class<T> enumClass, final boolean ignoreCase) {

        final EnumSet<T> set = EnumSet.allOf(enumClass);

        final Map<String, Enum<?>> map = new LinkedHashMap<>();
        for(T e : set) {
            final String key = (ignoreCase ? e.name().toLowerCase() : e.name());
            map.put(key, e);

        }

        return Collections.unmodifiableMap(map);
    }

    private static <T extends Enum<T>> Map<String, Enum<?>> createToObjectMap(final Class<T> enumClass, final boolean ignoreCase,
            final String alias) {

        final Method method = getEnumAliasMethod(enumClass, alias);

        final Map<String, Enum<?>> map = new LinkedHashMap<>();
        try {

            EnumSet<T> set = EnumSet.allOf(enumClass);
            for(T e : set) {
                Object returnValue = method.invoke(e);
                final String key = (ignoreCase ? returnValue.toString().toLowerCase() : returnValue.toString());

                map.put(key, e);
            }

        } catch(ReflectiveOperationException e) {
            throw new RuntimeException(e);
        }

        return Collections.unmodifiableMap(map);
    }

    @SuppressWarnings("unchecked")
    @Override
    public T parse(final String text) throws TextParseException {
        final String keyText = ignoreCase ? text.toLowerCase() : text;
        final Optional<T> obj = Optional.ofNullable((T)toObjectMap.get(keyText));

        return obj.orElseThrow(() -> {

            final Map<String, Object> vars = new HashMap<>();
            vars.put("type", getType().getName());
            vars.put("ignoreCase", isIgnoreCase());

            getAliasMethod().ifPresent(method -> vars.put("alias", method.getName()));
            vars.put("enums", getToStringMap().values());

            return new TextParseException(text, type, vars);
        });
    }

    @Override
    public String format(final T value) {
        final Optional<String> text = Optional.ofNullable(toStringMap.get(value));
        return text.orElseGet(() -> value.toString());
    }

    public Class<? extends Enum<?>> getType() {
        return type;
    }

    public boolean isIgnoreCase() {
        return ignoreCase;
    }

    public Optional<Method> getAliasMethod() {
        return aliasMethod;
    }

    public Map<Enum<?>, String> getToStringMap() {
        return toStringMap;
    }

    public Map<String, Enum<?>> getToObjectMap() {
        return toObjectMap;
    }

}