[Jython-checkins] jython: Allow bound methods as arg for single method interface param.

jim.baker jython-checkins at python.org
Wed Sep 24 02:00:24 CEST 2014


https://hg.python.org/jython/rev/0e4c6e7d2273
changeset:   7391:0e4c6e7d2273
user:        Jim Baker <jim.baker at rackspace.com>
date:        Tue Sep 23 18:00:10 2014 -0600
summary:
  Allow bound methods as arg for single method interface param.

Java widely uses single method interfaces (such as Callable or
Runnable) as parameters for methods, such as setting up a
callback. Jython since 2.5 has supported passing Python functions as
such arguments, which avoids having to define a class extending the
single method interface. This change allows bound methods to be used
in a similar fashion, by using PyMethod#__tojava__.

Note that any callable object should have this behavior, since there
is no possibility of ambiguity; however, this change did not add such
support for __call__ since it seems to be a bit more complex in how it
interacts with class derivation. But a test, currently skipped, as
been added for such future support.

Partial fix of http://bugs.jython.org/issue2115

files:
  Lib/_socket.py                    |  11 +--
  Lib/test/test_java_integration.py |  63 ++++++++++++++++++-
  src/org/python/core/PyMethod.java |  59 +++++++++++++++++-
  3 files changed, 122 insertions(+), 11 deletions(-)


diff --git a/Lib/_socket.py b/Lib/_socket.py
--- a/Lib/_socket.py
+++ b/Lib/_socket.py
@@ -742,14 +742,7 @@
         # is managed by this method.
         #
         # All sockets can be selected on, regardless of blocking/nonblocking state.
-
-        def workaround_jython_bug_for_bound_methods(_):
-            # FIXME check for errors, notify the corresponding socket accordingly
-            # FIXME wrapper function is needed because of http://bugs.jython.org/issue2115
-            self._notify_selectors()
-
-        future.addListener(workaround_jython_bug_for_bound_methods)
-
+        future.addListener(self._notify_selectors)
         if self.timeout is None:
             log.debug("Syncing on future %s for %s", future, reason, extra={"sock": self})
             return future.sync()
@@ -1194,7 +1187,7 @@
             else:
                 return _socktuple(self.bind_addr)
         # Netty 4 currently races between bind to ephemeral port and the availability
-        # of the local address for the channel. Workaround for now is to poll.
+        # of the local address for the channel. Poll to work around this issue.
         while True:
             local_addr = self.channel.localAddress()
             if local_addr:
diff --git a/Lib/test/test_java_integration.py b/Lib/test/test_java_integration.py
--- a/Lib/test/test_java_integration.py
+++ b/Lib/test/test_java_integration.py
@@ -22,6 +22,7 @@
                      FileNotFoundException, FileOutputStream, FileWriter, ObjectInputStream,
                      ObjectOutputStream, OutputStreamWriter, UnsupportedEncodingException)
 from java.util import ArrayList, Date, HashMap, Hashtable, StringTokenizer, Vector
+from java.util.concurrent import Executors
 
 from java.awt import Dimension, Color, Component, Container
 from java.awt.event import ComponentEvent
@@ -833,6 +834,65 @@
         self.assertEqual(target.attribute, value)
 
 
+class WrappedUp(object):
+    def __init__(self):
+        self.data = list()
+    def doit(self):
+        self.data.append(42)
+
+
+class CallableObject(object):
+    def __init__(self):
+        self.data = list()
+    def __call__(self):
+        self.data.append(42)
+
+
+class SingleMethodInterfaceTest(unittest.TestCase):
+
+    def setUp(self):
+        self.executor = Executors.newSingleThreadExecutor()
+
+    def tearDown(self):
+        self.executor.shutdown()
+
+    def test_function(self):
+        x = list()
+        def f():
+            x.append(42)
+        future = self.executor.submit(f)
+        future.get()
+        self.assertEqual(x, [42])
+
+    @unittest.skip("FIXME: not working")
+    def test_callable_object(self):
+        callable_obj = CallableObject()
+        future = self.executor.submit(callable_obj)
+        future.get()
+        self.assertEqual(callable_obj.data, [42])
+
+    def test_bound_method(self):
+        obj = WrappedUp()
+        future = self.executor.submit(obj.doit)
+        future.get()
+        self.assertEqual(obj.data, [42])
+
+    def test_unbound_method(self):
+        with self.assertRaises(TypeError) as exc:
+            future = self.executor.submit(WrappedUp.doit)  # not a bound method
+        self.assertIsInstance(
+            exc.exception, TypeError,
+            "submit(): 1st arg can't be coerced to java.util.concurrent.Callable, java.lang.Runnable")
+
+    def test_some_noncallable_object(self):
+        obj = WrappedUp()
+        with self.assertRaises(TypeError) as exc:
+            future = self.executor.submit(obj)
+        self.assertIsInstance(
+            exc.exception, TypeError,
+            "submit(): 1st arg can't be coerced to java.util.concurrent.Callable, java.lang.Runnable")
+
+
 def test_main():
     test_support.run_unittest(
         BeanPropertyTest,
@@ -852,7 +912,8 @@
         SerializationTest,
         SysIntegrationTest,
         TreePathTest,
-        UnicodeTest)
+        UnicodeTest,
+        SingleMethodInterfaceTest)
 
 if __name__ == "__main__":
     test_main()
diff --git a/src/org/python/core/PyMethod.java b/src/org/python/core/PyMethod.java
--- a/src/org/python/core/PyMethod.java
+++ b/src/org/python/core/PyMethod.java
@@ -8,11 +8,15 @@
 import org.python.expose.ExposedType;
 import org.python.expose.MethodType;
 
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+
 /**
  * A Python method.
  */
 @ExposedType(name = "instancemethod", isBaseType = false, doc = BuiltinDocs.instancemethod_doc)
-public class PyMethod extends PyObject {
+public class PyMethod extends PyObject implements InvocationHandler {
 
     public static final PyType TYPE = PyType.fromClass(PyMethod.class);
 
@@ -356,4 +360,57 @@
         }
         return funcName.toString();
     }
+
+    @Override
+    public Object __tojava__(Class<?> c) {
+        // Automatically coerce to single method interfaces
+        if (__self__ == null) {
+            return super.__tojava__(c); // not a bound method, so no special handling
+        }
+        if (c.isInstance(this) && c != InvocationHandler.class) {
+            // for base types, conversion is simple - so don't wrap!
+            // InvocationHandler is special, since it's a single method interface
+            // that we implement, but if we coerce to it we want the arguments
+            return c.cast( this );
+        } else if (c.isInterface()) {
+            if (c.getDeclaredMethods().length == 1 && c.getInterfaces().length == 0) {
+                // Proper single method interface
+                return proxy(c);
+            } else {
+                // Try coerce to interface with multiple overloaded versions of
+                // the same method (name)
+                String name = null;
+                for (Method method : c.getMethods()) {
+                    if (method.getDeclaringClass() != Object.class) {
+                        if (name == null || name.equals(method.getName())) {
+                            name = method.getName();
+                        } else {
+                            name = null;
+                            break;
+                        }
+                    }
+                }
+                if (name != null) { // single unique method name
+                    return proxy(c);
+                }
+            }
+        }
+        return super.__tojava__(c);
+    }
+
+    private Object proxy( Class<?> c ) {
+        return Proxy.newProxyInstance(c.getClassLoader(), new Class[]{c}, this);
+    }
+
+    public Object invoke( Object proxy, Method method, Object[] args ) throws Throwable {
+        // Handle invocation when invoked through Proxy (as coerced to single method interface)
+        if (method.getDeclaringClass() == Object.class) {
+            return method.invoke( this, args );
+        } else if (args == null || args.length == 0) {
+            return __call__().__tojava__(method.getReturnType());
+        } else {
+            return __call__(Py.javas2pys(args)).__tojava__(method.getReturnType());
+        }
+    }
+
 }

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


More information about the Jython-checkins mailing list