[Jython-checkins] jython: Raises NotImplementedError if abstract method from Java is not implemented

jim.baker jython-checkins at python.org
Thu Jan 8 05:07:44 CET 2015


https://hg.python.org/jython/rev/3235ec531b41
changeset:   7517:3235ec531b41
user:        Jim Baker <jim.baker at rackspace.com>
date:        Wed Jan 07 21:06:17 2015 -0700
summary:
  Raises NotImplementedError if abstract method from Java is not implemented

Abstract methods of an inherited class or interface from Java now raise
NotImplementedError, instead of returning None (in Java, null) or some
"zero", if they are not implemented in the extending Python class.

This is a (minor) backwards breaking change similar to other recent
changes to remove silent errors in Java integration.

Unlike ABCs in Python - or partial extensions of abstract classes in
Java - we allow such classes to be instantiated. We may wish to change
this in Jython 3.x, but it seems too radical of a change for the moment.

Example: here is the decompiled bytecode for a class implementing
Callable with a method body for the call abstract method like so:

    public Object call() throws Exception {
        final PyObject findPython = ProxyMaker.findPython((PyProxy)this, "call");
        if (findPython != null) {
            final PyObject pyObject = findPython;
            try {
                return Py.tojava(
                    pyObject._jcallexc((Object[])Py.EmptyObjects),
                    (Class)Class.forName("java.lang.Object"));
            }
            catch (Exception ex) {
                throw ex;
            }
            catch (Throwable t) {
                pyObject._jthrow(t);
                return null;
            }
        }
        throw (Throwable)Py.NotImplementedError("");
    }

Previously the last line of this method would have returned null, or
some "zero", per the return type, if it could not look up a method in
the extending Python class.

Fixes http://bugs.jython.org/issue1561

files:
  Lib/test/test_java_subclasses.py        |  77 +++++++++++-
  src/org/python/compiler/ProxyMaker.java |  13 ++
  2 files changed, 82 insertions(+), 8 deletions(-)


diff --git a/Lib/test/test_java_subclasses.py b/Lib/test/test_java_subclasses.py
--- a/Lib/test/test_java_subclasses.py
+++ b/Lib/test/test_java_subclasses.py
@@ -7,7 +7,7 @@
 
 from java.lang import (Boolean, Class, ClassLoader, Comparable,Integer, Object, Runnable, String,
         Thread, ThreadGroup)
-from java.util import Date, Hashtable, Vector
+from java.util import AbstractList, Date, Hashtable, HashSet, Vector
 from java.util.concurrent import Callable, Executors
 
 from java.awt import Color, Component, Dimension, Rectangle
@@ -374,14 +374,75 @@
         pool.shutdown()
 
 
+class AbstractMethodTest(unittest.TestCase):
+
+    def test_abstract_method_implemented(self):
+        class C(AbstractList):
+            def get(self, index):
+                return index * 2
+            def size(self):
+                return 7
+
+        c = C()
+        self.assertEqual(c.size(), 7)
+        self.assertEqual([c.get(i) for i in xrange(7)], range(0, 14, 2))
+
+    def test_abstract_method_not_implemented(self):
+        class C(AbstractList):
+            def size(self):
+                return 47
+
+        # note that unlike ABCs in Python - or partial extensions
+        # of abstract classes in Java - we allow such classes to
+        # be instantiated. We may wish to change this in Jython
+        # 3.x
+        c = C()
+        self.assertEqual(c.size(), 47)
+        with self.assertRaisesRegexp(NotImplementedError, r"^$"):
+            C().get(42)
+
+    def test_concrete_method(self):
+        class H(HashSet):
+            def __init__(self):
+                self.added = 0
+                HashSet.__init__(self)
+
+            def add(self, value):
+                self.added += 1
+                HashSet.add(self, value)
+
+        h = H()
+        h.add(42)
+        h.add(47)
+        h.discard(47)
+        self.assertEqual(list(h), [42])
+        self.assertEqual(h.added, 2)
+
+    def test_interface_method_implemented(self):
+        class C(Callable):
+            def call(self):
+                return 42
+
+        self.assertEqual(C().call(), 42)
+
+    def test_interface_method_not_implemented(self):
+        class C(Callable):
+            pass
+        
+        with self.assertRaisesRegexp(NotImplementedError, r"^$"):
+            C().call()
+
+
 def test_main():
-    test_support.run_unittest(InterfaceTest,
-            TableModelTest,
-            AutoSuperTest,
-            PythonSubclassesTest,
-            AbstractOnSyspathTest,
-            ContextClassloaderTest,
-            MetaClassTest)
+    test_support.run_unittest(
+        InterfaceTest,
+        TableModelTest,
+        AutoSuperTest,
+        PythonSubclassesTest,
+        AbstractOnSyspathTest,
+        ContextClassloaderTest,
+        MetaClassTest,
+        AbstractMethodTest)
 
 
 if __name__ == '__main__':
diff --git a/src/org/python/compiler/ProxyMaker.java b/src/org/python/compiler/ProxyMaker.java
--- a/src/org/python/compiler/ProxyMaker.java
+++ b/src/org/python/compiler/ProxyMaker.java
@@ -10,8 +10,12 @@
 import org.objectweb.asm.Label;
 import org.objectweb.asm.Opcodes;
 import org.python.core.Py;
+import org.python.core.PyException;
 import org.python.util.Generic;
 
+import static org.python.util.CodegenUtils.p;
+import static org.python.util.CodegenUtils.sig;
+
 
 public class ProxyMaker extends ProxyCodeHelpers implements ClassConstants, Opcodes
 {
@@ -382,6 +386,15 @@
             callMethod(code, name, parameters, ret, exceptions);
             code.label(returnNull);
             code.pop();
+
+            // throw an exception if we cannot load a Python method for this abstract method
+            // note that the unreachable return is simply to simplify bytecode gen
+            code.ldc("");
+            code.invokestatic(p(Py.class), "NotImplementedError",
+                    sig(PyException.class, String.class));
+            code.checkcast(p(Throwable.class));
+            code.athrow();
+
             doNullReturn(code, ret);
         }
     }

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


More information about the Jython-checkins mailing list