/* --------------------------------------------------------- *
* __________ 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();
}
}