/*
* Copyright (c) Corporation for National Research Initiatives
* Copyright (c) Jython Developers
*/
package org.python.core;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.python.expose.ExposedMethod;
import org.python.expose.ExposedNew;
import org.python.expose.ExposedType;
import org.python.expose.MethodType;
import org.python.util.Generic;
/**
* Special fast dict implementation for __dict__ instances. Allows interned String keys in addition
* to PyObject unlike PyDictionary.
*/
@ExposedType(name = "stringmap", isBaseType = false)
public class PyStringMap extends PyObject {
/**
* TYPE computed lazily, PyStringMap is used early in the bootstrap process and
* statically calling fromClass(PyStringMap.class) is unsafe.
*/
private static PyType lazyType;
private final ConcurrentMap<Object, PyObject> table;
public PyStringMap() {
this(4);
}
public PyStringMap(int capacity) {
super(getLazyType());
table = new ConcurrentHashMap<Object, PyObject>(capacity, Generic.CHM_LOAD_FACTOR,
Generic.CHM_CONCURRENCY_LEVEL);
}
public PyStringMap(Map<Object, PyObject> map) {
this(Math.max((int) (map.size() / Generic.CHM_LOAD_FACTOR) + 1,
Generic.CHM_INITIAL_CAPACITY));
table.putAll(map);
}
public PyStringMap(PyObject elements[]) {
this(elements.length);
for (int i = 0; i < elements.length; i += 2) {
__setitem__(elements[i], elements[i + 1]);
}
}
private static PyType getLazyType() {
if (lazyType == null) {
lazyType = PyType.fromClass(PyStringMap.class);
}
return lazyType;
}
@ExposedNew
final static PyObject stringmap_new(PyNewWrapper new_, boolean init, PyType subtype,
PyObject[] args, String[] keywords) {
PyStringMap map = new PyStringMap();
map.stringmap_update(args, keywords);
return map;
}
@Override
public int __len__() {
return stringmap___len__();
}
@ExposedMethod(doc = BuiltinDocs.dict___len___doc)
final int stringmap___len__() {
return table.size();
}
@Override
public boolean __nonzero__() {
return table.size() != 0;
}
@Override
public PyObject __finditem__(String key) {
if (key == null) {
return null;
}
return table.get(key);
}
@Override
public PyObject __finditem__(PyObject key) {
if (key instanceof PyString) {
return __finditem__(((PyString)key).internedString());
}
return table.get(key);
}
public PyObject __getitem__(String key) {
PyObject o = __finditem__(key);
if (null == o) {
throw Py.KeyError("'" + key + "'");
} else {
return o;
}
}
@Override
public PyObject __getitem__(PyObject key) {
return stringmap___getitem__(key);
}
@ExposedMethod(doc = BuiltinDocs.dict___getitem___doc)
final PyObject stringmap___getitem__(PyObject key) {
if (key instanceof PyString) {
return __getitem__(((PyString)key).internedString());
} else {
PyObject o = __finditem__(key);
if (null == o) {
throw Py.KeyError("'" + key.toString() + "'");
} else {
return o;
}
}
}
@Override
public PyObject __iter__() {
return stringmap___iter__();
}
@ExposedMethod(doc = BuiltinDocs.dict___iter___doc)
final PyObject stringmap___iter__() {
return stringmap_iterkeys();
}
@Override
public void __setitem__(String key, PyObject value) {
if (value == null) {
table.remove(key);
} else {
table.put(key, value);
}
}
@Override
public void __setitem__(PyObject key, PyObject value) {
stringmap___setitem__(key, value);
}
@ExposedMethod(doc = BuiltinDocs.dict___setitem___doc)
final void stringmap___setitem__(PyObject key, PyObject value) {
if (value == null) {
table.remove(pyToKey(key));
} else if (key instanceof PyString) {
__setitem__(((PyString)key).internedString(), value);
} else {
table.put(key, value);
}
}
@Override
public void __delitem__(String key) {
Object ret = table.remove(key);
if (ret == null) {
throw Py.KeyError(key);
}
}
@Override
public void __delitem__(PyObject key) {
stringmap___delitem__(key);
}
@ExposedMethod(doc = BuiltinDocs.dict___delitem___doc)
final void stringmap___delitem__(PyObject key) {
if (key instanceof PyString) {
__delitem__(((PyString)key).internedString());
} else {
Object ret = table.remove(key);
if (ret == null) {
throw Py.KeyError(key.toString());
}
}
}
/**
* Remove all items from the dictionary.
*/
public void clear() {
stringmap_clear();
}
@ExposedMethod(doc = BuiltinDocs.dict_clear_doc)
final void stringmap_clear() {
table.clear();
}
@Override
public String toString() {
return stringmap_toString();
}
@ExposedMethod(names = {"__repr__", "__str__"}, doc = BuiltinDocs.dict___str___doc)
final String stringmap_toString() {
ThreadState ts = Py.getThreadState();
if (!ts.enterRepr(this)) {
return "{...}";
}
StringBuilder buf = new StringBuilder("{");
for (Entry<Object, PyObject> entry : table.entrySet()) {
Object key = entry.getKey();
if (key instanceof String) {
// This is a bit complicated, but prevents us to duplicate
// PyString#__repr__ logic here.
buf.append(new PyString((String)key).__repr__().toString());
} else {
buf.append(((PyObject)key).__repr__().toString());
}
buf.append(": ");
buf.append(entry.getValue().__repr__().toString());
buf.append(", ");
}
if (buf.length() > 1) {
buf.delete(buf.length() - 2, buf.length());
}
buf.append("}");
ts.exitRepr(this);
return buf.toString();
}
@Override
public int __cmp__(PyObject other) {
return stringmap___cmp__(other);
}
@ExposedMethod(type = MethodType.CMP, doc = BuiltinDocs.dict___cmp___doc)
final int stringmap___cmp__(PyObject other) {
if (!(other instanceof PyStringMap || other instanceof PyDictionary)) {
return -2;
}
int an = __len__();
int bn = other.__len__();
if (an < bn) {
return -1;
}
if (an > bn) {
return 1;
}
PyList akeys = keys();
PyList bkeys = null;
if (other instanceof PyStringMap) {
bkeys = ((PyStringMap)other).keys();
} else {
bkeys = ((PyDictionary)other).keys();
}
akeys.sort();
bkeys.sort();
for (int i = 0; i < bn; i++) {
PyObject akey = akeys.pyget(i);
PyObject bkey = bkeys.pyget(i);
int c = akey._cmp(bkey);
if (c != 0) {
return c;
}
PyObject avalue = __finditem__(akey);
PyObject bvalue = other.__finditem__(bkey);
c = avalue._cmp(bvalue);
if (c != 0) {
return c;
}
}
return 0;
}
/**
* Return true if the key exist in the dictionary.
*/
public boolean has_key(String key) {
return table.containsKey(key);
}
public boolean has_key(PyObject key) {
return stringmap_has_key(key);
}
@ExposedMethod(doc = BuiltinDocs.dict_has_key_doc)
final boolean stringmap_has_key(PyObject key) {
return table.containsKey(pyToKey(key));
}
@Override
public boolean __contains__(PyObject o) {
return stringmap___contains__(o);
}
@ExposedMethod(doc = BuiltinDocs.dict___contains___doc)
final boolean stringmap___contains__(PyObject o) {
return stringmap_has_key(o);
}
/**
* Return this[key] if the key exists in the mapping, defaultObj is returned otherwise.
*
* @param key
* the key to lookup in the mapping.
* @param defaultObj
* the value to return if the key does not exists in the mapping.
*/
public PyObject get(PyObject key, PyObject defaultObj) {
return stringmap_get(key, defaultObj);
}
@ExposedMethod(defaults = "Py.None", doc = BuiltinDocs.dict_get_doc)
final PyObject stringmap_get(PyObject key, PyObject defaultObj) {
PyObject obj = __finditem__(key);
return obj == null ? defaultObj : obj;
}
/**
* Return this[key] if the key exists in the mapping, None is returned otherwise.
*
* @param key
* the key to lookup in the mapping.
*/
public PyObject get(PyObject key) {
return stringmap_get(key, Py.None);
}
/**
* Return a shallow copy of the dictionary.
*/
public PyStringMap copy() {
return stringmap_copy();
}
@ExposedMethod(doc = BuiltinDocs.dict_copy_doc)
final PyStringMap stringmap_copy() {
return new PyStringMap(table);
}
public void update(PyObject other) {
stringmap_update(new PyObject[] {other}, Py.NoKeywords);
}
/**
* Insert all the key:value pairs from <code>dict</code> into this mapping.
*/
@ExposedMethod(doc = BuiltinDocs.dict_update_doc)
final void stringmap_update(PyObject[] args, String[] keywords) {
int nargs = args.length - keywords.length;
if (nargs > 1) {
throw PyBuiltinCallable.DefaultInfo.unexpectedCall(nargs, false, "update", 0, 1);
}
if (nargs == 1) {
PyObject arg = args[0];
if (arg.__findattr__("keys") != null) {
merge(arg);
} else {
mergeFromSeq(arg);
}
}
for (int i = 0; i < keywords.length; i++) {
__setitem__(keywords[i], args[nargs + i]);
}
}
/**
* Merge another PyObject that supports keys() with this
* dict.
*
* @param other a PyObject with a keys() method
*/
private void merge(PyObject other) {
if (other instanceof PyStringMap) {
table.putAll(((PyStringMap)other).table);
} else if (other instanceof PyDictionary) {
mergeFromKeys(other, ((PyDictionary)other).keys());
} else {
mergeFromKeys(other, other.invoke("keys"));
}
}
/**
* Merge another PyObject via its keys() method
*
* @param other a PyObject with a keys() method
* @param keys the result of other's keys() method
*/
private void mergeFromKeys(PyObject other, PyObject keys) {
for (PyObject key : keys.asIterable()) {
__setitem__(key, other.__getitem__(key));
}
}
/**
* Merge any iterable object producing iterable objects of length
* 2 into this dict.
*
* @param other another PyObject
*/
private void mergeFromSeq(PyObject other) {
PyObject pairs = other.__iter__();
PyObject pair;
for (int i = 0; (pair = pairs.__iternext__()) != null; i++) {
try {
pair = PySequence.fastSequence(pair, "");
} catch(PyException pye) {
if (pye.match(Py.TypeError)) {
throw Py.TypeError(String.format("cannot convert dictionary update sequence "
+ "element #%d to a sequence", i));
}
throw pye;
}
int n;
if ((n = pair.__len__()) != 2) {
throw Py.ValueError(String.format("dictionary update sequence element #%d "
+ "has length %d; 2 is required", i, n));
}
__setitem__(pair.__getitem__(0), pair.__getitem__(1));
}
}
/**
* Return this[key] if the key exist, otherwise insert key with a None value and return None.
*
* @param key
* the key to lookup in the mapping.
*/
public PyObject setdefault(PyObject key) {
return setdefault(key, Py.None);
}
/**
* Return this[key] if the key exist, otherwise insert key with the value of failobj and return
* failobj
*
* @param key
* the key to lookup in the mapping.
* @param failobj
* the default value to insert in the mapping if key does not already exist.
*/
public PyObject setdefault(PyObject key, PyObject failobj) {
return stringmap_setdefault(key, failobj);
}
@ExposedMethod(defaults = "Py.None", doc = BuiltinDocs.dict_setdefault_doc)
final PyObject stringmap_setdefault(PyObject key, PyObject failobj) {
Object internedKey = (key instanceof PyString) ? ((PyString)key).internedString() : key;
PyObject oldValue = table.putIfAbsent(internedKey, failobj);
return oldValue == null ? failobj : oldValue;
}
/**
* Return a random (key, value) tuple pair and remove the pair from the mapping.
*/
public PyObject popitem() {
return stringmap_popitem();
}
@ExposedMethod(doc = BuiltinDocs.dict_popitem_doc)
final PyObject stringmap_popitem() {
Iterator<Entry<Object, PyObject>> it = table.entrySet().iterator();
if (!it.hasNext()) {
throw Py.KeyError("popitem(): dictionary is empty");
}
PyTuple tuple = itemTuple(it.next());
it.remove();
return tuple;
}
// not correct - we need to determine size and remove at the same time!
public PyObject pop(PyObject key) {
if (table.size() == 0) {
throw Py.KeyError("pop(): dictionary is empty");
}
return stringmap_pop(key, null);
}
public PyObject pop(PyObject key, PyObject failobj) {
return stringmap_pop(key, failobj);
}
@ExposedMethod(defaults = "null", doc = BuiltinDocs.dict_pop_doc)
final PyObject stringmap_pop(PyObject key, PyObject failobj) {
PyObject value = table.remove(pyToKey(key));
if (value == null) {
if (failobj == null) {
throw Py.KeyError(key.__repr__().toString());
} else {
return failobj;
}
}
return value;
}
/**
* Return a copy of the mappings list of (key, value) tuple pairs.
*/
public PyList items() {
return stringmap_items();
}
@ExposedMethod(doc = BuiltinDocs.dict_items_doc)
final PyList stringmap_items() {
return new PyList(stringmap_iteritems());
}
private PyTuple itemTuple(Entry<Object, PyObject> entry) {
return new PyTuple(keyToPy(entry.getKey()), entry.getValue());
}
/**
* Return a copy of the mappings list of keys. We have to take in account that we could be
* storing String or PyObject objects
*/
public PyList keys() {
return stringmap_keys();
}
@ExposedMethod(doc = BuiltinDocs.dict_keys_doc)
final PyList stringmap_keys() {
PyObject[] keyArray = new PyObject[table.size()];
int i = 0;
for (Object key : table.keySet()) {
keyArray[i++] = keyToPy(key);
}
return new PyList(keyArray);
}
/**
* Return a copy of the mappings list of values.
*/
public PyList values() {
return stringmap_values();
}
@ExposedMethod(doc = BuiltinDocs.dict_values_doc)
final PyList stringmap_values() {
return new PyList(table.values());
}
/**
* return an iterator over (key, value) pairs
*/
public PyObject iteritems() {
return stringmap_iteritems();
}
@ExposedMethod(doc = BuiltinDocs.dict_iteritems_doc)
final PyObject stringmap_iteritems() {
return new ItemsIter(table.entrySet());
}
/**
* return an iterator over the keys
*/
public PyObject iterkeys() {
return stringmap_iterkeys();
}
@ExposedMethod(doc = BuiltinDocs.dict_iterkeys_doc)
final PyObject stringmap_iterkeys() {
// Python allows one to change the dict while iterating over it, including
// deletion. Java does not. Can we resolve with CHM?
return new KeysIter(table.keySet());
}
/**
* return an iterator over the values
*/
public PyObject itervalues() {
return stringmap_itervalues();
}
@ExposedMethod(doc = BuiltinDocs.dict_itervalues_doc)
final PyObject stringmap_itervalues() {
return new ValuesIter(table.values());
}
@Override
public int hashCode() {
return stringmap___hash__();
}
@ExposedMethod(doc = BuiltinDocs.dict___hash___doc)
final int stringmap___hash__() {
throw Py.TypeError(String.format("unhashable type: '%.200s'", getType().fastGetName()));
}
@Override
public boolean isMappingType() {
return true;
}
@Override
public boolean isSequenceType() {
return false;
}
private abstract class StringMapIter<T> extends PyIterator {
protected final Iterator<T> iterator;
private final int size;
public StringMapIter(Collection<T> c) {
iterator = c.iterator();
size = c.size();
}
@Override
public PyObject __iternext__() {
if (table.size() != size) {
throw Py.RuntimeError("dictionary changed size during iteration");
}
if (!iterator.hasNext()) {
return null;
}
return stringMapNext();
}
protected abstract PyObject stringMapNext();
}
private class ValuesIter extends StringMapIter<PyObject> {
public ValuesIter(Collection<PyObject> c) {
super(c);
}
@Override
public PyObject stringMapNext() {
return iterator.next();
}
}
private class KeysIter extends StringMapIter<Object> {
public KeysIter(Set<Object> s) {
super(s);
}
@Override
protected PyObject stringMapNext() {
return keyToPy(iterator.next());
}
}
private class ItemsIter extends StringMapIter<Entry<Object, PyObject>> {
public ItemsIter(Set<Entry<Object, PyObject>> s) {
super(s);
}
@Override
public PyObject stringMapNext() {
return itemTuple(iterator.next());
}
}
private static PyObject keyToPy(Object objKey){
if (objKey instanceof String) {
return PyString.fromInterned((String)objKey);
} else {
return (PyObject)objKey;
}
}
private static Object pyToKey(PyObject pyKey) {
if (pyKey instanceof PyString) {
return ((PyString)pyKey).internedString();
} else {
return pyKey;
}
}
}