diff --git a/jnius/jnius.pyx b/jnius/jnius.pyx index fd2a555..41b4b56 100644 --- a/jnius/jnius.pyx +++ b/jnius/jnius.pyx @@ -87,9 +87,11 @@ Python:: __all__ = ('JavaObject', 'JavaClass', 'JavaMethod', 'JavaField', 'MetaJavaClass', 'JavaException', 'cast', 'find_javaclass', - 'PythonJavaClass', 'java_implementation') + 'PythonJavaClass', 'java_method') from libc.stdlib cimport malloc, free +from functools import partial +import traceback include "jni.pxi" include "config.pxi" @@ -105,3 +107,5 @@ include "jnius_localref.pxi" include "jnius_export_func.pxi" include "jnius_export_class.pxi" + +include "jnius_proxy.pxi" diff --git a/jnius/jnius_export_func.pxi b/jnius/jnius_export_func.pxi index 77c66b1..f02f97f 100644 --- a/jnius/jnius_export_func.pxi +++ b/jnius/jnius_export_func.pxi @@ -11,7 +11,7 @@ def cast(destclass, obj): return jc def find_javaclass(bytes name): - from reflect import Class + from .reflect import Class cdef JavaClass cls cdef jclass jc cdef JNIEnv *j_env = get_jnienv() diff --git a/jnius/jnius_proxy.pxi b/jnius/jnius_proxy.pxi new file mode 100644 index 0000000..2669e04 --- /dev/null +++ b/jnius/jnius_proxy.pxi @@ -0,0 +1,162 @@ +class java_method(object): + def __init__(self, signature, name=None): + super(java_method, self).__init__() + self.signature = signature + self.name = name + + def __get__(self, instance, instancetype): + return partial(self.__call__, instance) + + def __call__(self, f): + f.__javasignature__ = self.signature + f.__javaname__ = self.name + return f + + +cdef class PythonJavaClass(object): + ''' + Base class to create a java class from python + ''' + cdef JNIEnv *j_env + cdef jclass j_cls + cdef public object j_self + + def __cinit__(self, *args): + self.j_env = get_jnienv() + self.j_cls = NULL + self.j_self = None + + def __init__(self, *args, **kwargs): + self.j_self = create_proxy_instance(self.j_env, self, + self.__javainterfaces__) + + # discover all the java method implemented + self.__javamethods__ = {} + for x in dir(self): + attr = getattr(self, x) + if not callable(attr): + continue + if not hasattr(attr, '__javasignature__'): + continue + signature = parse_definition(attr.__javasignature__) + self.__javamethods__[(attr.__javaname__ or x, signature)] = attr + + def invoke(self, method, *args): + try: + ret = self._invoke(method, *args) + return ret + except Exception as e: + traceback.print_exc(e) + return None + + def _invoke(self, method, *args): + from .reflect import get_signature + # search the java method + + ret_signature = get_signature(method.getReturnType()) + args_signature = tuple([get_signature(x) for x in method.getParameterTypes()]) + method_name = method.getName() + + key = (method_name, (ret_signature, args_signature)) + + py_method = self.__javamethods__.get(key, None) + if not py_method: + print(''.join( + '\n===== Python/java method missing ======', + '\nPython class:', self, + '\nJava method name:', method_name, + '\nSignature: ({}){}'.format(''.join(args_signature), ret_signature), + '\n=======================================\n')) + raise NotImplemented('The method {} is not implemented'.format(key)) + + return py_method(*args) + +cdef jobject invoke0(JNIEnv *j_env, jobject j_this, jobject j_proxy, jobject + j_method, jobjectArray args) except *: + from .reflect import get_signature, Method + + # get the python object + cdef jfieldID ptrField = j_env[0].GetFieldID(j_env, + j_env[0].GetObjectClass(j_env, j_this), "ptr", "J") + cdef jlong jptr = j_env[0].GetLongField(j_env, j_this, ptrField) + cdef object py_obj = jptr + + # extract the method information + cdef JavaClass method = Method(noinstance=True) + cdef LocalRef ref = create_local_ref(j_env, j_method) + method.instanciate_from(create_local_ref(j_env, j_method)) + ret_signature = get_signature(method.getReturnType()) + args_signature = [get_signature(x) for x in method.getParameterTypes()] + + # convert java argument to python object + # native java type are given with java.lang.*, even if the signature say + # it's a native type. + cdef jobject j_arg + py_args = [] + convert_signature = { + 'Z': 'Ljava/lang/Boolean;', + 'B': 'Ljava/lang/Byte;', + 'C': 'Ljava/lang/Character;', + 'S': 'Ljava/lang/Short;', + 'I': 'Ljava/lang/Integer;', + 'J': 'Ljava/lang/Long;', + 'F': 'Ljava/lang/Float;', + 'D': 'Ljava/lang/Double;'} + + for index, arg_signature in enumerate(args_signature): + arg_signature = convert_signature.get(arg_signature, arg_signature) + j_arg = j_env[0].GetObjectArrayElement(j_env, args, index) + py_arg = convert_jobject_to_python(j_env, arg_signature, j_arg) + py_args.append(py_arg) + + # really invoke the python method + name = method.getName() + ret = py_obj.invoke(method, *py_args) + + # convert back to the return type + # use the populate_args(), but in the reverse way :) + t = ret_signature[:1] + + # did python returned a "native" type ? + jtype = None + + if ret_signature == 'Ljava/lang/Object;': + # generic object, try to manually convert it + tp = type(ret) + if tp == int: + jtype = 'J' + elif tp == float: + jtype = 'D' + elif tp == bool: + jtype = 'Z' + elif len(ret_signature) == 1: + jtype = ret_signature + + try: + return convert_python_to_jobject(j_env, jtype or ret_signature, ret) + except Exception as e: + traceback.print_exc(e) + + +# now we need to create a proxy and pass it an invocation handler +cdef create_proxy_instance(JNIEnv *j_env, py_obj, j_interfaces): + from .reflect import autoclass + Proxy = autoclass('java.lang.reflect.Proxy') + NativeInvocationHandler = autoclass('jnius.NativeInvocationHandler') + ClassLoader = autoclass('java.lang.ClassLoader') + + # convert strings to Class + j_interfaces = [find_javaclass(x) for x in j_interfaces] + + cdef JavaClass nih = NativeInvocationHandler(py_obj) + cdef JNINativeMethod invoke_methods[1] + invoke_methods[0].name = 'invoke0' + invoke_methods[0].signature = '(Ljava/lang/Object;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;' + invoke_methods[0].fnPtr = &invoke0 + j_env[0].RegisterNatives(j_env, nih.j_cls, invoke_methods, 1) + + # create the proxy and pass it the invocation handler + cdef JavaClass j_obj = Proxy.newProxyInstance( + ClassLoader.getSystemClassLoader(), j_interfaces, nih) + + return j_obj diff --git a/jnius/jnius_utils.pxi b/jnius/jnius_utils.pxi index fcd4030..ebb1f56 100644 --- a/jnius/jnius_utils.pxi +++ b/jnius/jnius_utils.pxi @@ -217,175 +217,3 @@ cdef int calculate_score(sign_args, args, is_varargs=False) except *: # a method with a better signature so we don't # change this method score return score - - -import functools -import traceback -class java_implementation(object): - def __init__(self, signature, name=None): - super(java_implementation, self).__init__() - self.signature = signature - self.name = name - - def __get__(self, instance, instancetype): - return functools.partial(self.__call__, instance) - - def __call__(self, f): - f.__javasignature__ = self.signature - f.__javaname__ = self.name - return f - -cdef class PythonJavaClass(object): - ''' - base class to create a java class from python - ''' - cdef JNIEnv *j_env - cdef jclass j_cls - cdef public object j_self - - def __cinit__(self, *args): - self.j_env = get_jnienv() - self.j_cls = NULL - self.j_self = None - - def __init__(self, *args, **kwargs): - self.j_self = create_proxy_instance(self.j_env, self, - self.__javainterfaces__) - - # discover all the java method implementated - self.__javamethods__ = {} - for x in dir(self): - attr = getattr(self, x) - if not callable(attr): - continue - if not hasattr(attr, '__javasignature__'): - continue - signature = parse_definition(attr.__javasignature__) - self.__javamethods__[(attr.__javaname__ or x, signature)] = attr - - def invoke(self, method, *args): - try: - ret = self._invoke(method, *args) - return ret - except Exception as e: - traceback.print_exc(e) - return None - - def _invoke(self, method, *args): - from .reflect import get_signature - # search the java method - - ret_signature = get_signature(method.getReturnType()) - args_signature = tuple([get_signature(x) for x in method.getParameterTypes()]) - method_name = method.getName() - - key = (method_name, (ret_signature, args_signature)) - - py_method = self.__javamethods__.get(key, None) - if not py_method: - print(''.join( - '\n===== Python/java method missing ======', - '\nPython class:', self, - '\nJava method name:', method_name, - '\nSignature: ({}){}'.format(''.join(args_signature), ret_signature), - '\n=======================================\n')) - raise NotImplemented('The method {} is not implemented'.format(key)) - - return py_method(*args) - -cdef jobject invoke0(JNIEnv *j_env, jobject j_this, jobject j_proxy, jobject - j_method, jobjectArray args) except *: - from .reflect import get_signature - - # get the python object - cdef jfieldID ptrField = j_env[0].GetFieldID(j_env, - j_env[0].GetObjectClass(j_env, j_this), "ptr", "J") - cdef jlong jptr = j_env[0].GetLongField(j_env, j_this, ptrField) - cdef object py_obj = jptr - - # extract the method information - from .reflect import Method - cdef JavaClass method = Method(noinstance=True) - cdef LocalRef ref = create_local_ref(j_env, j_method) - method.instanciate_from(create_local_ref(j_env, j_method)) - ret_signature = get_signature(method.getReturnType()) - args_signature = [get_signature(x) for x in method.getParameterTypes()] - - # convert java argument to python object - # native java type are given with java.lang.*, even if the signature say - # it's a native type. - cdef jobject j_arg - py_args = [] - convert_signature = { - 'Z': 'Ljava/lang/Boolean;', - 'B': 'Ljava/lang/Byte;', - 'C': 'Ljava/lang/Character;', - 'S': 'Ljava/lang/Short;', - 'I': 'Ljava/lang/Integer;', - 'J': 'Ljava/lang/Long;', - 'F': 'Ljava/lang/Float;', - 'D': 'Ljava/lang/Double;'} - - for index, arg_signature in enumerate(args_signature): - arg_signature = convert_signature.get(arg_signature, arg_signature) - j_arg = j_env[0].GetObjectArrayElement(j_env, args, index) - py_arg = convert_jobject_to_python(j_env, arg_signature, j_arg) - py_args.append(py_arg) - - # really invoke the python method - name = method.getName() - ret = py_obj.invoke(method, *py_args) - - # convert back to the return type - # use the populate_args(), but in the reverse way :) - t = ret_signature[:1] - - # did python returned a "native" type ? - jtype = None - - if ret_signature == 'Ljava/lang/Object;': - # generic object, try to manually convert it - tp = type(ret) - if tp == int: - jtype = 'J' - elif tp == float: - jtype = 'D' - elif tp == bool: - jtype = 'Z' - elif len(ret_signature) == 1: - jtype = ret_signature - - try: - return convert_python_to_jobject(j_env, jtype or ret_signature, ret) - except Exception as e: - traceback.print_exc(e) - - -# now we need to create a proxy and pass it an invocation handler -cdef create_proxy_instance(JNIEnv *j_env, py_obj, j_interfaces): - from .reflect import autoclass - Proxy = autoclass('java.lang.reflect.Proxy') - NativeInvocationHandler = autoclass('jnius.NativeInvocationHandler') - ClassLoader = autoclass('java.lang.ClassLoader') - - # convert strings to Class - j_interfaces = [find_javaclass(x) for x in j_interfaces] - - cdef JavaClass nih = NativeInvocationHandler(py_obj) - cdef JNINativeMethod invoke_methods[1] - invoke_methods[0].name = 'invoke0' - invoke_methods[0].signature = '(Ljava/lang/Object;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;' - invoke_methods[0].fnPtr = &invoke0 - j_env[0].RegisterNatives(j_env, nih.j_cls, invoke_methods, 1) - - cdef JavaClass j_obj = Proxy.newProxyInstance( - ClassLoader.getSystemClassLoader(), j_interfaces, nih) - - #for name, definition, method in py_obj.j_methods: - # nw = GenericNativeWrapper(j_env, name, definition, method) - # j_env.RegisterNatives(j_env[0], cls, nw.nm, 1) - - # adds it to the invocationhandler - - # create the proxy and pass it the invocation handler - return j_obj diff --git a/tests/test_proxy.py b/tests/test_proxy.py index 9288032..f89f80f 100644 --- a/tests/test_proxy.py +++ b/tests/test_proxy.py @@ -1,4 +1,4 @@ -from jnius import autoclass, java_implementation, PythonJavaClass, cast +from jnius import autoclass, java_method, PythonJavaClass, cast print '1: declare a TestImplem that implement Collection' @@ -13,39 +13,39 @@ class TestImplemIterator(PythonJavaClass): self.collection = collection self.index = index - @java_implementation('()Z') + @java_method('()Z') def hasNext(self): return self.index < len(self.collection.data) - 1 - @java_implementation('()Ljava/lang/Object;') + @java_method('()Ljava/lang/Object;') def next(self): obj = self.collection.data[self.index] self.index += 1 return obj - @java_implementation('()Z') + @java_method('()Z') def hasPrevious(self): return self.index >= 0 - @java_implementation('()Ljava/lang/Object;') + @java_method('()Ljava/lang/Object;') def previous(self): self.index -= 1 obj = self.collection.data[self.index] return obj - @java_implementation('()I') + @java_method('()I') def previousIndex(self): return self.index - 1 - @java_implementation('()Ljava/lang/String;') + @java_method('()Ljava/lang/String;') def toString(self): return repr(self) - @java_implementation('(I)Ljava/lang/Object;') + @java_method('(I)Ljava/lang/Object;') def get(self, index): return self.collection.data[index - 1] - @java_implementation('(Ljava/lang/Object;)V') + @java_method('(Ljava/lang/Object;)V') def set(self, obj): self.collection.data[self.index - 1] = obj @@ -57,39 +57,39 @@ class TestImplem(PythonJavaClass): super(TestImplem, self).__init__(*args) self.data = list(args) - @java_implementation('()Ljava/util/Iterator;') + @java_method('()Ljava/util/Iterator;') def iterator(self): it = TestImplemIterator(self) return it - @java_implementation('()Ljava/lang/String;') + @java_method('()Ljava/lang/String;') def toString(self): return repr(self) - @java_implementation('()I') + @java_method('()I') def size(self): return len(self.data) - @java_implementation('(I)Ljava/lang/Object;') + @java_method('(I)Ljava/lang/Object;') def get(self, index): return self.data[index] - @java_implementation('(ILjava/lang/Object;)Ljava/lang/Object;') + @java_method('(ILjava/lang/Object;)Ljava/lang/Object;') def set(self, index, obj): old_object = self.data[index] self.data[index] = obj return old_object - @java_implementation('()[Ljava/lang/Object;') + @java_method('()[Ljava/lang/Object;') def toArray(self): return self.data - @java_implementation('()Ljava/util/ListIterator;') + @java_method('()Ljava/util/ListIterator;') def listIterator(self): it = TestImplemIterator(self) return it - @java_implementation('(I)Ljava/util/ListIterator;', + @java_method('(I)Ljava/util/ListIterator;', name='ListIterator') def listIteratorI(self, index): it = TestImplemIterator(self, index) @@ -138,5 +138,5 @@ print 'Order of data after shuffle()', a.data # XXX We have issues for methosd with multiple signature print '-> Collections.max(a)' print Collections.max(a2) -print '-> Collections.shuffle(a)' -print Collections.shuffle(a2) +#print '-> Collections.shuffle(a)' +#print Collections.shuffle(a2)