Skip to content

Commit

Permalink
Merge pull request #115 from Lenbok/issue-96-exception-handling
Browse files Browse the repository at this point in the history
Issue 96 improved exception handling
  • Loading branch information
tito committed Mar 12, 2015
2 parents e149451 + d93c911 commit 9425a2c
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 3 deletions.
10 changes: 9 additions & 1 deletion jnius/jnius_export_class.pxi
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
73 changes: 71 additions & 2 deletions jnius/jnius_utils.pxi
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {}
Expand Down
11 changes: 11 additions & 0 deletions tests/org/jnius/BasicsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
28 changes: 28 additions & 0 deletions tests/test_bad_declaration.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,31 @@ def test_with_too_much_arguments(self):
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))

0 comments on commit 9425a2c

Please sign in to comment.