[Jython-checkins] jython: Various fixes for working with array.array objects. Fixes #1780, #2423, #2451

jim.baker jython-checkins at python.org
Wed Jan 6 16:42:26 EST 2016


https://hg.python.org/jython/rev/49d498968f34
changeset:   7851:49d498968f34
user:        Jim Baker <jim.baker at rackspace.com>
date:        Wed Jan 06 14:42:21 2016 -0700
summary:
  Various fixes for working with array.array objects. Fixes #1780, #2423, #2451

Now supports for array.array/jarray.array objects:

* Boxes primitive arrays when converting to Object[]
* Usage in varargs position
* Raises TypeError if attempt to hash

files:
  ACKNOWLEDGMENTS                        |   2 +
  Lib/test/test_array_jy.py              |  84 +++++++++++--
  src/org/python/core/PyArray.java       |  26 ++++-
  src/org/python/core/ReflectedArgs.java |  44 ++++--
  4 files changed, 125 insertions(+), 31 deletions(-)


diff --git a/ACKNOWLEDGMENTS b/ACKNOWLEDGMENTS
--- a/ACKNOWLEDGMENTS
+++ b/ACKNOWLEDGMENTS
@@ -170,6 +170,8 @@
     Stephen Drake
     Jan Vorwerk
     Eli Oxman
+    Robert Patrick
+    Kevin Edwards
 
 Local Variables:
 mode: indented-text
diff --git a/Lib/test/test_array_jy.py b/Lib/test/test_array_jy.py
--- a/Lib/test/test_array_jy.py
+++ b/Lib/test/test_array_jy.py
@@ -1,28 +1,40 @@
-# The jarray module is being phased out, with all functionality
-# now available in the array module.
-from __future__ import with_statement
+# The jarray module is mostly phased out, but it is still has one
+# constructor function - zeros - that is not directly available in the
+# array module.
+#
+# The equivalent of
+# jarray.zeros(n, typecode)
+# is
+# array.array(typecode, itertools.repeat(0, n))
+#
+# however itertools.repeat does not define __len__ and therefore we
+# have to first build out the zero sequence separately, then copy
+# over. This overhead could be significant for large arrays.
+
+import jarray
+import itertools
 import os
 import unittest
+from array import array
 from test import test_support
-from array import array
+from java.lang import String as JString
+from java.util import Arrays
+
 
 class ArrayJyTestCase(unittest.TestCase):
 
-    def test_jarray(self): # until it is fully formally removed
-        # While jarray is still being phased out, just flex the initializers.
-        # The rest of the test for array will catch all the big problems.
-        import jarray
+    def test_jarray(self):
         from java.lang import String
-        jarray.array(range(5), 'i')
-        jarray.array([String("a"), String("b"), String("c")], String)
-        jarray.zeros(5, 'i')
-        jarray.zeros(5, String)
+        
+        self.assertEqual(sum(jarray.array(range(5), 'i')), 10)
+        self.assertEqual(','.join(jarray.array([String("a"), String("b"), String("c")], String)), u'a,b,c')
+        self.assertEqual(sum(jarray.zeros(5, 'i')), 0)
+        self.assertEqual([x for x in jarray.zeros(5, String)], [None, None, None, None, None])
 
     def test_java_object_arrays(self):
         from array import zeros
         from java.lang import String
         from java.lang.reflect import Array
-        from java.util import Arrays
         jStringArr = array(String, [String("a"), String("b"), String("c")])
         self.assert_(
             Arrays.equals(jStringArr.typecode, 'java.lang.String'),
@@ -91,9 +103,53 @@
         ar += Foo()
         self.assertEqual('__radd__', ar)
 
+    def test_nonhashability(self):
+        "array.array objects are not hashable"
+        # http://bugs.jython.org/issue2451
+        a = array('b', itertools.repeat(0, 100))
+        with self.assertRaisesRegexp(TypeError, r"unhashable type: 'array.array'"):
+            hash(a)
+
+
+class ArrayConversionTestCase(unittest.TestCase):
+    
+    # Covers bugs raised in
+    # http://bugs.jython.org/issue1780,
+    # http://bugs.jython.org/issue2423
+
+    def assertAsList(self, a, b):
+        self.assertEqual(Arrays.asList(a), b)
+
+    def test_boxing_conversion(self):
+        "array objects support boxing, as they did in Jython 2.1"
+        from java.lang import Integer
+
+        self.assertAsList(jarray.array([1, 2, 3, 4, 5], Integer), [1, 2, 3, 4, 5])
+        self.assertAsList(array(Integer, [1, 2, 3, 4, 5]), [1, 2, 3, 4, 5])
+        self.assertAsList(jarray.array([1, 2, 3, 4, 5], "i"), [1, 2, 3, 4, 5])
+        self.assertAsList(array("i", [1, 2, 3, 4, 5]), [1, 2, 3, 4, 5])
+
+    def test_object_varargs(self):
+        "array.array objects can be used in the varargs position, with primitive boxing"
+        a = array('i', range(5, 10))
+        self.assertEqual(
+            'arg 0=5, arg 1=6, arg 2=7, arg 3=8, arg 4=9',
+            JString.format('arg 0=%d, arg 1=%d, arg 2=%d, arg 3=%d, arg 4=%d', [5, 6, 7, 8, 9]))
+
+    def test_assignable_varargs(self):
+        "array.array objects can be used in the varargs position"
+        # modified from test case in http://bugs.jython.org/issue2423;
+        from java.lang import Class
+        from java.net import URL, URLClassLoader
+        params = jarray.array([URL], Class)
+        # URLClassLoader.addURL is protected, so workaround via reflection
+        method = URLClassLoader.getDeclaredMethod('addURL', params)
+        # and verify we got the right method after all
+        self.assertEqual(method.name, "addURL")
+
 
 def test_main():
-    tests = [ToFromfileTestCase, ArrayOpsTestCase]
+    tests = [ToFromfileTestCase, ArrayOpsTestCase, ArrayConversionTestCase]
     if test_support.is_jython:
         tests.extend([ArrayJyTestCase])
     test_support.run_unittest(*tests)
diff --git a/src/org/python/core/PyArray.java b/src/org/python/core/PyArray.java
--- a/src/org/python/core/PyArray.java
+++ b/src/org/python/core/PyArray.java
@@ -209,6 +209,16 @@
         return seq___eq__(o);
     }
 
+    @Override
+    public int hashCode() {
+        return array___hash__();
+    }
+
+    @ExposedMethod
+    final int array___hash__() {
+        throw Py.TypeError(String.format("unhashable type: '%.200s'", getType().fastGetName()));
+    }
+
     @ExposedMethod(type = MethodType.BINARY)
     final PyObject array___lt__(PyObject o) {
         return seq___lt__(o);
@@ -450,7 +460,10 @@
      */
     @Override
     public Object __tojava__(Class<?> c) {
-        if (c == Object.class || (c.isArray() && c.getComponentType().isAssignableFrom(type))) {
+        boolean isArray = c.isArray();
+        Class componentType = c.getComponentType();
+
+        if (c == Object.class || (isArray && componentType.isAssignableFrom(type))) {
             if (delegate.capacity != delegate.size) {
                 // when unboxing, need to shrink the array first, otherwise incorrect
                 // results to Java
@@ -459,9 +472,20 @@
                 return data;
             }
         }
+
+        // rebox: this array is made of primitives but converting to Object[]
+        if (isArray && componentType == Object.class) {
+            Object[] boxed = new Object[delegate.size];
+            for (int i = 0; i < delegate.size; i++) {
+                boxed[i] = Array.get(data, i);
+            }
+            return boxed;
+        }
+
         if (c.isInstance(this)) {
             return this;
         }
+
         return Py.NoConversion;
     }
 
diff --git a/src/org/python/core/ReflectedArgs.java b/src/org/python/core/ReflectedArgs.java
--- a/src/org/python/core/ReflectedArgs.java
+++ b/src/org/python/core/ReflectedArgs.java
@@ -111,22 +111,7 @@
         // if that's what they need to do ;)
 
         if (isVarArgs) {
-            if (pyArgs.length == 0 || !(pyArgs[pyArgs.length - 1] instanceof PySequenceList)) {
-                int non_varargs_len = n - 1;
-                if (pyArgs.length >= non_varargs_len) {
-                    PyObject[] boxedPyArgs = new PyObject[n];
-                    for (int i = 0; i < non_varargs_len; i++) {
-                        boxedPyArgs[i] = pyArgs[i];
-                    }
-                    int varargs_len = pyArgs.length - non_varargs_len;
-                    PyObject[] varargs = new PyObject[varargs_len];
-                    for (int i = 0; i < varargs_len; i++) {
-                        varargs[i] = pyArgs[non_varargs_len + i];
-                    }
-                    boxedPyArgs[non_varargs_len] = new PyList(varargs);
-                    pyArgs = boxedPyArgs;
-                }
-            }
+            pyArgs = ensureBoxedVarargs(pyArgs, n);
         }
 
         if (pyArgs.length != n) {
@@ -164,6 +149,33 @@
         return true;
     }
 
+    /* Boxes argument in the varargs position if not already boxed */
+    private PyObject[] ensureBoxedVarargs(PyObject[] pyArgs, int n) {
+        if (pyArgs.length == 0) {
+            return pyArgs;
+        }
+        PyObject lastArg = pyArgs[pyArgs.length - 1];
+        if (lastArg instanceof PySequenceList || lastArg instanceof PyArray) {
+            // FIXME also check if lastArg is sequence-like
+            return pyArgs; // will be boxed in an array once __tojava__ is called
+        }
+        int non_varargs_len = n - 1;
+        if (pyArgs.length < non_varargs_len) {
+            return pyArgs;
+        }
+        PyObject[] boxedPyArgs = new PyObject[n];
+        for (int i = 0; i < non_varargs_len; i++) {
+            boxedPyArgs[i] = pyArgs[i];
+        }
+        int varargs_len = pyArgs.length - non_varargs_len;
+        PyObject[] varargs = new PyObject[varargs_len];
+        for (int i = 0; i < varargs_len; i++) {
+            varargs[i] = pyArgs[non_varargs_len + i];
+        }
+        boxedPyArgs[non_varargs_len] = new PyList(varargs);
+        return boxedPyArgs;
+    }
+
     public static int precedence(Class<?> arg) {
         if (arg == Object.class) {
             return 3000;

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


More information about the Jython-checkins mailing list