/*-
 * See the file LICENSE for redistribution information.
 *
 * Copyright (c) 1997, 1998, 1999, 2000
 *	Sleepycat Software.  All rights reserved.
 */
#include "db_config.h"

#ifndef lint
static const char revid[] = "$Id: java_info.c,v 11.15 2000/10/18 20:10:22 dda Exp $";
#endif /* not lint */

#include <jni.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>

#include "db.h"
#include "db_int.h"
#include "java_util.h"

/****************************************************************
 *
 * Callback functions
 *
 */

static void Db_feedback_callback(DB *db, int opcode, int percent)
{
	DB_JAVAINFO *dbinfo;

	DB_ASSERT(db != NULL);
	dbinfo = (DB_JAVAINFO *)db->cj_internal;
	dbji_call_feedback(dbinfo, db, dbinfo->jdbref_, opcode, percent);
}

static int Db_append_recno_callback(DB *db, DBT *dbt, db_recno_t recno)
{
	DB_JAVAINFO *dbinfo;

	dbinfo = (DB_JAVAINFO *)db->cj_internal;
	return (dbji_call_append_recno(dbinfo, db, dbinfo->jdbref_, dbt, recno));
}

static int Db_bt_compare_callback(DB *db, const DBT *dbt1, const DBT *dbt2)
{
	DB_JAVAINFO *dbinfo;

	dbinfo = (DB_JAVAINFO *)db->cj_internal;
	return (dbji_call_bt_compare(dbinfo, db, dbinfo->jdbref_, dbt1, dbt2));
}

static size_t Db_bt_prefix_callback(DB *db, const DBT *dbt1, const DBT *dbt2)
{
	DB_JAVAINFO *dbinfo;

	dbinfo = (DB_JAVAINFO *)db->cj_internal;
	return (dbji_call_bt_prefix(dbinfo, db, dbinfo->jdbref_, dbt1, dbt2));
}

static int Db_dup_compare_callback(DB *db, const DBT *dbt1, const DBT *dbt2)
{
	DB_JAVAINFO *dbinfo;

	dbinfo = (DB_JAVAINFO *)db->cj_internal;
	return (dbji_call_dup_compare(dbinfo, db, dbinfo->jdbref_, dbt1, dbt2));
}

static u_int32_t Db_h_hash_callback(DB *db, const void *data, u_int32_t len)
{
	DB_JAVAINFO *dbinfo;

	dbinfo = (DB_JAVAINFO *)db->cj_internal;
	return (dbji_call_h_hash(dbinfo, db, dbinfo->jdbref_, data, len));
}

static void DbEnv_feedback_callback(DB_ENV *dbenv, int opcode, int percent)
{
	DB_ENV_JAVAINFO *dbinfo;

	DB_ASSERT(dbenv != NULL);
	dbinfo = (DB_ENV_JAVAINFO *)dbenv->cj_internal;
	dbjie_call_feedback(dbinfo, dbenv, dbinfo->jenvref_, opcode, percent);
}

static int DbEnv_recovery_init_callback(DB_ENV *dbenv)
{
	DB_ENV_JAVAINFO *dbinfo;

	dbinfo = (DB_ENV_JAVAINFO *)dbenv->cj_internal;
	return (dbjie_call_recovery_init(dbinfo, dbenv, dbinfo->jenvref_));
}

static int DbEnv_tx_recover_callback(DB_ENV *dbenv, DBT *dbt,
				     DB_LSN *lsn, db_recops recops)
{
	DB_ENV_JAVAINFO *dbinfo;

	DB_ASSERT(dbenv != NULL);
	dbinfo = (DB_ENV_JAVAINFO *)dbenv->cj_internal;
	return dbjie_call_tx_recover(dbinfo, dbenv, dbinfo->jenvref_, dbt,
				     lsn, recops);
}

/****************************************************************
 *
 * Implementation of class DBT_javainfo
 *
 */
DBT_JAVAINFO *
dbjit_construct()
{
	DBT_JAVAINFO *dbjit;

	dbjit = (DBT_JAVAINFO *)malloc(sizeof(DBT_JAVAINFO));
	memset(dbjit, 0, sizeof(DBT_JAVAINFO));
	return (dbjit);
}

void dbjit_destroy(DBT_JAVAINFO *dbjit)
{
	/* Sanity check:
	 * We cannot delete the global ref because we don't have a JNIEnv.
	 */
	if (dbjit->array_ != NULL) {
		fprintf(stderr, "object is not freed\n");
	}

	/* Extra paranoia */
	memset(dbjit, 0, sizeof(DB_JAVAINFO));
	free(dbjit);
}

void dbjit_release(DBT_JAVAINFO *dbjit, JNIEnv *jnienv)
{
	if (dbjit->array_ != NULL) {
		DELETE_GLOBAL_REF(jnienv, dbjit->array_);
		dbjit->array_ = NULL;
	}
}

/****************************************************************
 *
 * Implementation of class DB_ENV_JAVAINFO
 *
 */

/* create/initialize an object */
DB_ENV_JAVAINFO *
dbjie_construct(JNIEnv *jnienv,
		jobject default_errcall,
		int is_dbopen)
{
	DB_ENV_JAVAINFO *dbjie;

	dbjie = (DB_ENV_JAVAINFO *)malloc(sizeof(DB_ENV_JAVAINFO));
	memset(dbjie, 0, sizeof(DB_ENV_JAVAINFO));
	dbjie->is_dbopen_ = is_dbopen;

	if ((*jnienv)->GetJavaVM(jnienv, &dbjie->javavm_) != 0) {
		free(dbjie);
		report_exception(jnienv, "cannot get Java VM", 0, 0);
		return (NULL);
	}

	/* The default error call just prints to the 'System.err'
	 * stream.  If the user does set_errcall to null, we'll
	 * want to have a reference to set it back to.
	 *
	 * Why do we have always set db_errcall to our own callback?
	 * Because it makes the interaction between setting the
	 * error prefix, error stream, and user's error callback
	 * that much easier.
	 */
	dbjie->default_errcall_ = NEW_GLOBAL_REF(jnienv, default_errcall);
	dbjie->errcall_ = NEW_GLOBAL_REF(jnienv, default_errcall);
	return (dbjie);
}

/* release all objects held by this this one */
void dbjie_dealloc(DB_ENV_JAVAINFO *dbjie, JNIEnv *jnienv)
{
	if (dbjie->recovery_init_ != NULL) {
		DELETE_GLOBAL_REF(jnienv, dbjie->recovery_init_);
		dbjie->recovery_init_ = NULL;
	}
	if (dbjie->feedback_ != NULL) {
		DELETE_GLOBAL_REF(jnienv, dbjie->feedback_);
		dbjie->feedback_ = NULL;
	}
	if (dbjie->tx_recover_ != NULL) {
		DELETE_GLOBAL_REF(jnienv, dbjie->tx_recover_);
		dbjie->tx_recover_ = NULL;
	}
	if (dbjie->errcall_ != NULL) {
		DELETE_GLOBAL_REF(jnienv, dbjie->errcall_);
		dbjie->errcall_ = NULL;
	}
	if (dbjie->default_errcall_ != NULL) {
		DELETE_GLOBAL_REF(jnienv, dbjie->default_errcall_);
		dbjie->default_errcall_ = NULL;
	}

	if (dbjie->conflict_ != NULL) {
		free(dbjie->conflict_);
		dbjie->conflict_ = NULL;
	}
	if (dbjie->errpfx_ != NULL) {
		free(dbjie->errpfx_);
		dbjie->errpfx_ = NULL;
	}
}

/* free this object, releasing anything allocated on its behalf */
void dbjie_destroy(DB_ENV_JAVAINFO *dbjie, JNIEnv *jnienv)
{
	dbjie_dealloc(dbjie, jnienv);

	/* Extra paranoia */
	memset(dbjie, 0, sizeof(DB_ENV_JAVAINFO));
	free(dbjie);
}

/* Attach to the current thread that is running and
 * return that.  We use the java virtual machine
 * that we saved in the constructor.
 */
JNIEnv *
dbjie_get_jnienv(DB_ENV_JAVAINFO *dbjie)
{
	/* Note:
	 * Different versions of the JNI disagree on the signature
	 * for AttachCurrentThread.  The most recent documentation
	 * seems to say that (JNIEnv **) is correct, but newer
	 * JNIs seem to use (void **), oddly enough.
	 */
#ifdef JNI_VERSION_1_2
	void *attachret = 0;
#else
	JNIEnv *attachret = 0;
#endif

	/* This should always succeed, as we are called via
	 * some Java activity.  I think therefore I am (a thread).
	 */
	if ((*dbjie->javavm_)->AttachCurrentThread(dbjie->javavm_, &attachret, 0) != 0)
		return (0);

	return ((JNIEnv *)attachret);
}

jstring
dbjie_get_errpfx(DB_ENV_JAVAINFO *dbjie, JNIEnv *jnienv)
{
	return (get_java_string(jnienv, dbjie->errpfx_));
}

void
dbjie_set_errcall(DB_ENV_JAVAINFO *dbjie, JNIEnv *jnienv, jobject new_errcall)
{
	/* If the new_errcall is null, we'll set the error call
	 * to the default one.
	 */
	if (new_errcall == NULL)
		new_errcall = dbjie->default_errcall_;

	DELETE_GLOBAL_REF(jnienv, dbjie->errcall_);
	dbjie->errcall_ = NEW_GLOBAL_REF(jnienv, new_errcall);
}

void
dbjie_set_errpfx(DB_ENV_JAVAINFO *dbjie, JNIEnv *jnienv, jstring errpfx)
{
	if (dbjie->errpfx_ != NULL)
		free(dbjie->errpfx_);

	if (errpfx)
		dbjie->errpfx_ = get_c_string(jnienv, errpfx);
	else
		dbjie->errpfx_ = NULL;
}

void
dbjie_set_conflict(DB_ENV_JAVAINFO *dbjie, unsigned char *newarr)
{
	if (dbjie->conflict_)
		free(dbjie->conflict_);
	dbjie->conflict_ = newarr;
}

void dbjie_set_feedback_object(DB_ENV_JAVAINFO *dbjie, JNIEnv *jnienv,
			       DB_ENV *dbenv, jobject jfeedback)
{
	int err;

	if (dbjie->feedback_ != NULL) {
		DELETE_GLOBAL_REF(jnienv, dbjie->feedback_);
	}
	if (jfeedback == NULL) {
		if ((err = dbenv->set_feedback(dbenv, NULL)) != 0)
			report_exception(jnienv, "set_feedback failed",
					 err, 0);
	}
	else {
		if ((err = dbenv->set_feedback(dbenv,
					       DbEnv_feedback_callback)) != 0)
			report_exception(jnienv, "set_feedback failed",
					 err, 0);
	}

	dbjie->feedback_ = NEW_GLOBAL_REF(jnienv, jfeedback);
}

void dbjie_call_feedback(DB_ENV_JAVAINFO *dbjie, DB_ENV *dbenv, jobject jenv,
			 int opcode, int percent)
{
	JNIEnv *jnienv;
	jclass feedback_class;
	jmethodID id;

	COMPQUIET(dbenv, NULL);
	jnienv = dbjie_get_jnienv(dbjie);
	if (jnienv == NULL) {
		fprintf(stderr, "Cannot attach to current thread!\n");
		return;
	}

	feedback_class = get_class(jnienv, name_DbEnvFeedback);
	id = (*jnienv)->GetMethodID(jnienv, feedback_class,
				    "feedback",
				    "(Lcom/sleepycat/db/DbEnv;II)V");
	if (!id) {
		fprintf(stderr, "Cannot find callback class\n");
		return;
	}

	(*jnienv)->CallVoidMethod(jnienv, dbjie->feedback_, id,
				  jenv, (jint)opcode, (jint)percent);
}

void dbjie_set_recovery_init_object(DB_ENV_JAVAINFO *dbjie,
				    JNIEnv *jnienv, DB_ENV *dbenv,
				    jobject jrecovery_init)
{
	int err;

	if (dbjie->recovery_init_ != NULL) {
		DELETE_GLOBAL_REF(jnienv, dbjie->recovery_init_);
	}
	if (jrecovery_init == NULL) {
		if ((err = dbenv->set_recovery_init(dbenv, NULL)) != 0)
			report_exception(jnienv, "set_recovery_init failed",
					 err, 0);
	}
	else {
		if ((err = dbenv->set_recovery_init(dbenv,
					DbEnv_recovery_init_callback)) != 0)
			report_exception(jnienv, "set_recovery_init failed",
					 err, 0);
	}

	dbjie->recovery_init_ = NEW_GLOBAL_REF(jnienv, jrecovery_init);
}

int dbjie_call_recovery_init(DB_ENV_JAVAINFO *dbjie, DB_ENV *dbenv,
			     jobject jenv)
{
	JNIEnv *jnienv;
	jclass recovery_init_class;
	jmethodID id;

	COMPQUIET(dbenv, NULL);
	jnienv = dbjie_get_jnienv(dbjie);
	if (jnienv == NULL) {
		fprintf(stderr, "Cannot attach to current thread!\n");
		return (EINVAL);
	}

	recovery_init_class = get_class(jnienv, name_DbRecoveryInit);
	id = (*jnienv)->GetMethodID(jnienv, recovery_init_class,
				    "recovery_init",
				    "(Lcom/sleepycat/db/DbEnv;)V");
	if (!id) {
		fprintf(stderr, "Cannot find callback class\n");
		return EINVAL;
	}
	return (*jnienv)->CallIntMethod(jnienv, dbjie->recovery_init_,
					id, jenv);
}

void dbjie_set_tx_recover_object(DB_ENV_JAVAINFO *dbjie, JNIEnv *jnienv,
				 DB_ENV *dbenv, jobject jtx_recover)
{
	int err;

	if (dbjie->tx_recover_ != NULL) {
		DELETE_GLOBAL_REF(jnienv, dbjie->tx_recover_);
	}
	if (jtx_recover == NULL) {
		if ((err = dbenv->set_tx_recover(dbenv, NULL)) != 0)
			report_exception(jnienv, "set_tx_recover failed",
					 err, 0);
	}
	else {
		if ((err = dbenv->set_tx_recover(dbenv,
						 DbEnv_tx_recover_callback)) != 0)
			report_exception(jnienv, "set_tx_recover failed",
					 err, 0);
	}

	dbjie->tx_recover_ = NEW_GLOBAL_REF(jnienv, jtx_recover);
}

int dbjie_call_tx_recover(DB_ENV_JAVAINFO *dbjie, DB_ENV *dbenv, jobject jenv,
			  DBT *dbt, DB_LSN *lsn, int recops)
{
	JNIEnv *jnienv;
	jclass tx_recover_class;
	jmethodID id;
	jobject jdbt;
	jobject jlsn;

	COMPQUIET(dbenv, NULL);
	jnienv = dbjie_get_jnienv(dbjie);
	if (jnienv == NULL) {
		fprintf(stderr, "Cannot attach to current thread!\n");
		return (0);
	}

	tx_recover_class = get_class(jnienv, name_DbTxnRecover);
	id = (*jnienv)->GetMethodID(jnienv, tx_recover_class,
				    "tx_recover",
				    "(Lcom/sleepycat/db/DbEnv;"
				    "Lcom/sleepycat/db/Dbt;"
				    "Lcom/sleepycat/db/DbLsn;"
				    "I)I");
	if (!id) {
		fprintf(stderr, "Cannot find callback class\n");
		return (0);
	}

	if (dbt == NULL)
		jdbt = NULL;
	else
		jdbt = get_Dbt(jnienv, dbt);

	if (lsn == NULL)
		jlsn = NULL;
	else
		jlsn = get_DbLsn(jnienv, *lsn);

	return (*jnienv)->CallIntMethod(jnienv, dbjie->tx_recover_, id, jenv,
					jdbt, jlsn, recops);
}

jobject dbjie_get_errcall(DB_ENV_JAVAINFO *dbjie)
{
	return (dbjie->errcall_);
}

int dbjie_is_dbopen(DB_ENV_JAVAINFO *dbjie)
{
	return (dbjie->is_dbopen_);
}

/****************************************************************
 *
 * Implementation of class DB_JAVAINFO
 *
 */

DB_JAVAINFO *dbji_construct(JNIEnv *jnienv, jint flags)
{
	DB_JAVAINFO *dbji;

	dbji = (DB_JAVAINFO *)malloc(sizeof(DB_JAVAINFO));
	memset(dbji, 0, sizeof(DB_JAVAINFO));

	if ((*jnienv)->GetJavaVM(jnienv, &dbji->javavm_) != 0) {
		report_exception(jnienv, "cannot get Java VM", 0, 0);
		free(dbji);
		return (NULL);
	}
	dbji->construct_flags_ = flags;
	return (dbji);
}

void
dbji_dealloc(DB_JAVAINFO *dbji, JNIEnv *jnienv)
{
	if (dbji->append_recno_ != NULL) {
		DELETE_GLOBAL_REF(jnienv, dbji->append_recno_);
		dbji->append_recno_ = NULL;
	}
	if (dbji->bt_compare_ != NULL) {
		DELETE_GLOBAL_REF(jnienv, dbji->bt_compare_);
		dbji->bt_compare_ = NULL;
	}
	if (dbji->bt_prefix_ != NULL) {
		DELETE_GLOBAL_REF(jnienv, dbji->bt_prefix_);
		dbji->bt_prefix_ = NULL;
	}
	if (dbji->dup_compare_ != NULL) {
		DELETE_GLOBAL_REF(jnienv, dbji->dup_compare_);
		dbji->dup_compare_ = NULL;
	}
	if (dbji->feedback_ != NULL) {
		DELETE_GLOBAL_REF(jnienv, dbji->feedback_);
		dbji->feedback_ = NULL;
	}
	if (dbji->h_hash_ != NULL) {
		DELETE_GLOBAL_REF(jnienv, dbji->h_hash_);
		dbji->h_hash_ = NULL;
	}
}

void
dbji_destroy(DB_JAVAINFO *dbji, JNIEnv *jnienv)
{
	dbji_dealloc(dbji, jnienv);
	free(dbji);
}

JNIEnv *dbji_get_jnienv(DB_JAVAINFO *dbji)
{
	/* Note:
	 * Different versions of the JNI disagree on the signature
	 * for AttachCurrentThread.  The most recent documentation
	 * seems to say that (JNIEnv **) is correct, but newer
	 * JNIs seem to use (void **), oddly enough.
	 */
#ifdef JNI_VERSION_1_2
	void *attachret = 0;
#else
	JNIEnv *attachret = 0;
#endif

	/* This should always succeed, as we are called via
	 * some Java activity.  I think therefore I am (a thread).
	 */
	if ((*dbji->javavm_)->AttachCurrentThread(dbji->javavm_, &attachret, 0) != 0)
		return (0);

	return ((JNIEnv *)attachret);
}

jint dbji_get_flags(DB_JAVAINFO *dbji)
{
	return (dbji->construct_flags_);
}

void dbji_set_feedback_object(DB_JAVAINFO *dbji, JNIEnv *jnienv,
			      DB *db, jobject jfeedback)
{
	jclass feedback_class;

	if (dbji->feedback_method_id_ == NULL) {
		feedback_class = get_class(jnienv, name_DbFeedback);
		dbji->feedback_method_id_ =
			(*jnienv)->GetMethodID(jnienv, feedback_class,
					       "feedback",
					       "(Lcom/sleepycat/db/Db;II)V");
		if (dbji->feedback_method_id_ != NULL) {
			/* XXX
			 * We should really have a better way
			 * to translate this to a Java exception class.
			 * In theory, it shouldn't happen.
			 */
			report_exception(jnienv, "Cannot find callback method",
					 EFAULT, 0);
			return;
		}
	}

	if (dbji->feedback_ != NULL) {
		DELETE_GLOBAL_REF(jnienv, dbji->feedback_);
	}
	if (jfeedback == NULL) {
		db->set_feedback(db, NULL);
	}
	else {
		db->set_feedback(db, Db_feedback_callback);
	}

	dbji->feedback_ = NEW_GLOBAL_REF(jnienv, jfeedback);

}

void dbji_call_feedback(DB_JAVAINFO *dbji, DB *db, jobject jdb,
			int opcode, int percent)
{
	JNIEnv *jnienv;

	COMPQUIET(db, NULL);
	jnienv = dbji_get_jnienv(dbji);
	if (jnienv == NULL) {
		fprintf(stderr, "Cannot attach to current thread!\n");
		return;
	}

	DB_ASSERT(dbji->feedback_method_id_ != NULL);
	(*jnienv)->CallVoidMethod(jnienv, dbji->feedback_,
				  dbji->feedback_method_id_,
				  jdb, (jint)opcode, (jint)percent);
}

void dbji_set_append_recno_object(DB_JAVAINFO *dbji, JNIEnv *jnienv,
				  DB *db, jobject jcallback)
{
	jclass append_recno_class;

	if (dbji->append_recno_method_id_ == NULL) {
		append_recno_class = get_class(jnienv, name_DbAppendRecno);
		dbji->append_recno_method_id_ =
			(*jnienv)->GetMethodID(jnienv, append_recno_class,
					       "db_append_recno",
					       "(Lcom/sleepycat/db/Db;"
					       "Lcom/sleepycat/db/Dbt;I)V");
		if (dbji->append_recno_method_id_ == NULL) {
			/* XXX
			 * We should really have a better way
			 * to translate this to a Java exception class.
			 * In theory, it shouldn't happen.
			 */
			report_exception(jnienv, "Cannot find callback method",
					 EFAULT, 0);
			return;
		}
	}

	if (dbji->append_recno_ != NULL) {
		DELETE_GLOBAL_REF(jnienv, dbji->append_recno_);
	}
	if (jcallback == NULL) {
		db->set_append_recno(db, NULL);
	}
	else {
		db->set_append_recno(db, Db_append_recno_callback);
	}

	dbji->append_recno_ = NEW_GLOBAL_REF(jnienv, jcallback);
}

extern int dbji_call_append_recno(DB_JAVAINFO *dbji, DB *db, jobject jdb,
				  DBT *dbt, jint recno)
{
	JNIEnv *jnienv;
	jobject jdbt;
	DBT_JAVAINFO *dbtji;
	jbyteArray arr;
	unsigned int arraylen;
	unsigned char *data;

	COMPQUIET(db, NULL);
	jnienv = dbji_get_jnienv(dbji);
	if (jnienv == NULL) {
		fprintf(stderr, "Cannot attach to current thread!\n");
		return (0);
	}

	/* XXX
	 * We should have a pool of Dbt objects used for this purpose
	 * instead of creating new ones each time.  Because of
	 * multithreading, we may need an arbitrary number (more than two).
	 * We might also have a byte arrays that grow as needed,
	 * so we don't need to allocate those either.
	 *
	 * Note, we do not set the 'create_array_' flag as on other
	 * callbacks as we are creating the array here.
	 */
	jdbt = create_default_object(jnienv, name_DBT);
	dbtji = get_DBT_JAVAINFO(jnienv, jdbt);
	memcpy(&dbtji->dbt, dbt, sizeof(DBT));
	dbtji->dbt.data = NULL;
	arr = (*jnienv)->NewByteArray(jnienv, dbt->size);
	(*jnienv)->SetByteArrayRegion(jnienv, arr, 0, dbt->size, 
				      (jbyte *)dbt->data);
	dbtji->array_ = (jbyteArray)NEW_GLOBAL_REF(jnienv, arr);

	DB_ASSERT(dbji->append_recno_method_id_ != NULL);
	(*jnienv)->CallVoidMethod(jnienv, dbji->append_recno_,
				  dbji->append_recno_method_id_,
				  jdb, jdbt, recno);

	/* The underlying C API requires that an errno be returned
	 * on error.  Java users know nothing of errnos, so we
	 * allow them to throw exceptions instead.  We leave the
	 * exception in place and return EFAULT to the C API
	 * that called us.  Eventually the DB->get will fail and
	 * when java prepares to throw an exception in
	 * report_exception(), EFAULT will be spotted as a special case,
	 * and the original exception will be preserved.
	 *
	 * Note: apparently with the Linux 1.1.7 JVM (maybe others?)
	 * the exception is lost (i.e. the next call to
	 * ExceptionOccurred() in report_exception() will
	 * return NULL).  Those users who need correct append_recno
	 * callback behavior are encouraged to get a more recent JVM,
	 * as we don't have an adequate workaround.
	 */
	if ((*jnienv)->ExceptionOccurred(jnienv) != NULL)
		return (EFAULT);

	if (dbtji->array_ == NULL) {
		report_exception(jnienv, "Dbt.data is null", 0, 0);
		return (EFAULT);
	}

	arraylen = (*jnienv)->GetArrayLength(jnienv, dbtji->array_);
	if (dbtji->offset_ < 0 ) {
		report_exception(jnienv, "Dbt.offset illegal", 0, 0);
		return (EFAULT);
	}
	if (dbt->ulen + dbtji->offset_ > arraylen) {
		report_exception(jnienv,
                   "Dbt.ulen + Dbt.offset greater than array length", 0, 0);
		return (EFAULT);
	}

	data = (*jnienv)->GetByteArrayElements(jnienv, dbtji->array_,
					       (jboolean *)0);
	dbt->data = data + dbtji->offset_;
	return (0);
}

void dbji_set_bt_compare_object(DB_JAVAINFO *dbji, JNIEnv *jnienv,
				DB *db, jobject jcompare)
{
	jclass bt_compare_class;

	if (dbji->bt_compare_method_id_ == NULL) {
		bt_compare_class = get_class(jnienv, name_DbBtreeCompare);
		dbji->bt_compare_method_id_ =
			(*jnienv)->GetMethodID(jnienv, bt_compare_class,
					       "bt_compare",
					       "(Lcom/sleepycat/db/Db;"
					       "Lcom/sleepycat/db/Dbt;"
					       "Lcom/sleepycat/db/Dbt;)I");
		if (dbji->bt_compare_method_id_ == NULL) {
			/* XXX
			 * We should really have a better way
			 * to translate this to a Java exception class.
			 * In theory, it shouldn't happen.
			 */
			report_exception(jnienv, "Cannot find callback method",
					 EFAULT, 0);
			return;
		}
	}

	if (dbji->bt_compare_ != NULL) {
		DELETE_GLOBAL_REF(jnienv, dbji->bt_compare_);
	}
	if (jcompare == NULL) {
		db->set_bt_compare(db, NULL);
	}
	else {
		db->set_bt_compare(db, Db_bt_compare_callback);
	}

	dbji->bt_compare_ = NEW_GLOBAL_REF(jnienv, jcompare);
}

int dbji_call_bt_compare(DB_JAVAINFO *dbji, DB *db, jobject jdb,
			 const DBT *dbt1, const DBT *dbt2)
{
	JNIEnv *jnienv;
	jobject jdbt1, jdbt2;
	DBT_JAVAINFO *dbtji1, *dbtji2;

	COMPQUIET(db, NULL);
	jnienv = dbji_get_jnienv(dbji);
	if (jnienv == NULL) {
		fprintf(stderr, "Cannot attach to current thread!\n");
		return (0);
	}

	/* XXX
	 * We should have a pool of Dbt objects used for this purpose
	 * instead of creating new ones each time.  Because of
	 * multithreading, we may need an arbitrary number (more than two).
	 * We might also have a byte arrays that grow as needed,
	 * so we don't need to allocate those either.
	 */
	jdbt1 = create_default_object(jnienv, name_DBT);
	jdbt2 = create_default_object(jnienv, name_DBT);
	dbtji1 = get_DBT_JAVAINFO(jnienv, jdbt1);
	memcpy(&dbtji1->dbt, dbt1, sizeof(DBT));
	dbtji1->create_array_ = 1;
	dbtji2 = get_DBT_JAVAINFO(jnienv, jdbt2);
	memcpy(&dbtji2->dbt, dbt2, sizeof(DBT));
	dbtji2->create_array_ = 1;

	DB_ASSERT(dbji->bt_compare_method_id_ != NULL);
	return (*jnienv)->CallIntMethod(jnienv, dbji->bt_compare_,
					dbji->bt_compare_method_id_,
					jdb, jdbt1, jdbt2);
}

void dbji_set_bt_prefix_object(DB_JAVAINFO *dbji, JNIEnv *jnienv,
				DB *db, jobject jprefix)
{
	jclass bt_prefix_class;

	if (dbji->bt_prefix_method_id_ == NULL) {
		bt_prefix_class = get_class(jnienv, name_DbBtreePrefix);
		dbji->bt_prefix_method_id_ =
			(*jnienv)->GetMethodID(jnienv, bt_prefix_class,
					       "bt_prefix",
					       "(Lcom/sleepycat/db/Db;"
					       "Lcom/sleepycat/db/Dbt;"
					       "Lcom/sleepycat/db/Dbt;)I");
		if (dbji->bt_prefix_method_id_ == NULL) {
			/* XXX
			 * We should really have a better way
			 * to translate this to a Java exception class.
			 * In theory, it shouldn't happen.
			 */
			report_exception(jnienv, "Cannot find callback method",
					 EFAULT, 0);
			return;
		}
	}

	if (dbji->bt_prefix_ != NULL) {
		DELETE_GLOBAL_REF(jnienv, dbji->bt_prefix_);
	}
	if (jprefix == NULL) {
		db->set_bt_prefix(db, NULL);
	}
	else {
		db->set_bt_prefix(db, Db_bt_prefix_callback);
	}

	dbji->bt_prefix_ = NEW_GLOBAL_REF(jnienv, jprefix);
}

size_t dbji_call_bt_prefix(DB_JAVAINFO *dbji, DB *db, jobject jdb,
			   const DBT *dbt1, const DBT *dbt2)
{
	JNIEnv *jnienv;
	jobject jdbt1, jdbt2;
	DBT_JAVAINFO *dbtji1, *dbtji2;

	COMPQUIET(db, NULL);
	jnienv = dbji_get_jnienv(dbji);
	if (jnienv == NULL) {
		fprintf(stderr, "Cannot attach to current thread!\n");
		return (0);
	}

	/* XXX
	 * We should have a pool of Dbt objects used for this purpose
	 * instead of creating new ones each time.  Because of
	 * multithreading, we may need an arbitrary number (more than two).
	 * We might also have a byte arrays that grow as needed,
	 * so we don't need to allocate those either.
	 */
	jdbt1 = create_default_object(jnienv, name_DBT);
	jdbt2 = create_default_object(jnienv, name_DBT);
	dbtji1 = get_DBT_JAVAINFO(jnienv, jdbt1);
	memcpy(&dbtji1->dbt, dbt1, sizeof(DBT));
	dbtji1->create_array_ = 1;
	dbtji2 = get_DBT_JAVAINFO(jnienv, jdbt2);
	memcpy(&dbtji2->dbt, dbt2, sizeof(DBT));
	dbtji2->create_array_ = 1;

	DB_ASSERT(dbji->bt_prefix_method_id_ != NULL);
	return (size_t)(*jnienv)->CallIntMethod(jnienv, dbji->bt_prefix_,
						dbji->bt_prefix_method_id_,
						jdb, jdbt1, jdbt2);
}

void dbji_set_dup_compare_object(DB_JAVAINFO *dbji, JNIEnv *jnienv,
				DB *db, jobject jcompare)
{
	jclass dup_compare_class;

	if (dbji->dup_compare_method_id_ == NULL) {
		dup_compare_class = get_class(jnienv, name_DbDupCompare);
		dbji->dup_compare_method_id_ =
			(*jnienv)->GetMethodID(jnienv, dup_compare_class,
					       "dup_compare",
					       "(Lcom/sleepycat/db/Db;"
					       "Lcom/sleepycat/db/Dbt;"
					       "Lcom/sleepycat/db/Dbt;)I");
		if (dbji->dup_compare_method_id_ == NULL) {
			/* XXX
			 * We should really have a better way
			 * to translate this to a Java exception class.
			 * In theory, it shouldn't happen.
			 */
			report_exception(jnienv, "Cannot find callback method",
					 EFAULT, 0);
			return;
		}
	}

	if (dbji->dup_compare_ != NULL)
		DELETE_GLOBAL_REF(jnienv, dbji->dup_compare_);

	if (jcompare == NULL)
		db->set_dup_compare(db, NULL);
	else
		db->set_dup_compare(db, Db_dup_compare_callback);

	dbji->dup_compare_ = NEW_GLOBAL_REF(jnienv, jcompare);
}

int dbji_call_dup_compare(DB_JAVAINFO *dbji, DB *db, jobject jdb,
			 const DBT *dbt1, const DBT *dbt2)
{
	JNIEnv *jnienv;
	jobject jdbt1, jdbt2;
	DBT_JAVAINFO *dbtji1, *dbtji2;

	COMPQUIET(db, NULL);
	jnienv = dbji_get_jnienv(dbji);
	if (jnienv == NULL) {
		fprintf(stderr, "Cannot attach to current thread!\n");
		return (0);
	}

	/* XXX
	 * We should have a pool of Dbt objects used for this purpose
	 * instead of creating new ones each time.  Because of
	 * multithreading, we may need an arbitrary number (more than two).
	 * We might also have a byte arrays that grow as needed,
	 * so we don't need to allocate those either.
	 */
	jdbt1 = create_default_object(jnienv, name_DBT);
	jdbt2 = create_default_object(jnienv, name_DBT);
	dbtji1 = get_DBT_JAVAINFO(jnienv, jdbt1);
	memcpy(&dbtji1->dbt, dbt1, sizeof(DBT));
	dbtji1->create_array_ = 1;
	dbtji2 = get_DBT_JAVAINFO(jnienv, jdbt2);
	memcpy(&dbtji2->dbt, dbt2, sizeof(DBT));
	dbtji2->create_array_ = 1;

	DB_ASSERT(dbji->dup_compare_method_id_ != NULL);
	return (*jnienv)->CallIntMethod(jnienv, dbji->dup_compare_,
					dbji->dup_compare_method_id_,
					jdb, jdbt1, jdbt2);
}

void dbji_set_h_hash_object(DB_JAVAINFO *dbji, JNIEnv *jnienv,
				DB *db, jobject jhash)
{
	jclass h_hash_class;

	if (dbji->h_hash_method_id_ == NULL) {
		h_hash_class = get_class(jnienv, name_DbHash);
		dbji->h_hash_method_id_ =
			(*jnienv)->GetMethodID(jnienv, h_hash_class,
					       "hash",
					       "(Lcom/sleepycat/db/Db;"
					       "[BI)I");
		if (dbji->h_hash_method_id_ == NULL) {
			/* XXX
			 * We should really have a better way
			 * to translate this to a Java exception class.
			 * In theory, it shouldn't happen.
			 */
			report_exception(jnienv, "Cannot find callback method",
					 EFAULT, 0);
			return;
		}
	}

	if (dbji->h_hash_ != NULL)
		DELETE_GLOBAL_REF(jnienv, dbji->h_hash_);

	if (jhash == NULL)
		db->set_h_hash(db, NULL);
	else
		db->set_h_hash(db, Db_h_hash_callback);

	dbji->h_hash_ = NEW_GLOBAL_REF(jnienv, jhash);
}

int dbji_call_h_hash(DB_JAVAINFO *dbji, DB *db, jobject jdb,
		     const void *data, int len)
{
	JNIEnv *jnienv;
	jbyteArray jarray;

	COMPQUIET(db, NULL);
	jnienv = dbji_get_jnienv(dbji);
	if (jnienv == NULL) {
		fprintf(stderr, "Cannot attach to current thread!\n");
		return (0);
	}

	DB_ASSERT(dbji->h_hash_method_id_ != NULL);

	jarray = (*jnienv)->NewByteArray(jnienv, len);
	(*jnienv)->SetByteArrayRegion(jnienv, jarray, 0, len, (void *)data);
	return (*jnienv)->CallIntMethod(jnienv, dbji->h_hash_,
					dbji->h_hash_method_id_,
					jdb, jarray, len);
}
