Skip to content

Commit

Permalink
Merge pull request #299 from drmoose/python-implementing-interface-fixes
Browse files Browse the repository at this point in the history
Fixes for Implementing a Java Interface (DONE BUT CANT REMOVE wip FLAG)
  • Loading branch information
tshirtman committed Apr 30, 2018
2 parents d5468fb + 1845a3d commit 61149f2
Show file tree
Hide file tree
Showing 9 changed files with 184 additions and 7 deletions.
3 changes: 2 additions & 1 deletion jnius/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,15 @@

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

HASHCODE_MAX = 2 ** 31 - 1


class PythonJavaClass_(PythonJavaClass):
class PythonJavaClass_(with_metaclass(MetaJavaBase, PythonJavaClass)):

@java_method('()I', name='hashCode')
def hashCode(self):
Expand Down
4 changes: 2 additions & 2 deletions jnius/jnius.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 7 additions & 1 deletion jnius/jnius_conversion.pxi
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)):
Expand Down
98 changes: 97 additions & 1 deletion jnius/jnius_export_class.pxi
Original file line number Diff line number Diff line change
@@ -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.
'''
Expand Down Expand Up @@ -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, <char *>'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, <char *>"")
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, <char *>'getInvocationHandler',
<char *>'(Ljava/lang/Object;)Ljava/lang/reflect/InvocationHandler;'
)
obj = j_env[0].CallStaticObjectMethod(j_env, proxy, meth, obj)
nih = j_env[0].FindClass(j_env, <char *>'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, <char *>'getPythonObjectPointer',
<char *>'()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 = <object><PyObject *>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)
Expand Down
3 changes: 3 additions & 0 deletions jnius/jnius_proxy.pxi
Original file line number Diff line number Diff line change
Expand Up @@ -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__
Expand Down
2 changes: 1 addition & 1 deletion jnius/jnius_utils.pxi
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
4 changes: 4 additions & 0 deletions jnius/src/org/jnius/NativeInvocationHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,9 @@ public Object invoke(Object proxy, Method method, Object[] args) {
return ret;
}

public long getPythonObjectPointer() {
return ptr;
}

native Object invoke0(Object proxy, Method method, Object[] args);
}
68 changes: 68 additions & 0 deletions tests/test_export_class.py
Original file line number Diff line number Diff line change
@@ -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)
1 change: 0 additions & 1 deletion tests/test_proxy.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down

0 comments on commit 61149f2

Please sign in to comment.