From dcfe369f1e376aa22f265c853975a39b685041a3 Mon Sep 17 00:00:00 2001 From: Mathieu Virbel Date: Mon, 31 Dec 2012 05:15:09 +0100 Subject: [PATCH] various fix for java/python implementation. It finally start working, but still lot to do. Use: make testimplem, and check the jnius/jnius_utils.pxi:test() function. --- Makefile | 4 + jnius/NativeInvocationHandler.java | 34 ++++++ jnius/jni.pxi | 4 +- jnius/jnius_conversion.pxi | 12 +- jnius/jnius_export_class.pxi | 27 +++-- jnius/jnius_utils.pxi | 182 +++++++++++++++++++++++++---- jnius/nativeinvocationhandler.java | 17 --- 7 files changed, 229 insertions(+), 51 deletions(-) create mode 100644 jnius/NativeInvocationHandler.java delete mode 100644 jnius/nativeinvocationhandler.java diff --git a/Makefile b/Makefile index 0601627..c2b0a52 100644 --- a/Makefile +++ b/Makefile @@ -14,3 +14,7 @@ tests: build_ext cd tests && javac org/jnius/InterfaceWithPublicEnum.java cd tests && javac org/jnius/ClassArgument.java cd tests && env PYTHONPATH=..:$(PYTHONPATH) nosetests -v + +testimpl: build_ext + javac jnius/NativeInvocationHandler.java + python -c 'import jnius.jnius; jnius.jnius.test()' diff --git a/jnius/NativeInvocationHandler.java b/jnius/NativeInvocationHandler.java new file mode 100644 index 0000000..024264b --- /dev/null +++ b/jnius/NativeInvocationHandler.java @@ -0,0 +1,34 @@ +package jnius; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; + +public class NativeInvocationHandler implements InvocationHandler { + public NativeInvocationHandler(long ptr) { + this.ptr = ptr; + } + + public Object invoke(Object proxy, Method method, Object[] args) { + /** + if ( method.getName() == "toString" ) { + System.out.println("+ java:invoke toString."); + return ""; + } + /**/ + System.out.print("+ java:invoke(, "); + // don't call it, or recursive lookup/proxy will go! + //System.out.print(proxy); + //System.out.print(", "); + System.out.print(method); + System.out.print(", "); + System.out.print(args); + System.out.println(")"); + System.out.println(method.getName()); + System.out.println(method.getParameterTypes()); + Object ret = invoke0(proxy, method, args); + return ret; + } + + native Object invoke0(Object proxy, Method method, Object[] args); + + private long ptr; +} diff --git a/jnius/jni.pxi b/jnius/jni.pxi index 6c71c00..d8f2161 100644 --- a/jnius/jni.pxi +++ b/jnius/jni.pxi @@ -41,6 +41,8 @@ cdef extern from "jni.h": const_char* signature void* fnPtr + ctypedef JNINativeMethod const_JNINativeMethod "const JNINativeMethod" + ctypedef union jvalue: jboolean z jbyte b @@ -368,7 +370,7 @@ cdef extern from "jni.h": jsize, jsize, const_jdouble*) #XXX not working with cython? - #jint (*RegisterNatives)(JNIEnv*, jclass, const_JNINativeMethod*, jint) + jint (*RegisterNatives)(JNIEnv*, jclass, const_JNINativeMethod*, jint) jint (*UnregisterNatives)(JNIEnv*, jclass) jint (*MonitorEnter)(JNIEnv*, jobject) jint (*MonitorExit)(JNIEnv*, jobject) diff --git a/jnius/jnius_conversion.pxi b/jnius/jnius_conversion.pxi index 7e1d1e2..0f172fc 100644 --- a/jnius/jnius_conversion.pxi +++ b/jnius/jnius_conversion.pxi @@ -1,4 +1,4 @@ -cdef void release_args(JNIEnv *j_env, list definition_args, jvalue *j_args, args) except *: +cdef void release_args(JNIEnv *j_env, tuple definition_args, jvalue *j_args, args) except *: # do the conversion from a Python object to Java from a Java definition cdef JavaObject jo cdef JavaClass jc @@ -15,11 +15,12 @@ cdef void release_args(JNIEnv *j_env, list definition_args, jvalue *j_args, args j_env[0].DeleteLocalRef(j_env, j_args[index].l) -cdef void populate_args(JNIEnv *j_env, list definition_args, jvalue *j_args, args) except *: +cdef void populate_args(JNIEnv *j_env, tuple definition_args, jvalue *j_args, args) except *: # do the conversion from a Python object to Java from a Java definition cdef JavaClassStorage jcs cdef JavaObject jo cdef JavaClass jc + cdef PythonJavaClass pc cdef int index for index, argtype in enumerate(definition_args): py_arg = args[index] @@ -59,6 +60,13 @@ cdef void populate_args(JNIEnv *j_env, list definition_args, jvalue *j_args, arg elif isinstance(py_arg, MetaJavaClass): jcs = py_arg.__cls_storage j_args[index].l = jcs.j_cls + elif isinstance(py_arg, PythonJavaClass): + # from python class, get the proxy/python class + pc = py_arg + # get the java class + jc = pc.j_self + # get the localref + j_args[index].l = jc.j_self.obj elif isinstance(py_arg, (tuple, list)): j_args[index].l = convert_pyarray_to_java(j_env, argtype, py_arg) else: diff --git a/jnius/jnius_export_class.pxi b/jnius/jnius_export_class.pxi index 5d95d5e..6ee676a 100644 --- a/jnius/jnius_export_class.pxi +++ b/jnius/jnius_export_class.pxi @@ -63,7 +63,7 @@ class MetaJavaClass(type): interfaces[n] = jcs.j_env[0].FindClass(jcs.j_env, i) getProxyClass = jcs.j_env[0].GetStaticMethodID( - jcs.j_env, Proxy, "getProxyClass", + jcs.j_env, baseclass, "getProxyClass", "(Ljava/lang/ClassLoader,[Ljava/lang/Class;)Ljava/lang/Class;") getClassLoader = jcs.j_env[0].GetStaticMethodID( @@ -76,7 +76,7 @@ class MetaJavaClass(type): jargs[0] = classLoader jargs[1] = interfaces jcs.j_cls = jcs.j_env[0].CallStaticObjectMethod( - jcs.j_env, Proxy, getProxyClass, jargs) + jcs.j_env, baseclass, getProxyClass, jargs) if jcs.j_cls == NULL: raise JavaException('Unable to create the class' @@ -93,6 +93,7 @@ class MetaJavaClass(type): # search all the static JavaMethod within our class, and resolve them cdef JavaMethod jm cdef JavaMultipleMethod jmm + cdef PythonMethod pm for name, value in classDict.iteritems(): if isinstance(value, JavaMethod): jm = value @@ -105,13 +106,13 @@ class MetaJavaClass(type): jmm.set_resolve_info(jcs.j_env, jcs.j_cls, None, name, __javaclass__) elif isinstance(value, PythonMethod): - if '__javabaseclass__' not in self.classDict: + if '__javabaseclass__' not in classDict: raise JavaException("Can't use PythonMethod on a java " "class, you must use inheritance to implement a java " "interface") pm = value - pm.set_resolve_info(self.j_env, self.j_cls, self.j_self, - name, self.__javaclass__) + pm.set_resolve_info(jcs.j_env, jcs.j_cls, jcs.j_self, + name, jcs.__javaclass__) # search all the static JavaField within our class, and resolve them @@ -235,6 +236,7 @@ cdef class JavaClass(object): # search all the JavaMethod within our class, and resolve them cdef JavaMethod jm cdef JavaMultipleMethod jmm + cdef PythonMethod pm for name, value in self.__class__.__dict__.iteritems(): if isinstance(value, JavaMethod): jm = value @@ -247,7 +249,6 @@ cdef class JavaClass(object): jmm.set_resolve_info(self.j_env, self.j_cls, self.j_self, name, self.__javaclass__) elif isinstance(value, PythonMethod): - # if self#XXX pm = value pm.set_resolve_info(self.j_env, self.j_cls, self.j_self, name, self.__javaclass__) @@ -473,6 +474,18 @@ cdef class PythonMethod(object): cdef bytes classname # XXX + cdef void set_resolve_info(self, JNIEnv *j_env, jclass j_cls, LocalRef j_self, + bytes name, bytes classname): + ''' + XXX TODO + self.name = name + self.classname = classname + self.j_env = j_env + self.j_cls = j_cls + self.j_self = j_self + ''' + pass + cdef class JavaMethod(object): '''Used to resolve a Java method, and do the call @@ -539,7 +552,7 @@ cdef class JavaMethod(object): def __call__(self, *args): # argument array to pass to the method cdef jvalue *j_args = NULL - cdef list d_args = self.definition_args + cdef tuple d_args = self.definition_args if self.is_varargs: args = args[:len(d_args) - 1] + (args[len(d_args) - 1:],) diff --git a/jnius/jnius_utils.pxi b/jnius/jnius_utils.pxi index 8a2a1d8..3635348 100644 --- a/jnius/jnius_utils.pxi +++ b/jnius/jnius_utils.pxi @@ -47,7 +47,7 @@ cdef parse_definition(definition): c, argdef = argdef.split(';', 1) args.append(prefix + c + ';') - return ret, args + return ret, tuple(args) cdef void check_exception(JNIEnv *j_env) except *: @@ -211,6 +211,10 @@ cdef int calculate_score(sign_args, args, is_varargs=False) except *: score += 1 continue + if isinstance(arg, PythonJavaClass): + score += 1 + continue + # native type? not accepted return -1 @@ -237,7 +241,7 @@ cdef int calculate_score(sign_args, args, is_varargs=False) except *: # change this method score return score - +''' cdef class GenericNativeWrapper(object): """ This class is to be used to register python method as methods of @@ -615,6 +619,20 @@ cdef class GenericNativeWrapper(object): # va_end(j_args) # return self.callback(*args) +''' + +import functools +class java_implementation(object): + def __init__(self, signature): + super(java_implementation, self).__init__() + self.signature = signature + + def __get__(self, instance, instancetype): + return functools.partial(self.__call__, instance) + + def __call__(self, f): + f.__javasignature__ = self.signature + return f cdef class PythonJavaClass(object): ''' @@ -622,42 +640,158 @@ cdef class PythonJavaClass(object): ''' cdef JNIEnv *j_env cdef jclass j_cls - cdef LocalRef j_self + cdef public object j_self def __cinit__(self, *args): - self.j_env = NULL + self.j_env = get_jnienv() self.j_cls = NULL - self.j_self = NULL - pass + self.j_self = None def __init__(self, *args, **kwargs): - self.j_self = create_proxy_instance() + self.j_self = create_proxy_instance(self.j_env, self, + self.__javainterfaces__) -cdef jobject invoke0( - JNIEnv *j_env, - jobject this, - jobject method, - jobjectArray args): + # 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__[(x, signature)] = attr - cdef jfieldID ptrField = j_env[0].GetFieldID(j_env.GetObjectClass(this), "ptr", "J") - cdef jlong jptr = j_env.GetLongField(this, ptrField) - return h.fnPtr(env, method, args); + def invoke(self, method, *args): + from .reflect import get_signature + print 'PythonJavaClass.invoke() called with args:', args + # 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)) + print 'PythonJavaClass.invoke() want to invoke', key + + py_method = self.__javamethods__.get(key, None) + if not py_method: + raise NotImplemented('The method {0} 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): + 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 + # TODO: cache ? + method = convert_jobject_to_python(j_env, b'Ljava/lang/reflect/Method;', j_method) + ret_signature = get_signature(method.getReturnType()) + args_signature = ''.join([get_signature(x) for x in method.getParameterTypes()]) + + # XX implement java array conversion + py_args = [] + ret = py_obj.invoke(method, *py_args) + + # convert back to the return type + # use the populate_args(), but in the reverse way :) + cdef jvalue j_ret[1] + populate_args(j_env, (ret_signature, ), j_ret, [ret]) + return j_ret # now we need to create a proxy and pass it an invocation handler -from jnius import autoclass -Proxy = autoclass('java.lang.reflec.Proxy') -NativeInvocationHandler('jnius.NativeInvocationHandler') +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') -def create_proxy_instance(j_env, py_obj, j_interfaces): - nih = NativeInvocationHandler(py_obj) - cls = Proxy.newProxyInstance(Null, j_interfaces, nih) # XXX wishful code + # convert strings to Class + j_interfaces = [find_javaclass(x) for x in j_interfaces] + print 'create_proxy_instance', j_interfaces - 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) + 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 + +def test(): + from .reflect import autoclass + + print '1: declare a TestImplem that implement Collection' + class TestImplemIterator(PythonJavaClass): + __javainterfaces__ = ['java/util/Iterator'] + + def __init__(self, collection): + super(TestImplemIterator, self).__init__() + self.collection = collection + self.index = 0 + + @java_implementation('()B') + def hasNext(self): + return self.index < len(self.collection.data) + + @java_implementation('()Ljava/lang/Object;') + def next(self): + obj = self.collection.data[self.index] + self.index += 1 + return obj + + + class TestImplem(PythonJavaClass): + __javainterfaces__ = ['java/util/Collection'] + + def __init__(self, *args): + super(TestImplem, self).__init__() + self.data = args + + @java_implementation('()Ljava/util/Iterator;') + def iterator(self): + it = TestImplemIterator(self) + print 'iterator called, and returned', it + return it + + print '2: instanciate the class, with some data' + a = TestImplem(129387, 'aoesrch', 987, 'aoenth') + print a + print dir(a) + + print '3: Do cast to a collection' + a2 = cast('java/util/Collection', a.j_self) + + print '4: Try few method on the collection' + Collections = autoclass('java.util.Collections') + print Collections.enumeration(a) + #print Collections.enumeration(a) + print Collections.max(a) + + + # XXX We have issues for methosd with multiple signature + #print '-> Collections.max(a)' + #print Collections.max(a2) + #print '-> Collections.max(a)' + #print Collections.max(a2) + #print '-> Collections.shuffle(a)' + #print Collections.shuffle(a2) diff --git a/jnius/nativeinvocationhandler.java b/jnius/nativeinvocationhandler.java deleted file mode 100644 index e9cd58a..0000000 --- a/jnius/nativeinvocationhandler.java +++ /dev/null @@ -1,17 +0,0 @@ -package jnius; -import java.lang.reflect.InvocationHandler; -import java.lang.reflect.Method; - -public class NativeInvocationHandler implements InvocationHandler { - public NativeInvocationHandler(long ptr) { - this.ptr = ptr; - } - - Object invoke(Object proxy, Method method, Object[] args) { - return invoke0(proxy, method, args); - } - - native private Object invoke0(Object proxy, Method method, Object[] args); - - private long ptr; -}