Rework the director.swg changes for director exception handling

- More robust implementation.
- Fix some bugs to give better exception messages when a user uses the
  director utility exception functions and classes.
- Replace unnecessarily shortened variable names for easier reading of
  code.
This commit is contained in:
William S Fulton 2013-11-05 18:49:21 +00:00
commit e7a6be289e

View file

@ -7,7 +7,7 @@
#ifdef __cplusplus #ifdef __cplusplus
#if defined(DEBUG_DIRECTOR_OWNED) #if defined(DEBUG_DIRECTOR_OWNED) || defined(DEBUG_DIRECTOR_EXCEPTION)
#include <iostream> #include <iostream>
#endif #endif
@ -196,43 +196,9 @@ namespace Swig {
}; };
// Utility methods and classes for exception handling. // Utility classes and functions for exception handling.
// Helper method to determine if a Java throwable matches a particular Java class type // Simple holder for a Java string during exception handling, providing access to a c-style string
bool ExceptionMatches(JNIEnv *jenv, jthrowable excp, const char *clzname) {
jboolean matches = false;
if (excp && jenv && clzname) {
// Exceptions need to be cleared for correct behavior.
// The caller of ExceptionMatches should restore pending exceptions if desired -
// the caller already has the throwable.
jenv->ExceptionClear();
jclass clz = jenv->FindClass(clzname);
if (clz && !jenv->ExceptionCheck()) {
jclass classclz = jenv->GetObjectClass(clz);
jmethodID isInstanceMethodID = jenv->GetMethodID(classclz, "isInstance", "(Ljava/lang/Object;)Z");
if (isInstanceMethodID) {
matches = (jboolean)jenv->CallBooleanMethod(clz, isInstanceMethodID, excp);
}
}
// This may happen if user typemaps or director:except
// features call ExceptionMatches incorrectly, typically with
// an invalid clzname argument. Uncommenting the debug lines
// may help to diagnose.
// As is, this leaves the underlying case as a pending exception
// which may not be that clear (e.g. ClassNotFoundException)
// if (jenv->ExceptionCheck()) {
// JavaExceptionMessage jstrmsg(jenv, jenv->ExceptionOccurred());
// std::cerr << "Error: ExceptionMatches: class '" <<
// clzname << "' : " << jstrmsg.message() << std::endl;
// }
}
return matches;
}
// Simple holder for a Java string during exception handling, allowing access to a c-style string
class JavaString { class JavaString {
public: public:
JavaString(JNIEnv *jenv, jstring jstr) : jenv_(jenv), jstr_(jstr), cstr_(0) { JavaString(JNIEnv *jenv, jstring jstr) : jenv_(jenv), jstr_(jstr), cstr_(0) {
@ -245,8 +211,8 @@ namespace Swig {
jenv_->ReleaseStringUTFChars(jstr_, cstr_); jenv_->ReleaseStringUTFChars(jstr_, cstr_);
} }
const char *cstr() { const char *c_str(const char *null_string = "null JavaString") const {
return cstr_ ? cstr_ : ""; return cstr_ ? cstr_ : null_string;
} }
private: private:
@ -259,17 +225,14 @@ namespace Swig {
const char *cstr_; const char *cstr_;
}; };
// Helper to extract the exception message from a Java throwable // Helper class to extract the exception message from a Java throwable
class JavaExceptionMessage { class JavaExceptionMessage {
public: public:
JavaExceptionMessage(JNIEnv *jenv, jthrowable excp) : jstrholder_(jenv, exceptionMsgJstr(jenv, excp)) { JavaExceptionMessage(JNIEnv *jenv, jthrowable throwable) : message_(jenv, exceptionMessageFromThrowable(jenv, throwable)) {
} }
~JavaExceptionMessage() { const char *message() const {
} return message_.c_str("Could not get exception message in JavaExceptionMessage");
const char *message() {
return jstrholder_.cstr();
} }
private: private:
@ -277,67 +240,61 @@ namespace Swig {
JavaExceptionMessage(const JavaExceptionMessage &); JavaExceptionMessage(const JavaExceptionMessage &);
JavaExceptionMessage &operator=(const JavaExceptionMessage &); JavaExceptionMessage &operator=(const JavaExceptionMessage &);
// Static method to initialize jstrholder_ // Get exception message by calling Java method Throwable.getMessage()
static jstring exceptionMsgJstr(JNIEnv *jenv, jthrowable excp) { static jstring exceptionMessageFromThrowable(JNIEnv *jenv, jthrowable throwable) {
jstring jmsg = NULL; jstring jmsg = NULL;
if (jenv && excp) { if (jenv && throwable) {
jenv->ExceptionClear(); // Cannot invoke methods with pending exception jenv->ExceptionClear(); // Cannot invoke methods with any pending exceptions
jclass thrwclz = jenv->GetObjectClass(excp); jclass throwclz = jenv->GetObjectClass(throwable);
if (thrwclz) { if (throwclz) {
// if no getMessage() or other exception, no msg available. // All Throwable classes have a getMessage() method, so call it to extract the exception message
jmethodID getThrowableMsgMethodID = jenv->GetMethodID(thrwclz, "getMessage", "()Ljava/lang/String;"); jmethodID getMessageMethodID = jenv->GetMethodID(throwclz, "getMessage", "()Ljava/lang/String;");
if (getThrowableMsgMethodID && !jenv->ExceptionCheck()) { if (getMessageMethodID)
// if problem accessing exception message string, no msg will be available. jmsg = (jstring)jenv->CallObjectMethod(throwable, getMessageMethodID);
jmsg = (jstring)jenv->CallObjectMethod(excp, getThrowableMsgMethodID);
}
} }
if (jmsg == NULL && jenv->ExceptionCheck()) { if (jmsg == NULL && jenv->ExceptionCheck())
jenv->ExceptionClear(); jenv->ExceptionClear();
}
} }
return jmsg; return jmsg;
} }
JavaString jstrholder_; JavaString message_;
}; };
// C++ Exception class for converting from Java exceptions thrown during a director method Java upcall // C++ Exception class for handling Java exceptions thrown during a director method Java upcall
class DirectorException : public std::exception { class DirectorException : public std::exception {
public: public:
// Construct a DirectorException from a Java throwable // Construct exception from a Java throwable
DirectorException(JNIEnv *jenv, jthrowable excp) : classname_(0), msg_(0) { DirectorException(JNIEnv *jenv, jthrowable throwable) : classname_(0), msg_(0) {
jstring jstr_classname = NULL;
if (excp) { // Call Java method Object.getClass().getName() to obtain the throwable's class name (delimited by '/')
// Get the exception class, like Exception if (throwable) {
jclass thrwclz = jenv->GetObjectClass(excp); jclass throwclz = jenv->GetObjectClass(throwable);
if (thrwclz) { if (throwclz) {
// Get the Java.lang.Class class jclass clzclz = jenv->GetObjectClass(throwclz);
jclass clzclz = jenv->GetObjectClass(thrwclz);
if (clzclz) { if (clzclz) {
jmethodID mid_getName = mid_getName = jenv->GetMethodID(clzclz, "getName", "()Ljava/lang/String;"); jmethodID getNameMethodID = jenv->GetMethodID(clzclz, "getName", "()Ljava/lang/String;");
if (mid_getName) { if (getNameMethodID) {
// Get the excp class name jstring jstr_classname = (jstring)(jenv->CallObjectMethod(throwclz, getNameMethodID));
jstr_classname = (jstring)(jenv->CallObjectMethod(thrwclz, mid_getName)); // Copy strings, since there is no guarantee that jenv will be active when handled
if (jstr_classname) {
JavaString jsclassname(jenv, jstr_classname);
const char *classname = jsclassname.c_str(0);
if (classname)
classname_ = copypath(classname);
}
} }
} }
} }
} }
// Copy strings, since no guarantee jenv will be active when handled
// If classname_ is 0, returned as "UnknownException" JavaExceptionMessage exceptionmsg(jenv, throwable);
if (jstr_classname) {
JavaString classname(jenv, jstr_classname);
classname_ = copypath(classname.cstr());
}
JavaExceptionMessage exceptionmsg(jenv, excp);
msg_ = copystr(exceptionmsg.message()); msg_ = copystr(exceptionmsg.message());
} }
// Throw as a wrapped RuntimeError explicitly. // More general constructor for handling as a java.lang.RuntimeException
DirectorException(const char *msg) : classname_(0), msg_(0) { DirectorException(const char *msg) : classname_(0), msg_(copystr(msg ? msg : "Unspecified DirectorException message")) {
classname_ = copypath("java/lang/RuntimeError");
msg_ = copystr(msg);
} }
~DirectorException() throw() { ~DirectorException() throw() {
@ -345,57 +302,49 @@ namespace Swig {
delete[] msg_; delete[] msg_;
} }
// If there was a problem finding classname, keep track of error
// On raiseJavaException will be mapped to a RuntimeException
const char *classname() const throw() {
return classname_ ? classname_ : "UnknownException";
}
const char *what() const throw() { const char *what() const throw() {
return msg_ ? msg_ : ""; return msg_;
} }
// Reconstruct and raise the Java Exception that caused the DirectorException // Reconstruct and raise/throw the Java Exception that caused the DirectorException
void raiseJavaException(JNIEnv *jenv) { // Note that any error in the JNI exception handling results in a Java RuntimeException
void raiseJavaException(JNIEnv *jenv) const {
if (jenv) { if (jenv) {
jenv->ExceptionClear(); jenv->ExceptionClear();
jmethodID strCtorID = 0; jmethodID ctorMethodID = 0;
jclass excpclz = jenv->FindClass(classname()); jclass throwableclass = 0;
if (classname_) {
if (excpclz) { throwableclass = jenv->FindClass(classname_);
strCtorID = jenv->GetMethodID(excpclz,"<init>","(Ljava/lang/String;)V"); if (throwableclass)
ctorMethodID = jenv->GetMethodID(throwableclass, "<init>", "(Ljava/lang/String;)V");
} }
if (strCtorID) { if (ctorMethodID) {
// If exception has a string constructor, throw an instance jenv->ThrowNew(throwableclass, what());
jenv->ThrowNew(excpclz, what());
} else { } else {
// Else, throw a runtime SWIG_JavaThrowException(jenv, SWIG_JavaRuntimeException, what());
SWIG_JavaThrowException(jenv, SWIG_JavaRuntimeException, what() );
} }
} }
} }
private: private:
static const char *copypath(const char *srcmsg) { static char *copypath(const char *srcmsg) {
return copystr(srcmsg, 1); char *target = copystr(srcmsg);
for (char *c=target; *c; ++c) {
if ('.' == *c)
*c = '/';
}
return target;
} }
static const char *copystr(const char *srcmsg, int pathrepl=0) { static char *copystr(const char *srcmsg) {
char *target = 0; char *target = 0;
if (srcmsg) { if (srcmsg) {
int msglen = 1 + strlen(srcmsg); int msglen = strlen(srcmsg) + 1;
target = new char[msglen]; target = new char[msglen];
strncpy(target, srcmsg, msglen); strncpy(target, srcmsg, msglen);
} }
// If pathrepl, replace any '.' with '/'
if (pathrepl) {
for (char *c=target; *c; ++c) {
if ('.' == *c)
*c = '/';
}
}
return target; return target;
} }
@ -403,6 +352,36 @@ namespace Swig {
const char *msg_; const char *msg_;
}; };
// Helper method to determine if a Java throwable matches a particular Java class type
bool ExceptionMatches(JNIEnv *jenv, jthrowable throwable, const char *classname) {
bool matches = false;
if (throwable && jenv && classname) {
// Exceptions need to be cleared for correct behavior.
// The caller of ExceptionMatches should restore pending exceptions if desired -
// the caller already has the throwable.
jenv->ExceptionClear();
jclass clz = jenv->FindClass(classname);
if (clz) {
jclass classclz = jenv->GetObjectClass(clz);
jmethodID isInstanceMethodID = jenv->GetMethodID(classclz, "isInstance", "(Ljava/lang/Object;)Z");
if (isInstanceMethodID) {
matches = jenv->CallBooleanMethod(clz, isInstanceMethodID, throwable) != 0;
}
}
#if defined(DEBUG_DIRECTOR_EXCEPTION)
if (jenv->ExceptionCheck()) {
// Typically occurs when an invalid classname argument is passed resulting in a ClassNotFoundException
JavaExceptionMessage exc(jenv, jenv->ExceptionOccurred());
std::cout << "Error: ExceptionMatches: class '" << classname << "' : " << exc.message() << std::endl;
}
#endif
}
return matches;
}
} }
#endif /* __cplusplus */ #endif /* __cplusplus */