Merge pull request #115 from Lenbok/issue-96-exception-handling

Issue 96 improved exception handling
This commit is contained in:
Mathieu Virbel 2015-03-12 16:20:17 +01:00
commit 9425a2cc31
4 changed files with 119 additions and 3 deletions

View File

@ -2,7 +2,15 @@
class JavaException(Exception):
'''Can be a real java exception, or just an exception from the wrapper.
'''
pass
classname = None # The classname of the exception
innermessage = None # The message of the inner exception
stacktrace = None # The stack trace of the inner exception
def __init__(self, message, classname=None, innermessage=None, stacktrace=None):
self.classname = classname
self.innermessage = innermessage
self.stacktrace = stacktrace
Exception.__init__(self, message)
cdef class JavaObject(object):

View File

@ -36,11 +36,80 @@ cdef parse_definition(definition):
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)
if exc:
j_env[0].ExceptionDescribe(j_env)
# 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)
raise JavaException('JVM exception occured')
toString = j_env[0].GetMethodID(j_env, j_env[0].FindClass(j_env, "java/lang/Object"), "toString", "()Ljava/lang/String;");
getMessage = j_env[0].GetMethodID(j_env, j_env[0].FindClass(j_env, "java/lang/Throwable"), "getMessage", "()Ljava/lang/String;");
getCause = j_env[0].GetMethodID(j_env, j_env[0].FindClass(j_env, "java/lang/Throwable"), "getCause", "()Ljava/lang/Throwable;");
getStackTrace = j_env[0].GetMethodID(j_env, j_env[0].FindClass(j_env, "java/lang/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('/', '.')
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)
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)
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 = {}

View File

@ -22,6 +22,17 @@ public class BasicsTest {
public float methodF() { return 1.23456789f; };
public double methodD() { return 1.23456789; };
public String methodString() { return new String("helloworld"); }
public void methodException(int depth) throws IllegalArgumentException {
if (depth == 0) throw new IllegalArgumentException("helloworld");
else methodException(depth -1);
}
public void methodExceptionChained() throws IllegalArgumentException {
try {
methodException(5);
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException("helloworld2", e);
}
}
static public boolean fieldStaticZ = true;
static public byte fieldStaticB = 127;

View File

@ -21,3 +21,31 @@ class BadDeclarationTest(unittest.TestCase):
Stack = autoclass('java.util.Stack')
stack = Stack()
self.assertRaises(JavaException, stack.push, 'hello', 'world', 123)
def test_java_exception_handling(self):
Stack = autoclass('java.util.Stack')
stack = Stack()
try:
stack.pop()
self.fail("Expected exception to be thrown")
except JavaException as je:
# print "Got JavaException: " + str(je)
# print "Got Exception Class: " + je.classname
# print "Got stacktrace: \n" + '\n'.join(je.stacktrace)
self.assertEquals("java.util.EmptyStackException", je.classname)
def test_java_exception_chaining(self):
BasicsTest = autoclass('org.jnius.BasicsTest')
basics = BasicsTest()
try:
basics.methodExceptionChained()
self.fail("Expected exception to be thrown")
except JavaException as je:
# print "Got JavaException: " + str(je)
# print "Got Exception Class: " + je.classname
# print "Got Exception Message: " + je.innermessage
# print "Got stacktrace: \n" + '\n'.join(je.stacktrace)
self.assertEquals("java.lang.IllegalArgumentException", je.classname)
self.assertEquals("helloworld2", je.innermessage)
self.assertIn("Caused by:", je.stacktrace)
self.assertEquals(11, len(je.stacktrace))