swig/Lib/java/director.swg
William S Fulton 07ab80b49e Add raise methods for throwing c++ exceptions in C#, Java, D
The director c++ exceptions are thrown in a helper method instead of in
the director overloaded method. This circumvents compiler warnings about
throwing exceptions when the method has an exception specification or
noexcept. If the exception is thrown, abort will still be called!
In Java, the "director:noexcept" typemap can be used to do something
else. This typemap should be ported to the other languages too.
2017-05-13 17:01:15 +01:00

417 lines
13 KiB
Text

/* -----------------------------------------------------------------------------
* director.swg
*
* This file contains support for director classes so that Java proxy
* methods can be called from C++.
* ----------------------------------------------------------------------------- */
#if defined(DEBUG_DIRECTOR_OWNED) || defined(DEBUG_DIRECTOR_EXCEPTION)
#include <iostream>
#endif
#include <exception>
namespace Swig {
/* Java object wrapper */
class JObjectWrapper {
public:
JObjectWrapper() : jthis_(NULL), weak_global_(true) {
}
~JObjectWrapper() {
jthis_ = NULL;
weak_global_ = true;
}
bool set(JNIEnv *jenv, jobject jobj, bool mem_own, bool weak_global) {
if (!jthis_) {
weak_global_ = weak_global || !mem_own; // hold as weak global if explicitly requested or not owned
if (jobj)
jthis_ = weak_global_ ? jenv->NewWeakGlobalRef(jobj) : jenv->NewGlobalRef(jobj);
#if defined(DEBUG_DIRECTOR_OWNED)
std::cout << "JObjectWrapper::set(" << jobj << ", " << (weak_global ? "weak_global" : "global_ref") << ") -> " << jthis_ << std::endl;
#endif
return true;
} else {
#if defined(DEBUG_DIRECTOR_OWNED)
std::cout << "JObjectWrapper::set(" << jobj << ", " << (weak_global ? "weak_global" : "global_ref") << ") -> already set" << std::endl;
#endif
return false;
}
}
jobject get(JNIEnv *jenv) const {
#if defined(DEBUG_DIRECTOR_OWNED)
std::cout << "JObjectWrapper::get(";
if (jthis_)
std::cout << jthis_;
else
std::cout << "null";
std::cout << ") -> return new local ref" << std::endl;
#endif
return (jthis_ ? jenv->NewLocalRef(jthis_) : jthis_);
}
void release(JNIEnv *jenv) {
#if defined(DEBUG_DIRECTOR_OWNED)
std::cout << "JObjectWrapper::release(" << jthis_ << "): " << (weak_global_ ? "weak global ref" : "global ref") << std::endl;
#endif
if (jthis_) {
if (weak_global_) {
if (jenv->IsSameObject(jthis_, NULL) == JNI_FALSE)
jenv->DeleteWeakGlobalRef((jweak)jthis_);
} else
jenv->DeleteGlobalRef(jthis_);
}
jthis_ = NULL;
weak_global_ = true;
}
/* Only call peek if you know what you are doing wrt to weak/global references */
jobject peek() {
return jthis_;
}
/* Java proxy releases ownership of C++ object, C++ object is now
responsible for destruction (creates NewGlobalRef to pin Java proxy) */
void java_change_ownership(JNIEnv *jenv, jobject jself, bool take_or_release) {
if (take_or_release) { /* Java takes ownership of C++ object's lifetime. */
if (!weak_global_) {
jenv->DeleteGlobalRef(jthis_);
jthis_ = jenv->NewWeakGlobalRef(jself);
weak_global_ = true;
}
} else {
/* Java releases ownership of C++ object's lifetime */
if (weak_global_) {
jenv->DeleteWeakGlobalRef((jweak)jthis_);
jthis_ = jenv->NewGlobalRef(jself);
weak_global_ = false;
}
}
}
private:
/* pointer to Java object */
jobject jthis_;
/* Local or global reference flag */
bool weak_global_;
};
/* Local JNI reference deleter */
class LocalRefGuard {
JNIEnv *jenv_;
jobject jobj_;
// non-copyable
LocalRefGuard(const LocalRefGuard &);
LocalRefGuard &operator=(const LocalRefGuard &);
public:
LocalRefGuard(JNIEnv *jenv, jobject jobj): jenv_(jenv), jobj_(jobj) {}
~LocalRefGuard() {
if (jobj_)
jenv_->DeleteLocalRef(jobj_);
}
};
/* director base class */
class Director {
/* pointer to Java virtual machine */
JavaVM *swig_jvm_;
protected:
#if defined (_MSC_VER) && (_MSC_VER<1300)
class JNIEnvWrapper;
friend class JNIEnvWrapper;
#endif
/* Utility class for managing the JNI environment */
class JNIEnvWrapper {
const Director *director_;
JNIEnv *jenv_;
int env_status;
public:
JNIEnvWrapper(const Director *director) : director_(director), jenv_(0), env_status(0) {
#if defined(__ANDROID__)
JNIEnv **jenv = &jenv_;
#else
void **jenv = (void **)&jenv_;
#endif
env_status = director_->swig_jvm_->GetEnv((void **)&jenv_, JNI_VERSION_1_2);
#if defined(SWIG_JAVA_ATTACH_CURRENT_THREAD_AS_DAEMON)
// Attach a daemon thread to the JVM. Useful when the JVM should not wait for
// the thread to exit upon shutdown. Only for jdk-1.4 and later.
director_->swig_jvm_->AttachCurrentThreadAsDaemon(jenv, NULL);
#else
director_->swig_jvm_->AttachCurrentThread(jenv, NULL);
#endif
}
~JNIEnvWrapper() {
#if !defined(SWIG_JAVA_NO_DETACH_CURRENT_THREAD)
// Some JVMs, eg jdk-1.4.2 and lower on Solaris have a bug and crash with the DetachCurrentThread call.
// However, without this call, the JVM hangs on exit when the thread was not created by the JVM and creates a memory leak.
if (env_status == JNI_EDETACHED)
director_->swig_jvm_->DetachCurrentThread();
#endif
}
JNIEnv *getJNIEnv() const {
return jenv_;
}
};
/* Java object wrapper */
JObjectWrapper swig_self_;
/* Disconnect director from Java object */
void swig_disconnect_director_self(const char *disconn_method) {
JNIEnvWrapper jnienv(this) ;
JNIEnv *jenv = jnienv.getJNIEnv() ;
jobject jobj = swig_self_.get(jenv);
LocalRefGuard ref_deleter(jenv, jobj);
#if defined(DEBUG_DIRECTOR_OWNED)
std::cout << "Swig::Director::disconnect_director_self(" << jobj << ")" << std::endl;
#endif
if (jobj && jenv->IsSameObject(jobj, NULL) == JNI_FALSE) {
jmethodID disconn_meth = jenv->GetMethodID(jenv->GetObjectClass(jobj), disconn_method, "()V");
if (disconn_meth) {
#if defined(DEBUG_DIRECTOR_OWNED)
std::cout << "Swig::Director::disconnect_director_self upcall to " << disconn_method << std::endl;
#endif
jenv->CallVoidMethod(jobj, disconn_meth);
}
}
}
public:
Director(JNIEnv *jenv) : swig_jvm_((JavaVM *) NULL), swig_self_() {
/* Acquire the Java VM pointer */
jenv->GetJavaVM(&swig_jvm_);
}
virtual ~Director() {
JNIEnvWrapper jnienv(this) ;
JNIEnv *jenv = jnienv.getJNIEnv() ;
swig_self_.release(jenv);
}
bool swig_set_self(JNIEnv *jenv, jobject jself, bool mem_own, bool weak_global) {
return swig_self_.set(jenv, jself, mem_own, weak_global);
}
jobject swig_get_self(JNIEnv *jenv) const {
return swig_self_.get(jenv);
}
// Change C++ object's ownership, relative to Java
void swig_java_change_ownership(JNIEnv *jenv, jobject jself, bool take_or_release) {
swig_self_.java_change_ownership(jenv, jself, take_or_release);
}
};
// Zero initialized bool array
template<size_t N> class BoolArray {
bool array_[N];
public:
BoolArray() {
memset(array_, 0, sizeof(array_));
}
bool& operator[](size_t n) {
return array_[n];
}
bool operator[](size_t n) const {
return array_[n];
}
};
// Utility classes and functions for exception handling.
// Simple holder for a Java string during exception handling, providing access to a c-style string
class JavaString {
public:
JavaString(JNIEnv *jenv, jstring jstr) : jenv_(jenv), jstr_(jstr), cstr_(0) {
if (jenv_ && jstr_)
cstr_ = (const char *) jenv_->GetStringUTFChars(jstr_, NULL);
}
~JavaString() {
if (jenv_ && jstr_ && cstr_)
jenv_->ReleaseStringUTFChars(jstr_, cstr_);
}
const char *c_str(const char *null_string = "null JavaString") const {
return cstr_ ? cstr_ : null_string;
}
private:
// non-copyable
JavaString(const JavaString &);
JavaString &operator=(const JavaString &);
JNIEnv *jenv_;
jstring jstr_;
const char *cstr_;
};
// Helper class to extract the exception message from a Java throwable
class JavaExceptionMessage {
public:
JavaExceptionMessage(JNIEnv *jenv, jthrowable throwable) : message_(jenv, exceptionMessageFromThrowable(jenv, throwable)) {
}
const char *message() const {
return message_.c_str("Could not get exception message in JavaExceptionMessage");
}
private:
// non-copyable
JavaExceptionMessage(const JavaExceptionMessage &);
JavaExceptionMessage &operator=(const JavaExceptionMessage &);
// Get exception message by calling Java method Throwable.getMessage()
static jstring exceptionMessageFromThrowable(JNIEnv *jenv, jthrowable throwable) {
jstring jmsg = NULL;
if (jenv && throwable) {
jenv->ExceptionClear(); // Cannot invoke methods with any pending exceptions
jclass throwclz = jenv->GetObjectClass(throwable);
if (throwclz) {
// All Throwable classes have a getMessage() method, so call it to extract the exception message
jmethodID getMessageMethodID = jenv->GetMethodID(throwclz, "getMessage", "()Ljava/lang/String;");
if (getMessageMethodID)
jmsg = (jstring)jenv->CallObjectMethod(throwable, getMessageMethodID);
}
if (jmsg == NULL && jenv->ExceptionCheck())
jenv->ExceptionClear();
}
return jmsg;
}
JavaString message_;
};
// C++ Exception class for handling Java exceptions thrown during a director method Java upcall
class DirectorException : public std::exception {
public:
// Construct exception from a Java throwable
DirectorException(JNIEnv *jenv, jthrowable throwable) : classname_(0), msg_(0) {
// Call Java method Object.getClass().getName() to obtain the throwable's class name (delimited by '/')
if (throwable) {
jclass throwclz = jenv->GetObjectClass(throwable);
if (throwclz) {
jclass clzclz = jenv->GetObjectClass(throwclz);
if (clzclz) {
jmethodID getNameMethodID = jenv->GetMethodID(clzclz, "getName", "()Ljava/lang/String;");
if (getNameMethodID) {
jstring jstr_classname = (jstring)(jenv->CallObjectMethod(throwclz, getNameMethodID));
// 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);
}
}
}
}
}
JavaExceptionMessage exceptionmsg(jenv, throwable);
msg_ = copystr(exceptionmsg.message());
}
// More general constructor for handling as a java.lang.RuntimeException
DirectorException(const char *msg) : classname_(0), msg_(copystr(msg ? msg : "Unspecified DirectorException message")) {
}
~DirectorException() throw() {
delete[] classname_;
delete[] msg_;
}
const char *what() const throw() {
return msg_;
}
// Reconstruct and raise/throw the Java Exception that caused the DirectorException
// Note that any error in the JNI exception handling results in a Java RuntimeException
void raiseJavaException(JNIEnv *jenv) const {
if (jenv) {
jenv->ExceptionClear();
jmethodID ctorMethodID = 0;
jclass throwableclass = 0;
if (classname_) {
throwableclass = jenv->FindClass(classname_);
if (throwableclass)
ctorMethodID = jenv->GetMethodID(throwableclass, "<init>", "(Ljava/lang/String;)V");
}
if (ctorMethodID) {
jenv->ThrowNew(throwableclass, what());
} else {
SWIG_JavaThrowException(jenv, SWIG_JavaRuntimeException, what());
}
}
}
static void raise(JNIEnv *jenv, jthrowable throwable) {
throw DirectorException(jenv, throwable);
}
private:
static char *copypath(const char *srcmsg) {
char *target = copystr(srcmsg);
for (char *c=target; *c; ++c) {
if ('.' == *c)
*c = '/';
}
return target;
}
static char *copystr(const char *srcmsg) {
char *target = 0;
if (srcmsg) {
size_t msglen = strlen(srcmsg) + 1;
target = new char[msglen];
strncpy(target, srcmsg, msglen);
}
return target;
}
const char *classname_;
const char *msg_;
};
// Helper method to determine if a Java throwable matches a particular Java class type
SWIGINTERN 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;
}
}