pyjnius/jnius/jnius_utils.pxi

532 lines
17 KiB
Cython
Raw Normal View History

2012-08-20 07:35:14 +00:00
cdef parse_definition(definition):
# not a function, just a field
if definition[0] != '(':
return definition, None
# it's a function!
argdef, ret = definition[1:].split(')')
args = []
while len(argdef):
c = argdef[0]
# read the array char
prefix = ''
if c == '[':
prefix = c
argdef = argdef[1:]
c = argdef[0]
# native type
if c in 'ZBCSIJFD':
args.append(prefix + c)
argdef = argdef[1:]
continue
# java class
if c == 'L':
c, argdef = argdef.split(';', 1)
args.append(prefix + c + ';')
return ret, tuple(args)
2012-08-20 07:35:14 +00:00
cdef void check_exception(JNIEnv *j_env) except *:
cdef jthrowable exc = j_env[0].ExceptionOccurred(j_env)
if exc:
j_env[0].ExceptionDescribe(j_env)
j_env[0].ExceptionClear(j_env)
raise JavaException('JVM exception occured')
2012-08-20 07:40:18 +00:00
2012-08-20 08:34:14 +00:00
cdef dict assignable_from = {}
cdef void check_assignable_from(JNIEnv *env, JavaClass jc, bytes signature) except *:
cdef jclass cls
# if we have a JavaObject, it's always ok.
if signature == 'java/lang/Object':
return
# if the signature is a direct match, it's ok too :)
if jc.__javaclass__ == signature:
return
# if we already did the test before, use the cache result!
result = assignable_from.get((jc.__javaclass__, signature), None)
if result is None:
# we got an object that doesn't match with the signature
# check if we can use it.
cls = env[0].FindClass(env, signature)
if cls == NULL:
raise JavaException('Unable to found the class for {0!r}'.format(
signature))
result = bool(env[0].IsAssignableFrom(env, jc.j_cls, cls))
env[0].ExceptionClear(env)
assignable_from[(jc.__javaclass__, signature)] = bool(result)
if result is False:
raise JavaException('Invalid instance of {0!r} passed for a {1!r}'.format(
jc.__javaclass__, signature))
2012-08-20 07:35:14 +00:00
cdef bytes lookup_java_object_name(JNIEnv *j_env, jobject j_obj):
cdef jclass jcls = j_env[0].GetObjectClass(j_env, j_obj)
cdef jclass jcls2 = j_env[0].GetObjectClass(j_env, jcls)
cdef jmethodID jmeth = j_env[0].GetMethodID(j_env, jcls2, 'getName', '()Ljava/lang/String;')
cdef jobject js = j_env[0].CallObjectMethod(j_env, jcls, jmeth)
name = convert_jobject_to_python(j_env, b'Ljava/lang/String;', js)
2012-08-20 07:35:14 +00:00
return name.replace('.', '/')
cdef int calculate_score(sign_args, args, is_varargs=False) except *:
2012-08-20 07:40:18 +00:00
cdef int index
cdef int score = 0
cdef bytes r
cdef JavaClass jc
if len(args) != len(sign_args) and not is_varargs:
# if the number of arguments expected is not the same
# as the number of arguments the method gets
# it can not be the method we are looking for except
# if the method has varargs aka. it takes
# an undefined number of arguments
2012-08-20 07:40:18 +00:00
return -1
elif len(args) == len(sign_args) and not is_varargs:
# if the method has the good number of arguments and
# the method doesn't take varargs increment the score
# so that it takes precedence over a method with the same
# signature and varargs e.g.
# (Integer, Integer) takes precedence over (Integer, Integer, Integer...)
# and
# (Integer, Integer, Integer) takes precedence over (Integer, Integer, Integer...)
score += 10
2012-08-20 07:40:18 +00:00
for index in range(len(sign_args)):
r = sign_args[index]
arg = args[index]
if r == 'Z':
if not isinstance(arg, bool):
return -1
score += 10
continue
if r == 'B':
if not isinstance(arg, int):
return -1
score += 10
continue
if r == 'C':
if not isinstance(arg, str) or len(arg) != 1:
return -1
score += 10
continue
if r == 'S' or r == 'I' or r == 'J':
if isinstance(arg, int):
score += 10
continue
elif isinstance(arg, float):
score += 5
continue
else:
return -1
if r == 'F' or r == 'D':
if isinstance(arg, int):
score += 5
continue
elif isinstance(arg, float):
score += 10
continue
else:
return -1
if r[0] == 'L':
r = r[1:-1]
if arg is None:
score += 10
continue
# if it's a string, accept any python string
if r == 'java/lang/String' and isinstance(arg, basestring):
score += 10
continue
# 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):
score += 10
continue
elif isinstance(arg, basestring):
score += 5
continue
return -1
# if we pass a JavaClass, ensure the definition is matching
# XXX FIXME what if we use a subclass or something ?
if isinstance(arg, JavaClass):
jc = arg
if jc.__javaclass__ == r:
score += 10
else:
2012-08-20 14:51:32 +00:00
#try:
# check_assignable_from(jc, r)
#except:
# return -1
score += 5
2012-08-20 07:40:18 +00:00
continue
# always accept unknow object, but can be dangerous too.
if isinstance(arg, JavaObject):
score += 1
continue
if isinstance(arg, PythonJavaClass):
score += 1
continue
2012-08-20 07:40:18 +00:00
# native type? not accepted
return -1
if r[0] == '[':
if arg is None:
return 10
if not isinstance(arg, tuple) and not isinstance(arg, list):
return -1
# calculate the score for our subarray
if len(arg) > 0:
# if there are supplemantal arguments we compute the score
subscore = calculate_score([r[1:]] * len(arg), arg)
if subscore == -1:
return -1
# the supplemental arguments match the varargs arguments
score += 10
continue
# else if there is no supplemental arguments
# it might be the good method but there may be
# a method with a better signature so we don't
# change this method score
2012-08-20 07:40:18 +00:00
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
#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))
py_method = self.__javamethods__.get(key, None)
if not py_method:
print
print '===== Python/java method missing ======'
print 'Python class:', self
print 'Java method name:', method_name
print 'Signature: ({}){}'.format(''.join(args_signature), ret_signature)
print '======================================='
print
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 = <object>jptr
# extract the method information
# FIXME: only one call is not working sometimes ????+??????O?O??O?O
method = convert_jobject_to_python(j_env, b'Ljava/lang/reflect/Method;', j_method)
method = convert_jobject_to_python(j_env, b'Ljava/lang/reflect/Method;', 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):
print 'convert signature', index, arg_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
print '- python invoke', method.getName(), py_args
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
cdef jobject jret
try:
jret = convert_python_to_jobject(j_env, jtype or ret_signature, ret)
return jret
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(<long><void *>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 = <void *>&invoke0
j_env[0].RegisterNatives(j_env, nih.j_cls, <JNINativeMethod *>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',
'java/util/ListIterator',]
def __init__(self, collection, index=0):
super(TestImplemIterator, self).__init__()
self.collection = collection
self.index = index
@java_implementation('()Z')
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
@java_implementation('()Z')
def hasPrevious(self):
return self.index >= 0
@java_implementation('()Ljava/lang/Object;')
def previous(self):
self.index -= 1
obj = self.collection.data[self.index]
print "previous called", obj
return obj
@java_implementation('()I')
def previousIndex(self):
return self.index - 1
@java_implementation('()Ljava/lang/String;')
def toString(self):
return repr(self)
class TestImplem(PythonJavaClass):
__javainterfaces__ = ['java/util/List']
def __init__(self, *args):
super(TestImplem, self).__init__()
self.data = list(args)
@java_implementation('()Ljava/util/Iterator;')
def iterator(self):
it = TestImplemIterator(self)
return it
@java_implementation('()Ljava/lang/String;')
def toString(self):
return repr(self)
@java_implementation('()I')
def size(self):
return len(self.data)
@java_implementation('(I)Ljava/lang/Object;')
def get(self, index):
return self.data[index]
@java_implementation('(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;')
def toArray(self):
return self.data
@java_implementation('()Ljava/util/ListIterator;')
def listIterator(self):
it = TestImplemIterator(self)
return it
@java_implementation('(I)Ljava/util/ListIterator;',
name='ListIterator')
def listIteratorI(self, index):
it = TestImplemIterator(self, index)
return it
print '2: instanciate the class, with some data'
a = TestImplem(*range(10))
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)
ret = Collections.max(a)
print 'MAX returned', ret
# the first one of the following methods will work, witchever it is
# the next ones will fail
print "reverse"
print Collections.reverse(a)
print a.data
print "before swap"
print Collections.swap(a, 2, 3)
print "after swap"
print a.data
print "rotate"
print Collections.rotate(a, 5)
print a.data
print 'Order of data before shuffle()', a.data
print Collections.shuffle(a)
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.max(a)'
#print Collections.max(a2)
#print '-> Collections.shuffle(a)'
#print Collections.shuffle(a2)