gthread-jni.c   [plain text]


/* 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., 59 Temple Place, Suite 330, Boston, MA
02111-1307 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 <stdio.h>		/* snprintf */
#include <stdarg.h>		/* va_list */
#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 *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 */

jclass runtimeException_class;	/* java.lang.RuntimeException */
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);
      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;
  (*the_vm)->GetEnv (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;
  (*the_vm)->GetEnv (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;
  (*the_vm)->GetEnv (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;
  (*the_vm)->GetEnv (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;
  (*the_vm)->GetEnv (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;
  (*the_vm)->GetEnv (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;
  (*the_vm)->GetEnv (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;
  (*the_vm)->GetEnv (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;
  (*the_vm)->GetEnv (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;
  (*the_vm)->GetEnv (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;
  (*the_vm)->GetEnv (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;
  (*the_vm)->GetEnv (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;
  (*the_vm)->GetEnv (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;
  (*the_vm)->GetEnv (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;
  (*the_vm)->GetEnv (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;
  (*the_vm)->GetEnv (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;
  (*the_vm)->GetEnv (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;
  (*the_vm)->GetEnv (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;
  (*the_vm)->GetEnv (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;
  (*the_vm)->GetEnv (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;
  (*the_vm)->GetEnv (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 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: */