[Jython-checkins] jython: Adds comparison ops to proxied java.util.Map objects

jim.baker jython-checkins at python.org
Tue Dec 23 17:41:49 CET 2014


https://hg.python.org/jython/rev/fc24f60fb32c
changeset:   7466:fc24f60fb32c
user:        Jim Baker <jim.baker at rackspace.com>
date:        Tue Dec 23 09:41:23 2014 -0700
summary:
  Adds comparison ops to proxied java.util.Map objects

Completes fix for http://bugs.jython.org/issue1631 by adding __le__,
__lt__, __gt__, __ge__ comparison operators. Now any proxied Map
objects are compared by their key sets for ordering
comparisons. Previously the default comparison was done, which is by
their class name (eg "HashMap") and then by
System.identityHashCode. (This default comparison is removed in Python
3.)

Note that when comparing java.util.Map objects with each other, Java
semantics apply. This means that keys are not converted to Python,
then compared. Likely the only scenario this comes up is creating a
Java Map from Python objects, specifically of int and long
objects, then doing a comparison. Example:

HashMap({1L:2L}) != HashMap({1:2})

but

HashMap({1L:2L}) == {1:2}

Supporting Python 3 will fix this problem by removing the distinction
between say 1 and 1L.

files:
  Lib/test/test_dict_jy.py            |   69 ++++++-
  src/org/python/core/PyJavaType.java |  150 +++++++++++----
  2 files changed, 164 insertions(+), 55 deletions(-)


diff --git a/Lib/test/test_dict_jy.py b/Lib/test/test_dict_jy.py
--- a/Lib/test/test_dict_jy.py
+++ b/Lib/test/test_dict_jy.py
@@ -1,5 +1,5 @@
 from test import test_support
-import java
+from java.util import HashMap, Hashtable
 import unittest
 from collections import defaultdict
 import test_dict
@@ -114,7 +114,7 @@
 class JavaIntegrationTest(unittest.TestCase):
     "Tests for instantiating dicts from Java maps and hashtables"
     def test_hashmap(self):
-        x = java.util.HashMap()
+        x = HashMap()
         x.put('a', 1)
         x.put('b', 2)
         x.put('c', 3)
@@ -123,7 +123,7 @@
         self.assertEqual(set(y.items()), set([('a', 1), ('b', 2), ('c', 3), ((1,2), "xyz")]))
 
     def test_hashmap_builtin_pymethods(self):
-        x = java.util.HashMap()
+        x = HashMap()
         x['a'] = 1
         x[(1, 2)] = 'xyz'
         self.assertEqual({tup for tup in x.iteritems()}, {('a', 1), ((1, 2), 'xyz')})
@@ -132,18 +132,18 @@
 
     def test_hashtable_equal(self):
         for d in ({}, {1:2}):
-            x = java.util.Hashtable(d)
+            x = Hashtable(d)
             self.assertEqual(x, d)
             self.assertEqual(d, x)
-            self.assertEqual(x, java.util.HashMap(d))
+            self.assertEqual(x, HashMap(d))
 
     def test_hashtable_remove(self):
-        x = java.util.Hashtable({})
+        x = Hashtable({})
         with self.assertRaises(KeyError):
             del x[0]
 
     def test_hashtable(self):
-        x = java.util.Hashtable()
+        x = Hashtable()
         x.put('a', 1)
         x.put('b', 2)
         x.put('c', 3)
@@ -154,10 +154,10 @@
 
 class JavaDictTest(test_dict.DictTest):
 
-    _class = java.util.HashMap
+    _class = HashMap
 
     def test_copy_java_hashtable(self):
-        x = java.util.Hashtable()
+        x = Hashtable()
         xc = x.copy()
         self.assertEqual(type(x), type(xc))
 
@@ -179,6 +179,57 @@
         self.assertEqual(x.__delitem__(1), None)
         self.assertEqual(len(x), 0)
 
+    def assert_property(self, prop, a, b):
+        prop(self._make_dict(a), self._make_dict(b))
+        prop(a, self._make_dict(b))
+        prop(self._make_dict(a), b)
+
+    def assert_not_property(self, prop, a, b):
+        with self.assertRaises(AssertionError):
+            prop(self._make_dict(a), self._make_dict(b))
+        with self.assertRaises(AssertionError):
+            prop(a, self._make_dict(b))
+        with self.assertRaises(AssertionError):
+            prop(self._make_dict(a), b)
+
+    # NOTE: when comparing dictionaries below exclusively in Java
+    # space, keys like 1 and 1L are different objects. Only when they
+    # are brought into Python space by Py.java2py, as is needed when
+    # comparing a Python dict with a Java Map, do we see them become
+    # equal.
+
+    def test_le(self):
+        self.assert_property(self.assertLessEqual, {}, {})
+        self.assert_property(self.assertLessEqual, {1: 2}, {1: 2})
+        self.assert_not_property(self.assertLessEqual, {1: 2, 3: 4}, {1: 2})
+        self.assert_property(self.assertLessEqual, {}, {1: 2})
+        self.assertLessEqual(self._make_dict({1: 2}), {1L: 2L, 3L: 4L})
+        self.assertLessEqual({1L: 2L}, self._make_dict({1: 2, 3L: 4L}))
+
+    def test_lt(self):
+        self.assert_not_property(self.assertLess, {}, {})
+        self.assert_not_property(self.assertLess, {1: 2}, {1: 2})
+        self.assert_not_property(self.assertLessEqual, {1: 2, 3: 4}, {1: 2})
+        self.assert_property(self.assertLessEqual, {}, {1: 2})
+        self.assertLess(self._make_dict({1: 2}), {1L: 2L, 3L: 4L})
+        self.assertLess({1L: 2L}, self._make_dict({1: 2, 3L: 4L}))
+
+    def test_ge(self):
+        self.assert_property(self.assertGreaterEqual, {}, {})
+        self.assert_property(self.assertGreaterEqual, {1: 2}, {1: 2})
+        self.assert_not_property(self.assertLessEqual, {1: 2, 3: 4}, {1: 2})
+        self.assert_property(self.assertLessEqual, {}, {1: 2})
+        self.assertGreaterEqual(self._make_dict({1: 2, 3: 4}), {1L: 2L})
+        self.assertGreaterEqual({1L: 2L, 3L: 4L}, self._make_dict({1: 2}))
+
+    def test_gt(self):
+        self.assert_not_property(self.assertGreater, {}, {})
+        self.assert_not_property(self.assertGreater, {1: 2}, {1: 2})
+        self.assert_not_property(self.assertLessEqual, {1: 2, 3: 4}, {1: 2})
+        self.assert_property(self.assertLessEqual, {}, {1: 2})
+        self.assertGreater(self._make_dict({1: 2, 3: 4}), {1L: 2L})
+        self.assertGreater({1L: 2L, 3L: 4L}, self._make_dict({1: 2}))
+
 
 def test_main():
     test_support.run_unittest(DictInitTest, DictCmpTest, DerivedDictTest, JavaIntegrationTest, JavaDictTest)
diff --git a/src/org/python/core/PyJavaType.java b/src/org/python/core/PyJavaType.java
--- a/src/org/python/core/PyJavaType.java
+++ b/src/org/python/core/PyJavaType.java
@@ -946,6 +946,58 @@
         protected abstract boolean getResult(int comparison);
     }
 
+    private static PyObject mapEq(PyObject self, PyObject other) {
+        Map<Object, Object> selfMap = ((Map<Object, Object>) self.getJavaProxy());
+        if (other.getType().isSubType(PyDictionary.TYPE)) {
+            PyDictionary oDict = (PyDictionary) other;
+            if (selfMap.size() != oDict.size()) {
+                return Py.False;
+            }
+            for (Object jkey : selfMap.keySet()) {
+                Object jval = selfMap.get(jkey);
+                PyObject oVal = oDict.__finditem__(Py.java2py(jkey));
+                if (oVal == null) {
+                    return Py.False;
+                }
+                if (!Py.java2py(jval)._eq(oVal).__nonzero__()) {
+                    return Py.False;
+                }
+            }
+            return Py.True;
+        } else {
+            Object oj = other.getJavaProxy();
+            if (oj instanceof Map) {
+                Map<Object, Object> oMap = (Map<Object, Object>) oj;
+                return Py.newBoolean(selfMap.equals(oMap));
+            } else {
+                return null;
+            }
+        }
+    }
+
+    // Map ordering comparisons (lt, le, gt, ge) are based on the key sets;
+    // we just define mapLe + mapEq for total ordering of such key sets
+    private static PyObject mapLe(PyObject self, PyObject other) {
+        Set<Object> selfKeys = ((Map<Object, Object>) self.getJavaProxy()).keySet();
+        if (other.getType().isSubType(PyDictionary.TYPE)) {
+            PyDictionary oDict = (PyDictionary) other;
+            for (Object jkey : selfKeys) {
+                if (!oDict.__contains__(Py.java2py(jkey))) {
+                    return Py.False;
+                }
+            }
+            return Py.True;
+        } else {
+            Object oj = other.getJavaProxy();
+            if (oj instanceof Map) {
+                Map<Object, Object> oMap = (Map<Object, Object>) oj;
+                return Py.newBoolean(oMap.keySet().containsAll(selfKeys));
+            } else {
+                return null;
+            }
+        }
+    }
+
     /**
      * Build a map of common Java collection base types (Map, Iterable, etc) that need to be
      * injected with Python's equivalent types' builtin methods (__len__, __iter__, iteritems, etc).
@@ -1030,31 +1082,31 @@
             PyBuiltinMethodNarrow mapEqProxy = new MapMethod("__eq__", 1) {
                 @Override
                 public PyObject __call__(PyObject other) {
-                    if (other.getType().isSubType(PyDictionary.TYPE)) {
-                        PyDictionary oDict = (PyDictionary) other;
-                        if (asMap().size() != oDict.size()) {
-                            return Py.False;
-                        }
-                        for (Object jkey : asMap().keySet()) {
-                            Object jval = asMap().get(jkey);
-                            PyObject oVal = oDict.__finditem__(Py.java2py(jkey));
-                            if (oVal == null) {
-                                return Py.False;
-                            }
-                            if (!Py.java2py(jval)._eq(oVal).__nonzero__()) {
-                                return Py.False;
-                            }
-                        }
-                        return Py.True;
-                    } else {
-                        Object oj = other.getJavaProxy();
-                        if (oj instanceof Map) {
-                            Map<Object, Object> oMap = (Map<Object, Object>) oj;
-                            return asMap().equals(oMap) ? Py.True : Py.False;
-                        } else {
-                            return null;
-                        }
-                    }
+                    return mapEq(self, other);
+                }
+            };
+            PyBuiltinMethodNarrow mapLeProxy = new MapMethod("__le__", 1) {
+                @Override
+                public PyObject __call__(PyObject other) {
+                    return mapLe(self, other);
+                }
+            };
+            PyBuiltinMethodNarrow mapGeProxy = new MapMethod("__ge__", 1) {
+                @Override
+                public PyObject __call__(PyObject other) {
+                    return (mapLe(self, other).__not__()).__or__(mapEq(self, other));
+                }
+            };
+            PyBuiltinMethodNarrow mapLtProxy = new MapMethod("__lt__", 1) {
+                @Override
+                public PyObject __call__(PyObject other) {
+                    return mapLe(self, other).__and__(mapEq(self, other).__not__());
+                }
+            };
+            PyBuiltinMethodNarrow mapGtProxy = new MapMethod("__gt__", 1) {
+                @Override
+                public PyObject __call__(PyObject other) {
+                    return mapLe(self, other).__not__();
                 }
             };
             PyBuiltinMethodNarrow mapIterProxy = new MapMethod("__iter__", 0) {
@@ -1340,27 +1392,33 @@
                     }
                 }
             };
-            collectionProxies.put(Map.class, new PyBuiltinMethod[] {mapLenProxy,
-                    // map IterProxy can conflict with Iterable.class; fix after the fact in handleMroError
-                                                                    mapIterProxy,
-                                                                    mapReprProxy,
-                                                                    mapEqProxy,
-                                                                    mapContainsProxy,
-                                                                    mapGetItemProxy,
-                                                                    //mapGetProxy,
-                                                                    mapPutProxy,
-                                                                    mapRemoveProxy,
-                                                                    mapIterItemsProxy,
-                                                                    mapHasKeyProxy,
-                                                                    mapKeysProxy,
-                                                                    //mapValuesProxy,
-                                                                    mapSetDefaultProxy,
-                                                                    mapPopProxy,
-                                                                    mapPopItemProxy,
-                                                                    mapItemsProxy,
-                                                                    mapCopyProxy,
-                                                                    mapUpdateProxy,
-                                                                    mapFromKeysProxy});     // class method
+            collectionProxies.put(Map.class, new PyBuiltinMethod[] {
+                    mapLenProxy,
+                    // map IterProxy can conflict with Iterable.class;
+                    // fix after the fact in handleMroError
+                    mapIterProxy,
+                    mapReprProxy,
+                    mapEqProxy,
+                    mapLeProxy,
+                    mapLtProxy,
+                    mapGeProxy,
+                    mapGtProxy,
+                    mapContainsProxy,
+                    mapGetItemProxy,
+                    //mapGetProxy,
+                    mapPutProxy,
+                    mapRemoveProxy,
+                    mapIterItemsProxy,
+                    mapHasKeyProxy,
+                    mapKeysProxy,
+                    //mapValuesProxy,
+                    mapSetDefaultProxy,
+                    mapPopProxy,
+                    mapPopItemProxy,
+                    mapItemsProxy,
+                    mapCopyProxy,
+                    mapUpdateProxy,
+                    mapFromKeysProxy});     // class method
             postCollectionProxies.put(Map.class, new PyBuiltinMethod[] {mapGetProxy,
                                                                         mapValuesProxy});
 

-- 
Repository URL: https://hg.python.org/jython


More information about the Jython-checkins mailing list