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

#ifndef lint
static const char sccsid[] = "@(#)java_util.cpp	10.13 (Sleepycat) 10/28/98";
#endif /* not lint */

#include "java_util.h"
#include <errno.h>
#include <stdlib.h>
#include <string.h>

#ifdef WIN32
#define sys_errlist _sys_errlist
#define sys_nerr _sys_nerr
#endif

/****************************************************************
 *
 * Utility functions used by "glue" functions.
 *
 */

/* Use our own malloc for any objects allocated via DB_DBT_MALLOC,
 * since we must free them in the same library address space.
 */
extern "C"
void * java_db_malloc(size_t size)
{
    return malloc(size);
}

/* Get the private data from a Db* object as a (64 bit) java long.
 */
jlong get_private_long_info(JNIEnv *jnienv, const char *classname,
                            jobject obj)
{
    if (!obj)
        return 0;

    jclass dbClass = get_class(jnienv, classname);
    jfieldID id = jnienv->GetFieldID(dbClass, "private_info_", "J");
    return jnienv->GetLongField(obj, id);
}

/* Get the private data from a Db* object.
 * The private data is stored in the object as a Java long (64 bits),
 * which is long enough to store a pointer on current architectures.
 */
void *get_private_info(JNIEnv *jnienv, const char *classname,
                       jobject obj)
{
    long_to_ptr lp;
    if (!obj)
        return 0;
    lp.java_long = get_private_long_info(jnienv, classname, obj);
    return lp.ptr;
}

/* Set the private data in a Db* object as a (64 bit) java long.
 */
void set_private_long_info(JNIEnv *jnienv, const char *classname,
                           jobject obj, jlong value)
{
    jclass dbClass = get_class(jnienv, classname);
    jfieldID id = jnienv->GetFieldID(dbClass, "private_info_", "J");
    jnienv->SetLongField(obj, id, value);
}

/* Set the private data in a Db* object.
 * The private data is stored in the object as a Java long (64 bits),
 * which is long enough to store a pointer on current architectures.
 */
void set_private_info(JNIEnv *jnienv, const char *classname,
                      jobject obj, void *value)
{
    long_to_ptr lp;
    lp.java_long = 0;
    lp.ptr = value;
    set_private_long_info(jnienv, classname, obj, lp.java_long);
}

/*
 * Given a non-qualified name (e.g. "foo"), get the class handl
 * for the fully qualified name (e.g. "com.sleepycat.db.foo")
 */
jclass get_class(JNIEnv *jnienv, const char *classname)
{
    // TODO: cache these (remember to use NewGlobalRef)
    char fullname[128] = DB_PACKAGE_NAME;
    strcat(fullname, classname);
    return jnienv->FindClass(fullname);
}

/* Set an individual field in a Db* object.
 * The field must be a DB object type.
 */
void set_object_field(JNIEnv *jnienv, jclass class_of_this,
                      jobject jthis, const char *object_classname,
                      const char *name_of_field, jobject obj)
{
    char signature[512];

    strcpy(signature, "L");
    strcat(signature, DB_PACKAGE_NAME);
    strcat(signature, object_classname);
    strcat(signature, ";");

    jfieldID id  = jnienv->GetFieldID(class_of_this, name_of_field, signature);
    jnienv->SetObjectField(jthis, id, obj);
}

/* Set an individual field in a Db* object.
 * The field must be an integer type.
 */
void set_int_field(JNIEnv *jnienv, jclass class_of_this,
                   jobject jthis, const char *name_of_field, jint value)
{
    jfieldID id  = jnienv->GetFieldID(class_of_this, name_of_field, "I");
    jnienv->SetIntField(jthis, id, value);
}

/* Set an individual field in a Db* object.
 * The field must be an integer type.
 */
void set_long_field(JNIEnv *jnienv, jclass class_of_this,
                   jobject jthis, const char *name_of_field, jlong value)
{
    jfieldID id  = jnienv->GetFieldID(class_of_this, name_of_field, "J");
    jnienv->SetLongField(jthis, id, value);
}

/* Set an individual field in a Db* object.
 * The field must be an integer type.
 */
void set_lsn_field(JNIEnv *jnienv, jclass class_of_this,
                   jobject jthis, const char *name_of_field, DB_LSN value)
{
    set_object_field(jnienv, class_of_this, jthis, name_DB_LSN,
                     name_of_field, get_DbLsn(jnienv, value));
}

/* Report an exception back to the java side.
 */
void report_exception(JNIEnv *jnienv, const char *text, int err)
{
    jstring textString = get_java_string(jnienv, text);
    jclass dbexcept;
    if (err == DB_RUNRECOVERY) {
        dbexcept = get_class(jnienv, name_DB_RUNRECOVERY);
    }
    else {
        dbexcept = get_class(jnienv, name_DB_EXCEPTION);
    }
    jmethodID constructId = jnienv->GetMethodID(dbexcept, "<init>",
                                             "(Ljava/lang/String;I)V");
    jthrowable obj = (jthrowable)jnienv->AllocObject(dbexcept);
    jnienv->CallVoidMethod(obj, constructId, textString, err);
    jnienv->Throw(obj);
}

/* If the object is null, report an exception and return false (0),
 * otherwise return true (1).
 */
int verify_non_null(JNIEnv *jnienv, void *obj)
{
    if (obj == NULL) {
        report_exception(jnienv, "null object", EINVAL);
        return 0;
    }
    return 1;
}

/* If the DB_ENV* is null or has already been initialized (via appinit),
 * report an exception and return false (0), otherwise return true (1).
 * Setting a field in the environment after appinit has been called
 * will never have any effect, so we raise the error to alert the
 * user to a potential configuration bug.
 */
int verify_dbenv(JNIEnv *jnienv, DB_ENV *env)
{
    if (verify_non_null(jnienv, env)) {
        if ((env->flags & DB_ENV_APPINIT) != 0) {
            report_exception(jnienv, "DbEnv.appinit already called", EINVAL);
            return 0;
        }
    }
    return 1;
}

/* If the error code is non-zero, report an exception and return false (0),
 * otherwise return true (1).
 */
int verify_return(JNIEnv *jnienv, int err)
{
    char *errstring;

    if (err != 0) {
        if (err > 0 && (errstring = strerror(err)) != NULL) {
            report_exception(jnienv, errstring, err);
        }
        else {
            char buffer[64];
            sprintf(buffer, "error %d", err);
            report_exception(jnienv, buffer, err);
        }
        return 0;
    }
    return 1;
}

/* Create an object of the given class, calling its default constructor.
 */
jobject create_default_object(JNIEnv *jnienv, const char *class_name)
{
    jclass dbclass = get_class(jnienv, class_name);
    jmethodID id = jnienv->GetMethodID(dbclass, "<init>", "()V");
    jobject object = jnienv->NewObject(dbclass, id);
    return object;
}

/* Convert an DB object to a Java encapsulation of that object.
 * Note: This implementation creates a new Java object on each call,
 * so it is generally useful when a new DB object has just been created.
 */
jobject convert_object(JNIEnv *jnienv, const char *class_name, void *dbobj)
{
    if (!dbobj)
        return 0;

    jobject jo = create_default_object(jnienv, class_name);
    set_private_info(jnienv, class_name, jo, dbobj);
    return jo;
}

/* Create a copy of the string
 */
char *dup_string(const char *str)
{
    char *retval = NEW_ARRAY(char, strlen(str)+1);
    strcpy(retval, str);
    return retval;
}

/* Create a java string from the given string
 */
jstring get_java_string(JNIEnv *jnienv, const char* string)
{
    if (string == 0)
        return 0;
    return jnienv->NewStringUTF(string);
}

/* Storage allocator
 */
void *allocMemory(size_t n)
{
    return malloc(n);
}

void freeMemory(void *p)
{
    free(p);
}

/* Convert a java object to the various C pointers they represent.
 */
DB *get_DB(JNIEnv *jnienv, jobject obj)
{
    return (DB *)get_private_info(jnienv, name_DB, obj);
}

DB_BTREE_STAT *get_DB_BTREE_STAT(JNIEnv *jnienv, jobject obj)
{
    return (DB_BTREE_STAT *)get_private_info(jnienv, name_DB_BTREE_STAT, obj);
}

DBC *get_DBC(JNIEnv *jnienv, jobject obj)
{
    return (DBC *)get_private_info(jnienv, name_DBC, obj);
}

DB_ENV *get_DB_ENV(JNIEnv *jnienv, jobject obj)
{
    return (DB_ENV *)get_private_info(jnienv, name_DB_ENV, obj);
}

DB_INFO *get_DB_INFO(JNIEnv *jnienv, jobject obj)
{
    return (DB_INFO *)get_private_info(jnienv, name_DB_INFO, obj);
}

// A DB_LOCK is just a size_t, so no need for indirection.
DB_LOCK get_DB_LOCK(JNIEnv *jnienv, jobject obj)
{
    return (DB_LOCK)get_private_long_info(jnienv, name_DB_LOCK, obj);
}

DB_LOCKTAB *get_DB_LOCKTAB(JNIEnv *jnienv, jobject obj)
{
    return (DB_LOCKTAB *)get_private_info(jnienv, name_DB_LOCKTAB, obj);
}

DB_LOG *get_DB_LOG(JNIEnv *jnienv, jobject obj)
{
    return (DB_LOG *)get_private_info(jnienv, name_DB_LOG, obj);
}

DB_LOG_STAT *get_DB_LOG_STAT(JNIEnv *jnienv, jobject obj)
{
    return (DB_LOG_STAT *)get_private_info(jnienv, name_DB_LOG_STAT, obj);
}

DB_LSN *get_DB_LSN(JNIEnv *jnienv, jobject obj)
{
    return (DB_LSN *)get_private_info(jnienv, name_DB_LSN, obj);
}

DB_MPOOL *get_DB_MPOOL(JNIEnv *jnienv, jobject obj)
{
    return (DB_MPOOL *)get_private_info(jnienv, name_DB_MPOOL, obj);
}

DB_MPOOL_FSTAT *get_DB_MPOOL_FSTAT(JNIEnv *jnienv, jobject obj)
{
    return (DB_MPOOL_FSTAT *)get_private_info(jnienv, name_DB_MPOOL_FSTAT, obj);
}

DB_MPOOL_STAT *get_DB_MPOOL_STAT(JNIEnv *jnienv, jobject obj)
{
    return (DB_MPOOL_STAT *)get_private_info(jnienv, name_DB_MPOOL_STAT, obj);
}

DB_TXN *get_DB_TXN(JNIEnv *jnienv, jobject obj)
{
    return (DB_TXN *)get_private_info(jnienv, name_DB_TXN, obj);
}

DB_TXNMGR *get_DB_TXNMGR(JNIEnv *jnienv, jobject obj)
{
    return (DB_TXNMGR *)get_private_info(jnienv, name_DB_TXNMGR, obj);
}

DB_TXN_STAT *get_DB_TXN_STAT(JNIEnv *jnienv, jobject obj)
{
    return (DB_TXN_STAT *)get_private_info(jnienv, name_DB_TXN_STAT, obj);
}

DBT_info *get_DBT(JNIEnv *jnienv, jobject obj)
{
    return (DBT_info *)get_private_info(jnienv, name_DBT, obj);
}


/* Convert a C pointer to the various Java objects they represent.
 */
jobject get_DbBtreeStat(JNIEnv *jnienv, DB_BTREE_STAT *dbobj)
{
    return convert_object(jnienv, name_DB_BTREE_STAT, dbobj);
}

jobject get_Dbc(JNIEnv *jnienv, DBC *dbobj)
{
    return convert_object(jnienv, name_DBC, dbobj);
}

jobject get_DbLockTab(JNIEnv *jnienv, DB_LOCKTAB *dbobj)
{
    return convert_object(jnienv, name_DB_LOCKTAB, dbobj);
}

jobject get_DbLog(JNIEnv *jnienv, DB_LOG *dbobj)
{
    return convert_object(jnienv, name_DB_LOG, dbobj);
}

jobject get_DbLogStat(JNIEnv *jnienv, DB_LOG_STAT *dbobj)
{
    return convert_object(jnienv, name_DB_LOG_STAT, dbobj);
}

// LSNs are different since they are really normally
// treated as by-value objects.  We actually create
// a pointer to the LSN and store that, deleting it
// when the LSN is GC'd.
//
jobject get_DbLsn(JNIEnv *jnienv, DB_LSN dbobj)
{
    DB_LSN *lsnp = NEW(DB_LSN);
    *lsnp = dbobj;
    return convert_object(jnienv, name_DB_LSN, lsnp);
}

jobject get_DbMpool(JNIEnv *jnienv, DB_MPOOL *dbobj)
{
    return convert_object(jnienv, name_DB_MPOOL, dbobj);
}

jobject get_DbMpoolFStat(JNIEnv *jnienv, DB_MPOOL_FSTAT *dbobj)
{
    return convert_object(jnienv, name_DB_MPOOL_FSTAT, dbobj);
}

jobject get_DbMpoolStat(JNIEnv *jnienv, DB_MPOOL_STAT *dbobj)
{
    return convert_object(jnienv, name_DB_MPOOL_STAT, dbobj);
}

jobject get_DbTxn(JNIEnv *jnienv, DB_TXN *dbobj)
{
    return convert_object(jnienv, name_DB_TXN, dbobj);
}

jobject get_DbTxnMgr(JNIEnv *jnienv, DB_TXNMGR *dbobj)
{
    return convert_object(jnienv, name_DB_TXNMGR, dbobj);
}

jobject get_DbTxnStat(JNIEnv *jnienv, DB_TXN_STAT *dbobj)
{
    return convert_object(jnienv, name_DB_TXN_STAT, dbobj);
}

/****************************************************************
 *
 * Implementation of class DBT_info
 *
 */
DBT_info::DBT_info()
:   array_(0)
,   offset_(0)
{
    memset((DBT*)this, 0, sizeof(DBT));
}

void DBT_info::release(JNIEnv *jnienv)
{
    if (array_ != 0) {
        jnienv->DeleteGlobalRef(array_);
    }
}

DBT_info::~DBT_info()
{
}

/****************************************************************
 *
 * Implementation of class LockedDBT
 *
 */
LockedDBT::LockedDBT(JNIEnv *jnienv, jobject obj, OpKind kind)
    : env_(jnienv)
    , obj_(obj)
    , has_error_(0)
    , kind_(kind)
    , java_array_len_(0)
    , java_data_(0)
    /* dbt initialized below */
{
    dbt = (DBT_info *)get_private_info(jnienv, name_DBT, obj);
    if (!verify_non_null(jnienv, dbt)) {
        has_error_ = 1;
        return;
    }

    if (kind == outOp &&
        (dbt->flags & (DB_DBT_USERMEM | DB_DBT_MALLOC)) == 0) {
        report_exception(jnienv,
                         "Dbt.flags must set DB_DBT_USERMEM or DB_DBT_MALLOC",
                         0);
        has_error_ = 1;
        return;
    }

    if ((dbt->flags & DB_DBT_USERMEM) || kind != outOp) {

        // If writing with DB_DBT_USERMEM or it's a set (or get/set)
        // operation, then the data should point to a java array.
        // Note that outOp means data is coming out of the database
        // (it's a get).  inOp means data is going into the database
        // (either a put, or a key input).
        //
        if (!dbt->array_) {
            report_exception(jnienv, "Dbt.data is null", 0);
            has_error_ = 1;
            return;
        }

        // Verify other parameters
        //
        java_array_len_ = jnienv->GetArrayLength(dbt->array_);
        if (dbt->offset_ < 0 ) {
            report_exception(jnienv, "Dbt.offset illegal", 0);
            has_error_ = 1;
            return;
        }
        if (dbt->ulen + dbt->offset_ > java_array_len_) {
            report_exception(jnienv,
                           "Dbt.ulen + Dbt.offset greater than array length", 0);
            has_error_ = 1;
            return;
        }

        java_data_ = jnienv->GetByteArrayElements(dbt->array_, NULL);
        dbt->data = java_data_ + dbt->offset_;
    }
    else {

        // If writing with DB_DBT_MALLOC, then the data is
        // allocated by DB.
        //
        dbt->data = 0;
    }
}

// The LockedDBT destructor is called when the java handler returns
// to the user, since that's when the LockedDBT objects go out of scope.
// Since it is thus called after any call to the underlying database,
// it copies any information from temporary structures back to user
// accessible arrays, and of course must free memory and remove references.
//
LockedDBT::~LockedDBT()
{
    // If there was an error in the constructor,
    // everything is already cleaned up.
    //
    if (has_error_)
        return;

    if ((dbt->flags & DB_DBT_USERMEM) || kind_ == inOp) {

        // If writing with DB_DBT_USERMEM or it's a set (or get/set)
        // operation, then the data may be already in the java array,
        // in which case, we just need to release it.
        // If DB didn't put it in the array (indicated by the
        // dbt->data changing), we need to do that
        //
        jbyte *data = (jbyte *)dbt->data;
        data -= dbt->offset_;
        if (data != java_data_) {
            env_->SetByteArrayRegion(dbt->array_, dbt->offset_, dbt->ulen, data);
        }
        env_->ReleaseByteArrayElements(dbt->array_, java_data_, 0);
        dbt->data = 0;
    }
    if ((dbt->flags & DB_DBT_MALLOC) && kind_ != inOp) {

        // If writing with DB_DBT_MALLOC, then the data was allocated
        // by DB.  If dbt->data is zero, it means an error occurred
        // (and should have been already reported).
        //
        if (dbt->data) {

            // Release any old references.
            //
            dbt->release(env_);

            dbt->array_ = (jbyteArray)
                env_->NewGlobalRef(env_->NewByteArray(dbt->size));
            dbt->offset_ = 0;
            env_->SetByteArrayRegion(dbt->array_, 0, dbt->size, (jbyte *)dbt->data);
            free(dbt->data);
            dbt->data = 0;
        }
    }
}

/****************************************************************
 *
 * Implementation of class LockedString
 *
 */
LockedString::LockedString(JNIEnv *jnienv, jstring jstr)
    : env_(jnienv)
    , jstr_(jstr)
{
    if (jstr == 0)
        string = 0;
    else
        string = jnienv->GetStringUTFChars(jstr, NULL);
}

LockedString::~LockedString()
{
    if (jstr_)
        env_->ReleaseStringUTFChars(jstr_, string);
}


/****************************************************************
 *
 * Implementation of class LockedStringArray
 *
 */
LockedStringArray::LockedStringArray(JNIEnv *jnienv, jobjectArray arr)
    : env_(jnienv)
    , arr_(arr)
    , string_array(0)
{
    typedef const char *conststr;

    if (arr != 0) {
        int count = jnienv->GetArrayLength(arr);
        string_array = NEW_ARRAY(conststr, count+1);
        for (int i=0; i<count; i++) {
            jstring jstr = (jstring)jnienv->GetObjectArrayElement(arr, i);
            if (jstr == 0) {
                //
                // An embedded null in the string array is treated
                // as an endpoint.
                //
                string_array[i] = 0;
                break;
            }
            else {
                string_array[i] = jnienv->GetStringUTFChars(jstr, NULL);
            }
        }
        string_array[count] = 0;
    }
}

LockedStringArray::~LockedStringArray()
{
    if (arr_) {
        int count = env_->GetArrayLength(arr_);
        for (int i=0; i<count; i++) {
            if (string_array[i] == 0)
                break;
            jstring jstr = (jstring)env_->GetObjectArrayElement(arr_, i);
            env_->ReleaseStringUTFChars(jstr, string_array[i]);
        }
        DELETE(string_array);
    }
}
