pyjnius/jnius/jnius_utils.pxi

384 lines
13 KiB
Cython

from cpython.version cimport PY_MAJOR_VERSION
cdef str_for_c(s):
if PY_MAJOR_VERSION < 3:
if isinstance(s, unicode):
return s.encode('utf-8')
else:
return s
else:
return s.encode('utf-8')
cdef items_compat(d):
if PY_MAJOR_VERSION >= 3:
return d.items()
else:
return d.iteritems()
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(s)
prefix = ''
while 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 + ';')
continue
raise Exception('Invalid "{}" character in definition "{}"'.format(
c, definition[1:]))
return ret, tuple(args)
cdef void check_exception(JNIEnv *j_env) except *:
cdef jmethodID toString = NULL
cdef jmethodID getCause = NULL
cdef jmethodID getStackTrace = NULL
cdef jmethodID getMessage = NULL
cdef jstring e_msg
cdef jboolean isCopy
cdef jthrowable exc = j_env[0].ExceptionOccurred(j_env)
cdef jclass cls_object = NULL
cdef jclass cls_throwable = NULL
if exc:
# ExceptionDescribe always writes to stderr, preventing tidy exception
# handling, so should only be for debugging
# j_env[0].ExceptionDescribe(j_env)
j_env[0].ExceptionClear(j_env)
cls_object = j_env[0].FindClass(j_env, "java/lang/Object")
cls_throwable = j_env[0].FindClass(j_env, "java/lang/Throwable")
toString = j_env[0].GetMethodID(j_env, cls_object, "toString", "()Ljava/lang/String;");
getMessage = j_env[0].GetMethodID(j_env, cls_throwable, "getMessage", "()Ljava/lang/String;");
getCause = j_env[0].GetMethodID(j_env, cls_throwable, "getCause", "()Ljava/lang/Throwable;");
getStackTrace = j_env[0].GetMethodID(j_env, cls_throwable, "getStackTrace", "()[Ljava/lang/StackTraceElement;");
e_msg = j_env[0].CallObjectMethod(j_env, exc, getMessage);
pymsg = None if e_msg == NULL else convert_jobject_to_python(j_env, <bytes> 'Ljava/lang/String;', e_msg)
pystack = []
_append_exception_trace_messages(j_env, pystack, exc, getCause, getStackTrace, toString)
pyexcclass = lookup_java_object_name(j_env, exc).replace('/', '.')
j_env[0].DeleteLocalRef(j_env, cls_object)
j_env[0].DeleteLocalRef(j_env, cls_throwable)
if e_msg != NULL:
j_env[0].DeleteLocalRef(j_env, e_msg)
j_env[0].DeleteLocalRef(j_env, exc)
raise JavaException('JVM exception occurred: %s' % (pymsg if pymsg is not None else pyexcclass), pyexcclass, pymsg, pystack)
cdef void _append_exception_trace_messages(
JNIEnv* j_env,
list pystack,
jthrowable exc,
jmethodID mid_getCause,
jmethodID mid_getStackTrace,
jmethodID mid_toString):
# Get the array of StackTraceElements.
cdef jobjectArray frames = j_env[0].CallObjectMethod(j_env, exc, mid_getStackTrace)
cdef jsize frames_length = j_env[0].GetArrayLength(j_env, frames)
cdef jstring msg_obj
cdef jobject frame
cdef jthrowable cause
# Add Throwable.toString() before descending stack trace messages.
if frames != NULL:
msg_obj = j_env[0].CallObjectMethod(j_env, exc, mid_toString)
pystr = None if msg_obj == NULL else convert_jobject_to_python(j_env, <bytes> 'Ljava/lang/String;', msg_obj)
# If this is not the top-of-the-trace then this is a cause.
if len(pystack) > 0:
pystack.append("Caused by:")
pystack.append(pystr)
if msg_obj != NULL:
j_env[0].DeleteLocalRef(j_env, msg_obj)
# Append stack trace messages if there are any.
if frames_length > 0:
for i in range(frames_length):
# Get the string returned from the 'toString()' method of the next frame and append it to the error message.
frame = j_env[0].GetObjectArrayElement(j_env, frames, i)
msg_obj = j_env[0].CallObjectMethod(j_env, frame, mid_toString)
pystr = None if msg_obj == NULL else convert_jobject_to_python(j_env, <bytes> 'Ljava/lang/String;', msg_obj)
pystack.append(pystr)
if msg_obj != NULL:
j_env[0].DeleteLocalRef(j_env, msg_obj)
j_env[0].DeleteLocalRef(j_env, frame)
# If 'exc' has a cause then append the stack trace messages from the cause.
if frames != NULL:
cause = j_env[0].CallObjectMethod(j_env, exc, mid_getCause)
if cause != NULL:
_append_exception_trace_messages(j_env, pystack, cause,
mid_getCause, mid_getStackTrace, mid_toString)
j_env[0].DeleteLocalRef(j_env, cause)
j_env[0].DeleteLocalRef(j_env, frames)
cdef dict assignable_from = {}
cdef int assignable_from_order = 0
cdef void check_assignable_from(JNIEnv *env, JavaClass jc, signature) except *:
global assignable_from_order
cdef jclass cls, clsA, clsB
cdef jthrowable exc
# first call, we need to get over the libart issue, which implemented
# IsAssignableFrom the wrong way.
# Ref: https://github.com/kivy/pyjnius/issues/92
# Google Bug: https://android.googlesource.com/platform/art/+/1268b74%5E!/
if assignable_from_order == 0:
clsA = env[0].FindClass(env, "java/lang/String")
clsB = env[0].FindClass(env, "java/lang/Object")
if env[0].IsAssignableFrom(env, clsB, clsA):
# Bug triggered, IsAssignableFrom said we can do things like:
# String a = Object()
assignable_from_order = -1
else:
assignable_from_order = 1
# if we have a JavaObject, it's always ok.
if signature == 'java/lang/Object':
return
# FIXME Android/libART specific check
# check_jni.cc crash when calling the IsAssignableFrom with
# org/jnius/NativeInvocationHandler java/lang/reflect/InvocationHandler
# Because we know it's ok, just return here.
if signature == 'java/lang/reflect/InvocationHandler' and \
jc.__javaclass__ == 'org/jnius/NativeInvocationHandler':
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, str_for_c(signature))
if cls == NULL:
raise JavaException('Unable to found the class for {0!r}'.format(
signature))
if assignable_from_order == 1:
result = bool(env[0].IsAssignableFrom(env, jc.j_cls, cls))
else:
result = bool(env[0].IsAssignableFrom(env, cls, jc.j_cls))
exc = env[0].ExceptionOccurred(env)
if exc:
env[0].ExceptionDescribe(env)
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))
cdef 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, 'Ljava/lang/String;', js)
j_env[0].DeleteLocalRef(j_env, js)
j_env[0].DeleteLocalRef(j_env, jcls)
j_env[0].DeleteLocalRef(j_env, jcls2)
return name.replace('.', '/')
cdef int calculate_score(sign_args, args, is_varargs=False) except *:
cdef int index
cdef int score = 0
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
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
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) and PY_MAJOR_VERSION < 3:
score += 10
continue
if r == 'java/lang/String' and isinstance(arg, str) and PY_MAJOR_VERSION >= 3:
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
# accept an autoclass class for java/lang/Class.
if hasattr(arg, '__javaclass__') and r == 'java/lang/Class':
score += 10
continue
# 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:
#try:
# check_assignable_from(jc, r)
#except:
# return -1
score += 5
continue
# always accept unknow object, but can be dangerous too.
if isinstance(arg, JavaObject):
score += 1
continue
if isinstance(arg, PythonJavaClass):
score += 1
continue
# native type? not accepted
return -1
if r[0] == '[':
if arg is None:
score += 10
continue
if (r == '[B' or r == '[C') and isinstance(arg, basestring) and PY_MAJOR_VERSION < 3:
score += 10
continue
if (r == '[B') and isinstance(arg, bytes) and PY_MAJOR_VERSION >= 3:
score += 10
continue
if (r == '[C') and isinstance(arg, str) and PY_MAJOR_VERSION >= 3:
score += 10
continue
if r == '[B' and isinstance(arg, ByteArray):
score += 10
continue
if not isinstance(arg, (list, tuple)):
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
return score