/* 
 * JGimp - A Java extension for the GIMP
 * Copyright (C) 2002  Georgia Tech Research Corp.
 * Written by Michael Terry, mterry@cc.gatech.edu
 *
 * This program 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 of the License, or
 * (at your option) any later version.
 *
 * This program 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 this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#ifndef _WIN32
 #include <pthread.h>
#else
 #include <WINDOWS.H>
 #ifndef _MT
 #define _MT /* defines the multi-threading option so we're compiled as multi-threaded */
 #endif
#endif /* _WIN32 */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include "jgimp_jni_utils.h"
#include "jgimp_constants.h"

#include "JGimpPlugInProxy.h"

#ifndef JGIMP_COMPILE_AS_PLUGIN
#include "JGimpExtendedProxy.h"
#endif /* JGIMP_COMPILE_AS_PLUGIN */

#ifdef _WIN32
/*
 * The Windows code dynamically loads jvm.dll, rather than have the system
 * do it. This lets us distribute our own JRE with a self-installing binary.
 * After dynamically loading the DLL, we need to get a pointer to the function
 * to create the JVM. Below is the typedef for this function.
 */
typedef jint (JNICALL * Custom_JNI_CreateJavaVM)(JavaVM **, void **, void *);

/*
 * The following is a global variable pointing to the loaded DLL
 */
HINSTANCE g_DLL_instance = NULL;
#endif /* _WIN32 */


#include <glib.h>

/* Local variables */
static JavaVM *jvm = NULL;

/*
 * Used to keep track of threads being attached to the JVM
 * so that we don't detach from the JVM when other functions
 * are still interacting with it
 */
static GHashTable *thread_count_hash_table = NULL;
static GHashTable *thread_env_hash_table   = NULL;

#ifndef _WIN32
 static pthread_mutex_t* thread_count_hash_table_mutex = NULL;
#else
 HANDLE thread_count_hash_table_mutex = NULL;
#endif /* _WIN32 */

/*
 * Basic hash function
 */
gint    hash_compare_func(gconstpointer a, gconstpointer b);

/* Wrapper functions to make the code a bit cleaner across ports */
static void lock_thread_count_hash_table_mutex();
static void unlock_thread_count_hash_table_mutex();

/* Starts the JVM, sets the classpath */
int load_JVM(const char *class_path, const char* jgimp_base_dir);

/* Function that registers the native functions with the JVM */
int register_native_functions();

/* Starts the Java proxy class */
int start_proxy(const char* jgimp_jarfile_location, const char* jgimp_base_dir, int loadPlugIns, int loadExtensions);

int jni_utils_load_jgimp(const char* jgimp_jarfile_location, const char* jgimp_base_dir, int loadPlugIns, int loadExtensions)
{
	int result = -1;

	result = load_JVM(jgimp_jarfile_location, jgimp_base_dir);
	if (result == 0)
	{
		result = register_native_functions();
	}
	if (result == 0)
	{
		result = start_proxy(jgimp_jarfile_location, jgimp_base_dir, loadPlugIns, loadExtensions);
	}
	return result;
}

int load_JVM(const char *class_path, const char* jgimp_base_dir)
{

	JavaVMInitArgs vmArgs;
	JavaVMOption classpathOption;
	gchar *full_classpath = NULL;
	JNIEnv *temp_env      = NULL;
	jint result           = 0;
#ifdef _WIN32 /* In Windows, do run-time linking of jvm library to make custom installations easier */
	Custom_JNI_CreateJavaVM createJavaVM = NULL;
	LPCTSTR jvm_dll_path = NULL;
#endif /* _WIN32 */

	if (jvm != NULL)
	{
		return 0;
	}

	if (class_path != NULL)
	{
		if (getenv(CLASSPATH_VAR))
		{
			if (strlen(getenv(CLASSPATH_VAR)) > 0)
			{
#ifdef _WIN32
				full_classpath = g_strdup_printf("-Djava.class.path=%s;%s", class_path, getenv(CLASSPATH_VAR));
#else
				full_classpath = g_strdup_printf("-Djava.class.path=%s:%s", class_path, getenv(CLASSPATH_VAR));
#endif /* _WIN32 */
			}
		}
		else {
			full_classpath = g_strdup_printf("-Djava.class.path=%s", class_path);
		}
	}
	else
	{
		if (getenv(CLASSPATH_VAR))
		{
			if (strlen(getenv(CLASSPATH_VAR)) > 0)
			{
				full_classpath = g_strdup_printf("-Djava.class.path=%s", getenv(CLASSPATH_VAR));
			}
		}
		if (!full_classpath)
		{
			full_classpath = g_strdup("-Djava.class.path=.");
		}
	}
	classpathOption.optionString = full_classpath;
	classpathOption.extraInfo = NULL;
	vmArgs.options = &classpathOption;
	vmArgs.nOptions = 1;
	vmArgs.version = JNI_VERSION_1_4;
	vmArgs.ignoreUnrecognized = 1;

#ifdef _WIN32
	jvm_dll_path = g_strdup_printf("%s\\jre\\bin\\client\\jvm.dll", jgimp_base_dir);
	g_DLL_instance = LoadLibrary(jvm_dll_path);
	if (g_DLL_instance == NULL)
	{
		g_DLL_instance = LoadLibrary("jvm");
	}
	if (g_DLL_instance == NULL)
	{
		fprintf(stderr, "JGimp Windows error: Could not dynamically load jvm.dll\n");
		result = -1;
	}
	if (result == 0)
	{
		createJavaVM = (Custom_JNI_CreateJavaVM)GetProcAddress(g_DLL_instance, "JNI_CreateJavaVM");
		if (createJavaVM == NULL)
		{
			fprintf(stderr, "JGimp Windows error: Could not load JNI_CreateJavaVM function\n");
			result = -1;
		}
	}
	if (result == 0)
	{
		result = (createJavaVM)(&jvm, (void**) &temp_env, &vmArgs);
	}
#else
	result = JNI_CreateJavaVM(&jvm, (void**) &temp_env, &vmArgs);
#endif
	if (result < 0)
	{
		fprintf(stderr, "JGimp error: Can't create JVM with CLASSPATH %s: %d\n", full_classpath, result);
	}

	if (full_classpath)
	{
		g_free(full_classpath);
		full_classpath = NULL;
	}

	thread_count_hash_table = g_hash_table_new(g_direct_hash, hash_compare_func);
	if (thread_count_hash_table == NULL)
	{
		fprintf(stderr, "JGimp error: Couldn't create thread count hash table: %d\n", result);
		result = -1;
	}
	thread_env_hash_table = g_hash_table_new(g_direct_hash, hash_compare_func);
	if (thread_env_hash_table == NULL)
	{
		fprintf(stderr, "JGimp error: Couldn't create thread env hash table: %d\n", result);
		result = -1;
	}

	if (thread_count_hash_table_mutex == NULL)
	{
#ifndef _WIN32
		thread_count_hash_table_mutex = g_new(pthread_mutex_t, 1);
		if (thread_count_hash_table_mutex == NULL)
		{
			fprintf(stderr, "Error creating new thread_count_hash_table_mutex in jgimp_jni_utils.c\n");
			result = -1;
		}
		pthread_mutex_init(thread_count_hash_table_mutex, NULL);
#else
		thread_count_hash_table_mutex = CreateMutex(NULL, FALSE, NULL);
		if (thread_count_hash_table_mutex == NULL)
		{
			fprintf(stderr, "Error creating new thread_count_hash_table_mutex in jgimp_jni_utils.c\n");
			result = -1;
		}
#endif /* _WIN32 */
	}

	return result;
}

int start_proxy(const char* jgimp_jarfile_location, const char* jgimp_base_dir, int loadPlugIns, int loadExtensions)
{
	jclass       proxy_class_ID = NULL;
	jmethodID    method_ID      = NULL;
	int          error          = 0;
	jthrowable   throwable      = NULL;
	JNIEnv      *env            = NULL;

	env = jni_utils_attach_thread();
	if (!env)
	{
		fprintf(stderr, "JGimp error: Couldn't attach thread in load_class\n");
		return -1;
	}

	proxy_class_ID = (*env)->FindClass(env, JGIMP_PLUG_IN_PROXY_CLASS);
	if (proxy_class_ID == NULL)
	{
		fprintf(stderr, "JGimp error: Couldn't find proxy class in jgimp.load_proxy\n");
		error = -1;
		goto cleanup;
	}

	method_ID = (*env)->GetStaticMethodID(env, proxy_class_ID, "_startProxy", "(Ljava/lang/String;Ljava/lang/String;ZZ)V");
	if (method_ID == NULL)
	{
		fprintf(stderr, "JGimp error: Couldn't find _startProxy method in load_proxy");
		error = -1;
		goto cleanup;
	}

	(*env)->CallStaticObjectMethod(env, proxy_class_ID, method_ID, (*env)->NewStringUTF(env, jgimp_jarfile_location), (*env)->NewStringUTF(env, jgimp_base_dir), loadPlugIns, loadExtensions);

cleanup:
	throwable = (*env)->ExceptionOccurred(env);
	if (throwable)
	{
		fprintf(stderr, "JGimp: Caught exception in jgimp.load_proxy\n");
		(*env)->ExceptionDescribe(env);
		(*env)->ExceptionClear(env);
		error = -1;
	}

	jni_utils_detach_thread();

	return error;
}


int register_native_functions()
{
	JNIEnv         *env = NULL;
	jclass          java_plug_in_class_ID = NULL;
	int             result = 0;
	JNINativeMethod methods[] = {
		/* Format: name, signature, function pointer */
		{ "_unloadPlugInOrExtension",
			"(Ljava/lang/String;)V",
			Java_org_gimp_jgimp_proxy_JGimpPlugInProxy__1unloadPlugInOrExtension },
		{ "_installPlugInNatively",
			"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;[Lorg/gimp/jgimp/plugin/JGimpParamDescriptor;[Lorg/gimp/jgimp/plugin/JGimpParamDescriptor;)I",
			Java_org_gimp_jgimp_proxy_JGimpPlugInProxy__1installPlugInNatively },
		{ "_runPDBProcedureImpl",
			"(Ljava/lang/String;[Lorg/gimp/jgimp/JGimpData;)[Lorg/gimp/jgimp/JGimpData;",
			Java_org_gimp_jgimp_proxy_JGimpPlugInProxy__1runPDBProcedureImpl },
		{ "_readPixelRegionInNativeByteFormat",
			"(IIIII[BI)J",
			Java_org_gimp_jgimp_proxy_JGimpPlugInProxy__1readPixelRegionInNativeByteFormat },
		{ "_readPixelRegionInNativeIntFormat",
			"(IIIII[II)J",
			Java_org_gimp_jgimp_proxy_JGimpPlugInProxy__1readPixelRegionInNativeIntFormat },
		{ "_readPixelRegionInJavaIntFormat",
			"(IIIII[II)J",
			Java_org_gimp_jgimp_proxy_JGimpPlugInProxy__1readPixelRegionInJavaIntFormat },
		{ "_writePixelRegionInNativeByteFormat",
			"(IIIII[BI)J",
			Java_org_gimp_jgimp_proxy_JGimpPlugInProxy__1writePixelRegionInNativeByteFormat },
		{ "_writePixelRegionInNativeIntFormat",
			"(IIIII[II)J",
			Java_org_gimp_jgimp_proxy_JGimpPlugInProxy__1writePixelRegionInNativeIntFormat },
		{ "_writePixelRegionInJavaIntFormat",
			"(IIIII[II)J",
			Java_org_gimp_jgimp_proxy_JGimpPlugInProxy__1writePixelRegionInJavaIntFormat }
	};

	env = jni_utils_attach_thread();
	if (!env)
	{
		fprintf(stderr, "Could attach thread in register_native_functions. Aborting.\n");
		return -1;
	}
	java_plug_in_class_ID = (*env)->FindClass(env, JGIMP_PLUG_IN_PROXY_CLASS);
	if (java_plug_in_class_ID == NULL)
	{
		fprintf(stderr, "JGimp error: Couldn't get class ID for JGimpPlugInProxy in register_native_functions\n");
		jni_utils_detach_thread();
		return -1;
	}
	result = (*env)->RegisterNatives(env, java_plug_in_class_ID, methods, 8);

#ifndef JGIMP_COMPILE_AS_PLUGIN
	if (result == 0)
	{
		jclass          extended_proxy_class_ID = NULL;
		JNINativeMethod extended_methods[] = {
				{"_renderImageInJavaIntFormat",
					"(IIIII[II)J",
					Java_org_gimp_jgimp_proxy_JGimpExtendedProxyImpl__1renderImageInJavaIntFormat },
				{"_startPainting",
					"(IIDDDDDD)V",
					Java_org_gimp_jgimp_proxy_JGimpExtendedProxyImpl__1startPainting },
				{"_addToPainting",
					"(IIDDDDDD)Ljava/awt/Rectangle;",
					Java_org_gimp_jgimp_proxy_JGimpExtendedProxyImpl__1addToPainting },
				{"_stopPainting",
					"(II)Ljava/awt/Rectangle;",
					Java_org_gimp_jgimp_proxy_JGimpExtendedProxyImpl__1stopPainting }
		};
		extended_proxy_class_ID = (*env)->FindClass(env, JGIMP_EXTENDED_PROXY_IMPL_CLASS);
		if (extended_proxy_class_ID == NULL)
		{
			fprintf(stderr, "JGimp error: Couldn't get class ID for JGimpExtendedProxyImpl in register_native_functions\n");
			jni_utils_detach_thread();
			return -1;
		}
		result = (*env)->RegisterNatives(env, extended_proxy_class_ID, extended_methods, 4);
	}
	if (result)
	{
		fprintf(stderr, "JGimp error: Couldn't register all native functions\n");
	}
#endif
	jni_utils_detach_thread();
	return result;
}

void jni_utils_shutdown_jgimp()
{
	JNIEnv      *env                      = NULL;
	jclass       java_plug_in_class_ID    = NULL;
	jobject      java_plug_in_object      = NULL;
	jmethodID    method_ID                = NULL;

	env = jni_utils_attach_thread();
	if (!env)
	{
		return;
	}

	/* Get JavaPlugInProxy object */
	java_plug_in_class_ID = (*env)->FindClass(env, JGIMP_PLUG_IN_PROXY_CLASS);
	if (java_plug_in_class_ID == NULL)
	{
		fprintf(stderr, "JGimp error: Couldn't get JGimpPlugInProxy class ID in shutdown_plugins\n");
		goto cleanup;
	}
	method_ID = (*env)->GetStaticMethodID(env, java_plug_in_class_ID, "_getInstance", "()Lorg/gimp/jgimp/proxy/JGimpPlugInProxy;");
	if (method_ID == NULL)
	{
		fprintf(stderr, "JGimp error: Couldn't get JGimpPlugInProxy getInstance ID in shutdown_plugins\n");
		goto cleanup;
	}
	java_plug_in_object = (*env)->CallStaticObjectMethod(env, java_plug_in_class_ID, method_ID);
	if (java_plug_in_object == NULL)
	{
		fprintf(stderr, "JGimp error: Couldn't get reference to JGimpPlugInProxy in shutdown_plugins\n");
		goto cleanup;
	}
	method_ID = (*env)->GetMethodID(env, java_plug_in_class_ID, "shutdown", "()V");
	if (method_ID == NULL)
	{
		fprintf(stderr, "JGimp error: Couldn't get method ID for 'shutdown_plugins' in shutdown_plugins\n");
	}
	(*env)->CallVoidMethod(env, java_plug_in_object, method_ID);

cleanup:
	jni_utils_detach_thread();

#ifdef _WIN32
	if (g_DLL_instance != NULL)
	{
		FreeLibrary(g_DLL_instance);
		g_DLL_instance = NULL;
	}
#endif /* _WIN32 */
}




JNIEnv* jni_utils_attach_thread()
{
#ifndef _WIN32
	pthread_t    this_thread;
#else
	DWORD        this_thread;
#endif /* _WIN32 */
	int          num_threads = 0;
	JNIEnv      *env         = NULL;
	int          result      = 0;

	if (jvm == NULL)
	{
		return NULL;
	}

	lock_thread_count_hash_table_mutex();

#ifndef _WIN32
	this_thread = pthread_self();
#else
	this_thread = GetCurrentThreadId();
#endif /* _WIN32 */

	num_threads = (int)g_hash_table_lookup(thread_count_hash_table, (gconstpointer)this_thread);
	if (num_threads < 1)
	{
		result = (*jvm)->AttachCurrentThread(jvm, (void**)&env, NULL);
		if (result)
		{
			fprintf(stderr, "JGimp internal error: Could not attach to thread. Result: %d\n", result);
			unlock_thread_count_hash_table_mutex();
			return NULL;
		}
		g_hash_table_insert(thread_env_hash_table, (gpointer)this_thread, (gpointer)env);
	}
	env = g_hash_table_lookup(thread_env_hash_table, (gconstpointer)this_thread);
	if (env == NULL)
	{
		fprintf(stderr, "JGimp internal error: Could not get JNI environment\n");
		unlock_thread_count_hash_table_mutex();
		return NULL;
	}
	num_threads++;
	g_hash_table_insert(thread_count_hash_table, (gpointer)this_thread, (gpointer)num_threads);

	unlock_thread_count_hash_table_mutex();
	return env;
}

void jni_utils_detach_thread()
{
#ifndef _WIN32
	pthread_t    this_thread;
#else
	DWORD        this_thread;
#endif /* _WIN32 */
	int          num_threads;
	int          result;

	lock_thread_count_hash_table_mutex();

#ifndef _WIN32
	this_thread = pthread_self();
#else
	this_thread = GetCurrentThreadId();
#endif /* _WIN32 */

	num_threads = (int)g_hash_table_lookup(thread_count_hash_table, (gconstpointer)this_thread);
	num_threads--;
	if (num_threads == 0)
	{
		result = (*jvm)->DetachCurrentThread(jvm);
		if (result)
		{
			fprintf(stderr, "Caught error trying to detach thread: %d\n", result);
		}
		g_hash_table_remove(thread_env_hash_table, (gconstpointer)this_thread);
	}
	if (num_threads < 0)
	{
		fprintf(stderr, "Warning: calling jni_utils_detach_thread without a corresponding jni_utils_attach_thread\n");
		num_threads = 0;
	}
	g_hash_table_insert(thread_count_hash_table, (gpointer)this_thread, (gpointer)num_threads);

	unlock_thread_count_hash_table_mutex();
}


void jni_utils_throw_invalid_drawable_exception(JNIEnv* env, int drawable_ID)
{
	jclass    exception_class = NULL;
	jobject   the_exception   = NULL;
	jmethodID constructor_ID  = NULL;

	exception_class = (*env)->FindClass(env, JGIMP_INVALID_DRAWABLE_EXCEPTION_CLASS);
	if (!exception_class)
	{
	
		fprintf(stderr, "JGimp internal error: Could not locate JGimpInvalidDrawableException class");
		return;
	}
	constructor_ID = (*env)->GetMethodID(env, exception_class, "<init>", "(I)V");
	if (constructor_ID == NULL)
	{
		fprintf(stderr, "JGimp internal error: Could not locate JGimpInvalidDrawableException constructor");
		return;
	}
	the_exception = (*env)->NewObject(env, exception_class, constructor_ID, drawable_ID);
	if (!the_exception)
	{
		fprintf(stderr, "JGimp internal error: Could not allocate JGimpInvalidDrawableException object");
		return;
	}
	(*env)->Throw(env, the_exception);
}

void jni_utils_throw_invalid_image_exception(JNIEnv* env, int image_ID)
{
	jclass    exception_class = NULL;
	jobject   the_exception   = NULL;
	jmethodID constructor_ID  = NULL;

	exception_class = (*env)->FindClass(env, JGIMP_INVALID_IMAGE_EXCEPTION_CLASS);
	if (!exception_class)
	{
	
		fprintf(stderr, "JGimp internal error: Could not locate JGimpInvalidImageException class");
		return;
	}
	constructor_ID = (*env)->GetMethodID(env, exception_class, "<init>", "(I)V");
	if (constructor_ID == NULL)
	{
		fprintf(stderr, "JGimp internal error: Could not locate JGimpInvalidImageException constructor");
		return;
	}
	the_exception = (*env)->NewObject(env, exception_class, constructor_ID, image_ID);
	if (!the_exception)
	{
		fprintf(stderr, "JGimp internal error: Could not allocate JGimpInvalidImageException object");
		return;
	}
	(*env)->Throw(env, the_exception);
}

void jni_utils_throw_invalid_pixel_region_exception(JNIEnv* env, int x, int y, int desired_width, int desired_height, int drawable_width, int drawable_height)
{
	jclass    exception_class = NULL;
	jobject   the_exception   = NULL;
	jmethodID constructor_ID  = NULL;

	exception_class = (*env)->FindClass(env, JGIMP_INVALID_PIXEL_REGION_EXCEPTION_CLASS);
	if (!exception_class)
	{
		fprintf(stderr, "JGimp internal error: Could not locate JGimpInvalidPixelRegionException class");
		return;
	}
	constructor_ID = (*env)->GetMethodID(env, exception_class, "<init>", "(IIIIII)V");
	the_exception = (*env)->NewObject(env, exception_class, constructor_ID, x, y, desired_width, desired_height, drawable_width, drawable_height);
	if (!the_exception)
	{
		fprintf(stderr, "JGimp internal error: Could not allocate JGimpInvalidPixelRegionException object");
		return;
	}
	(*env)->Throw(env, the_exception);
}

void jni_utils_throw_buffer_overflow_exception(JNIEnv* env, int x, int y, int width, int height, int actual_width, int actual_height, int bytes_per_pixel, int num_bytes_in_array)
{
	jclass    exception_class = NULL;
	jobject   the_exception   = NULL;
	jmethodID constructor_ID  = NULL;

	exception_class = (*env)->FindClass(env, JGIMP_BUFFER_OVERFLOW_EXCEPTION_CLASS);
	if (!exception_class)
	{
		fprintf(stderr, "JGimp internal error: Could not locate BufferOverflowException class");
		return;
	}
	constructor_ID = (*env)->GetMethodID(env, exception_class, "<init>", "(IIIIIIII)V");
	if (constructor_ID == NULL)
	{
		fprintf(stderr, "JGimp internal error: Could not locate BufferOverflowException constructor");
		return;
	}
	the_exception = (*env)->NewObject(env, exception_class, constructor_ID, x, y, width, height, actual_width, actual_height, bytes_per_pixel, num_bytes_in_array);
	if (!the_exception)
	{
		fprintf(stderr, "JGimp internal error: Could not allocate BufferOverflowException object");
		return;
	}
	(*env)->Throw(env, the_exception);
}

void    jni_utils_throw_proxy_exception(JNIEnv *env, const char* error_msg)
{
	jclass    exception_class = NULL;
	jobject   the_exception   = NULL;
	jmethodID constructor_ID  = NULL;

	exception_class = (*env)->FindClass(env, JGIMP_PROXY_EXCEPTION_CLASS);
	if (!exception_class)
	{
		fprintf(stderr, "JGimp internal error: Could not locate JGimpProxyException class");
		return;
	}
	constructor_ID = (*env)->GetMethodID(env, exception_class, "<init>", "(Ljava/lang/String;)V");
	if (constructor_ID == NULL)
	{
		fprintf(stderr, "JGimp internal error: Could not locate JGimpProxyException constructor");
		return;
	}
	the_exception = (*env)->NewObject(env, exception_class, constructor_ID, (*env)->NewStringUTF(env, error_msg) );
	if (!the_exception)
	{
		fprintf(stderr, "JGimp internal error: Could not allocate JGimpProxyException object");
		return;
	}
	(*env)->Throw(env, the_exception);
}

void    jni_utils_throw_invalid_paint_tool_exception(JNIEnv *env, int tool_id)
{
	jclass    exception_class = NULL;
	jobject   the_exception   = NULL;
	jmethodID constructor_ID  = NULL;

	exception_class = (*env)->FindClass(env, JGIMP_INVALID_PAINT_TOOL_EXCEPTION_CLASS);
	if (!exception_class)
	{
		fprintf(stderr, "JGimp internal error: Could not locate JGimpInvalidPaintToolException class");
		return;
	}
	constructor_ID = (*env)->GetMethodID(env, exception_class, "<init>", "(I)V");
	if (constructor_ID == NULL)
	{
		fprintf(stderr, "JGimp internal error: Could not locate JGimpInvalidPaintToolException constructor");
		return;
	}
	the_exception = (*env)->NewObject(env, exception_class, constructor_ID, tool_id);
	if (!the_exception)
	{
		fprintf(stderr, "JGimp internal error: Could not allocate JGimpInvalidPaintToolException object");
		return;
	}
	(*env)->Throw(env, the_exception);
}

gint hash_compare_func(gconstpointer a, gconstpointer b)
{
	return (a == b);
}

static void lock_thread_count_hash_table_mutex()
{
#ifndef _WIN32
	pthread_mutex_lock(thread_count_hash_table_mutex);
#else
	WaitForSingleObject(thread_count_hash_table_mutex, INFINITE);
#endif /* _WIN32 */
}

static void unlock_thread_count_hash_table_mutex()
{
#ifndef _WIN32
	pthread_mutex_unlock(thread_count_hash_table_mutex);
#else
	ReleaseMutex(thread_count_hash_table_mutex);
#endif /* _WIN32 */
}

