package water.util; import water.AutoBuffer; import water.Freezable; import water.H2O; import water.Iced; import water.nbhm.NonBlockingHashMap; import java.io.Serializable; import java.util.Collection; import java.util.Map; import java.util.Set; /** * * Generalization of standard IcedHashMap (Iced NBHM wrapper) with relaxed restrictions on K/V pairs. * * K/V pairs do not have to follow the same mode, each K/V pair is independent and can be one of: * * String | Freezable -> Integer | String | Freezable | Freezable[]. * * Values are type checked during put operation. * */ public class IcedHashMapGeneric<K, V> extends Iced implements Map<K, V>, Cloneable, Serializable { public boolean isSupportedKeyType(Object K) { return (K instanceof Freezable[] || K instanceof Freezable || K instanceof String); } public boolean isSupportedValType(Object V) { return (V instanceof Freezable[] || V instanceof Freezable || V instanceof String || V instanceof Integer); } public IcedHashMapGeneric(){init();} private transient volatile boolean _write_lock; transient NonBlockingHashMap<K,V> _map; protected Map<K,V> map(){return _map;} public int size() { return map().size(); } public boolean isEmpty() { return map().isEmpty(); } public boolean containsKey(Object key) { return map().containsKey(key); } public boolean containsValue(Object value) { return map().containsValue(value); } public V get(Object key) { return (V)map().get(key); } public V put(K key, V val) { assert !_write_lock; if(!isSupportedKeyType(key)) throw new IllegalArgumentException("given key type is not supported: " + key.getClass().getName()); if(!isSupportedValType(val)) throw new IllegalArgumentException("given val type is not supported: " + val.getClass().getName()); return (V)map().put(key, val); } public V remove(Object key) { assert !_write_lock; return map().remove(key); } public void putAll(Map<? extends K, ? extends V> m) { assert !_write_lock; for(Entry<? extends K, ? extends V> e:m.entrySet()) put(e.getKey(),e.getValue()); } public void clear() { assert !_write_lock; map().clear(); } public Set<K> keySet() { return map().keySet(); } public Collection<V> values() { return map().values(); } public Set<Entry<K, V>> entrySet() { return map().entrySet(); } public boolean equals(Object o) { return map().equals(o); } public int hashCode() { return map().hashCode(); } private boolean isStringKey(int mode){ return mode % 2 == 1; } private boolean isStringVal(int mode){return mode == 1 || mode == 2;} private boolean isFreezeVal(int mode){return mode == 3 || mode == 4;} private boolean isFArrayVal(int mode){return mode == 5 || mode == 6;} private boolean isIntegrVal(int mode){return mode == 7 || mode == 8;} // This comment is stolen from water.parser.Categorical: // // Since this is a *concurrent* hashtable, writing it whilst its being // updated is tricky. If the table is NOT being updated, then all is written // as expected. If the table IS being updated we only promise to write the // Keys that existed at the time the table write began. If elements are // being deleted, they may be written anyways. If the Values are changing, a // random Value is written. public final AutoBuffer write_impl( AutoBuffer ab ) { _write_lock = true; try{ for( Entry<K, V> e : map().entrySet() ) { K key = e.getKey(); assert key != null; V val = e.getValue(); assert val != null; int mode = 0; if (key instanceof String) { if (val instanceof String) { mode = 1; } else if(val instanceof Freezable){ mode = 3; } else if(val instanceof Freezable[]) { mode = 5; } else if( val instanceof Integer ){ mode = 7; } else { throw new IllegalArgumentException("unsupported value class " + val.getClass().getName()); } } else { if(!(key instanceof Iced)) throw new IllegalArgumentException("key must be String or Freezable, got " + key.getClass().getName()); if (val instanceof String) { mode = 2; } else if(val instanceof Freezable) { mode = 4; } else if(val instanceof Freezable[]) { mode = 6; } else if (val instanceof Integer){ mode = 8; } else { throw new IllegalArgumentException("unsupported value class " + val.getClass().getName()); } } ab.put1(mode); // Type of hashmap being serialized // put key if (isStringKey(mode)) ab.putStr((String) key); else ab.put((Freezable) key); // put value if (isStringVal(mode)) ab.putStr((String) val); else if(isFreezeVal(mode)) ab.put((Freezable) val); else if (isFArrayVal(mode)) { ab.put4(((Freezable[]) val).length); for (Freezable v : (Freezable[]) val) ab.put(v); } else if(isIntegrVal(mode)) ab.put4((Integer)val); else throw H2O.fail(); } ab.put1(-1); } catch(Throwable t){ System.err.println("Iced hash map serialization failed! " + t.toString() + ", msg = " + t.getMessage()); t.printStackTrace(); throw H2O.fail("Iced hash map serialization failed!" + t.toString() + ", msg = " + t.getMessage()); } finally { _write_lock = false; } return ab; } protected Map<K, V> init() { return _map = new NonBlockingHashMap<>(); } /** * Helper for serialization - fills the mymap() from K-V pairs in the AutoBuffer object * @param ab Contains the serialized K-V pairs */ public final IcedHashMapGeneric read_impl(AutoBuffer ab) { try { assert map() == null || map().isEmpty(); // Fresh from serializer, no constructor has run Map<K, V> map = init(); K key; V val; int mode; while ((mode = ab.get1()) != -1) { key = isStringKey(mode)?(K)ab.getStr():(K)ab.get(); if (isStringVal(mode)) val = (V)ab.getStr(); else if(isFreezeVal(mode)) val = (V)ab.get(); else if (isFArrayVal(mode)) { Freezable[] vals = new Freezable[ab.get4()]; for (int i = 0; i < vals.length; ++i) vals[i] = ab.get(); val = (V)vals; } else if(isIntegrVal(mode)) val = (V) (new Integer(ab.get4())); else throw H2O.fail(); map.put(key,val); } return this; } catch(Throwable t) { t.printStackTrace(); if (null == t.getCause()) { throw H2O.fail("IcedHashMap deserialization failed! + " + t.toString() + ", msg = " + t.getMessage() + ", cause: null"); } else { throw H2O.fail("IcedHashMap deserialization failed! + " + t.toString() + ", msg = " + t.getMessage() + ", cause: " + t.getCause().toString() + ", cause msg: " + t.getCause().getMessage() + ", cause stacktrace: " + java.util.Arrays.toString(t.getCause().getStackTrace())); } } } public final IcedHashMapGeneric readJSON_impl(AutoBuffer ab ) {throw H2O.unimpl();} public final AutoBuffer writeJSON_impl( AutoBuffer ab ) { boolean first = true; for (Entry<K, V> entry : map().entrySet()) { K key = entry.getKey(); V value = entry.getValue(); assert entry.getKey() instanceof String; assert value instanceof String || value instanceof String[] || value instanceof Integer || value instanceof Freezable || value instanceof Freezable[]; if (first) { first = false; } else {ab.put1(',').put1(' '); } ab.putJSONName((String) key); ab.put1(':'); if (value instanceof String) ab.putJSONName((String) value); else if (value instanceof String[]) ab.putJSONAStr((String[]) value); else if (value instanceof Integer) ab.putJSON4((Integer) value); else if (value instanceof Freezable) ab.putJSON((Freezable) value); else if (value instanceof Freezable[]) ab.putJSONA((Freezable[]) value); } // ab.put1('}'); // NOTE: the serialization framework adds this automagically return ab; } // Subtypes which allow us to determine the type parameters at runtime, for generating schema metadata. public static class IcedHashMapStringString extends IcedHashMapGeneric<String, String> {} public static class IcedHashMapStringObject extends IcedHashMapGeneric<String, Object> {} }