/* --------------------------------------------------------- * * __________ D E L T A S C R I P T * * (_________() * * / === / - A fast, dynamic scripting language * * | == | - Version 4.13.11.0 * * / === / - Developed by Adam R. Nelson * * | = = | - 2011-2013 * * / === / - Distributed under GNU LGPL v3 * * (________() - http://github.com/ar-nelson/deltascript * * * * --------------------------------------------------------- */ package com.sector91.delta.script.objects; import java.io.Serializable; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import com.sector91.delta.script.DScriptErr; import com.sector91.delta.script.DeltaScript; import com.sector91.delta.script.NumberTypes; import com.sector91.delta.script.Operator; import com.sector91.delta.script.annotations.DSDynamicField; import com.sector91.delta.script.annotations.DSInaccessible; import com.sector91.delta.script.annotations.DSName; import com.sector91.delta.script.annotations.DSType; import com.sector91.delta.script.objects.reflect.DS_JavaClass; import com.sector91.util.NestedPrintable; /** * <p>A mutable, heterogeneous map of keys to values. It is more powerful than * a {@link DS_Scope} for mapping purposes, because any type of object (not * just tags) can be used as a key. In general, it is conventional to use * scopes as immutable maps or to pass arguments, and maps when a mutable * structure is needed.</p> * * <p>Maps are defined in DeltaScript with the JSON-like syntax * <code>{key1: value1, key2: value2}</code>. Identifiers used as map keys will * be parsed as tags rather than variable names (similarly to JavaScript's * automatic conversion to strings).</p> * * @author Adam R. Nelson * @version 4.13.11.0 */ @DSType("Map") public class DS_Map extends DS_AbstractObject implements DS_Indexable, Map<DS_Object, DS_Object>, NestedPrintable, Serializable { private static final long serialVersionUID = DeltaScript.VERSION.majorVersion(); public static final String TYPE_NAME = "Map"; private static final DS_JavaClass DSCLASS = DS_JavaClass.fromClass( DS_Map.class); private final Map<DS_Object, DS_Object> map; public DS_Map(DS_Iterable seq) {this(seq.dumpToArray());} public DS_Map(DS_Object... objs) { map = new HashMap<DS_Object, DS_Object>(); if (objs.length % 2 != 0) throw new IllegalArgumentException("A Map must be constructed" + " with an even number of arguments, in key-value pairs. Got " + objs.length + " argument(s), an odd number."); for (int i=0; i<objs.length; i+=2) map.put(objs[i], objs[i+1]); } @DSInaccessible public DS_Map(Map<DS_Object, DS_Object> map) {this.map = new HashMap<DS_Object, DS_Object>(map);} // API Methods // ---------------------------------------------------- @DSName("containsKey") public boolean containsKey(Object key) {return map.containsKey(key);} @DSName("containsValue") public boolean containsValue(Object value) {return map.containsValue(value);} @DSName("get") public DS_Object get(Object key) {return map.get(key);} @DSName("put") public DS_Object put(DS_Object key, DS_Object value) {return map.put(key, value);} @DSInaccessible public void putAll(Map<? extends DS_Object,? extends DS_Object> m) {map.putAll(m);} @DSName("putAll") public void putAll(DS_Map m) {map.putAll(m.unbox());} @DSName("keys") @DSDynamicField public DS_Set keySet() {return new DS_Set(map.keySet());} @DSName("values") @DSDynamicField public DS_Set values() {return new DS_Set(map.values());} @DSInaccessible public Set<Map.Entry<DS_Object, DS_Object>> entrySet() {return map.entrySet();} @DSName("entries") @DSDynamicField public DS_Array entryArrays() { DS_Array[] arrays = new DS_Array[map.size()]; int i=0; for (Map.Entry<DS_Object, DS_Object> entry : map.entrySet()) { arrays[i] = new DS_Array(entry.getKey(), entry.getValue()); ++i; } return new DS_Array(arrays); } // DS_Sequence Methods // ---------------------------------------------------- @DSName("size") @DSDynamicField public int size() {return map.size();} @DSName("empty") @DSDynamicField public boolean isEmpty() {return map.isEmpty();} @DSName("remove") public DS_Object remove(Object o) {return map.remove(o);} @DSInaccessible public boolean removeAll(Collection<DS_Object> c) { int s = map.size(); for (Object o : c) map.remove(o); return map.size() != s; } @DSName("removeAll") public boolean removeAll(DS_Object... items) {return removeAll(Arrays.asList(items));} @DSInaccessible public boolean retainAll(Collection<DS_Object> c) { int s = map.size(); for (DS_Object k : map.keySet()) if (!c.contains(k)) map.remove(k); return map.size() != s; } @DSName("retainAll") public boolean retainAll(DS_Object... items) {return retainAll(Arrays.asList(items));} @DSName("clear") public void clear() {map.clear();} @DSInaccessible public DS_Object getIndex(DS_Object index) throws DScriptErr { DS_Object obj = map.get(index); if (obj == null) return DS_Blank.BLANK; else return obj; } @DSInaccessible public DS_Object setIndex(DS_Object index, DS_Object value) throws DScriptErr { if (value == DS_Blank.BLANK || value == null) return map.remove(index); else { map.put(index, value); return value; } } // DS_Object Methods // ---------------------------------------------------- public Map<DS_Object, DS_Object> unbox() {return map;} public String getTypeName() {return TYPE_NAME;} @Override public DS_Object dotGet(DS_Tag key) throws DScriptErr { if (getDeltaScriptClass().getInstanceMembers().contains(key)) return super.dotGet(key); else if (containsKey(key)) return get(key); else throw new DScriptErr("Map does not contain a key named '" + key + "'.", DScriptErr.T_UNDEFINED); } @Override public void dotSet(DS_Tag key, DS_Object value) throws DScriptErr { if (getDeltaScriptClass().getInstanceMembers().contains(key)) super.dotSet(key, value); else put(key, value); } @Override public Set<DS_Tag> getMembers() { final Set<DS_Tag> members = new HashSet<DS_Tag>(super.getMembers()); for (DS_Object key : map.keySet()) if (key instanceof DS_Tag) members.add((DS_Tag)key); return members; } @Override protected DS_JavaClass getDeltaScriptClass() {return DSCLASS;} @SuppressWarnings("incomplete-switch") @Override public DS_Object operator(Operator op, DS_Object other) throws DScriptErr { switch (op) { case ABSOLUTE: return ScalarFactory.fromNumber(size(), NumberTypes.SHORT_INT); case RANDOM: return entryArrays().random(); case IN: return DS_Boolean.box(containsKey(other)); } if (other instanceof DS_Map) { DS_Map map = (DS_Map)other; DS_Map newMap = new DS_Map(); switch (op) { case BIT_OR: newMap.putAll(this); newMap.putAll(map); return newMap; case SUBTRACT: newMap.putAll(this); newMap.removeAll(map.keySet()); return newMap; case BIT_AND: newMap.putAll(this); newMap.retainAll(map.keySet()); return newMap; } } return super.operator(op, other); } public boolean equals(DS_Object other) {return this == other;} @Override public int hashCode() {return map.hashCode();} @Override public String toString() {return toStringNested(1);} @DSInaccessible public String toStringNested(int indentLevel) { if (indentLevel >= 10) return "(Exceeded max nesting level...)"; StringBuffer out = new StringBuffer("{"); Set<Map.Entry<DS_Object, DS_Object>> nodes = entrySet(); for (Map.Entry<DS_Object, DS_Object> n : nodes) { out.append('\n'); StringBuffer def = new StringBuffer(); for (int i=0; i<indentLevel; i++) def.append(" "); def.append(n.getKey()); def.append(": "); out.append(def); if (n.getValue() instanceof NestedPrintable) { if (n.getValue() == this) out.append("(Recursive self-reference)"); else out.append(((NestedPrintable)n.getKey()).toStringNested( indentLevel+1)); } else { StringBuffer indent = new StringBuffer(); for (int i=0; i<def.length(); i++) indent.append(' '); out.append(n.getValue().toString().replaceAll("\n", "\n"+indent)); } } if (!nodes.isEmpty()) { out.append('\n'); for (int i=0; i<indentLevel-1; i++) out.append(" "); } out.append("}"); return out.toString(); } }