octavian-nita
8/18/2016 - 11:58 AM

"Dynamic" Enums

"Dynamic" Enums

import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;

import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;

import static org.slf4j.LoggerFactory.getLogger;

/**
 * @author Octavian Theodor NITA (https://github.com/octavian-nita/)
 * @version 1.0, Aug 18, 2016
 */
public abstract class TypeValue<TV extends TypeValue<TV>> implements Comparable<TV>, Serializable {

    private static final Map<Class<?>, Map<String, ? super Object>> valueCache = new HashMap<>();

    private final String value;

    private final String description;

    protected TypeValue(String value) {
        this.value = required(value);
        this.description = createDescription(value);
    }

    /**
     * Override when a description can be inferred based on the provided value.
     *
     * @return description computed based on the provided <code>value</code>; <code>value</code> by default
     */
    protected String createDescription(String value) { return value; }

    /**
     * Factory method ensuring that only one instance of each possible value is ever created.
     *
     * @param value if <code>null</code>, this method returns <code>null</code>
     */
    public static <T> T valueOf(Class<T> klass, String value) {
        if (value == null) {
            return null;
        }

        Map<String, ? super Object> tvCache = valueCache.get(klass);
        if (tvCache == null) {
            valueCache.put(klass, tvCache = new TreeMap<>()); // (almost) free alphabetical ordering of values by name
        }

        T typeValue = klass.cast(tvCache.get(value));
        if (typeValue == null) {
            try {
                Constructor<T> ctor = klass.getDeclaredConstructor(String.class);
                if (!ctor.isAccessible()) {
                    ctor.setAccessible(true);
                }
                tvCache.put(value, typeValue = ctor.newInstance(value));
            } catch (Throwable throwable) {
                getLogger(TypeValue.class).error("cannot instantiate class " + klass.getName(), throwable);
            }
        }

        return typeValue;
    }

    public String getValue() { return value; }

    public String getDescription() { return description; }

    @Override
    public String toString() {
        return "TypeValue{" +
               "value='" + value + '\'' +
               ", description='" + description + '\'' +
               '}';
    }

    @Override
    public int compareTo(TV o) { return value.compareTo(o.getValue()); }

    @Override
    public int hashCode() { return new HashCodeBuilder().append(value).toHashCode(); }

    @Override
    public boolean equals(Object that) {
        return this == that || that != null && that instanceof TypeValue &&
                               new EqualsBuilder().append(value, ((TypeValue) that).value).isEquals();
    }
}