package water.util;

import water.H2O;

import java.util.Arrays;
import java.util.HashMap;

/** Utilities to deal with Java enums. */
public class EnumUtils {

  // Helper for .valueOf()
  private static HashMap<Class<? extends Enum>, HashMap<String, Enum>> enumMappings = new HashMap<>();

  /**
   * 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) {
    HashMap<String, Enum> map = enumMappings.get(clz);
    if (map == null) {
      map = new HashMap<>();
      for (Enum item : clz.getEnumConstants()) {
        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.
      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;
  }

}

@@ -31,16 +31,54 @@
 ter.H2O;
+%0Aimport water.nbhm.NonBlockingHashMap;
 %0A%0Aimport
@@ -126,16 +126,19 @@
 ap;%0A%0A/**
+%0A *
  Utiliti
@@ -164,16 +164,17 @@
 a enums.
+%0A
  */%0Apubl
@@ -202,31 +202,66 @@
 %0A  /
-/ Helper for .valueOf()
+**%0A   * Memoizer for %7B@link #valueOf(Class, String)%7D%0A   */
 %0A  p
@@ -274,16 +274,27 @@
  static 
+NonBlocking
 HashMap%3C
@@ -316,16 +316,27 @@
  Enum%3E, 
+NonBlocking
 HashMap%3C
@@ -349,16 +349,22 @@
 , Enum%3E%3E
+%0A     
  enumMap
@@ -371,24 +371,35 @@
 pings = new 
+NonBlocking
 HashMap%3C%3E();
@@ -396,16 +396,19 @@
 shMap%3C%3E(
+150
 );%0A%0A  /*
@@ -1151,16 +1151,17 @@
 aram %3CT%3E
+ 
  The enu
@@ -1216,16 +1216,17 @@
 ram clz 
+ 
 the %7B@co
@@ -1510,16 +1510,27 @@
 ) %7B%0A    
+NonBlocking
 HashMap%3C
@@ -1605,28 +1605,107 @@
     
-map = new HashMap%3C%3E(
+T%5B%5D enumValues = clz.getEnumConstants();%0A      map = new NonBlockingHashMap%3C%3E(enumValues.length * 2
 );%0A 
@@ -1726,38 +1726,26 @@
  item : 
-clz.getEnumConstants()
+enumValues
 ) %7B%0A    

Use NonBlockingHashMap instead of simple HashMap in EnumUtils, since we expect potentially lots of concurrent access. Also provide expected number of entries during initialization of the hashmap