/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 *
 * The contents of this file are subject to the Netscape Public License
 * Version 1.0 (the "License"); you may not use this file except in
 * compliance with the License.  You may obtain a copy of the License at
 * http://www.mozilla.org/NPL/
 *
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied.  See
 * the License for the specific language governing rights and limitations
 * under the License.
 *
 * The Original Code is Mozilla Communicator client code.
 *
 * The Initial Developer of the Original Code is Netscape Communications
 * Corporation.  Portions created by Netscape are Copyright (C) 1998
 * Netscape Communications Corporation.  All Rights Reserved.
 */
 
/* This file is part of the Java-vendor-neutral implementation of LiveConnect
 *
 * It contains the implementation of the native methods for the 
 * netscape.javascript.JSObject Java class, which are used for calling into
 * JavaScript from Java.  It also contains the code that handles propagation
 * of exceptions both into and out of Java.
 *
 */

#include <stdlib.h>
#include <string.h>
#include "prtypes.h"
#include "prprintf.h"
#include "prassert.h"

#include "jsj_private.h"
#include "jsjava.h"

#include "jscntxt.h"        /* For js_ReportErrorAgain().
                               TODO - get rid of private header */

#include "netscape_javascript_JSObject.h"   /* javah-generated headers */


/* A captured JavaScript error, created when JS_ReportError() is called while
   running JavaScript code that is itself called from Java. */
struct CapturedJSError {
    char *              message;
    JSErrorReport       report;         /* Line # of error, etc. */
    jthrowable          java_exception; /* Java exception, error, or null */
    CapturedJSError *   next;                   /* Next oldest captured JS error */
};


/*********************** Reflection of JSObjects ****************************/

/*
 * This is a hash table that maps from JS objects to Java objects.
 * It is used to ensure that the same Java object results when a JS
 * object is reflected more than once, so that Java object equality
 * tests work in the expected manner.
 *
 * The entry keys are JSObject pointers and the entry values are Java objects
 * (of type jobject).  When the corresponding Java instance is finalized,
 * the entry is removed from the table, and a JavaScript GC root for the JS
 * object is removed.
 */
static PRHashTable *js_obj_reflections = NULL;

#ifdef JS_THREADSAFE
/*
 * Used to protect the js_obj_reflections hashtable from simultaneous
 * read/write or * write/write access.
 */
static PRMonitor *js_obj_reflections_monitor = NULL;
#endif

static JSBool
init_js_obj_reflections_table()
{
    js_obj_reflections = PR_NewHashTable(128, NULL, PR_CompareValues,
                                         PR_CompareValues, NULL, NULL);
    if (!js_obj_reflections)
        return JS_FALSE;

#ifdef JS_THREADSAFE
    js_obj_reflections_monitor = PR_NewNamedMonitor("js_obj_reflections");
    if (!js_obj_reflections_monitor) {
        PR_HashTableDestroy(js_obj_reflections);
        return JS_FALSE;
    }
#endif

    return JS_TRUE;
}

/*
 * Return a Java object that "wraps" a given JS object by storing the address
 * of the JS object in a private field of the Java object.  A hash table is
 * used to ensure that the mapping of JS objects to Java objects is done on
 * one-to-one basis.
 *
 * If an error occurs, returns NULL and reports an error.
 */
jobject
jsj_WrapJSObject(JSContext *cx, JNIEnv *jEnv, JSObject *js_obj)
{
    jobject java_wrapper_obj;
    PRHashEntry *he, **hep;

    java_wrapper_obj = NULL;

#ifdef JS_THREADSAFE
    PR_EnterMonitor(js_obj_reflections_monitor);
#endif

    /* First, look in the hash table for an existing reflection of the same
       JavaScript object.  If one is found, return it. */
    hep = PR_HashTableRawLookup(js_obj_reflections, (prhashcode)js_obj, js_obj);

#ifdef PRESERVE_JSOBJECT_IDENTITY
    /* If the same JSObject is reflected into Java more than once then we should
       return the same Java object, both for efficiency and so that the '=='
       operator works as expected in Java when comparing two JSObjects.
       However, it is not possible to hold a reference to a Java object without
       inhibiting GC of that object, at least not in a way that is portable
       to all vendor's JVM, i.e. a weak reference. So, for now, JSObject identity
       is broken. */

    he = *hep;
    if (he) {
        java_wrapper_obj = (jobject)he->value;
        PR_ASSERT(java_wrapper_obj);
        if (java_wrapper_obj)
            goto done;
    }
#endif /* PRESERVE_JSOBJECT_IDENTITY */

    /* No existing reflection found, so create a new Java object that wraps
       the JavaScript object by storing its address in a private integer field. */
    java_wrapper_obj =
        (*jEnv)->NewObject(jEnv, njJSObject, njJSObject_JSObject, (jint)js_obj);
    if (!java_wrapper_obj) {
        jsj_UnexpectedJavaError(cx, jEnv, "Couldn't create new instance of "
                                          "netscape.javascript.JSObject");
        goto done;
    }

    /* Add the new reflection to the hash table. */
    he = PR_HashTableRawAdd(js_obj_reflections, hep, (prhashcode)js_obj,
                            js_obj, java_wrapper_obj);
    if (he) {
        /* Tell the JavaScript GC about this object since the only reference
           to it may be in Java-land. */
        JS_AddRoot(cx, (void*)&he->key);
    } else {
        JS_ReportOutOfMemory(cx);
        /* No need to delete java_wrapper_obj because Java GC will reclaim it */
        java_wrapper_obj = NULL;
    }
    
    /*
     * Release local reference to wrapper object, since some JVMs seem reticent
     * about collecting it otherwise.
     */
    (*jEnv)->DeleteLocalRef(jEnv, java_wrapper_obj);

done:
#ifdef JS_THREADSAFE
        PR_ExitMonitor(js_obj_reflections_monitor);
#endif
        
    return java_wrapper_obj;
}

/*
 * Remove the mapping of a JS object from the hash table that maps JS objects
 * to Java objects.  This is called from the finalizer of an instance of
 * netscape.javascript.JSObject.
 */
static JSBool
remove_js_obj_reflection_from_hashtable(JSContext *cx, JSObject *js_obj)
{
    PRHashEntry *he, **hep;
    JSBool success = JS_FALSE;

#ifdef JS_THREADSAFE
    PR_EnterMonitor(js_obj_reflections_monitor);
#endif

    /* Get the hash-table entry for this wrapper object */
    hep = PR_HashTableRawLookup(js_obj_reflections, (prhashcode)js_obj, js_obj);
    he = *hep;

    PR_ASSERT(he);
    if (he) {
        /* Tell the JS GC that Java no longer keeps a reference to this
           JS object. */
        success = JS_RemoveRoot(cx, (void *)&he->key);

        PR_HashTableRawRemove(js_obj_reflections, hep, he);
    }

#ifdef JS_THREADSAFE
    PR_ExitMonitor(js_obj_reflections_monitor);
#endif

    return success;
}

/*************** Handling of Java exceptions in JavaScript ******************/

/*
 * Free up a JavaScript error that has been stored by the
 * capture_js_error_reports_for_java() function.
 */
static CapturedJSError *
destroy_saved_js_error(JNIEnv *jEnv, CapturedJSError *error)
{
    CapturedJSError *next_error;
    if (!error)
        return NULL;
    next_error = error->next;

    if (error->java_exception)
        (*jEnv)->DeleteGlobalRef(jEnv, error->java_exception);
    if (error->message)
        free((char*)error->message);
    if (error->report.filename)
        free((char*)error->report.filename);
    if (error->report.linebuf)
        free((char*)error->report.linebuf);
    free(error);

    return next_error;
}

/*
 * Capture a JS error that has been reported using JS_ReportError() by JS code
 * that has itself been called from Java.  A copy of the JS error data is made
 * and hung off the JSJ environment.  When JS completes and returns to its Java
 * caller, this data is used to synthesize an instance of
 * netscape.javascript.JSException.  If the resulting JSException is not caught
 * within Java, it may be propagated up the stack beyond the Java caller back
 * into JavaScript, in which case the error will be re-reported as a JavaScript
 * error.
 */
PR_STATIC_CALLBACK(void)
capture_js_error_reports_for_java(JSContext *cx, const char *message,
                                  JSErrorReport *report)
{
    CapturedJSError *new_error;
    JSJavaThreadState *jsj_env;
    jthrowable java_exception;
    JNIEnv *jEnv;

    /* Create an empty struct to hold the saved JS error state */
    new_error = malloc(sizeof(CapturedJSError));
    if (!new_error)
        goto out_of_memory;
    memset(new_error, 0, sizeof(CapturedJSError));

    /* Copy all the error info out of the original report into a private copy */
    if (message) {
        new_error->message = strdup(message);
        if (!new_error->message)
            goto out_of_memory;
    }
    if (report) {
        new_error->report.lineno = report->lineno;

        if (report->filename) {
            new_error->report.filename = strdup(report->filename);
            if (!new_error->report.filename)
                goto out_of_memory;
        }

        if (report->linebuf) {
            new_error->report.linebuf = strdup(report->linebuf);
            if (!new_error->report.linebuf)
                goto out_of_memory;
            new_error->report.tokenptr =
                new_error->report.linebuf + (report->tokenptr - report->linebuf);
        }
    }

    /* Get the head of the list of pending JS errors */
    jsj_env = jsj_MapJSContextToJSJThread(cx, &jEnv);
    if (!jsj_env)
        goto abort;

    /* If there's a Java exception associated with this error, save it too. */
    java_exception = (*jEnv)->ExceptionOccurred(jEnv);
    if (java_exception) {
        (*jEnv)->ExceptionClear(jEnv);
        java_exception = (*jEnv)->NewGlobalRef(jEnv, java_exception);
        new_error->java_exception = java_exception;
    }

    /* Push this error onto the list of pending JS errors */
    new_error->next = jsj_env->pending_js_errors;
    jsj_env->pending_js_errors = new_error;
    return;

abort:
out_of_memory:
    /* No recovery action possible */
    PR_ASSERT(0);
    destroy_saved_js_error(jEnv, new_error);
    return;
}

void
jsj_ClearPendingJSErrors(JSJavaThreadState *jsj_env)
{
    while (jsj_env->pending_js_errors)
        jsj_env->pending_js_errors = destroy_saved_js_error(jsj_env->jEnv, jsj_env->pending_js_errors);
}

/*
 * This is called upon returning from JS back into Java.  Any JS errors
 * reported during that time will be converted into Java exceptions.  It's
 * possible that a JS error was actually triggered by Java at some point, in
 * which case the original Java exception is thrown.
 */
static void
throw_any_pending_js_error_as_a_java_exception(JSJavaThreadState *jsj_env)
{
    CapturedJSError *error;
    JNIEnv *jEnv;
    jstring message_jstr, linebuf_jstr, filename_jstr;
    jint index, lineno;
    JSErrorReport *report;
    jthrowable java_exception;

    /* Get the Java JNI environment */
    jEnv = jsj_env->jEnv;

    if (!jsj_env->pending_js_errors) {
#ifdef DEBUG
        /* Any exception should be cleared as soon as it's detected, so there
           shouldn't be any pending. */
        if ((*jEnv)->ExceptionOccurred(jEnv)) {
            /* A Java exception occurred, but nobody in JS-land noticed. */
            PR_ASSERT(0);
            (*jEnv)->ExceptionClear(jEnv);
        }
#endif
        return;
    }

    /* Get the deepest (oldest) saved JS error */
    /* XXX - What's the right thing to do about newer errors ?
       For now we just throw them away */
    error = jsj_env->pending_js_errors;
    while (error->next)
        error = error->next;
    
    /*
     * If the JS error was originally the result of a Java exception, rethrow
     * the original exception.
     */
    if (error->java_exception) {
        (*jEnv)->Throw(jEnv, error->java_exception);
        goto done;
    }

    /* Propagate any JS errors that did not originate as Java exceptions into
       Java as an instance of netscape.javascript.JSException */

    /* First, marshall the arguments to the JSException constructor */
    message_jstr = NULL;
    if (error->message) {
        message_jstr = (*jEnv)->NewStringUTF(jEnv, error->message);
        if (!message_jstr)
            goto out_of_memory;
    }

    report = &error->report;

    filename_jstr = NULL;
    if (report->filename) {
        filename_jstr = (*jEnv)->NewStringUTF(jEnv, report->filename);
        if (!filename_jstr)
            goto out_of_memory;
    }

    linebuf_jstr = NULL;
    if (report->linebuf) {
        linebuf_jstr = (*jEnv)->NewStringUTF(jEnv, report->linebuf);
        if (!linebuf_jstr)
            goto out_of_memory;
    }

    lineno = report->lineno;
    index = report->linebuf ? report->tokenptr - report->linebuf : 0;

    /* Call the JSException constructor */
    java_exception = (*jEnv)->NewObject(jEnv, njJSException, njJSException_JSException,
                                        message_jstr, filename_jstr, lineno, linebuf_jstr, index);
    if (!java_exception)
        goto out_of_memory;

    /* Throw the newly-created JSException */
    if ((*jEnv)->Throw(jEnv, java_exception) < 0) {
        PR_ASSERT(0);
        jsj_LogError("Couldn't throw JSException\n");
        goto done;
    }

    /*
     * Release local references to Java objects, since some JVMs seem reticent
     * about collecting them otherwise.
     */
    (*jEnv)->DeleteLocalRef(jEnv, message_jstr);
    (*jEnv)->DeleteLocalRef(jEnv, filename_jstr);
    (*jEnv)->DeleteLocalRef(jEnv, linebuf_jstr);
    (*jEnv)->DeleteLocalRef(jEnv, java_exception);

    goto done;

out_of_memory:
    /* No recovery possible */
    PR_ASSERT(0);
    jsj_LogError("Out of memory while attempting to throw JSException\n");

done:
    jsj_ClearPendingJSErrors(jsj_env);
}

/*
 * This function is called up returning from Java back into JS as a result of
 * a thrown netscape.javascript.JSException, which itself must have been caused
 * by a JS error when Java called into JS.  The original JS error is
 * reconstituted from the JSException and re-reported as a JS error.
 *
 * Returns JS_FALSE if an internal error occurs, JS_TRUE otherwise.
 */
JSBool
jsj_ReportUncaughtJSException(JSContext *cx, JNIEnv *jEnv, jthrowable java_exception)
{
    JSBool success;
    JSErrorReport report;
    const char *linebuf, *filename, *message, *tokenptr;
    jint lineno, token_index;
    jstring linebuf_jstr, filename_jstr, message_jstr;

    /* Initialize everything to NULL */
    memset(&report, 0, sizeof(JSErrorReport));
    success = JS_FALSE;
    filename_jstr = linebuf_jstr = NULL;
    filename = message = linebuf = tokenptr = NULL;

    lineno = (*jEnv)->GetIntField(jEnv, java_exception, njJSException_lineno);
    report.lineno = lineno;

    filename_jstr = (*jEnv)->GetObjectField(jEnv, java_exception, njJSException_filename);
    if ((*jEnv)->ExceptionOccurred(jEnv)) {
        jsj_UnexpectedJavaError(cx, jEnv, "Unable to access filename field of a JSException");
        goto done;
    }
    if (filename_jstr)
        filename = (*jEnv)->GetStringUTFChars(jEnv, filename_jstr, 0);
    report.filename = filename;

    linebuf_jstr = (*jEnv)->GetObjectField(jEnv, java_exception, njJSException_source);
    if ((*jEnv)->ExceptionOccurred(jEnv)) {
        jsj_UnexpectedJavaError(cx, jEnv, "Unable to access source field of a JSException");
        goto done;
    }
    if (linebuf_jstr)
        linebuf = (*jEnv)->GetStringUTFChars(jEnv, linebuf_jstr, 0);
    report.linebuf = linebuf;

    token_index = (*jEnv)->GetIntField(jEnv, java_exception, njJSException_lineno);
    report.tokenptr = linebuf + token_index;

    message_jstr = (*jEnv)->CallObjectMethod(jEnv, java_exception, jlThrowable_getMessage);
    if ((*jEnv)->ExceptionOccurred(jEnv)) {
        jsj_UnexpectedJavaError(cx, jEnv, "Unable to access message of a JSException");
        goto done;
    }
    if (message_jstr)
        message = (*jEnv)->GetStringUTFChars(jEnv, message_jstr, 0);

    js_ReportErrorAgain(cx, message, &report);

    success = JS_TRUE;

done:

    if (filename_jstr && filename)
        (*jEnv)->ReleaseStringUTFChars(jEnv, filename_jstr, filename);
    if (linebuf_jstr && linebuf)
        (*jEnv)->ReleaseStringUTFChars(jEnv, linebuf_jstr, linebuf);
    if (message_jstr && message)
        (*jEnv)->ReleaseStringUTFChars(jEnv, message_jstr, message);
    return success;
}



/*************** Utilities for calling JavaScript from Java *****************/

/*
 * This routine is called just prior to invoking any JS API function from
 * Java.  It performs a common set of chores, such as obtaining the LiveConnect
 * state for the invoking Java thread and determining the JSContext to be
 * used for this thread.
 *
 * Returns NULL on failure.
 */
static JSJavaThreadState *
enter_js(JNIEnv *jEnv, jobject java_wrapper_obj,
         JSContext **cxp, JSObject **js_objp, JSErrorReporter *old_error_reporterp)
{
    JSContext *cx;
    char *err_msg;
    JSObject *js_obj;
    JSJavaThreadState *jsj_env;

    cx = NULL;
    err_msg = NULL;

    /* Invoke callback, presumably used to implement concurrency constraints */
    if (JSJ_callbacks->enter_js_from_java) {
        if (!JSJ_callbacks->enter_js_from_java(&err_msg))
            goto entry_failure;
    }

    /* Check the JSObject pointer in the wrapper object for null while holding
       the JS lock to deal with shutdown issues. */
    if (js_objp) {
        js_obj = (JSObject *)((*jEnv)->GetIntField(jEnv, java_wrapper_obj, njJSObject_internal));
        PR_ASSERT(js_obj);
        if (!js_obj)
            goto error;
        *js_objp = js_obj;
    }

    /* Get the per-thread state corresponding to the current Java thread */
    jsj_env = jsj_MapJavaThreadToJSJavaThreadState(jEnv, &err_msg);
    if (!jsj_env)
        goto error;

    /* Get the JSContext that we're supposed to use for this Java thread */
    cx = jsj_env->cx;
    if (!cx) {
        /* We called spontaneously into JS from Java, rather than from JS into
           Java and back into JS.  Invoke a callback to obtain/create a
           JSContext for us to use. */
        if (JSJ_callbacks->map_jsj_thread_to_js_context) {
            cx = JSJ_callbacks->map_jsj_thread_to_js_context(jsj_env, &err_msg);
            if (!cx)
                goto error;
        } else {
            err_msg = PR_smprintf("Unable to find/create JavaScript execution "
                                  "context for JNI thread 0x%08x", jEnv);
            goto error;
        }
    }
    *cxp = cx;

    /*
     * Capture all JS error reports so that they can be thrown into the Java
     * caller as an instance of netscape.javascript.JSException.
     */
    *old_error_reporterp =
        JS_SetErrorReporter(cx, capture_js_error_reports_for_java);

    return jsj_env;

error:
    /* Invoke callback, presumably used to implement concurrency constraints */
    if (JSJ_callbacks->exit_js)
        JSJ_callbacks->exit_js();

entry_failure:
    if (err_msg) {
        if (cx)
            JS_ReportError(cx, err_msg);
        else
            jsj_LogError(err_msg);
        free(err_msg);
    }

    return NULL;
}

/*
 * This utility function is called just prior to returning into Java from JS.
 */
static JSBool
exit_js(JSContext *cx, JSJavaThreadState *jsj_env, JSErrorReporter original_reporter)
{
    JNIEnv *jEnv;

    /* Restore the JS error reporter */
    JS_SetErrorReporter(cx, original_reporter);

    jEnv = jsj_env->jEnv;

#ifdef DEBUG
    /* Any Java exceptions should have been noticed and reported already */
    if ((*jEnv)->ExceptionOccurred(jEnv)) {
        PR_ASSERT(0);
        jsj_LogError("Unhandled Java exception detected");
        return JS_FALSE;
    }
#endif

    /*
     * Convert reported JS errors to JSExceptions, unless the errors were
     * themselves the result of Java exceptions, in which case the original
     * Java exception is simply propagated.
     */
    throw_any_pending_js_error_as_a_java_exception(jsj_env);

    /* Invoke callback, presumably used to implement concurrency constraints */
    if (JSJ_callbacks->exit_js)
        JSJ_callbacks->exit_js();

    return JS_TRUE;
}


/* Get the JavaClassDescriptor that corresponds to java.lang.Object */
static JavaClassDescriptor *
get_jlObject_descriptor(JSContext *cx, JNIEnv *jEnv)
{
    /* The JavaClassDescriptor for java.lang.Object */
    static JavaClassDescriptor *jlObject_descriptor = NULL;

    if (!jlObject_descriptor)
        jlObject_descriptor = jsj_GetJavaClassDescriptor(cx, jEnv, jlObject);
    return jlObject_descriptor;
}

/****************** Implementation of methods of JSObject *******************/

#ifdef XP_MAC
#pragma export on
#endif

/*
 * Class:     netscape_javascript_JSObject
 * Method:    initClass
 * Signature: ()V
 */
JNIEXPORT void JNICALL
Java_netscape_javascript_JSObject_initClass(JNIEnv *jEnv, jclass java_class)
{
    init_js_obj_reflections_table();
}

/*
 * Class:     netscape_javascript_JSObject
 * Method:    getMember
 * Signature: (Ljava/lang/String;)Ljava/lang/Object;
 */
JNIEXPORT jobject JNICALL
Java_netscape_javascript_JSObject_getMember(JNIEnv *jEnv,
                                            jobject java_wrapper_obj,
                                            jstring property_name_jstr)
{
    JSContext *cx;
    JSObject *js_obj;
    jsval js_val;
    int dummy_cost;
    JSBool dummy_bool;
    const jchar *property_name_ucs2;
    jsize property_name_len;
    JSErrorReporter saved_reporter;
    jobject member;
    jboolean is_copy;
    JSJavaThreadState *jsj_env;
    
    jsj_env = enter_js(jEnv, java_wrapper_obj, &cx, &js_obj, &saved_reporter);
    if (!jsj_env)
        return NULL;

    property_name_ucs2 = NULL;
    if (!property_name_jstr) {
        JS_ReportError(cx, "illegal null member name");
        member = NULL;
        goto done;
    }

    /* Get the Unicode string for the JS property name */
    property_name_ucs2 = (*jEnv)->GetStringChars(jEnv, property_name_jstr, &is_copy);
    if (!property_name_ucs2) {
        PR_ASSERT(0);
        goto done;
    }
    property_name_len = (*jEnv)->GetStringLength(jEnv, property_name_jstr);
    
    if (!JS_GetUCProperty(cx, js_obj, property_name_ucs2, property_name_len, &js_val))
        goto done;

    jsj_ConvertJSValueToJavaObject(cx, jEnv, js_val, get_jlObject_descriptor(cx, jEnv),
                                   &dummy_cost, &member, &dummy_bool);

done:
    if (property_name_ucs2)
        (*jEnv)->ReleaseStringChars(jEnv, property_name_jstr, property_name_ucs2);
    if (!exit_js(cx, jsj_env, saved_reporter))
        return NULL;
    
    return member;
}

/*
 * Class:     netscape_javascript_JSObject
 * Method:    getSlot
 * Signature: (I)Ljava/lang/Object;
 */
JNIEXPORT jobject JNICALL
Java_netscape_javascript_JSObject_getSlot(JNIEnv *jEnv,
                                          jobject java_wrapper_obj,
                                          jint slot)
{
    JSContext *cx;
    JSObject *js_obj;
    jsval js_val;
    int dummy_cost;
    JSBool dummy_bool;
    JSErrorReporter saved_reporter;
    jobject member;
    JSJavaThreadState *jsj_env;
    
    jsj_env = enter_js(jEnv, java_wrapper_obj, &cx, &js_obj, &saved_reporter);
    if (!jsj_env)
        return NULL;
    

    if (!JS_GetElement(cx, js_obj, slot, &js_val))
        goto done;
    if (!jsj_ConvertJSValueToJavaObject(cx, jEnv, js_val, get_jlObject_descriptor(cx, jEnv),
                                        &dummy_cost, &member, &dummy_bool))
        goto done;

done:
    if (!exit_js(cx, jsj_env, saved_reporter))
        return NULL;
    
    return member;
}

/*
 * Class:     netscape_javascript_JSObject
 * Method:    setMember
 * Signature: (Ljava/lang/String;Ljava/lang/Object;)V
 */
JNIEXPORT void JNICALL
Java_netscape_javascript_JSObject_setMember(JNIEnv *jEnv,
                                            jobject java_wrapper_obj,
                                            jstring property_name_jstr,
                                            jobject java_obj)
{
    JSContext *cx;
    JSObject *js_obj;
    jsval js_val;
    const jchar *property_name_ucs2;
    jsize property_name_len;
    JSErrorReporter saved_reporter;
    jboolean is_copy;
    JSJavaThreadState *jsj_env;
    
    jsj_env = enter_js(jEnv, java_wrapper_obj, &cx, &js_obj, &saved_reporter);
    if (!jsj_env)
        return;
    
    property_name_ucs2 = NULL;
    if (!property_name_jstr) {
        JS_ReportError(cx, "illegal null member name");
        goto done;
    }

    /* Get the Unicode string for the JS property name */
    property_name_ucs2 = (*jEnv)->GetStringChars(jEnv, property_name_jstr, &is_copy);
    if (!property_name_ucs2) {
        PR_ASSERT(0);
        goto done;
    }
    property_name_len = (*jEnv)->GetStringLength(jEnv, property_name_jstr);
    
    if (!jsj_ConvertJavaObjectToJSValue(cx, jEnv, java_obj, &js_val))
        goto done;

    JS_SetUCProperty(cx, js_obj, property_name_ucs2, property_name_len, &js_val);

done:
    if (property_name_ucs2)
        (*jEnv)->ReleaseStringChars(jEnv, property_name_jstr, property_name_ucs2);
    exit_js(cx, jsj_env, saved_reporter);
}

/*
 * Class:     netscape_javascript_JSObject
 * Method:    setSlot
 * Signature: (ILjava/lang/Object;)V
 */
JNIEXPORT void JNICALL
Java_netscape_javascript_JSObject_setSlot(JNIEnv *jEnv,
                                          jobject java_wrapper_obj,
                                          jint slot,
                                          jobject java_obj)
{
    JSContext *cx;
    JSObject *js_obj;
    jsval js_val;
    JSErrorReporter saved_reporter;
    JSJavaThreadState *jsj_env;
    
    jsj_env = enter_js(jEnv, java_wrapper_obj, &cx, &js_obj, &saved_reporter);
    if (!jsj_env)
        return;
    
    if (!jsj_ConvertJavaObjectToJSValue(cx, jEnv, java_obj, &js_val))
        goto done;
    JS_SetElement(cx, js_obj, slot, &js_val);

done:
    exit_js(cx, jsj_env, saved_reporter);
}

/*
 * Class:     netscape_javascript_JSObject
 * Method:    removeMember
 * Signature: (Ljava/lang/String;)V
 */
JNIEXPORT void JNICALL
Java_netscape_javascript_JSObject_removeMember(JNIEnv *jEnv,
                                               jobject java_wrapper_obj,
                                               jstring property_name_jstr)
{
    JSContext *cx;
    JSObject *js_obj;
    jsval js_val;
    const jchar *property_name_ucs2;
    jsize property_name_len;
    JSErrorReporter saved_reporter;
    jboolean is_copy;
    JSJavaThreadState *jsj_env;
    
    jsj_env = enter_js(jEnv, java_wrapper_obj, &cx, &js_obj, &saved_reporter);
    if (!jsj_env)
        return;
    
    if (!property_name_jstr) {
        JS_ReportError(cx, "illegal null member name");
        goto done;
    }
    /* Get the Unicode string for the JS property name */
    property_name_ucs2 = (*jEnv)->GetStringChars(jEnv, property_name_jstr, &is_copy);
    if (!property_name_ucs2) {
        PR_ASSERT(0);
        goto done;
    }
    property_name_len = (*jEnv)->GetStringLength(jEnv, property_name_jstr);
    
    JS_DeleteUCProperty2(cx, js_obj, property_name_ucs2, property_name_len, &js_val);

    (*jEnv)->ReleaseStringChars(jEnv, property_name_jstr, property_name_ucs2);

done:
    exit_js(cx, jsj_env, saved_reporter);
    return;
}

/*
 * Class:     netscape_javascript_JSObject
 * Method:    call
 * Signature: (Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/Object;
 */
JNIEXPORT jobject JNICALL
Java_netscape_javascript_JSObject_call(JNIEnv *jEnv, jobject java_wrapper_obj,
                                       jstring function_name_jstr, jobjectArray java_args)
{
    int i, argc, arg_num;
    jsval *argv;
    JSContext *cx;
    JSObject *js_obj;
    jsval js_val, function_val;
    int dummy_cost;
    JSBool dummy_bool;
    const jchar *function_name_ucs2;
    jsize function_name_len;
    JSErrorReporter saved_reporter;
    jboolean is_copy;
    jobject result;
    JSJavaThreadState *jsj_env;
    
    jsj_env = enter_js(jEnv, java_wrapper_obj, &cx, &js_obj, &saved_reporter);
    if (!jsj_env)
        return NULL;
    
    function_name_ucs2 = NULL;
    result = NULL;
    if (!function_name_jstr) {
        JS_ReportError(cx, "illegal null JavaScript function name");
        goto done;
    }

    /* Get the function name to eval as raw Unicode characters */
    function_name_ucs2 = (*jEnv)->GetStringChars(jEnv, function_name_jstr, &is_copy);
    if (!function_name_ucs2) {
        PR_ASSERT(0);
        goto done;
    }
    function_name_len = (*jEnv)->GetStringLength(jEnv, function_name_jstr);
    
    /* Allocate space for JS arguments */
    if (java_args) {
        argc = (*jEnv)->GetArrayLength(jEnv, java_args);
        argv = (jsval*)JS_malloc(cx, argc * sizeof(jsval));
    } else {
        argc = 0;
        argv = 0;
    }

    /* Convert arguments from Java to JS values */
    for (arg_num = 0; arg_num < argc; arg_num++) {
        jobject arg = (*jEnv)->GetObjectArrayElement(jEnv, java_args, arg_num);

        if (!jsj_ConvertJavaObjectToJSValue(cx, jEnv, arg, &argv[arg_num]))
            goto cleanup_argv;
        JS_AddRoot(cx, &argv[arg_num]);
    }

    if (!JS_GetUCProperty(cx, js_obj, function_name_ucs2, function_name_len,
                          &function_val))
        goto cleanup_argv;

    if (!JS_CallFunctionValue(cx, js_obj, function_val, argc, argv, &js_val))
        goto cleanup_argv;

    jsj_ConvertJSValueToJavaObject(cx, jEnv, js_val, get_jlObject_descriptor(cx, jEnv),
                                   &dummy_cost, &result, &dummy_bool);

cleanup_argv:
    if (argv) {
        for (i = 0; i < arg_num; i++)
            JS_RemoveRoot(cx, &argv[i]);
        JS_free(cx, argv);
    }

done:
    if (function_name_ucs2)
        (*jEnv)->ReleaseStringChars(jEnv, function_name_jstr, function_name_ucs2);
    if (!exit_js(cx, jsj_env, saved_reporter))
        return NULL;
    
    return result;
}

/*
 * Class:     netscape_javascript_JSObject
 * Method:    eval
 * Signature: (Ljava/lang/String;)Ljava/lang/Object;
 */
JNIEXPORT jobject JNICALL
Java_netscape_javascript_JSObject_eval(JNIEnv *jEnv,
                                       jobject java_wrapper_obj,
                                       jstring eval_jstr)
{
    const char *codebase;
    JSPrincipals *principals;
    JSContext *cx;
    JSBool eval_succeeded;
    JSObject *js_obj;
    jsval js_val;
    int dummy_cost;
    JSBool dummy_bool;
    const jchar *eval_ucs2;
    jsize eval_len;
    JSErrorReporter saved_reporter;
    jboolean is_copy;
    jobject result;
    JSJavaThreadState *jsj_env;
    
    jsj_env = enter_js(jEnv, java_wrapper_obj, &cx, &js_obj, &saved_reporter);
    if (!jsj_env)
        return NULL;
    
    result = NULL;
    eval_ucs2 = NULL;
    if (!eval_jstr) {
        JS_ReportError(cx, "illegal null string eval argument");
        goto done;
    }

    /* Get the string to eval as raw Unicode characters */
    eval_ucs2 = (*jEnv)->GetStringChars(jEnv, eval_jstr, &is_copy);
    if (!eval_ucs2) {
        PR_ASSERT(0);
        goto done;
    }
    eval_len = (*jEnv)->GetStringLength(jEnv, eval_jstr);
    
    /* Set up security stuff */
    principals = NULL;
    if (JSJ_callbacks->get_JSPrincipals_from_java_caller)
        principals = JSJ_callbacks->get_JSPrincipals_from_java_caller(jEnv);
    codebase = principals ? principals->codebase : NULL;

    /* Have the JS engine evaluate the unicode string */
    eval_succeeded = JS_EvaluateUCScriptForPrincipals(cx, js_obj, principals,
                                                      eval_ucs2, eval_len,
                                                      codebase, 0, &js_val);
    if (!eval_succeeded)
        goto done;

    /* Convert result to a subclass of java.lang.Object */
    jsj_ConvertJSValueToJavaObject(cx, jEnv, js_val, get_jlObject_descriptor(cx, jEnv),
                                   &dummy_cost, &result, &dummy_bool);

done:
    if (eval_ucs2)
        (*jEnv)->ReleaseStringChars(jEnv, eval_jstr, eval_ucs2);
    if (!exit_js(cx, jsj_env, saved_reporter))
        return NULL;
    
    return result;
}

/*
 * Class:     netscape_javascript_JSObject
 * Method:    toString
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL
Java_netscape_javascript_JSObject_toString(JNIEnv *jEnv,
                                           jobject java_wrapper_obj)
{
    jstring result;
    JSContext *cx;
    JSObject *js_obj;
    JSString *jsstr;
    JSErrorReporter saved_reporter;
    JSJavaThreadState *jsj_env;
    
    jsj_env = enter_js(jEnv, java_wrapper_obj, &cx, &js_obj, &saved_reporter);
    if (!jsj_env)
        return NULL;
    
    result = NULL;
    jsstr = JS_ValueToString(cx, OBJECT_TO_JSVAL(js_obj));
    if (jsstr)
        result = jsj_ConvertJSStringToJavaString(cx, jEnv, jsstr);
    if (!result)
        result = (*jEnv)->NewStringUTF(jEnv, "*JavaObject*");

    if (!exit_js(cx, jsj_env, saved_reporter))
        return NULL;
    
    return result;
}

/*
 * Class:     netscape_javascript_JSObject
 * Method:    getWindow
 * Signature: (Ljava/applet/Applet;)Lnetscape/javascript/JSObject;
 */
JNIEXPORT jobject JNICALL
Java_netscape_javascript_JSObject_getWindow(JNIEnv *jEnv,
                                            jclass js_object_class,
                                            jobject java_applet_obj)
{
    char *err_msg;
    JSContext *cx;
    JSObject *js_obj;
    jsval js_val;
    int dummy_cost;
    JSBool dummy_bool;
    JSErrorReporter saved_reporter;
    jobject java_obj;
    JSJavaThreadState *jsj_env;
    
    jsj_env = enter_js(jEnv, NULL, &cx, NULL, &saved_reporter);
    if (!jsj_env)
        return NULL;
    
    err_msg = NULL;
    java_obj = NULL;
    js_obj = JSJ_callbacks->map_java_object_to_js_object(jEnv, java_applet_obj, &err_msg);
    if (!js_obj) {
        if (err_msg) {
            JS_ReportError(cx, err_msg);
            free(err_msg);
        }
        goto done;
    }
    js_val = OBJECT_TO_JSVAL(js_obj);
    jsj_ConvertJSValueToJavaObject(cx, jEnv, js_val, get_jlObject_descriptor(cx, jEnv),
                                   &dummy_cost, &java_obj, &dummy_bool);
done:
    if (!exit_js(cx, jsj_env, saved_reporter))
        return NULL;
    
    return java_obj;
}

/*
 * Class:     netscape_javascript_JSObject
 * Method:    finalize
 * Signature: ()V
 */
JNIEXPORT void JNICALL
Java_netscape_javascript_JSObject_finalize(JNIEnv *jEnv, jobject java_wrapper_obj)
{
    JSContext *cx;
    JSErrorReporter saved_reporter;
    JSJavaThreadState *jsj_env;
    JSObject *js_obj;
    
    jsj_env = enter_js(jEnv, java_wrapper_obj, &cx, &js_obj, &saved_reporter);
    if (!jsj_env)   /* Note: memory leak if we exit here */
        return;
    remove_js_obj_reflection_from_hashtable(cx, js_obj);
    exit_js(cx, jsj_env, saved_reporter);
}

