diff --git a/jnius/__init__.py b/jnius/__init__.py index 3751e57..c5f1dde 100644 --- a/jnius/__init__.py +++ b/jnius/__init__.py @@ -11,6 +11,7 @@ __version__ = '1.1.2-dev' from .jnius import * # noqa from .reflect import * # noqa +from six import with_metaclass # XXX monkey patch methods that cannot be in cython. # Cython doesn't allow to set new attribute on methods it compiled @@ -18,7 +19,7 @@ from .reflect import * # noqa HASHCODE_MAX = 2 ** 31 - 1 -class PythonJavaClass_(PythonJavaClass): +class PythonJavaClass_(with_metaclass(MetaJavaBase, PythonJavaClass)): @java_method('()I', name='hashCode') def hashCode(self): diff --git a/jnius/jnius.pyx b/jnius/jnius.pyx index b3b1b3e..28966cc 100644 --- a/jnius/jnius.pyx +++ b/jnius/jnius.pyx @@ -87,8 +87,8 @@ Python:: __all__ = ('JavaObject', 'JavaClass', 'JavaMethod', 'JavaField', 'JavaStaticMethod', 'JavaStaticField', 'JavaMultipleMethod', - 'MetaJavaClass', 'JavaException', 'cast', 'find_javaclass', - 'PythonJavaClass', 'java_method', 'detach') + 'MetaJavaBase', 'MetaJavaClass', 'JavaException', 'cast', + 'find_javaclass', 'PythonJavaClass', 'java_method', 'detach') from libc.stdlib cimport malloc, free from functools import partial diff --git a/jnius/jnius_conversion.pxi b/jnius/jnius_conversion.pxi index b58d965..679a97e 100644 --- a/jnius/jnius_conversion.pxi +++ b/jnius/jnius_conversion.pxi @@ -73,6 +73,9 @@ cdef void populate_args(JNIEnv *j_env, tuple definition_args, jvalue *j_args, ar pc = py_arg # get the java class jc = pc.j_self + if jc is None: + pc._init_j_self_ptr() + jc = pc.j_self # get the localref j_args[index].l = jc.j_self.obj elif isinstance(py_arg, type): @@ -332,7 +335,7 @@ cdef convert_jarray_to_python(JNIEnv *j_env, definition, jobject j_object): cdef jobject convert_python_to_jobject(JNIEnv *j_env, definition, obj) except *: cdef jobject retobject, retsubobject cdef jclass retclass - cdef jmethodID redmidinit + cdef jmethodID redmidinit = NULL cdef jvalue j_ret[1] cdef JavaClass jc cdef JavaObject jo @@ -376,6 +379,9 @@ cdef jobject convert_python_to_jobject(JNIEnv *j_env, definition, obj) except *: pc = obj # get the java class jc = pc.j_self + if jc is None: + pc._init_j_self_ptr() + jc = pc.j_self # get the localref return jc.j_self.obj elif isinstance(obj, (tuple, list)): diff --git a/jnius/jnius_export_class.pxi b/jnius/jnius_export_class.pxi index 15f9ce7..49c8e5e 100644 --- a/jnius/jnius_export_class.pxi +++ b/jnius/jnius_export_class.pxi @@ -1,3 +1,6 @@ +from cpython cimport PyObject +from warnings import warn + class JavaException(Exception): '''Can be a real java exception, or just an exception from the wrapper. ''' @@ -36,15 +39,108 @@ cdef class JavaClassStorage: self.j_cls = NULL +class MetaJavaBase(type): + def __instancecheck__(cls, value): + cdef JNIEnv *j_env = get_jnienv() + cdef JavaClassStorage meta = getattr(cls, '__cls_storage', None) + cdef JavaObject jo + cdef JavaClass jc + cdef PythonJavaClass pc + cdef jobject obj = NULL + cdef jclass proxy = j_env[0].FindClass(j_env, 'java/lang/reflect/Proxy') + cdef jclass nih + cdef jmethodID meth + cdef object wrapped_python + + if isinstance(value, basestring): + obj = j_env[0].NewStringUTF(j_env, "") + elif isinstance(value, JavaClass): + jc = value + obj = jc.j_self.obj + elif isinstance(value, JavaObject): + jo = value + obj = jo.obj + elif isinstance(value, PythonJavaClass): + pc = value + jc = pc.j_self + if jc is None: + pc._init_j_self_ptr() + jc = pc.j_self + obj = jc.j_self.obj + + if NULL != obj: + if meta is not None and 0 != j_env[0].IsInstanceOf(j_env, obj, meta.j_cls): + return True + + if NULL != proxy and 0 != j_env[0].IsInstanceOf(j_env, obj, proxy): + # value is a proxy object. check whether it's one of ours + meth = j_env[0].GetStaticMethodID(j_env, proxy, 'getInvocationHandler', + '(Ljava/lang/Object;)Ljava/lang/reflect/InvocationHandler;' + ) + obj = j_env[0].CallStaticObjectMethod(j_env, proxy, meth, obj) + nih = j_env[0].FindClass(j_env, 'org/jnius/NativeInvocationHandler') + if NULL == nih: + # nih is not reliably in the classpath. don't crash if it's + # not there, because it's impossible to get this far with + # a PythonJavaClass without it, so we can safely assume this + # is just a POJO from elsewhere. + j_env[0].ExceptionClear(j_env) + else: + meth = j_env[0].GetMethodID(j_env, nih, 'getPythonObjectPointer', + '()J') + if NULL == meth: + # Perhaps we have an old nih + j_env[0].ExceptionClear(j_env) + warn("The org.jnius.NativeInvocationHandler on your classpath" + " is out of date. isinstance will be unreliable.") + else: + wrapped_python = j_env[0].CallLongMethod(j_env, obj, meth) + if wrapped_python is not value and wrapped_python is not None: + if isinstance(wrapped_python, cls): + return True + + # All else fails, defer to python. + return super(MetaJavaBase, cls).__instancecheck__(value) + + cdef dict jclass_register = {} -class MetaJavaClass(type): + +class MetaJavaClass(MetaJavaBase): def __new__(meta, classname, bases, classDict): meta.resolve_class(classDict) tp = type.__new__(meta, str(classname), bases, classDict) jclass_register[classDict['__javaclass__']] = tp return tp + def __subclasscheck__(cls, value): + cdef JNIEnv *j_env = get_jnienv() + cdef JavaClassStorage me = getattr(cls, '__cls_storage') + cdef JavaClassStorage jcs + cdef JavaClass jc + cdef jclass obj = NULL + + if isinstance(value, JavaClass): + jc = value + obj = jc.j_self.obj + else: + jcs = getattr(value, '__cls_storage', None) + if jcs is not None: + obj = jcs.j_cls + + if NULL == obj: + for interface in getattr(value, '__javainterfaces__', []): + obj = j_env[0].FindClass(j_env, str_for_c(interface)) + if obj == NULL: + j_env[0].ExceptionClear(j_env) + elif 0 != j_env[0].IsAssignableFrom(j_env, obj, me.j_cls): + return True + else: + if 0 != j_env[0].IsAssignableFrom(j_env, obj, me.j_cls): + return True + + return super(MetaJavaClass, cls).__subclasscheck__(value) + @staticmethod def get_javaclass(name): return jclass_register.get(name) diff --git a/jnius/jnius_proxy.pxi b/jnius/jnius_proxy.pxi index 65d60b0..da9b43a 100644 --- a/jnius/jnius_proxy.pxi +++ b/jnius/jnius_proxy.pxi @@ -25,6 +25,9 @@ cdef class PythonJavaClass(object): self.j_self = None def __init__(self, *args, **kwargs): + self._init_j_self_ptr() + + def _init_j_self_ptr(self): javacontext = 'system' if hasattr(self, '__javacontext__'): javacontext = self.__javacontext__ diff --git a/jnius/jnius_utils.pxi b/jnius/jnius_utils.pxi index f79f8a2..4a430b9 100644 --- a/jnius/jnius_utils.pxi +++ b/jnius/jnius_utils.pxi @@ -315,7 +315,7 @@ cdef int calculate_score(sign_args, args, is_varargs=False) except *: # if it's a generic object, accept python string, or any java # class/object if r == 'java/lang/Object': - if isinstance(arg, JavaClass) or isinstance(arg, JavaObject): + if isinstance(arg, (PythonJavaClass, JavaClass, JavaObject)): score += 10 continue elif isinstance(arg, basestring): diff --git a/jnius/src/org/jnius/NativeInvocationHandler.java b/jnius/src/org/jnius/NativeInvocationHandler.java index ed5ba66..03d9bad 100644 --- a/jnius/src/org/jnius/NativeInvocationHandler.java +++ b/jnius/src/org/jnius/NativeInvocationHandler.java @@ -33,5 +33,9 @@ public class NativeInvocationHandler implements InvocationHandler { return ret; } + public long getPythonObjectPointer() { + return ptr; + } + native Object invoke0(Object proxy, Method method, Object[] args); } diff --git a/tests/test_export_class.py b/tests/test_export_class.py new file mode 100644 index 0000000..59b3fc8 --- /dev/null +++ b/tests/test_export_class.py @@ -0,0 +1,68 @@ +from __future__ import print_function +from __future__ import division +from __future__ import absolute_import +import unittest +from jnius import autoclass, java_method, PythonJavaClass + +Iterable = autoclass('java.lang.Iterable') +ArrayList = autoclass('java.util.ArrayList') +Runnable = autoclass('java.lang.Runnable') +Thread = autoclass('java.lang.Thread') +Object = autoclass('java.lang.Object') + +class SampleIterable(PythonJavaClass): + __javainterfaces__ = ['java/lang/Iterable'] + + @java_method('()Ljava/lang/Iterator;') + def iterator(self): + sample = ArrayList() + sample.add(1) + sample.add(2) + return sample.iterator() + +class ExportClassTest(unittest.TestCase): + def test_is_instance(self): + array_list = ArrayList() + thread = Thread() + sample_iterable = SampleIterable() + + self.assertIsInstance(sample_iterable, Iterable) + self.assertIsInstance(sample_iterable, Object) + self.assertIsInstance(sample_iterable, SampleIterable) + self.assertNotIsInstance(sample_iterable, Runnable) + self.assertNotIsInstance(sample_iterable, Thread) + + self.assertIsInstance(array_list, Iterable) + self.assertIsInstance(array_list, ArrayList) + self.assertIsInstance(array_list, Object) + + self.assertNotIsInstance(thread, Iterable) + self.assertIsInstance(thread, Thread) + self.assertIsInstance(thread, Runnable) + + def test_subclasses_work_for_arg_matching(self): + array_list = ArrayList() + array_list.add(SampleIterable()) + self.assertIsInstance(array_list.get(0), Iterable) + self.assertIsInstance(array_list.get(0), SampleIterable) + + + def assertIsSubclass(self, cls, parent): + if not issubclass(cls, parent): + self.fail("%s is not a subclass of %s" % + (cls.__name__, parent.__name__)) + + def assertNotIsSubclass(self, cls, parent): + if issubclass(cls, parent): + self.fail("%s is a subclass of %s" % + (cls.__name__, parent.__name__)) + + def test_is_subclass(self): + self.assertIsSubclass(Thread, Runnable) + self.assertIsSubclass(ArrayList, Iterable) + self.assertIsSubclass(ArrayList, Object) + self.assertIsSubclass(SampleIterable, Iterable) + self.assertNotIsSubclass(Thread, Iterable) + self.assertNotIsSubclass(ArrayList, Runnable) + self.assertNotIsSubclass(Runnable, Thread) + self.assertNotIsSubclass(Iterable, SampleIterable) diff --git a/tests/test_proxy.py b/tests/test_proxy.py index 1ed01b9..4efefb3 100644 --- a/tests/test_proxy.py +++ b/tests/test_proxy.py @@ -15,7 +15,6 @@ class TestImplemIterator(PythonJavaClass): 'java/util/ListIterator', ] def __init__(self, collection, index=0): - super(TestImplemIterator, self).__init__() self.collection = collection self.index = index