Index: jython/src/org/python/core/PyDictionary.java =================================================================== --- jython/src/org/python/core/PyDictionary.java (revision 3763) +++ jython/src/org/python/core/PyDictionary.java (working copy) @@ -3,12 +3,16 @@ import java.util.Hashtable; import java.util.Enumeration; +import java.util.Map; +import java.util.Set; +import java.util.Iterator; +import java.util.Collection; /** * A builtin python dictionary. */ -public class PyDictionary extends PyObject { +public class PyDictionary extends PyObject implements Map { //~ BEGIN GENERATED REGION -- DO NOT EDIT SEE gexpose.py /* type info */ @@ -1120,13 +1124,6 @@ return new PyList(elements); } - /** - * Return a copy of the dictionarys list of values. - */ - public PyList values() { - return dict_values(); - } - final PyList dict_values() { java.util.Enumeration e = table.elements(); int n = table.size(); @@ -1185,9 +1182,375 @@ return false; } + /* The following methods implement the java.util.Map interface + which allows PyDictionary to be passed to java methods that take + java.util.Map as a parameter. Basically, the Map methods are a + wrapper around the PyDictionarie's Map container stored in member + variable 'table'. These methods simply convert java Object to + PyObjects on insertion, and PyObject to Objects on retrieval. */ + /** @see java.util.Map#entrySet() */ + public Set entrySet() { + return new PyMapEntrySet(table.entrySet()); + } + + /** @see java.util.Map#keySet() */ + public Set keySet() { + return new PyMapKeyValSet(table.keySet()); + } + + /** Return a copy of the dictionarys list of values. */ + public Collection values() { + return new PyMapKeyValSet(table.values()); + } + + + /** @see java.util.Map#putAll(Map map) */ + public void putAll(Map map) { + Iterator i = map.keySet().iterator(); + while (i.hasNext()) { + Object key = i.next(); + Object val = map.get(key); + table.put(Py.java2py(key), Py.java2py(val)); + } + } + + /** @see java.util.Map#remove(Object key) */ + public Object remove(Object key) { + return tojava(table.remove(Py.java2py(key))); + } + + /** @see java.util.Map#put(Object key, Object value) */ + public Object put(Object key, Object value) { + return tojava(table.put(Py.java2py(key), Py.java2py(value))); + } + + /** @see java.util.Map#get(Object key) */ + public Object get(Object key) { + return tojava(table.get(Py.java2py(key))); + } + + /** @see java.util.Map#containsValue(Object key) */ + public boolean containsValue(Object value) { + return table.containsValue(Py.java2py(value)); + } + + /** @see java.util.Map#containsValue(Object key) */ + public boolean containsKey(Object key) { + return table.containsKey(Py.java2py(key)); + } + + /** @see java.util.Map#isEmpty(Object key) */ + public boolean isEmpty() { + return table.isEmpty(); + } + + /** @see java.util.Map#size(Object key) */ + public int size() { + return table.size(); + } + + /** Convert return values to java objects */ + static final Object tojava(Object val) { + return val == null ? null : ((PyObject)val).__tojava__(Object.class); + } } +/** + * Wrapper for a Map.Entry object returned from the java.util.Set + * object which in turn is returned by the entrySet method of + * java.util.Map. This is needed to correctly convert from PyObjects + * to java Objects. Note that we take care in the equals and hashCode + * methods to make sure these methods are consistent with Map.Entry + * objects that contain java Objects for a value so that on the java + * side they can be reliable compared. + */ +class PyToJavaMapEntry implements Map.Entry { + + private Map.Entry entry = null; + + /** Create a copy of the Map.Entry with Py.None coverted to null */ + PyToJavaMapEntry(Map.Entry entry) { + this.entry = entry; + } + + public boolean equals(Object o) { + if (o == null || !(o instanceof Map.Entry)) return false; + Map.Entry me = new JavaToPyMapEntry((Map.Entry)o); + return o.equals(me); + } + + public Object getKey() { + return PyDictionary.tojava(entry.getKey()); + } + + public Object getValue() { + return PyDictionary.tojava(entry.getValue()); + } + + public int hashCode() { + // formula for the hash code is taken from the Map.Entry documentation. + // Given the source we assume that key is not null. + Object val = entry.getValue(); + return getKey().hashCode() ^ (val == null ? 0 : val.hashCode()); + } + + public Object setValue(Object value) { + return entry.setValue(Py.java2py(value)); + } + + public Map.Entry getEntry() { + return entry; + } + +} + +/** + * MapEntry Object for java MapEntry objects passed to the + * java.util.Set interface which is returned by the setEntry method of + * PyDictionary. Essentially like PyTojavaMapEntry, but going the + * other way converting java Objects to PyObjects. + */ +class JavaToPyMapEntry implements Map.Entry { + private PyObject key = null; + private PyObject val = null; + + public JavaToPyMapEntry(Map.Entry entry) { + this.key = Py.java2py(entry.getKey()); + this.val = Py.java2py(entry.getValue()); + } + + public boolean equals(Object o) { + if (o == null || !(o instanceof Map.Entry)) return false; + Map.Entry oe = (Map.Entry)o; + // The objects comming in will always be a Map.Entry from a + // PyDictionary, so getKey and getValue will always be PyObjects + return oe.getKey().equals(key) && oe.getValue().equals(val); + } + + public int hashCode() { + return key.hashCode() ^ val.hashCode(); + } + + public Object getKey() { + return key; + } + + public Object getValue() { + return val; + } + + public Object setValue(Object val) { + throw new RuntimeException("This should never get called"); + } +} + +/** + * Wrapper collection class for the keySet and values methods of + * java.util.Map + */ +class PyMapKeyValSet extends PyMapSet { + + + PyMapKeyValSet(Collection coll) { + super(coll); + } + + Object toJava(Object o) { + return o == null ? null : ((PyObject)o).__tojava__(Object.class); + } + + Object toPython(Object o) { + return Py.java2py(o); + } +} + +/** + * Set wrapper for the java.util.Map.entrySet method. Map.Entry + * objects are wrapped further in JavaToPyMapEntry and + * PyToJavaMapEntry. Note - The set interface is reliable for + * standard objects like strings and integers, but may be inconstant + * for other types of objects since the equals method may return false + * for Map.Entry object that hold more elaborate PyObject types. + * However, We insure that this iterface works when the Map.Entry + * object originates from a Set object retrieved from a PyDictionary. + */ +class PyMapEntrySet extends PyMapSet { + + PyMapEntrySet(Collection coll) { + super(coll); + } + + // We know that PyMapEntrySet will only contains Map.Entrys, so + // if the object being passed in is null or not a Map.Entry, then + // return null which will match nothing for remove and contains methods. + Object toPython(Object o) { + if (o == null || !(o instanceof Map.Entry)) return null; + if (o instanceof PyToJavaMapEntry) + // Use the original entry from PyDictionary + return ((PyToJavaMapEntry)o).getEntry(); + else + return new JavaToPyMapEntry((Map.Entry)o); + } + + Object toJava(Object o) { + return new PyToJavaMapEntry((Map.Entry)o); + } +} + +/** + * PyMapSet serves as a wrapper around Set Objects returned by the + * java.util.Map interface of PyDictionary. entrySet, values and + * keySet methods return this type for the java.util.Map + * implementation. This class is necessary as a wrapper to convert + * PyObjects to java Objects for methods that return values, and + * convert Objects to PyObjects for methods that take values. The + * translation is necessary to provide java access to jython + * dictionary objects. This wrapper also provides the expected backing + * functionality such that changes to the wrapper set or reflected in + * PyDictionary. + */ +abstract class PyMapSet implements Set +{ + + Collection coll = null; + + PyMapSet(Collection coll) { + this.coll = coll; + } + + abstract Object toJava(Object obj); + abstract Object toPython(Object obj); + + public int size() { + return coll.size(); + } + + public boolean isEmpty() { + return coll.isEmpty(); + } + + public Object[] toArray() { + Object arr[] = new Object[size()]; + int i = 0; + Iterator itr = iterator(); + while (itr.hasNext()) + arr[i++] = itr.next(); + return arr; + } + + public Object[] toArray(Object[] a) { + int size = size(); + if (a.length < size) + a = (Object[])java.lang.reflect.Array. + newInstance(a.getClass().getComponentType(), size); + Iterator itr = iterator(); + for (int i=0; i < size; i++) { + a[i] = itr.next(); + } + if (a.length > size) a[size] = null; + return a; + } + + public boolean contains(Object o) { + return coll.contains(toPython(o)); + } + + public boolean add(Object o) { + throw new UnsupportedOperationException("Bad method for this Collection"); + } + + public boolean addAll(Collection c) { + throw new UnsupportedOperationException("Bad method for this Collection"); + } + + + public boolean remove(Object o) { + return coll.remove(toPython(o)); + } + + public boolean containsAll(Collection c) { + Iterator i = c.iterator(); + while (i.hasNext()) { + if (!contains(i.next())) return false; + } + return true; + } + + public boolean removeAll(Collection c) { + if (c == null) throw new NullPointerException(); + Iterator itr = c.iterator(); + boolean result = false; + while (itr.hasNext()) { + if (remove(itr.next())) result = true; + } + return result; + } + + public boolean retainAll(Collection c) { + if (c == null) throw new NullPointerException(); + Iterator itr = iterator(); + boolean result = false; + while (itr.hasNext()) { + if (!c.contains(itr.next())) { + itr.remove(); + result = true; + } + } + return result; + } + + public void clear() { + coll.clear(); + } + + public boolean equals(Object o) { + if (o == null || !(o instanceof Collection)) + return false; + Collection ocoll = (Collection)o; + if (ocoll.size() != size()) + return false; + return containsAll((Collection)o); + } + + public int hashCode() { + int hc = 0; + Iterator itr = iterator(); + while (itr.hasNext()) { + hc += toJava(itr.next()).hashCode(); + } + return hc; + } + + // Iterator wrapper class returned by the PyMapSet iterator + // method. We need this wrapper to return PyToJavaMapEntry objects + // for the 'next()' method. + class PySetIter implements Iterator { + Iterator itr = null; + + PySetIter(Iterator itr) { + this.itr = itr; + } + + public boolean hasNext() { + return itr.hasNext(); + } + + public Object next() { + return toJava(itr.next()); + } + + public void remove() { + itr.remove(); + } + } + + public Iterator iterator() { + return new PySetIter(coll.iterator()); + } +} + + class PyDictionaryIter extends PyIterator { public static final int KEYS = 0; public static final int VALUES = 1; @@ -1218,3 +1581,5 @@ } } + +// LocalWords: Cice versa Index: jython/Lib/test/test_dict2java.py =================================================================== --- jython/Lib/test/test_dict2java.py (revision 0) +++ jython/Lib/test/test_dict2java.py (revision 0) @@ -0,0 +1,135 @@ +from javatests import Dict2JavaTest +import unittest, test.test_support + +# Test the java.util.Map interface of org.python.core.PyDictionary. +# This tests the functionality of being able to pass a dictionaries +# created in Jython to a java method, and the ability to manipulate +# the dictionary object once in Java code. The Java Dict2JavaTest is +# used to run some tests in Java code since they cannot be done on +# the Jython side. + +class JythonMapInJavaTest(unittest.TestCase): + + def test_pydictionary_in_java(self): + + dict = {"a":"x", "b":"y", "c":"z", "d": None, None: "foo"} + jmap = Dict2JavaTest(dict) + + # Make sure we see it on the java side + self.assertEqual(True, len(dict) == jmap.size() and jmap.containsKey("a") + and jmap.containsKey("b") and jmap.containsKey("c") + and jmap.containsKey("d")) + + + # Add {"e":"1", "f":null, "g":"2"} using the Map.putAll method + oldlen = len(dict) + self.assertEqual(True, jmap.test_putAll_efg()) + self.assertEqual(True, jmap.size() == len(dict) == oldlen + 3) + self.assertEqual(True, dict["e"] == "1" and dict["f"] == None and dict["g"] == "2") + + # test Map.get method, get "g" and "d" test will throw an exception if fail + self.assertEqual(True, jmap.test_get_gd()) + + # remove elements with keys "a" and "c" with the Map.remove method + oldlen = len(dict) + self.assertEqual(True, jmap.test_remove_ac()) + self.assertEqual(True, jmap.size() == len(dict) == oldlen - 2 + and "a" not in dict and "c" not in dict) + + # test Map.put method, adds {"h":null} and {"i": Integer(3)} and {"g": "3"} + # "g" replaces a previous value of "2" + oldlen = len(dict) + self.assertEqual(True, jmap.test_put_hig()) + self.assertEqual(True, dict["h"] == None and dict["i"] == 3 and dict["g"] == "3" + and len(dict) == oldlen+2) + + self.assertEqual(True, jmap.test_java_mapentry()) + + set = jmap.entrySet() + self.assertEqual(True, set.size() == len(dict)) + + # Make sure the set is consistent with the dictionary + for entry in set: + self.assertEqual(True, dict.has_key(entry.getKey())) + self.assertEqual(True, dict[entry.getKey()] == entry.getValue()) + self.assertEqual(True, set.contains(entry)) + + # make sure changes in the set are reflected in the dictionary + for entry in set: + if entry.getKey() == "h": + hentry = entry + if entry.getKey() == "e": + eentry = entry + + # Make sure nulls and non Map.Entry object do not match anything in the set + self.assertEqual(True, jmap.test_entry_set_nulls()) + + self.assertEqual(True, hentry != None and eentry != None) + self.assertEqual(True, set.remove(eentry)) + self.assertEqual(True, not set.contains(eentry) and "e" not in dict) + self.assertEqual(True, set.remove(hentry)) + self.assertEqual(True, not set.contains(hentry) and "h" not in dict) + self.assertEqual(True, jmap.size() == set.size() == len(dict)) + oldlen = set.size() + self.assertEqual(True, not set.remove(eentry)) + self.assertEqual(True, jmap.size() == set.size() == len(dict) == oldlen) + + # test Set.removeAll method + oldlen = len(dict) + elist = [ entry for entry in set if entry.key in ["b", "g", "d", None]] + self.assertEqual(len(elist), 4) + self.assertEqual(True, set.removeAll(elist)) + self.assertEqual(True, "b" not in dict and "g" not in dict and "d" + not in dict and None not in dict) + self.assertEqual(True, len(dict) == set.size() == jmap.size() == oldlen - 4) + + itr = set.iterator() + while (itr.hasNext()): + val = itr.next() + itr.remove() + self.assertEqual(True, set.isEmpty() and len(dict) == jmap.size() == 0) + + # Test collections returned by keySet() + jmap.put("foo", "bar") + jmap.put("num", 5) + jmap.put(None, 4.3) + jmap.put(34, None) + keyset = jmap.keySet() + self.assertEqual(True, len(dict) == jmap.size() == keyset.size() == 4) + + self.assertEqual(True, keyset.remove(None)) + self.assertEqual(True, len(dict) == 3 and not keyset.contains(None)) + self.assertEqual(True, keyset.remove(34)) + self.assertEqual(True, len(dict) == 2 and not keyset.contains(34)) + itr = keyset.iterator() + while itr.hasNext(): + key = itr.next() + if key == "num": + itr.remove() + self.assertEqual(True, len(dict) == jmap.size() == keyset.size() == 1) + + # test collections returned by values() + jmap.put("foo", "bar") + jmap.put("num", "bar") + jmap.put(None, 3.2) + jmap.put(34, None) + values = jmap.values() + self.assertEqual(True, len(dict) == jmap.size() == values.size() == 4) + + self.assertEqual(True, values.remove(None)) + self.assertEqual(True, values.size() == 3) + itr = values.iterator() + while itr.hasNext(): + val = itr.next() + if val == "bar": + itr.remove() + self.assertEqual(True, len(dict) == values.size() == jmap.size() == 1) + values.clear() + self.assertEqual(True, values.isEmpty() and len(dict) == 0 and jmap.size() == 0) + + +def test_main(): + test.test_support.run_unittest(JythonMapInJavaTest) + +if __name__ == '__main__': + test_main() Index: jython/Lib/test/javatests/Dict2JavaTest.java =================================================================== --- jython/Lib/test/javatests/Dict2JavaTest.java (revision 0) +++ jython/Lib/test/javatests/Dict2JavaTest.java (revision 0) @@ -0,0 +1,110 @@ +package javatests; +import java.util.Map; +import java.util.Set; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; + +/** + * This class is used by the test_dict2java.py test script for testing + * the org.python.core.PyDictionary object's java.util.Map. We verifiy + * that the Map interface can be seamlessly passed to java code and + * manipulated in a consistent manner. + */ +public class Dict2JavaTest { + + private Map map = null; + + public Dict2JavaTest(Map map) { + this.map = map; + } + + public Set entrySet() { + return map.entrySet(); + } + + public Set keySet() { + return map.keySet(); + } + + public Collection values() { + return map.values(); + } + + public Object put(Object key, Object val) { + return map.put(key, val); + } + + + public boolean containsKey(Object key) { + return map.containsKey(key.toString()); + } + + public boolean test_putAll_efg() { + HashMap hmap = new HashMap(); + hmap.put("e", "1"); + hmap.put("f", null); + hmap.put("g", "2"); + map.putAll(hmap); + return true; + } + + public boolean test_remove_ac() { + Object val1 = map.remove("a"); + Object val2 = map.remove("c"); + Object val3 = map.remove("bar"); + return val1.equals("x") && val2.equals("z") && val3 == null; + } + + public boolean test_get_gd() { + return map.get("b").equals("y") && map.get("d") == null + && map.get(null).equals("foo"); + } + + public boolean test_put_hig() { + map.put("h", null); + map.put("i", new Integer(3)); + Object val = map.put("g", "3"); + return val.equals("2"); + } + + public boolean test_java_mapentry() { + // created outside of Jython with non PyOjects + HashMap hmap = new HashMap(); + hmap.put("b", "y"); + Map.Entry entry = (Map.Entry)hmap.entrySet().iterator().next(); + if (!map.entrySet().contains(entry)) return false; + + // Test a number + hmap = new HashMap(); + hmap.put("i", new Integer(3)); + entry = (Map.Entry)hmap.entrySet().iterator().next(); + if (!map.entrySet().contains(entry)) return false; + + // test Null + hmap = new HashMap(); + hmap.put("f", null); + entry = (Map.Entry)hmap.entrySet().iterator().next(); + if (!map.entrySet().contains(entry)) return false; + return true; + } + + // make sure nulls are handled and other object types, nulls + // should never match anything in the entry set. + public boolean test_entry_set_nulls() { + Set set = map.entrySet(); + return set.contains(null) == false && set.remove(null) == false && + set.contains(new Boolean(true)) == false && set.remove(new String("")) == false; + } + + + public void remove(Object key) { + // toString so we insure there are no PyObject influences + map.remove(key.toString()); + } + + public int size() { + return map.size(); + } + +}