diff options
Diffstat (limited to 'libjava/classpath/native/jni/gtk-peer/gthread-jni.c')
-rw-r--r-- | libjava/classpath/native/jni/gtk-peer/gthread-jni.c | 2592 |
1 files changed, 2592 insertions, 0 deletions
diff --git a/libjava/classpath/native/jni/gtk-peer/gthread-jni.c b/libjava/classpath/native/jni/gtk-peer/gthread-jni.c new file mode 100644 index 0000000..e673de0 --- /dev/null +++ b/libjava/classpath/native/jni/gtk-peer/gthread-jni.c @@ -0,0 +1,2592 @@ +/* gthread-jni.c -- JNI threading routines for GLIB + Copyright (C) 1998, 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +/************************************************************************/ +/* Header */ +/************************************************************************/ + +/* + * @author Julian Dolby (dolby@us.ibm.com) + * @date February 7, 2003 implemented for GLIB v.1 + * + * + * @author Steven Augart + * <steve+classpath at augart dot com>, <augart at watson dot ibm dot com> + * @date April 30, 2004 -- May 10 2004: Support new functions for Glib v.2, + * fix cond_wait to free and re-acquire the mutex, + * replaced trylock stub implementation with a full one. + * + * This code implements the GThreadFunctions interface for GLIB using + * Java threading primitives. All of the locking and conditional variable + * functionality required by GThreadFunctions is implemented using the + * monitor and wait/notify functionality of Java objects. The thread- + * local functionality uses the java.lang.ThreadLocal class. + * + * Classpath's AWT support uses GTK+ peers. GTK+ uses GLIB. GLIB by default + * uses the platform's native threading model -- pthreads in most cases. If + * the Java runtime doesn't use the native threading model, then it needs this + * code in order to use Classpath's (GTK+-based) AWT routines. + * + * This code should be portable; I believe it makes no assumptions + * about the underlying VM beyond that it implements the JNI functionality + * that this code uses. + * + * Currently, use of this code is governed by the configuration option + * --enable-portable-native-sync. We will soon add a VM hook so the VM can + * select which threading model it wants to use at run time; at that point, + * the configuration option will go away. + * + * The code in this file uses only JNI 1.1, except for one JNI 1.2 function: + * GetEnv, in the JNI Invocation API. (There seems to be no way around using + * GetEnv). + * + * ACKNOWLEDGEMENT: + * + * I would like to thank Mark Wielaard for his kindness in spending at least + * six hours of his own time in reviewing this code and correcting my GNU + * coding and commenting style. --Steve Augart + * + * + * NOTES: + * + * This code has been tested with Jikes RVM and with Kaffe. + * + * This code should have proper automated unit tests. I manually tested it + * by running an application that uses AWT. --Steven Augart + * + * MINOR NIT: + * + * - Using a jboolean in the arglist to "throw()" and "rethrow()" + * triggers many warnings from GCC's -Wconversion operation, because that + * is not the same as the conversion (upcast to an int) that would occur in + * the absence of a prototype. + * + * It would be very slightly more efficient to just pass the jboolean, but + * is not worth the clutter of messages. The right solution would be to + * turn off the -Wconversion warning for just this file, *except* that + * -Wconversion also warns you against constructs such as: + * unsigned u = -1; + * and that is a useful warning. So I went from a "jboolean" to a + * "gboolean" (-Wconversion is not enabled by default for GNU Classpath, + * but it is in my own CFLAGS, which, for gcc 3.3.3, read: -pipe -ggdb3 -W + * -Wall -Wbad-function-cast -Wcast-align -Wpointer-arith -Wcast-qual + * -Wshadow -Wstrict-prototypes -Wmissing-prototypes -Wmissing-declarations + * -fkeep-static-consts -fkeep-inline-functions -Wundef -Wwrite-strings + * -Wno-aggregate-return -Wmissing-noreturn -Wnested-externs -Wtrigraphs + * -Wconversion -Wsign-compare -Wno-float-equal -Wmissing-format-attribute + * -Wno-unreachable-code -Wdisabled-optimization ) + */ + +#include <config.h> + +/************************************************************************/ +/* Configuration */ +/************************************************************************/ + +/** Tracing and Reporting **/ +#define TRACE_API_CALLS 0 /* announce entry and exit into each method, + by printing to stderr. */ + +#define TRACE_MONITORS 0 /* Every enterMonitor() and exitMonitor() goes + to stderr. */ + +/** Trouble handling. There is a discussion below of this. **/ +#define EXPLAIN_TROUBLE 1 /* Describe any unexpected trouble that + happens. This is a superset + of EXPLAIN_BROKEN, and if set trumps an + unset EXPLAIN_BROKEN. It is not a strict + superset, since at the moment there is no + TROUBLE that is not also BROKEN. + + Use criticalMsg() to describe the problem. + */ + +#define EXPLAIN_BROKEN 1 /* Describe trouble that is serious enough to + be BROKEN. (Right now all trouble is at + least BROKEN.) */ + +/* There is no EXPLAIN_BADLY_BROKEN definition. We always explain + BADLY_BROKEN trouble, since there is no other way to report it. */ + + +/** Error Handling **/ +#define DIE_IF_BROKEN 1 /* Dies if serious trouble happens. There is + really no non-serious trouble, except + possibly problems that arise during + pthread_create, which are reported by a + GError. + + If you do not set DIE_IF_BROKEN, then + trouble will raise a Java RuntimeException. + We probably do want to die right away, + since anything that's BROKEN really + indicates a programming error or a + system-wide error, and that's what the glib + documentation says you should do in case of + that kind of error in a glib-style + function. But it does work to turn this + off. */ + +#if DIE_IF_BROKEN +#define DIE_IF_BADLY_BROKEN 1 /* DIE_IF_BROKEN implies DIE_IF_BADLY_BROKEN */ +#else +#define DIE_IF_BADLY_BROKEN 1 /* Die if the system is badly broken -- + that is, if we have further trouble while + attempting to throw an exception + upwards, or if we are unable to generate + one of the classes we'll need in order to + throw wrapped exceptions upward. + + If unset, we will print a warning message, + and limp along anyway. Not that the system + is likely to work. */ +#endif + +/** Performance tuning parameters **/ + +#define ENABLE_EXPENSIVE_ASSERTIONS 0 /* Enable expensive assertions? */ + +#define DELETE_LOCAL_REFS 1 /* Whether to delete local references. + + JNI only guarantees that there wil be 16 + available. (Jikes RVM provides an number + only limited by VM memory.) + + Jikes RVM will probably perform faster if + this is turned off, but other VMs may need + this to be turned on in order to perform at + all, or might need it if things change. + + Remember, we don't know how many of those + local refs might have already been used up + by higher layers of JNI code that end up + calling g_thread_self(), + g_thread_set_private(), and so on. + + We set this to 1 for GNU Classpath, since + one of our principles is "always go for the + most robust implementation" */ + +#define HAVE_JNI_VERSION_1_2 0 /* Assume we don't. We could + dynamically check for this. We will + assume JNI 1.2 in later versions of + Classpath. + + As it stands, the code in this file + already needs one JNI 1.2 function: + GetEnv, in the JNI Invocation API. + + TODO This code hasn't been tested yet. + And really hasn't been implemented yet. + */ + +/************************************************************************/ +/* Global data */ +/************************************************************************/ + +#if defined HAVE_STDINT_H +#include <stdint.h> /* provides intptr_t */ +#elif defined HAVE_INTTYPES_H +#include <inttypes.h> +#endif +#include <stdarg.h> /* va_list */ +#include <glib.h> +#include "gthread-jni.h" +#include <assert.h> /* assert() */ + +/* For Java thread priority constants. */ +#include <gnu_java_awt_peer_gtk_GThreadNativeMethodRunner.h> + +/* Since not all JNI header generators actually define constants we + define them here explicitly. */ +#ifndef gnu_java_awt_peer_gtk_GThreadNativeMethodRunner_MIN_PRIORITY +#define gnu_java_awt_peer_gtk_GThreadNativeMethodRunner_MIN_PRIORITY 1 +#endif +#ifndef gnu_java_awt_peer_gtk_GThreadNativeMethodRunner_NORM_PRIORITY +#define gnu_java_awt_peer_gtk_GThreadNativeMethodRunner_NORM_PRIORITY 5 +#endif +#ifndef gnu_java_awt_peer_gtk_GThreadNativeMethodRunner_MAX_PRIORITY +#define gnu_java_awt_peer_gtk_GThreadNativeMethodRunner_MAX_PRIORITY 10 +#endif + +/* The VM handle. This is set in + Java_gnu_java_awt_peer_gtk_GtkMainThread_gtkInit */ +JavaVM *cp_gtk_the_vm; + +/* Unions used for type punning. */ +union env_union +{ + void **void_env; + JNIEnv **jni_env; +}; + +union func_union +{ + void *void_func; + GThreadFunc g_func; +}; + +/* Forward Declarations for Functions */ +static int threadObj_set_priority (JNIEnv * env, jobject threadObj, + GThreadPriority gpriority); +static void fatalMsg (const char fmt[], ...) + __attribute__ ((format (printf, 1, 2))) + __attribute__ ((noreturn)); + +static void criticalMsg (const char fmt[], ...) + __attribute__ ((format (printf, 1, 2))); + +static void tracing (const char fmt[], ...) + __attribute__ ((format (printf, 1, 2))); + +static jint javaPriorityLevel (GThreadPriority priority) + __attribute__ ((const)); + +/************************************************************************/ +/* Trouble-handling, including utilities to reflect exceptions */ +/* back to the VM. Also some status reporting. */ +/************************************************************************/ + +/* How are we going to handle problems? + + There are several approaches: + + 1) Report them with the GError mechanism. + + (*thread_create)() is the only one of these functions that takes a + GError pointer. And the only G_THREAD error defined maps onto EAGAIN. + We don't have any errors in our (*thread_create)() implementation that + can be mapped to EAGAIN. So this idea is a non-starter. + + 2) Reflect the exception back to the VM, wrapped in a RuntimeException. + This will fail sometimes, if we're so broken (BADLY_BROKEN) that we + fail to throw the exception. + + 3) Abort execution. This is what the glib functions themselves do for + errors that they can't report via GError. + + Enable DIE_IF_BROKEN and/or DIE_IF_BADLY_BROKEN to + make this the default for BROKEN and/or BADLY_BROKEN trouble. + + 4) Display messages to stderr. We always do this for BADLY_BROKEN + trouble. The glib functions do that for errors they can't report via + GError. + + There are some complications. + + When I attempted to report a problem in g_thread_self() using g_critical (a + macro around g_log(), I found that g_log in turn looks for thread-private + data and calls g_thread_self() again. + + We got a segfault, probably due to stack overflow. So, this code doesn't + use the g_critical() and g_error() functions any more. Nor do we use + g_assert(); we use the C library's assert() instead. +*/ + + +#define WHERE __FILE__ ":" G_STRINGIFY(__LINE__) ": " + +/* This is portable to older compilers that lack variable-argument macros. + This used to be just g_critical(), but then we ran into the error reporting + problem discussed above. +*/ +static void +fatalMsg (const char fmt[], ...) +{ + va_list ap; + va_start (ap, fmt); + vfprintf (stderr, fmt, ap); + va_end (ap); + fputs ("\nAborting execution\n", stderr); + abort (); +} + + +static void +criticalMsg (const char fmt[], ...) +{ + va_list ap; + va_start (ap, fmt); + vfprintf (stderr, fmt, ap); + va_end (ap); + putc ('\n', stderr); +} + +/* Unlike the other two, this one does not append a newline. This is only + used if one of the TRACE_ macros is defined. */ +static void +tracing (const char fmt[], ...) +{ + va_list ap; + va_start (ap, fmt); + vfprintf (stderr, fmt, ap); + va_end (ap); +} + +#define assert_not_reached() \ + do \ + { \ + fputs(WHERE "You should never get here. Aborting execution.\n", \ + stderr); \ + abort(); \ + } \ + while(0) + + +#if DIE_IF_BADLY_BROKEN +#define BADLY_BROKEN fatalMsg +#else +#define BADLY_BROKEN criticalMsg +/* So, the user may still attempt to recover, even though we do not advise + this. */ +#endif + +/* I find it so depressing to have to use C without varargs macros. */ +#define BADLY_BROKEN_MSG WHERE "Something fundamental" \ + " to GNU Classpath's AWT JNI broke while we were trying to pass up a Java error message" + +#define BADLY_BROKEN0() \ + BADLY_BROKEN(BADLY_BROKEN_MSG); +#define BADLY_BROKEN1(msg) \ + BADLY_BROKEN(BADLY_BROKEN_MSG ": " msg) +#define BADLY_BROKEN2(msg, arg) \ + BADLY_BROKEN(BADLY_BROKEN_MSG ": " msg, arg) +#define BADLY_BROKEN3(msg, arg, arg2) \ + BADLY_BROKEN(BADLY_BROKEN_MSG ": " msg, arg1, arg2) +#define BADLY_BROKEN4(msg, arg, arg2, arg3) \ + BADLY_BROKEN(BADLY_BROKEN_MSG ": " msg, arg1, arg2, arg3) + +#define DELETE_LOCAL_REF(env, ref) \ + do \ + { \ + if ( DELETE_LOCAL_REFS ) \ + { \ + (*env)->DeleteLocalRef (env, ref); \ + (ref) = NULL; \ + } \ + } \ + while(0) + +/* Cached info for Exception-wrapping */ + +static jclass runtimeException_class; /* java.lang.RuntimeException */ +static jmethodID runtimeException_ctor; /* constructor for it */ + + +/* Throw a new RuntimeException. It may wrap around an existing exception. + 1 if we did rethrow, -1 if we had trouble while rethrowing. + isBroken is always true in this case. */ +static int +throw (JNIEnv * env, jthrowable cause, const char *message, + gboolean isBroken, const char *file, int line) +{ + jstring jmessage; + gboolean describedException = FALSE; /* Did we already describe the + exception to stderr or the + equivalent? */ + jthrowable wrapper; + + /* allocate local message in Java */ + const char fmt[] = "In AWT JNI, %s (at %s:%d)"; + size_t len = strlen (message) + strlen (file) + sizeof fmt + 25; + char *buf; + + if (EXPLAIN_TROUBLE || (isBroken && EXPLAIN_BROKEN)) + { + criticalMsg ("%s:%d: AWT JNI failure%s: %s\n", file, line, + isBroken ? " (BROKEN)" : "", message); + if (cause) + { + jthrowable currentException = (*env)->ExceptionOccurred (env); + + if (cause == currentException) + { + criticalMsg ("Description follows to System.err:"); + (*env)->ExceptionDescribe (env); + /* ExceptionDescribe has the side-effect of clearing the pending + exception; relaunch it. */ + describedException = TRUE; + + if ((*env)->Throw (env, cause)) + { + BADLY_BROKEN1 + ("Relaunching an exception with Throw failed."); + return -1; + } + } + else + { + DELETE_LOCAL_REF (env, currentException); + criticalMsg (WHERE + "currentException != cause; something else happened" + " while handling an exception."); + } + } + } /* if (EXPLAIN_TROUBLE) */ + + if (isBroken && DIE_IF_BROKEN) + fatalMsg ("%s:%d: Aborting execution; BROKEN: %s\n", file, line, message); + + if ((buf = malloc (len))) + { + memset (buf, 0, len); + g_snprintf (buf, len, fmt, message, file, line); + jmessage = (*env)->NewStringUTF (env, buf); + free (buf); + } + else + { + jmessage = NULL; + } + + /* Create the RuntimeException wrapper object and throw it. It is OK for + CAUSE to be NULL. */ + wrapper = (jthrowable) (*env)->NewObject + (env, runtimeException_class, runtimeException_ctor, jmessage, cause); + DELETE_LOCAL_REF (env, jmessage); + + if (!wrapper) + { + /* I think this should only happen: + - if there are bugs in my JNI code, or + - if the VM is broken, or + - if we run out of memory. + */ + if (EXPLAIN_TROUBLE) + { + criticalMsg (WHERE "GNU Classpath: JNI NewObject() could not create" + " a new java.lang.RuntimeException."); + criticalMsg ("We were trying to warn about the following" + " previous failure:"); + criticalMsg ("%s:%d: %s", file, line, message); + criticalMsg ("The latest (NewObject()) exception's description" + " follows, to System.err:"); + (*env)->ExceptionDescribe (env); + } + BADLY_BROKEN1 ("Failure of JNI NewObject()" + " to make a java.lang.RuntimeException"); + return -1; + } + + + /* throw it */ + if ((*env)->Throw (env, wrapper)) + { + /* Throw() should just never fail, unless we're in such severe trouble + that we might as well die. */ + BADLY_BROKEN1 + ("GNU Classpath: Failure of JNI Throw to report an Exception"); + return -1; + } + + DELETE_LOCAL_REF (env, wrapper); + return 1; +} + + + +/* Rethrow an exception we received, wrapping it with a RuntimeException. 1 + if we did rethrow, -1 if we had trouble while rethrowing. + CAUSE should be identical to the most recent exception that happened, so + that ExceptionDescribe will work. (Otherwise nix.) */ +static int +rethrow (JNIEnv * env, jthrowable cause, const char *message, + gboolean isBroken, const char *file, int line) +{ + assert (cause); + return throw (env, cause, message, isBroken, file, line); +} + + +/* This function checks for a pending exception, and rethrows it with + * a wrapper RuntimeException to deal with possible type problems (in + * case some calling piece of code does not expect the exception being + * thrown) and to include the given extra message. + * + * Returns 0 if no problems found (so no exception thrown), 1 if we rethrew an + * exception. Returns -1 on failure. + */ +static int +maybe_rethrow (JNIEnv * env, const char *message, gboolean isBroken, + const char *file, int line) +{ + jthrowable cause = (*env)->ExceptionOccurred (env); + int ret = 0; + + /* rethrow if an exception happened */ + if (cause) + { + ret = rethrow (env, cause, message, isBroken, file, line); + DELETE_LOCAL_REF (env, cause); + } + + return 0; +} + +/* MAYBE_TROUBLE() is used to include a source location in the exception + message. Once we have run maybe_rethrow, if there WAS trouble, + return TRUE, else FALSE. + + MAYBE_TROUBLE() is actually never used; all problems that throw exceptions + are BROKEN, at least. Nothing is recoverable :(. See the discussion of + possible errors at thread_create_jni_impl(). */ +#define MAYBE_TROUBLE(_env, _message) \ + maybe_rethrow(_env, _message, FALSE, __FILE__, __LINE__) + +/* MAYBE_TROUBLE(), but something would be BROKEN if it were true. */ +#define MAYBE_BROKEN(_env, _message) \ + maybe_rethrow(_env, _message, TRUE, __FILE__, __LINE__) + +/* Like MAYBE_TROUBLE(), TROUBLE() is never used. */ +#define TROUBLE(_env, _message) \ + rethrow(_env, (*env)->ExceptionOccurred (env), _message, FALSE, \ + __FILE__, __LINE__) + +#define BROKEN(_env, _message) \ + rethrow (_env, (*env)->ExceptionOccurred (env), _message, TRUE, \ + __FILE__, __LINE__) + +/* Like MAYBE_TROUBLE(), NEW_TROUBLE() is never used. */ +#define NEW_TROUBLE(_env, _message) \ + throw (_env, NULL, _message, FALSE, __FILE__, __LINE__) + +#define NEW_BROKEN(_env, _message) \ + throw (_env, NULL, _message, TRUE, __FILE__, __LINE__) + +/* Like MAYBE_TROUBLE(), RETHROW_CAUSE() is never used. */ +#define RETHROW_CAUSE(_env, _cause, _message) \ + rethrow (_env, _cause, _message, FALSE, __FILE__, __LINE__) + +#define BROKEN_CAUSE(_env, _cause, _message) \ + rethrow (_env, _cause, _message, TRUE, __FILE__, __LINE__) + +/* Macros to handle the possibility that someone might have called one of the + GThreadFunctions API functions with a Java exception pending. It is + generally discouraged to continue to use JNI after a Java exception has + been raised. Sun's JNI book advises that one trap JNI errors immediately + and not continue with an exception pending. + + These are #if'd out for these reasons: + + 1) They do not work in the C '89 subset that Classpath is currently + (2004 May 10) sticking to; HIDE_OLD_TROUBLE() includes a declaration + that should be in scope for the rest of the function, so it needs a + language version that lets you mix declarations and statements. (This + could be worked around if it were important.) + + 2) They chew up more time and resources. + + 3) There does not ever seem to be old trouble -- the assertion in + HIDE_OLD_TROUBLE never goes off. + + You will want to re-enable them if this code needs to be used in a context + where old exceptions might be pending when the GThread functions are + called. + + The implementations in this file are responsible for skipping around calls + to SHOW_OLD_TROUBLE() if they've raised exceptions during the call. So, if + we reach SHOW_OLD_TROUBLE, we are guaranteed that there are no exceptions + pending. */ +#if 1 +#define HIDE_OLD_TROUBLE(env) \ + assert ( NULL == (*env)->ExceptionOccurred (env) ) + +#define SHOW_OLD_TROUBLE() \ + assert ( NULL == (*env)->ExceptionOccurred (env) ) +#else /* 0 */ +#define HIDE_OLD_TROUBLE(env) \ + jthrowable savedTrouble = (*env)->ExceptionOccurred (env); \ + (*env)->ExceptionClear (env); + +#define SHOW_OLD_TROUBLE() do \ +{ \ + assert ( NULL == (*env)->ExceptionOccurred (env) ) \ + if (savedTrouble) \ + { \ + if ((*env)->Throw (env, savedTrouble)) \ + BADLY_BROKEN ("ReThrowing the savedTrouble failed"); \ + } \ + DELETE_LOCAL_REF (env, savedTrouble); \ +} while(0) + +#endif /* 0 */ + +/* Set up the cache of jclass and jmethodID primitives we need + in order to throw new exceptions and rethrow exceptions. We do this + independently of the other caching. We need to have this cache set up + first, so that we can then report errors properly. + + If any errors while setting up the error cache, the world is BADLY_BROKEN. + + May be called more than once. + + Returns -1 if the cache was not initialized properly, 1 if it was. +*/ +static int +setup_exception_cache (JNIEnv * env) +{ + static int exception_cache_initialized = 0; /* -1 for trouble, 1 for proper + init. */ + + jclass lcl_class; /* a class used for local refs */ + + if (exception_cache_initialized) + return exception_cache_initialized; + lcl_class = (*env)->FindClass (env, "java/lang/RuntimeException"); + if ( ! lcl_class ) + { + BADLY_BROKEN1 ("Broken Class library or VM?" + " Couldn't find java/lang/RuntimeException"); + return exception_cache_initialized = -1; + } + /* Pin it down. */ + runtimeException_class = (jclass) (*env)->NewGlobalRef (env, lcl_class); + DELETE_LOCAL_REF (env, lcl_class); + if (!runtimeException_class) + { + BADLY_BROKEN1 ("Serious trouble: could not turn" + " java.lang.RuntimeException into a global reference"); + return exception_cache_initialized = -1; + } + + runtimeException_ctor = + (*env)->GetMethodID (env, runtimeException_class, "<init>", + "(Ljava/lang/String;Ljava/lang/Throwable;)V"); + if ( ! runtimeException_ctor ) + { + BADLY_BROKEN1 ("Serious trouble: classpath couldn't find a" + " two-arg constructor for java/lang/RuntimeException"); + return exception_cache_initialized = -1; + } + + return exception_cache_initialized = 1; +} + + +/**********************************************************/ +/***** The main cache *************************************/ +/**********************************************************/ + +/** This is a cache of all classes, methods, and field IDs that we use during + the run. We maintain a permanent global reference to each of the classes + we cache, since otherwise the (local) jclass that refers to that class + would go out of scope and possibly be reused in further calls. + + The permanent global reference also achieves the secondary goal of + protecting the validity of the methods and field IDs in case the classes + were otherwise unloaded and then later loaded again. Obviously, this will + never happen to classes such as java.lang.Thread and java.lang.Object, but + the primary reason for maintaining permanent global refs is sitll valid. + + The code in jnilink.c has a similar objective. TODO: Consider using that + code instead. + + --Steven Augart +*/ + +/* All of these are cached classes and method IDs: */ +/* java.lang.Object */ +static jclass obj_class; /* java.lang.Object */ +static jmethodID obj_ctor; /* no-arg Constructor for java.lang.Object */ +static jmethodID obj_notify_mth; /* java.lang.Object.notify() */ +static jmethodID obj_notifyall_mth; /* java.lang.Object.notifyall() */ +static jmethodID obj_wait_mth; /* java.lang.Object.wait() */ +static jmethodID obj_wait_nanotime_mth; /* java.lang.Object.wait(JI) */ + +/* GThreadMutex and its methods */ +static jclass mutex_class; +static jmethodID mutex_ctor; +static jfieldID mutex_lockForPotentialLockers_fld; +static jfieldID mutex_potentialLockers_fld; + +/* java.lang.Thread and its methods*/ +static jclass thread_class; /* java.lang.Thread */ +static jmethodID thread_current_mth; /* Thread.currentThread() */ +static jmethodID thread_equals_mth; /* Thread.equals() */ +static jmethodID thread_join_mth; /* Thread.join() */ +static jmethodID thread_setPriority_mth; /* Thread.setPriority() */ +static jmethodID thread_stop_mth; /* Thread.stop() */ +static jmethodID thread_yield_mth; /* Thread.yield() */ + +/* java.lang.ThreadLocal and its methods */ +static jclass threadlocal_class; /* java.lang.ThreadLocal */ +static jmethodID threadlocal_ctor; /* Its constructor */ +static jmethodID threadlocal_set_mth; /* ThreadLocal.set() */ +static jmethodID threadlocal_get_mth; /* ThreadLocal.get() */ + +/* java.lang.Long and its methods */ +static jclass long_class; /* java.lang.Long */ +static jmethodID long_ctor; /* constructor for it: (J) */ +static jmethodID long_longValue_mth; /* longValue()J */ + + +/* GThreadNativeMethodRunner */ +static jclass runner_class; +static jmethodID runner_ctor; +static jmethodID runner_threadToThreadID_mth; +static jmethodID runner_threadIDToThread_mth; +static jmethodID runner_deRegisterJoinable_mth; +static jmethodID runner_start_mth; /* Inherited Thread.start() */ + + +/* java.lang.InterruptedException */ +static jclass interrupted_exception_class; + + + + +/* Returns a negative value if there was trouble during initialization. + Returns a positive value of the cache was initialized correctly. + Never returns zero. */ +static int +setup_cache (JNIEnv * env) +{ + jclass lcl_class; + static int initialized = 0; /* 1 means initialized, 0 means uninitialized, + -1 means mis-initialized */ + + if (initialized) + return initialized; + + /* make sure we can report on trouble */ + if (setup_exception_cache (env) < 0) + return initialized = -1; + +#ifdef JNI_VERSION_1_2 + if (HAVE_JNI_VERSION_1_2) + assert ( ! (*env)->ExceptionCheck (env)); + else +#endif + assert ( ! (*env)->ExceptionOccurred (env)); + + /* java.lang.Object and its methods */ + lcl_class = (*env)->FindClass (env, "java/lang/Object"); + if (!lcl_class) + { + BROKEN (env, "cannot find java.lang.Object"); + return initialized = -1; + } + + /* Pin it down. */ + obj_class = (jclass) (*env)->NewGlobalRef (env, lcl_class); + DELETE_LOCAL_REF (env, lcl_class); + if (!obj_class) + { + BROKEN (env, "Cannot get a global reference to java.lang.Object"); + return initialized = -1; + } + + obj_ctor = (*env)->GetMethodID (env, obj_class, "<init>", "()V"); + if (!obj_ctor) + { + BROKEN (env, "cannot find constructor for java.lang.Object"); + return initialized = -1; + } + + obj_notify_mth = (*env)->GetMethodID (env, obj_class, "notify", "()V"); + if ( ! obj_notify_mth ) + { + BROKEN (env, "cannot find java.lang.Object.notify()V"); + return initialized = -1; + } + + obj_notifyall_mth = + (*env)->GetMethodID (env, obj_class, "notifyAll", "()V"); + if ( ! obj_notifyall_mth) + { + BROKEN (env, "cannot find java.lang.Object.notifyall()V"); + return initialized = -1; + } + + obj_wait_mth = (*env)->GetMethodID (env, obj_class, "wait", "()V"); + if ( ! obj_wait_mth ) + { + BROKEN (env, "cannot find Object.<wait()V>"); + return initialized = -1; + } + + obj_wait_nanotime_mth = + (*env)->GetMethodID (env, obj_class, "wait", "(JI)V"); + if ( ! obj_wait_nanotime_mth ) + { + BROKEN (env, "cannot find Object.<wait(JI)V>"); + return initialized = -1; + } + + /* GThreadMutex and its methods */ + lcl_class = (*env)->FindClass (env, "gnu/java/awt/peer/gtk/GThreadMutex"); + if ( ! lcl_class) + { + BROKEN (env, "cannot find gnu.java.awt.peer.gtk.GThreadMutex"); + return initialized = -1; + } + /* Pin it down. */ + mutex_class = (jclass) (*env)->NewGlobalRef (env, lcl_class); + DELETE_LOCAL_REF (env, lcl_class); + if ( ! mutex_class) + { + BROKEN (env, "Cannot get a global reference to GThreadMutex"); + return initialized = -1; + } + + mutex_ctor = (*env)->GetMethodID (env, mutex_class, "<init>", "()V"); + if ( ! mutex_ctor) + { + BROKEN (env, "cannot find zero-arg constructor for GThreadMutex"); + return initialized = -1; + } + + mutex_potentialLockers_fld = (*env)->GetFieldID + (env, mutex_class, "potentialLockers", "I"); + if ( ! mutex_class ) + { + BROKEN (env, "cannot find GThreadMutex.potentialLockers"); + return initialized = -1; + } + + if (! (mutex_lockForPotentialLockers_fld = (*env)->GetFieldID + (env, mutex_class, "lockForPotentialLockers", "Ljava/lang/Object;"))) + { + BROKEN (env, "cannot find GThreadMutex.lockForPotentialLockers"); + return initialized = -1; + } + + + /* java.lang.Thread */ + if (! (lcl_class = (*env)->FindClass (env, "java/lang/Thread"))) + { + BROKEN (env, "cannot find java.lang.Thread"); + return initialized = -1; + } + + /* Pin it down. */ + thread_class = (jclass) (*env)->NewGlobalRef (env, lcl_class); + DELETE_LOCAL_REF (env, lcl_class); + if (!thread_class) + { + BROKEN (env, "Cannot get a global reference to java.lang.Thread"); + return initialized = -1; + } + + thread_current_mth = + (*env)->GetStaticMethodID (env, thread_class, "currentThread", + "()Ljava/lang/Thread;"); + if (!thread_current_mth) + { + BROKEN (env, "cannot find Thread.currentThread() method"); + return initialized = -1; + } + + thread_equals_mth = + (*env)->GetMethodID (env, thread_class, "equals", "(Ljava/lang/Object;)Z"); + if (!thread_equals_mth) + { + BROKEN (env, "cannot find Thread.equals() method"); + return initialized = -1; + } + + thread_join_mth = (*env)->GetMethodID (env, thread_class, "join", "()V"); + if (!thread_join_mth) + { + BROKEN (env, "cannot find Thread.join() method"); + return initialized = -1; + } + + thread_stop_mth = (*env)->GetMethodID (env, thread_class, "stop", "()V"); + if ( ! thread_stop_mth ) + { + BROKEN (env, "cannot find Thread.stop() method"); + return initialized = -1; + } + + thread_setPriority_mth = + (*env)->GetMethodID (env, thread_class, "setPriority", "(I)V"); + if ( ! thread_setPriority_mth ) + { + BROKEN (env, "cannot find Thread.setPriority() method"); + return initialized = -1; + } + + thread_yield_mth = + (*env)->GetStaticMethodID (env, thread_class, "yield", "()V"); + if ( ! thread_yield_mth ) + { + BROKEN (env, "cannot find Thread.yield() method"); + return initialized = -1; + } + + /* java.lang.ThreadLocal */ + lcl_class = (*env)->FindClass (env, "java/lang/ThreadLocal"); + if ( ! lcl_class ) + { + BROKEN (env, "cannot find class java.lang.ThreadLocal"); + return initialized = -1; + } + + /* Pin it down. */ + threadlocal_class = (jclass) (*env)->NewGlobalRef (env, lcl_class); + DELETE_LOCAL_REF (env, lcl_class); + if ( ! threadlocal_class ) + { + BROKEN (env, "Cannot get a global reference to java.lang.ThreadLocal"); + return initialized = -1; + } + + threadlocal_ctor = (*env)->GetMethodID (env, threadlocal_class, + "<init>", "()V"); + if ( ! threadlocal_ctor ) + { + BROKEN (env, "cannot find ThreadLocal.<init>()V"); + return initialized = -1; + } + + threadlocal_get_mth = (*env)->GetMethodID (env, threadlocal_class, + "get", "()Ljava/lang/Object;"); + if ( ! threadlocal_get_mth ) + { + BROKEN (env, "cannot find java.lang.ThreadLocal.get()Object"); + return initialized = -1; + } + + threadlocal_set_mth = (*env)->GetMethodID (env, threadlocal_class, + "set", "(Ljava/lang/Object;)V"); + if ( ! threadlocal_set_mth ) + { + BROKEN (env, "cannot find ThreadLocal.set(Object)V"); + return initialized = -1; + } + + /* java.lang.Long */ + lcl_class = (*env)->FindClass (env, "java/lang/Long"); + if ( ! lcl_class ) + { + BROKEN (env, "cannot find class java.lang.Long"); + return initialized = -1; + } + + /* Pin it down. */ + long_class = (jclass) (*env)->NewGlobalRef (env, lcl_class); + DELETE_LOCAL_REF (env, lcl_class); + if (!long_class) + { + BROKEN (env, "Cannot get a global reference to java.lang.Long"); + return initialized = -1; + } + + long_ctor = (*env)->GetMethodID (env, long_class, "<init>", "(J)V"); + if (!long_ctor) + { + BROKEN (env, "cannot find method java.lang.Long.<init>(J)V"); + return initialized = -1; + } + + long_longValue_mth = + (*env)->GetMethodID (env, long_class, "longValue", "()J"); + if (!long_longValue_mth) + { + BROKEN (env, "cannot find method java.lang.Long.longValue()J"); + return initialized = -1; + } + + + /* GThreadNativeMethodRunner */ + lcl_class = + (*env)->FindClass (env, + "gnu/java/awt/peer/gtk/GThreadNativeMethodRunner"); + if ( ! lcl_class ) + { + BROKEN (env, + "cannot find gnu.java.awt.peer.gtk.GThreadNativeMethodRunner"); + return initialized = -1; + } + + /* Pin it down. */ + runner_class = (jclass) (*env)->NewGlobalRef (env, lcl_class); + DELETE_LOCAL_REF (env, lcl_class); + if (!runner_class) + { + BROKEN (env, + "Cannot get a global reference to the class GThreadNativeMethodRunner"); + return initialized = -1; + } + + runner_ctor = (*env)->GetMethodID (env, runner_class, "<init>", "(JJZ)V"); + if ( ! runner_ctor ) + { + BROKEN (env, + "cannot find method GThreadNativeMethodRunner.<init>(JJZ)"); + return initialized = -1; + } + + runner_start_mth = (*env)->GetMethodID (env, runner_class, "start", "()V"); + if ( ! runner_start_mth ) + { + BROKEN (env, "cannot find method GThreadNativeMethodRunner.start()V"); + return initialized = -1; + } + + + runner_threadToThreadID_mth = + (*env)->GetStaticMethodID (env, runner_class, + "threadToThreadID", "(Ljava/lang/Thread;)I"); + if ( ! runner_threadToThreadID_mth ) + { + BROKEN (env, + "cannot find method GThreadNativeMethodRunner.threadToThreadID(java.lang.Thread)I"); + return initialized = -1; + } + + + runner_threadIDToThread_mth = + (*env)->GetStaticMethodID (env, runner_class, + "threadIDToThread", "(I)Ljava/lang/Thread;"); + if ( ! runner_threadIDToThread_mth ) + { + BROKEN (env, + "cannot find method GThreadNativeMethodRunner.threadIDToThread(I)java.lang.Thread"); + return initialized = -1; + } + + + runner_deRegisterJoinable_mth = + (*env)->GetStaticMethodID (env, runner_class, "deRegisterJoinable", + "(Ljava/lang/Thread;)V"); + if (!runner_deRegisterJoinable_mth) + { + BROKEN (env, + "cannot find method GThreadNativeMethodRunner.deRegisterJoinable(java.lang.Thread)V"); + return initialized = -1; + } + + + /* java.lang.InterruptedException */ + lcl_class = (*env)->FindClass (env, "java/lang/InterruptedException"); + if ( ! lcl_class ) + { + BROKEN (env, "cannot find class java.lang.InterruptedException"); + return initialized = -1; + } + + /* Pin it down. */ + interrupted_exception_class = (jclass) (*env)->NewGlobalRef (env, lcl_class); + DELETE_LOCAL_REF (env, lcl_class); + if (!interrupted_exception_class) + { + BROKEN (env, "Cannot make a global reference" + " to java.lang.InterruptedException"); + return initialized = -1; + } + +#ifdef JNI_VERSION_1_2 + if (HAVE_JNI_VERSION_1_2) + assert ( ! (*env)->ExceptionCheck (env)); + else +#endif + assert ( ! (*env)->ExceptionOccurred (env)); + + + return initialized = 1; +} + + + + + +/************************************************************************/ +/* Utilities to allocate and free java.lang.Objects */ +/************************************************************************/ + +/* The condition variables are java.lang.Object objects, + * which this method allocates and returns a global ref. Note that global + * refs must be explicitly freed (isn't C fun?). + */ +static jobject +allocatePlainObject (JNIEnv * env) +{ + jobject lcl_obj, global_obj; + + lcl_obj = (*env)->NewObject (env, obj_class, obj_ctor); + if (!lcl_obj) + { + BROKEN (env, "cannot allocate object"); + return NULL; + } + + global_obj = (*env)->NewGlobalRef (env, lcl_obj); + DELETE_LOCAL_REF (env, lcl_obj); + if (!global_obj) + { + NEW_BROKEN (env, "cannot make global ref for a new plain Java object"); + /* Deliberate fall-through */ + } + + return global_obj; +} + +/* Frees any Java object given a global ref (isn't C fun?) */ +static void +freeObject (JNIEnv * env, jobject obj) +{ + if (obj) + { + (*env)->DeleteGlobalRef (env, obj); + /* DeleteGlobalRef can never fail */ + } +} + + +/************************************************************************/ +/* Utilities to allocate and free Java mutexes */ +/************************************************************************/ + +/* The mutexes are gnu.java.awt.peer.gtk.GThreadMutex objects, + * which this method allocates and returns a global ref. Note that global + * refs must be explicitly freed (isn't C fun?). + * + * Free this with freeObject() + */ +static jobject +allocateMutexObject (JNIEnv * env) +{ + jobject lcl_obj, global_obj; + + lcl_obj = (*env)->NewObject (env, mutex_class, mutex_ctor); + if (!lcl_obj) + { + BROKEN (env, "cannot allocate a GThreadMutex"); + return NULL; + } + + global_obj = (*env)->NewGlobalRef (env, lcl_obj); + DELETE_LOCAL_REF (env, lcl_obj); + if (!global_obj) + { + NEW_BROKEN (env, "cannot make global ref"); + /* Deliberate fallthrough */ + } + + return global_obj; +} + + +/************************************************************************/ +/* Locking code */ +/************************************************************************/ + +/* Lock a Java object */ +#define ENTER_MONITOR(env, m) \ + enterMonitor(env, m, G_STRINGIFY(m)) + +/* Return -1 on failure, 0 on success. */ +static int +enterMonitor (JNIEnv * env, jobject monitorObj, const char monName[]) +{ + if (TRACE_MONITORS) + tracing (" <MonitorEnter(%s)>", monName); + assert (monitorObj); + if ((*env)->MonitorEnter (env, monitorObj) < 0) + { + BROKEN (env, "cannot enter monitor"); + return -1; + } + return 0; +} + + +/* Unlock a Java object */ +#define EXIT_MONITOR(env, m) \ + exitMonitor(env, m, G_STRINGIFY(m)) + +static int +exitMonitor (JNIEnv * env, jobject mutexObj, const char monName[]) +{ + if (TRACE_MONITORS) + tracing (" <MonitorExit(%s)>", monName); + assert (mutexObj); + if ((*env)->MonitorExit (env, mutexObj) < 0) + { + BROKEN (env, "cannot exit monitor "); + return -1; + } + return 0; +} + + +/************************************************************************/ +/* Miscellaneous utilities */ +/************************************************************************/ + +/* Get the Java Thread object that corresponds to a particular thread ID. + A negative thread Id gives us a null object. + + Returns a local reference. +*/ +static jobject +getThreadFromThreadID (JNIEnv * env, gpointer gThreadID) +{ + jint threadNum = (jint) gThreadID; + jobject thread; + + if (threadNum < 0) + { + NEW_BROKEN (env, "getThreadFromThreadID asked to look up" + " a negative thread index"); + return NULL; + } + + thread = (*env)->CallStaticObjectMethod + (env, runner_class, runner_threadIDToThread_mth, threadNum); + + if (MAYBE_BROKEN (env, "cannot get Thread for threadID ")) + return NULL; + + return thread; +} + +/** Return the unique threadID of THREAD. + + Error handling: Return (gpointer) -1 on all failures, + and propagate an exception. +*/ +static gpointer +getThreadIDFromThread (JNIEnv * env, jobject thread) +{ + jint threadNum; + + if (ENABLE_EXPENSIVE_ASSERTIONS) + assert ((*env)->IsInstanceOf (env, thread, thread_class)); + + HIDE_OLD_TROUBLE (env); + + threadNum = (*env)->CallStaticIntMethod + (env, runner_class, runner_threadToThreadID_mth, thread); + + if (MAYBE_BROKEN (env, "cannot get ThreadID for a Thread ")) + { + threadNum = -1; + goto done; + } + + + SHOW_OLD_TROUBLE (); + +done: + return (gpointer) threadNum; +} + + +/************************************************************************/ +/* The Actual JNI functions that we pass to the function vector. */ +/************************************************************************/ + + +/************************************************************************/ +/* Mutex Functions */ +/************************************************************************/ + +/*** Mutex Utilities ****/ +struct mutexObj_cache +{ + jobject lockForPotentialLockersObj; /* Lock for the potentialLockers + field. Local reference. */ + jobject lockObj; /* The real lock we use. This is a GLOBAL + reference and must not be freed. */ +}; + +/* Initialize the cache of sub-locks for a particular mutex object. + + -1 on error, 0 on success. The caller is not responsible for freeing the + partially-populated cache in case of failure (but in practice does anyway) + (This actually never fails, though, since GetObjectField allegedly never + fails.) + + Guaranteed to leave all fields of the cache initialized, even if only to + zero. +*/ +static int +populate_mutexObj_cache (JNIEnv * env, jobject mutexObj, + struct mutexObj_cache *mcache) +{ + mcache->lockObj = mutexObj; /* the mutexObj is its own lock. */ + assert (mcache->lockObj); + + mcache->lockForPotentialLockersObj = (*env)->GetObjectField + (env, mutexObj, mutex_lockForPotentialLockers_fld); + /* GetObjectField can never fail. */ + + /* Retrieving a NULL object could only happen if we somehow got a + a mutex object that was not properly intialized. */ + assert (mcache->lockForPotentialLockersObj); + + return 0; +} + + +/* Clean out the mutexObj_cache, even if it was never populated. */ +static void +clean_mutexObj_cache (JNIEnv * env, struct mutexObj_cache *mcache) +{ + /* OK to pass NULL refs to DELETE_LOCAL_REF */ + DELETE_LOCAL_REF (env, mcache->lockForPotentialLockersObj); + /* mcache->lockObj is a GLOBAL reference. */ + mcache->lockObj = NULL; +} + +/* -1 on failure, 0 on success. + The mutexObj_cache is already populated for this particular object. */ +static int +mutexObj_lock (JNIEnv * env, jobject mutexObj, struct mutexObj_cache *mcache) +{ + jint potentialLockers; + + if (ENTER_MONITOR (env, mcache->lockForPotentialLockersObj)) + return -1; + + assert(mutexObj); + potentialLockers = + (*env)->GetIntField (env, mutexObj, mutex_potentialLockers_fld); + /* GetIntField() never fails. */ + + ++potentialLockers; + + (*env)->SetIntField + (env, mutexObj, mutex_potentialLockers_fld, potentialLockers); + + if (EXIT_MONITOR (env, mcache->lockForPotentialLockersObj)) + return -1; + + if (ENTER_MONITOR (env, mcache->lockObj)) + return -1; + + SHOW_OLD_TROUBLE (); + + return 0; +} + +/* Unlock a GMutex, once we're already in JNI and have already gotten the + mutexObj for it. This skips the messages that TRACE_API_CALLS would + print. + + Returns -1 on error, 0 on success. */ +static int +mutexObj_unlock (JNIEnv * env, jobject mutexObj, + struct mutexObj_cache *mcache) +{ + jint potentialLockers; + int ret = -1; /* assume failure until we suceed. */ + + /* Free the lock first, so that someone waiting for the lock can get it + ASAP. */ + /* This is guaranteed not to block. */ + if (EXIT_MONITOR (env, mcache->lockObj) < 0) + goto done; + + /* Kick down potentialLockers by one. We do this AFTER we free the lock, so + that we hold it no longer than necessary. */ + if (ENTER_MONITOR (env, mcache->lockForPotentialLockersObj) < 0) + goto done; + + potentialLockers = (*env)->GetIntField + (env, mutexObj, mutex_potentialLockers_fld); + /* GetIntField never fails */ + + assert (potentialLockers >= 1); + --potentialLockers; + + (*env)->SetIntField + (env, mutexObj, mutex_potentialLockers_fld, potentialLockers); + /* Never fails, so the JNI book says. */ + + /* Clean up. */ + if (EXIT_MONITOR (env, mcache->lockForPotentialLockersObj) < 0) + goto done; + ret = 0; + +done: + return ret; +} + +/*** Mutex Implementations ****/ + +/* Create a mutex, which is a java.lang.Object for us. + In case of failure, we'll return NULL. Which will implicitly + cause future calls to fail. */ +static GMutex * +mutex_new_jni_impl (void) +{ + jobject mutexObj; + JNIEnv *env; + union env_union e; + + if (TRACE_API_CALLS) + tracing ("mutex_new_jni_impl()"); + + e.jni_env = &env; + (*cp_gtk_the_vm)->GetEnv (cp_gtk_the_vm, e.void_env, JNI_VERSION_1_1); + + if (setup_cache (env) < 0) + { + mutexObj = NULL; + goto done; + } + + mutexObj = allocateMutexObject (env); + +done: + if (TRACE_API_CALLS) + tracing (" ==> %p \n", mutexObj); + + return (GMutex *) mutexObj; + +} + +/* Lock a mutex. */ +static void +mutex_lock_jni_impl (GMutex * mutex) +{ + struct mutexObj_cache mcache; + jobject mutexObj = (jobject) mutex; + JNIEnv *env; + union env_union e; + + if (TRACE_API_CALLS) + tracing ("mutex_lock_jni_impl( mutexObj = %p )", mutexObj); + + assert (mutexObj); + e.jni_env = &env; + (*cp_gtk_the_vm)->GetEnv (cp_gtk_the_vm, e.void_env, JNI_VERSION_1_1); + + if (setup_cache (env) < 0) + goto done; + + HIDE_OLD_TROUBLE (env); + + if (populate_mutexObj_cache (env, mutexObj, &mcache) < 0) + goto done; + + mutexObj_lock (env, mutexObj, &mcache); + /* No need to error check; we've already reported it in any case. */ + +done: + clean_mutexObj_cache (env, &mcache); + if (TRACE_API_CALLS) + tracing (" ==> VOID \n"); +} + + +/* Try to lock a mutex. Return TRUE if we succeed, FALSE if we fail. + FALSE on error. */ +static gboolean +mutex_trylock_jni_impl (GMutex * gmutex) +{ + jobject mutexObj = (jobject) gmutex; + jint potentialLockers; + gboolean ret = FALSE; + JNIEnv *env; + union env_union e; + struct mutexObj_cache mcache; + + if (TRACE_API_CALLS) + tracing ("mutex_trylock_jni_impl(mutexObj=%p)", mutexObj); + + assert (mutexObj); + + e.jni_env = &env; + (*cp_gtk_the_vm)->GetEnv (cp_gtk_the_vm, e.void_env, JNI_VERSION_1_1); + if (setup_cache (env) < 0) + goto done; + HIDE_OLD_TROUBLE (env); + + if (populate_mutexObj_cache (env, mutexObj, &mcache) < 0) + goto done; + + if (ENTER_MONITOR (env, mcache.lockForPotentialLockersObj)) + goto done; + + potentialLockers = (*env)->GetIntField + (env, mutexObj, mutex_potentialLockers_fld); + + assert (potentialLockers >= 0); + + if (potentialLockers) + { + /* Already locked. Clean up and leave. */ + EXIT_MONITOR (env, mcache.lockForPotentialLockersObj); + /* Ignore any error code from EXIT_MONITOR; there's nothing we could do + at this level, in any case. */ + goto done; + } + + /* Guaranteed not to block. */ + if (ENTER_MONITOR (env, mcache.lockObj)) + { + /* Clean up the existing lock. */ + EXIT_MONITOR (env, mcache.lockForPotentialLockersObj); + /* Ignore any error code from EXIT_MONITOR; there's nothing we could do + at this level, in any case. */ + goto done; + } + + + /* We have the monitor. Record that fact. */ + potentialLockers = 1; + (*env)->SetIntField + (env, mutexObj, mutex_potentialLockers_fld, potentialLockers); + /* Set*Field() never fails */ + + ret = TRUE; /* We have the lock. */ + + /* Clean up. */ + if (EXIT_MONITOR (env, mcache.lockForPotentialLockersObj)) + goto done; /* If we fail at this point, still keep the + main lock. */ + + SHOW_OLD_TROUBLE (); +done: + clean_mutexObj_cache (env, &mcache); + if (TRACE_API_CALLS) + tracing (" ==> %s\n", ret ? "TRUE" : "FALSE"); + return ret; +} + + +/* Unlock a mutex. */ +static void +mutex_unlock_jni_impl (GMutex * gmutex) +{ + jobject mutexObj = (jobject) gmutex; + struct mutexObj_cache mcache; + JNIEnv *env; + union env_union e; + + if (TRACE_API_CALLS) + tracing ("mutex_unlock_jni_impl(mutexObj=%p)", mutexObj); + + e.jni_env = &env; + (*cp_gtk_the_vm)->GetEnv (cp_gtk_the_vm, e.void_env, JNI_VERSION_1_1); + if (setup_cache (env) < 0) + goto done; + HIDE_OLD_TROUBLE (env); + + assert (mutexObj); + + if ( populate_mutexObj_cache (env, mutexObj, &mcache) < 0) + goto done; + + (void) mutexObj_unlock (env, mutexObj, &mcache); + + SHOW_OLD_TROUBLE (); + +done: + clean_mutexObj_cache (env, &mcache); + if (TRACE_API_CALLS) + tracing (" ==> VOID\n"); +} + + + +/* Free a mutex (isn't C fun?). OK this time for it to be NULL. + No failure conditions, for a change. */ +static void +mutex_free_jni_impl (GMutex * mutex) +{ + jobject mutexObj = (jobject) mutex; + JNIEnv *env; + union env_union e; + + e.jni_env = &env; + (*cp_gtk_the_vm)->GetEnv (cp_gtk_the_vm, e.void_env, JNI_VERSION_1_1); + + if (TRACE_API_CALLS) + tracing ("mutex_free_jni_impl(%p)", mutexObj); + + freeObject (env, mutexObj); + + if (TRACE_API_CALLS) + tracing (" ==> VOID\n"); +} + + + + +/************************************************************************/ +/* Condition variable code */ +/************************************************************************/ + +/* Create a new condition variable. This is a java.lang.Object for us. */ +static GCond * +cond_new_jni_impl (void) +{ + jobject condObj; + JNIEnv *env; + union env_union e; + + if (TRACE_API_CALLS) + tracing ("mutex_free_jni_impl()"); + + e.jni_env = &env; + (*cp_gtk_the_vm)->GetEnv (cp_gtk_the_vm, e.void_env, JNI_VERSION_1_1); + + condObj = allocatePlainObject (env); + + if (TRACE_API_CALLS) + tracing (" ==> %p\n", condObj); + + return (GCond *) condObj; +} + +/* Signal on a condition variable. This is simply calling Object.notify + * for us. + */ +static void +cond_signal_jni_impl (GCond * gcond) +{ + JNIEnv *env; + union env_union e; + jobject condObj = (jobject) gcond; + + if (TRACE_API_CALLS) + tracing ("cond_signal_jni_impl(condObj = %p)", condObj); + + e.jni_env = &env; + (*cp_gtk_the_vm)->GetEnv (cp_gtk_the_vm, e.void_env, JNI_VERSION_1_1); + if (setup_cache (env) < 0) + goto done; + HIDE_OLD_TROUBLE (env); + + assert (condObj); + + /* Must have locked an object to call notify */ + if (ENTER_MONITOR (env, condObj)) + goto done; + + (*env)->CallVoidMethod (env, condObj, obj_notify_mth); + if (MAYBE_BROKEN (env, "cannot signal mutex with Object.notify()")) + { + if (EXIT_MONITOR (env, condObj)) + BADLY_BROKEN1 ("Failed to unlock a monitor; the VM may deadlock."); + goto done; + } + + EXIT_MONITOR (env, condObj); + + SHOW_OLD_TROUBLE (); + +done: + if (TRACE_API_CALLS) + tracing (" ==> VOID\n"); +} + +/* Broadcast to all waiting on a condition variable. This is simply + * calling Object.notifyAll for us. + */ +static void +cond_broadcast_jni_impl (GCond * gcond) +{ + jobject condObj = (jobject) gcond; + JNIEnv *env; + union env_union e; + + if (TRACE_API_CALLS) + tracing ("cond_broadcast_jni_impl(condObj=%p)", condObj); + + e.jni_env = &env; + (*cp_gtk_the_vm)->GetEnv (cp_gtk_the_vm, e.void_env, JNI_VERSION_1_1); + if (setup_cache (env) < 0) + goto done; + HIDE_OLD_TROUBLE (env); + + assert (condObj); + /* Must have locked an object to call notifyAll */ + if (ENTER_MONITOR (env, condObj)) + goto done; + + (*env)->CallVoidMethod (env, condObj, obj_notifyall_mth); + if (MAYBE_BROKEN (env, "cannot broadcast to mutex with Object.notify()")) + { + EXIT_MONITOR (env, condObj); + goto done; + } + + EXIT_MONITOR (env, condObj); + + SHOW_OLD_TROUBLE (); + +done: + if (TRACE_API_CALLS) + tracing (" ==> VOID\n"); +} + + +/* Wait on a condition variable. For us, this simply means calling + * Object.wait. + * + * Throws a Java exception on trouble; may leave the mutexes set arbitrarily. + * XXX TODO: Further improve error recovery. + */ +static void +cond_wait_jni_impl (GCond * gcond, GMutex * gmutex) +{ + struct mutexObj_cache cache; + jobject condObj = (jobject) gcond; + jobject mutexObj = (jobject) gmutex; + JNIEnv *env; + union env_union e; + + if (TRACE_API_CALLS) + tracing ("cond_wait_jni_impl(condObj=%p, mutexObj=%p)", + condObj, mutexObj); + + e.jni_env = &env; + (*cp_gtk_the_vm)->GetEnv (cp_gtk_the_vm, e.void_env, JNI_VERSION_1_1); + if (setup_cache (env) < 0) + goto done; + HIDE_OLD_TROUBLE (env); + + assert (condObj); + assert (mutexObj); + /* Must have locked a Java object to call wait on it */ + if (ENTER_MONITOR (env, condObj) < 0) + goto done; + + /* Our atomicity is now guaranteed; we're protected by the Java monitor on + condObj. Unlock the GMutex. */ + if (mutexObj_unlock (env, mutexObj, &cache)) + goto done; + + (*env)->CallVoidMethod (env, condObj, obj_wait_mth); + if (MAYBE_BROKEN (env, "cannot wait on condObj")) + { + EXIT_MONITOR (env, condObj); /* ignore err checking */ + goto done; + } + + /* Re-acquire the lock on the GMutex. Do this while we're protected by the + Java monitor on condObj. */ + if (mutexObj_lock (env, mutexObj, &cache)) + goto done; + + EXIT_MONITOR (env, condObj); + + SHOW_OLD_TROUBLE (); + +done: + if (TRACE_API_CALLS) + tracing (" ==> VOID\n"); +} + + +/** Wait on a condition variable until a timeout. This is a little tricky + * for us. We first call Object.wait(J) giving it the appropriate timeout + * value. On return, we check whether an InterruptedException happened. If + * so, that is Java-speak for wait timing out. + * + * We return FALSE if we timed out. Return TRUE if the condition was + * signalled first, before we timed out. + * + * In case of trouble we throw a Java exception. Whether we return FALSE or + * TRUE depends upon whether the condition was raised before the trouble + * happened. + * + * I believe that this function goes to the proper lengths to try to unlock + * all of the locked mutexes and monitors, as appropriate, and that it further + * tries to make sure that the thrown exception is the current one, not any + * future cascaded one from something like a failure to unlock the monitors. + */ +static gboolean +cond_timed_wait_jni_impl (GCond * gcond, GMutex * gmutex, GTimeVal * end_time) +{ + JNIEnv *env; + union env_union e; + jlong time_millisec; + jint time_nanosec; + jthrowable cause; + jobject condObj = (jobject) gcond; + jobject mutexObj = (jobject) gmutex; + gboolean condRaised = FALSE; /* Condition has not been raised yet. */ + struct mutexObj_cache cache; + gboolean interrupted; + + if (TRACE_API_CALLS) + { + tracing ("cond_timed_wait_jni_impl(cond=%p, mutex=%p," + " end_time=< sec=%lu, usec=%lu >)", condObj, mutexObj, + (unsigned long) end_time->tv_sec, + (unsigned long) end_time->tv_usec); + } + + + e.jni_env = &env; + (*cp_gtk_the_vm)->GetEnv (cp_gtk_the_vm, e.void_env, JNI_VERSION_1_1); + if (setup_cache (env) < 0) + goto done; + HIDE_OLD_TROUBLE (env); + + time_millisec = end_time->tv_sec * 1000 + end_time->tv_usec / 1000; + time_nanosec = 1000 * (end_time->tv_usec % 1000); + + /* Must have locked an object to call wait */ + if (ENTER_MONITOR (env, condObj) < 0) + goto done; + + if (mutexObj_unlock (env, mutexObj, &cache) < 0) + { + if (EXIT_MONITOR (env, condObj) < 0) + criticalMsg + ("Unable to unlock an existing lock on a condition; your proram may deadlock"); + goto done; + } + + + (*env)->CallVoidMethod (env, condObj, obj_wait_nanotime_mth, + time_millisec, time_nanosec); + + /* If there was trouble, save that fact, and the reason for the trouble. We + want to respond to this condition as fast as possible. */ + cause = (*env)->ExceptionOccurred (env); + + if ( ! cause ) + { + condRaised = TRUE; /* condition was signalled */ + } + else if ((*env)->IsInstanceOf (env, cause, interrupted_exception_class)) + { + condRaised = FALSE; /* Condition was not raised before timeout. + (This is redundant with the initialization + of condRaised above) */ + (*env)->ExceptionClear (env); /* Clear the InterruptedException. */ + cause = NULL; /* no pending cause now. */ + } + else + { + interrupted = FALSE; /* Trouble, but not because of + InterruptedException. Assume the condition + was not raised. */ + /* Leave condRaised set to FALSE */ + } + + /* Irrespective of whether there is a pending problem to report, go ahead + and try to clean up. This may end up throwing an exception that is + different from the one that was thrown by the call to Object.wait(). + So we will override it with the first exception (don't want to have + cascading problems). */ + if (mutexObj_lock (env, mutexObj, &cache) && !cause) + { + cause = (*env)->ExceptionOccurred (env); + assert (cause); + } + + if (EXIT_MONITOR (env, condObj) && !cause) + { + cause = (*env)->ExceptionOccurred (env); + assert (cause); + } + + if (cause) /* Raise the first cause. */ + { + BROKEN_CAUSE (env, cause, "error in timed wait or during its cleanup"); + goto done; + } + + SHOW_OLD_TROUBLE (); + +done: + if (TRACE_API_CALLS) + tracing (" ==> condRaised = %s\n", condRaised ? "TRUE" : "FALSE"); + return condRaised; +} + + +/* Free a condition variable. (isn't C fun?). Can not fail. */ +static void +cond_free_jni_impl (GCond * cond) +{ + jobject condObj = (jobject) cond; + JNIEnv *env; + union env_union e; + + if (TRACE_API_CALLS) + tracing ("cond_free_jni_impl(condObj = %p)", condObj); + e.jni_env = &env; + (*cp_gtk_the_vm)->GetEnv (cp_gtk_the_vm, e.void_env, JNI_VERSION_1_1); + + freeObject (env, condObj); + + if (TRACE_API_CALLS) + tracing (" ==> VOID\n"); +} + + +/************************************************************************/ +/* Thread-local data code */ +/************************************************************************/ + +/* Create a new thread-local key. We use java.lang.ThreadLocal objects + * for this. This returns the pointer representation of a Java global + * reference. + * + * We will throw a Java exception and return NULL in case of failure. + */ +static GPrivate * +private_new_jni_impl (GDestroyNotify notify __attribute__ ((unused))) +{ + JNIEnv *env; + union env_union e; + jobject lcl_key; + jobject global_key; + GPrivate *gkey = NULL; /* Error return code */ + + if (TRACE_API_CALLS) + tracing ("private_new_jni_impl()"); + + e.jni_env = &env; + (*cp_gtk_the_vm)->GetEnv (cp_gtk_the_vm, e.void_env, JNI_VERSION_1_1); + if (setup_cache (env) < 0) + goto done; + HIDE_OLD_TROUBLE (env); + + lcl_key = (*env)->NewObject (env, threadlocal_class, threadlocal_ctor); + if ( ! lcl_key ) + { + BROKEN (env, "cannot allocate a ThreadLocal"); + goto done; + } + + global_key = ((*env)->NewGlobalRef (env, lcl_key)); + DELETE_LOCAL_REF (env, lcl_key); + if ( ! global_key) + { + NEW_BROKEN (env, "cannot create a GlobalRef to a new ThreadLocal"); + goto done; + } + + gkey = (GPrivate *) global_key; + SHOW_OLD_TROUBLE (); + +done: + if (TRACE_API_CALLS) + tracing (" ==> %p\n", (void *) gkey); + + return gkey; +} + +/* Get this thread's value for a thread-local key. This is simply + * ThreadLocal.get for us. Return NULL if no value. (I can't think of + * anything else to do.) + */ +static gpointer +private_get_jni_impl (GPrivate * gkey) +{ + JNIEnv *env; + union env_union e; + jobject val_wrapper; + jobject keyObj = (jobject) gkey; + gpointer thread_specific_data = NULL; /* Init to the error-return value */ + + jlong val; + + if (TRACE_API_CALLS) + tracing ("private_get_jni_impl(keyObj=%p)", keyObj); + + e.jni_env = &env; + (*cp_gtk_the_vm)->GetEnv (cp_gtk_the_vm, e.void_env, JNI_VERSION_1_1); + if (setup_cache (env) < 0) + goto done; + HIDE_OLD_TROUBLE (env); + + val_wrapper = (*env)->CallObjectMethod (env, keyObj, threadlocal_get_mth); + if (MAYBE_BROKEN (env, "cannot find thread-local object")) + goto done; + + if (! val_wrapper ) + { + /* It's Java's "null" object. No ref found. This is OK; we must never + have set a value in this thread. Note that this next statement is + not necessary, strictly speaking, since we're already initialized to + NULL. A good optimizing C compiler will detect that and optimize out + this statement. */ + thread_specific_data = NULL; + goto done; + } + + val = (*env)->CallLongMethod (env, val_wrapper, long_longValue_mth); + + if (MAYBE_BROKEN (env, "cannot get thread local value")) + goto done; + + thread_specific_data = (gpointer) (intptr_t) val; + + /* Only re-raise the old pending exception if a new one hasn't come along to + supersede it. */ + SHOW_OLD_TROUBLE (); + +done: + + if (TRACE_API_CALLS) + tracing (" ==> %p\n", thread_specific_data); + + return thread_specific_data; +} + +/* Set this thread's value for a thread-local key. This is simply + * ThreadLocal.set() for us. + */ +static void +private_set_jni_impl (GPrivate * gkey, gpointer thread_specific_data) +{ + JNIEnv *env; + union env_union e; + jobject val_wrapper; + jobject keyObj = (jobject) gkey; + + + if (TRACE_API_CALLS) + tracing ("private_set_jni_impl(keyObj=%p, thread_specific_data=%p)", + keyObj, thread_specific_data); + + e.jni_env = &env; + (*cp_gtk_the_vm)->GetEnv (cp_gtk_the_vm, e.void_env, JNI_VERSION_1_1); + if (setup_cache (env) < 0) + goto done; + HIDE_OLD_TROUBLE (env); + + /* We are just going to always use a Java long to represent a C pointer. + Otherwise all of the code would end up being conditionalized for various + pointer sizes, and that seems like too much of a hassle, in order to save + a paltry few bytes, especially given the horrendous overhead of JNI in + any case. + */ + + val_wrapper = (*env)->NewObject (env, long_class, long_ctor, + (jlong) (intptr_t) thread_specific_data); + if ( ! val_wrapper ) + { + BROKEN (env, "cannot create a java.lang.Long"); + goto done; + } + + /* At this point, we now have set lcl_obj as a numeric class that wraps + around the thread-specific data we were given. */ + (*env)->CallVoidMethod (env, keyObj, threadlocal_set_mth, val_wrapper); + if (MAYBE_BROKEN (env, "cannot set thread local value")) + goto done; + + SHOW_OLD_TROUBLE (); +done: + if (TRACE_API_CALLS) + tracing (" ==> VOID\n"); +} + + +/** Create an object of type gnu.java.awt.peer.gtk.GThreadNativeMethodRunner. + Run it. + + We need to create joinable threads. We handle the notion of a joinable + thread by determining whether or not we are going to maintain a permanent + hard reference to it until it croaks. + + Posix does not appear to have a Java-like concept of daemon threads, where + the JVM will exit when there are only daemon threads running. + + Error handling: + + To quote from the glib guide: + "GError should only be used to report recoverable runtime errors, never + to report programming errors." + + So how do we consider the failure to create a thread? Well, each of the + failure cases in this function are discussed, and none of them are really + recoverable. + + The glib library is really designed so that you should fail + catastrophically in case of "programming errors". The only error defined + for the GThread functions is G_THREAD_ERROR_AGAIN, and that for + thread_create. + + Most of these GThread functions could fail if we run out of memory, for + example, but the only one capable of reporting that fact is + thread_create. */ +static void +thread_create_jni_impl (GThreadFunc func, + gpointer data, + gulong stack_size __attribute__((unused)), + gboolean joinable, + gboolean bound __attribute__((unused)), + GThreadPriority gpriority, + /* This prototype is horrible. threadIDp is actually + a gpointer to the thread's thread-ID. Which is, + of course, itself a gpointer-typed value. Ouch. */ + gpointer threadIDp, + /* Do not touch the GError stuff unless you have + RECOVERABLE trouble. There is no recoverable + trouble in this implementation. */ + GError **errorp __attribute__((unused))) +{ + JNIEnv *env; + union env_union e; + union func_union f; + jboolean jjoinable = joinable; + jobject newThreadObj; + gpointer threadID; /* to be filled in */ + + if (TRACE_API_CALLS) + { + f.g_func = func; + tracing ("thread_create_jni_impl(func=%p, data=%p, joinable=%s," + " threadIDp=%p, *(int *) threadIDp = %d)", + f.void_func, data, joinable ? "TRUE" : "FALSE", + threadIDp, *(int *) threadIDp); + } + + e.jni_env = &env; + (*cp_gtk_the_vm)->GetEnv (cp_gtk_the_vm, e.void_env, JNI_VERSION_1_1); + if (setup_cache (env) < 0) + { + /* The failed call to setup the cache is certainly not recoverable; + not appropriate for G_THREAD_ERROR_AGAIN. */ + *(gpointer *) threadIDp = NULL; + goto done; + } + HIDE_OLD_TROUBLE (env); + + /* If a thread is joinable, then notify its constructor. The constructor + will enter a hard reference for it, and the hard ref. won't go away until + the thread has been joined. */ + newThreadObj = + (*env)->NewObject (env, runner_class, runner_ctor, + (jlong) (intptr_t) func, (jlong) (intptr_t) data, + jjoinable); + if ( ! newThreadObj ) + { + BROKEN (env, "creating a new thread failed in the constructor"); + *(gpointer *) threadIDp = NULL; + /* The failed call to the constructor does not throw any errors such + that G_THREAD_ERROR_AGAIN is appropriate. No other recoverable + errors defined. Once again, we go back to the VM. */ + goto done; + } + + if (threadObj_set_priority (env, newThreadObj, gpriority) < 0) + { + *(gpointer *) threadIDp = NULL; + /* None of these possible exceptions from Thread.setPriority() are + recoverable, so they are not appropriate for EAGAIN. So we should + fail. */ + goto done; + } + + (*env)->CallVoidMethod (env, runner_class, runner_start_mth); + + if (MAYBE_BROKEN (env, "starting a new thread failed")) + { + *(gpointer *) threadIDp = NULL; + /* The only exception Thread.start() throws is + IllegalStateException. And that would indicate a programming error. + + So there are no situations such that G_THREAD_ERROR_AGAIN would be + OK. + + So, we don't use g_set_error() here to perform any error reporting. + */ + goto done; + } + + threadID = getThreadIDFromThread (env, newThreadObj); + + *(gpointer *) threadIDp = threadID; + SHOW_OLD_TROUBLE (); + +done: + if (TRACE_API_CALLS) + tracing (" ==> (threadID = %p) \n", threadID); +} + + +/* Wraps a call to g_thread_yield. */ +static void +thread_yield_jni_impl (void) +{ + JNIEnv *env; + union env_union e; + + if (TRACE_API_CALLS) + tracing ("thread_yield_jni_impl()"); + + e.jni_env = &env; + (*cp_gtk_the_vm)->GetEnv (cp_gtk_the_vm, e.void_env, JNI_VERSION_1_1); + if (setup_cache (env) < 0) + goto done; + HIDE_OLD_TROUBLE (env); + + (*env)->CallStaticVoidMethod (env, thread_class, thread_yield_mth); + if (MAYBE_BROKEN (env, "Thread.yield() failed")) + goto done; + + SHOW_OLD_TROUBLE (); + +done: + if (TRACE_API_CALLS) + tracing (" ==> VOID\n"); +} + + +static void +thread_join_jni_impl (gpointer threadID) +{ + JNIEnv *env; + union env_union e; + jobject threadObj = NULL; + + if ( TRACE_API_CALLS ) + tracing ("thread_join_jni_impl(threadID=%p) ", threadID); + + e.jni_env = &env; + (*cp_gtk_the_vm)->GetEnv (cp_gtk_the_vm, e.void_env, JNI_VERSION_1_1); + if (setup_cache (env) < 0) + goto done; + HIDE_OLD_TROUBLE (env); + + threadObj = getThreadFromThreadID (env, threadID); + if ( ! threadObj ) /* Already reported with BROKEN */ + goto done; + + (*env)->CallVoidMethod (env, threadObj, thread_join_mth); + if (MAYBE_BROKEN (env, "Thread.join() failed")) + goto done; + + + (*env)->CallStaticVoidMethod + (env, runner_class, runner_deRegisterJoinable_mth, threadObj); + if (MAYBE_BROKEN (env, "Thread.deRegisterJoinableThread() failed")) + goto done; + + SHOW_OLD_TROUBLE (); + +done: + DELETE_LOCAL_REF (env, threadObj); + if (TRACE_API_CALLS) + tracing (" ==> VOID \n"); +} + +/* Terminate the current thread. Unlike pthread_exit(), here we do not need + to bother with a return value or exit value for the thread which is about + to croak. (The gthreads abstraction doesn't use it.) However, we *do* + need to bail immediately. We handle this with Thread.stop(), which is + a deprecated method. + + It's deprecated since we might leave objects protected by monitors in + half-constructed states on the way out -- Thread.stop() throws a + ThreadDeath exception, which is usually unchecked. There is no good + solution that I can see. */ +static void +thread_exit_jni_impl (void) +{ + JNIEnv *env; + union env_union e; + jobject this_thread; + + if (TRACE_API_CALLS) + tracing ("thread_exit_jni_impl() "); + + e.jni_env = &env; + (*cp_gtk_the_vm)->GetEnv (cp_gtk_the_vm, e.void_env, JNI_VERSION_1_1); + if (setup_cache (env) < 0) + goto done; + + HIDE_OLD_TROUBLE (env); + + this_thread = (*env)-> + CallStaticObjectMethod (env, thread_class, thread_current_mth); + + if ( ! this_thread ) + { + BROKEN (env, "cannot get current thread"); + goto done; + } + + (*env)->CallVoidMethod (env, this_thread, thread_stop_mth); + if (MAYBE_BROKEN (env, "cannot call Thread.stop() on current thread")) + goto done; + + SHOW_OLD_TROUBLE (); + +done: + if (TRACE_API_CALLS) + tracing (" ==> VOID \n"); +} + + +/* Translate a GThreadPriority to a Java priority level. */ +static jint +javaPriorityLevel (GThreadPriority priority) +{ + /* We have these fields in java.lang.Thread to play with: + + static int MIN_PRIORITY The minimum priority that a thread can have. + static int NORM_PRIORITY The default priority that is assigned to a + thread. + static int MAX_PRIORITY The maximum priority that a thread can have. + + We get these from the header file generated by javah, even though they're + documented as being 1, 5, and 10. + */ + static const jint minJPri = + gnu_java_awt_peer_gtk_GThreadNativeMethodRunner_MIN_PRIORITY; + static const jint normJPri = + gnu_java_awt_peer_gtk_GThreadNativeMethodRunner_NORM_PRIORITY; + static const jint maxJPri = + gnu_java_awt_peer_gtk_GThreadNativeMethodRunner_MAX_PRIORITY; + + switch (priority) + { + case G_THREAD_PRIORITY_LOW: + return minJPri; + break; + + default: + assert_not_reached (); + /* Deliberate fall-through if assertions are turned off; also shuts up + GCC warnings if they're turned on. */ + case G_THREAD_PRIORITY_NORMAL: + return normJPri; + break; + + case G_THREAD_PRIORITY_HIGH: + return (normJPri + maxJPri) / 2; + break; + + case G_THREAD_PRIORITY_URGENT: + return maxJPri; + break; + } +} + + +/** It would be safe not to implement this, according to the JNI docs, since + not all platforms do thread priorities. However, we might as well + provide the hint for those who want it. +*/ +static void +thread_set_priority_jni_impl (gpointer gThreadID, GThreadPriority gpriority) +{ + jobject threadObj = NULL; + JNIEnv *env; + union env_union e; + + if (TRACE_API_CALLS) + tracing ("thread_set_priority_jni_impl(gThreadID=%p, gpriority = %u) ", + gThreadID, gpriority); + + e.jni_env = &env; + (*cp_gtk_the_vm)->GetEnv (cp_gtk_the_vm, e.void_env, JNI_VERSION_1_1); + + if (setup_cache (env) < 0) + goto done; + + HIDE_OLD_TROUBLE (env); + + + threadObj = getThreadFromThreadID (env, gThreadID); + if ( ! threadObj) /* Reported with BROKEN already. */ + goto done; + + if (threadObj_set_priority (env, threadObj, gpriority)) + goto done; + + SHOW_OLD_TROUBLE (); + +done: + DELETE_LOCAL_REF (env, threadObj); + + if (TRACE_API_CALLS) + tracing (" ==> VOID\n"); +} + + +/** It would be safe not to implement this, according to the JNI docs, since + not all platforms do thread priorities. However, we might as well + provide the hint for those who want it. + + -1 on failure, 0 on success. */ +static int +threadObj_set_priority (JNIEnv * env, jobject threadObj, + GThreadPriority gpriority) +{ + jint javaPriority = javaPriorityLevel (gpriority); + (*env)->CallVoidMethod (env, threadObj, thread_setPriority_mth, + javaPriority); + return MAYBE_BROKEN (env, "Thread.setPriority() failed"); +} + + +/** Return the result of Thread.currentThread(), a static method. */ +static void +thread_self_jni_impl (/* Another confusing glib prototype. This is + actually a gpointer to the thread's thread-ID. + Which is, of course, a gpointer. */ + gpointer my_thread_IDp) +{ + JNIEnv *env; + union env_union e; + jobject this_thread; + gpointer my_threadID; + + if (TRACE_API_CALLS) + tracing ("thread_self_jni_impl(my_thread_IDp=%p)", my_thread_IDp); + + e.jni_env = &env; + (*cp_gtk_the_vm)->GetEnv (cp_gtk_the_vm, e.void_env, JNI_VERSION_1_1); + + if (setup_cache (env) < 0) + return; + + HIDE_OLD_TROUBLE (env); + + this_thread = (*env)-> + CallStaticObjectMethod (env, thread_class, thread_current_mth); + if (! this_thread ) + { + BROKEN (env, "cannot get current thread"); + my_threadID = NULL; + goto done; + } + + my_threadID = getThreadIDFromThread (env, this_thread); + SHOW_OLD_TROUBLE (); + +done: + if (TRACE_API_CALLS) + tracing (" ==> (my_threadID = %p) \n", my_threadID); + + *(gpointer *) my_thread_IDp = my_threadID; +} + + +static gboolean +thread_equal_jni_impl (gpointer thread1, gpointer thread2) +{ + JNIEnv *env; + union env_union e; + + gpointer threadID1 = *(gpointer *) thread1; + gpointer threadID2 = *(gpointer *) thread2; + + jobject thread1_obj = NULL; + jobject thread2_obj = NULL; + gboolean ret; + + if (TRACE_API_CALLS) + tracing ("thread_equal_jni_impl(threadID1=%p, threadID2=%p)", + threadID1, threadID2); + + e.jni_env = &env; + (*cp_gtk_the_vm)->GetEnv (cp_gtk_the_vm, e.void_env, JNI_VERSION_1_1); + if (setup_cache (env) < 0) + { + ret = FALSE; /* what is safer? We really don't ever want + to return from here. */ + goto done; + } + + HIDE_OLD_TROUBLE (env); + thread1_obj = getThreadFromThreadID (env, threadID1); + thread2_obj = getThreadFromThreadID (env, threadID2); + + ret = (*env)->CallBooleanMethod (env, thread1_obj, + thread_equals_mth, thread2_obj); + + if (MAYBE_BROKEN (env, "Thread.equals() failed")) + { + ret = FALSE; + goto done; + } + + SHOW_OLD_TROUBLE (); + + +done: + DELETE_LOCAL_REF (env, thread1_obj); + DELETE_LOCAL_REF (env, thread2_obj); + + if (TRACE_API_CALLS) + tracing (" ==> %s\n", ret ? "TRUE" : "FALSE"); + + return ret; +} + + + + +/************************************************************************/ +/* GLIB interface */ +/************************************************************************/ + +/* set of function pointers to give to glib. */ +GThreadFunctions cp_gtk_portable_native_sync_jni_functions = { + mutex_new_jni_impl, /* mutex_new */ + mutex_lock_jni_impl, /* mutex_lock */ + mutex_trylock_jni_impl, /* mutex_trylock */ + mutex_unlock_jni_impl, /* mutex_unlock */ + mutex_free_jni_impl, /* mutex_free */ + cond_new_jni_impl, /* cond_new */ + cond_signal_jni_impl, /* cond_signal */ + cond_broadcast_jni_impl, /* cond_broadcast */ + cond_wait_jni_impl, /* cond_wait */ + cond_timed_wait_jni_impl, /* cond_timed_wait */ + cond_free_jni_impl, /* cond_free */ + private_new_jni_impl, /* private_new */ + private_get_jni_impl, /* private_get */ + private_set_jni_impl, /* private_set */ + thread_create_jni_impl, /* thread_create */ + thread_yield_jni_impl, /* thread_yield */ + thread_join_jni_impl, /* thread_join */ + thread_exit_jni_impl, /* thread_exit */ + thread_set_priority_jni_impl, /* thread_set_priority */ + thread_self_jni_impl, /* thread_self */ + thread_equal_jni_impl, /* thread_equal */ +}; + + +/* Keep c-font-lock-extra-types in alphabetical order. */ +/* Local Variables: */ +/* c-file-style: "gnu" */ +/* c-font-lock-extra-types: ("\\sw+_t" "gboolean" "GError" "gpointer" + "GPrivate" "GThreadFunc" "GThreadFunctions" "GThreadPriority" + "gulong" + "JNIEnv" + "jboolean" "jclass" "jfieldID" "jint" "jlong" "jmethodID" "jobject" "jstring" "jthrowable" ) */ +/* End: */ |