[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