package water.util;
import water.H2O;
import water.nbhm.NonBlockingHashMap;
import java.util.Arrays;
import java.util.HashMap;
/**
* Utilities to deal with Java enums.
*/
public class EnumUtils {
/**
* Memoizer for {@link #valueOf(Class, String)}
*/
private static NonBlockingHashMap<Class<? extends Enum>, NonBlockingHashMap<String, Enum>>
enumMappings = new NonBlockingHashMap<>(150);
/**
* Return an array of Strings of all the enum levels.
* <p>
* Taken from http://stackoverflow.com/questions/13783295/getting-all-names-in-an-enum-as-a-string.
*/
public static String[] getNames(Class<? extends Enum<?>> e) {
return Arrays.toString(e.getEnumConstants()).replaceAll("^.|.$", "").split(", ");
}
/**
* This is like Enum.valueOf() only better: it matches enum constants very loosely: case-insensitive and disregarding
* any non-alphanumeric characters (e.g. "_"). For example, if Enum declares constant LOG_NORMAL, then all of the
* following would also match to this constant:
* log_normal, logNormal, LogNormal, __LoGnOrmaL___, "LogNormal", $Log.Normal, lognormal, etc.
*
* @param <T> The enum type whose constant is to be returned
* @param clz the {@code Class} object of the enum type from which to return a constant
* @param name the name of the constant to return
* @return the enum constant of the specified enum type with the specified name
*/
public static <T extends Enum<T>> T valueOf(Class<T> clz, String name) {
NonBlockingHashMap<String, Enum> map = enumMappings.get(clz);
if (map == null) {
T[] enumValues = clz.getEnumConstants();
map = new NonBlockingHashMap<>(enumValues.length * 2);
for (Enum item : enumValues) {
String origName = item.name();
String unifName = origName.toUpperCase().replaceAll("[^0-9A-Z]", "");
if (map.containsKey(origName)) throw H2O.fail("Unexpected key " + origName + " in enum " + clz);
if (map.containsKey(unifName)) throw H2O.fail("Non-unique key " + unifName + " in enum " + clz);
map.put(origName, item);
map.put(unifName, item);
}
// Put the map into {enumMappings} no sooner than it is fully constructed. If there are multiple threads
// accessing the same enum mapping, then it is possible they'll begin constructing the map simultaneously and
// then overwrite each other's results. This is harmless.
// However it would be an error to put the {map} into {enumMappings} before it is filled, because then the
// other thread would think that the map is complete, and may not find some of the legitimate keys.
enumMappings.put(clz, map);
}
Enum value = map.get(name);
if (value == null && name != null) {
String unifName = name.toUpperCase().replaceAll("[^0-9A-Z]", "");
value = map.get(unifName);
// Save the mapping name -> value, so that subsequent requests with the same name will be faster.
if (value != null)
map.put(name, value);
}
if (value == null)
throw new IllegalArgumentException("No enum constant " + clz.getCanonicalName() + "." + name);
if (name == null)
throw new NullPointerException("Name is null");
// noinspection unchecked
return (T) value;
}
}