package com.zillabyte.motherbrain.flow; import java.io.Serializable; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import net.sf.json.JSONNull; import net.sf.json.JSONObject; import org.apache.commons.lang.StringUtils; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import com.google.common.collect.Lists; import com.google.common.collect.MapDifference; import com.google.common.collect.Maps; import com.zillabyte.motherbrain.utils.JSONUtil; import com.zillabyte.motherbrain.utils.queue.ByteSizable; @NonNullByDefault public class MapTuple implements Serializable, ByteSizable { private static final long serialVersionUID = 4458658589928148304L; public static final List<String> RESERVED = Lists.newArrayList("source", "confidence", "since"); public static final MapTuple EMPTY = new MapTuple(); final HashMap<String, Object> _values; final HashMap<String, String> _aliases; public MapTuple() { _values = new HashMap<>(); _aliases = new HashMap<>(); } public MapTuple(final MapTuple m) { _values = new HashMap<>(m._values); _aliases = new HashMap<>(m._aliases); } public final Map<String, Object> values() { return _values; } public final @Nullable Object get(final String field) { final Object value = _values.get(field); if (value != null) { return value; } return null; } public final Object getOrException(final String field) { final Object value = _values.get(field); if (value == null) { throw new NullPointerException("Field " + field + " not found!"); } return value; } public final MapTuple put(final String field, final @Nullable Object obj) { if (obj == null) { /* * We need this because of joins. We don't use Java null because * too many libraries either return null to represent absence, * don't allow null entries, or use null to represent something else. */ _values.put(field, JSONNull.getInstance()); } else { _values.put(field, obj); } return this; // for chaining } public final MapTuple remove(String field) { _values.remove(field); return this; // for chaining } // alias public final MapTuple add(String field, Object obj) { return put(field, obj); } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("{"); for(Entry<String, Object> e : this._values.entrySet()) { sb.append("\""); sb.append(e.getKey()); sb.append("\"="); if (e.getValue() instanceof String) { sb.append("\""); } if (e.getValue() == null) { sb.append("null"); } else { sb.append(StringUtils.abbreviate(e.getValue().toString().replace("\n", "\\n").replace("\r", "\\r"), 40)); } if (e.getValue() instanceof String) { sb.append("\""); } sb.append(","); } sb.append("}"); final String string = sb.toString(); assert (string != null); return string; } public final int sizeValues() { return this.values().size(); } public final long getApproxMemSize() { // Hacky: we assume strings are the main consumer of memory. // everything else we assume 4 bytes. long l = 0; for(Object o : this.values().values()) { if (o instanceof String) { l += ((String)o).length(); } else { l += 4; } } l += (10); return l; } /*** * Used mostly for testing. * @param val * @return T if all the values in val are found in this. Note that 'this' may have fields not in 'val' */ public final boolean containsAllValues(Map<String, ?> val) { for(Entry<String, ?> e : val.entrySet()) { if (this._values.containsKey(e.getKey()) && this._values.get(e.getKey()).equals(e.getValue()) ) { // Found! } else { return false; } } return true; } public final boolean equalsTuple(Object oo, boolean print) { if (oo != null && oo instanceof MapTuple) { MapTuple t = (MapTuple)oo; MapDifference<String, Object> diff = Maps.difference(t._values, this._values); if (diff.areEqual()) { return true; } else { if (print) { System.err.println(diff); } } } return false; } public final boolean equalsTuple(Object oo) { return equalsTuple(oo, false); } public final boolean equalsTupleP(Object oo) { return equalsTuple(oo, true); } /*** * */ public boolean equals(Object oo) { return equalsTuple(oo); } public final boolean containsValueKey(String col) { return this._values.containsKey(col); } public final Set<String> getValueKeys() { final Set<String> keySet = this._values.keySet(); assert (keySet != null); return keySet; } public final JSONObject getValuesJSON() { return JSONUtil.toJSON(this._values); } public final void setValuesJSON(JSONObject values) { for (Object key : values.keySet()){ String s = (String) key; _values.put(s, values.getString(s)); } } public final void addAlias(String alias, String concrete) { _aliases.put(alias, concrete); } public final void addAliases(JSONObject aliases) { for(Object alias : aliases.keySet()) { _aliases.put((String) alias, (String) aliases.get(alias)); } } public final HashMap<String, String> getAliases() { return _aliases; } public static final MapTuple create() { return new MapTuple(); } public static MapTuple create(String k, Object v) { return create().put(k,v); } public static MapTuple create(JSONObject o) { MapTuple m = new MapTuple(); for(Object key : o.keySet()) { m.put((String) key, o.get(key)); } return m; } @Override public long getByteSize() { return this.getApproxMemSize(); } }