/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 *
 * The contents of this file are subject to the Netscape Public License
 * Version 1.0 (the "NPL"); you may not use this file except in
 * compliance with the NPL.  You may obtain a copy of the NPL at
 * http://www.mozilla.org/NPL/
 *
 * Software distributed under the NPL is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
 * for the specific language governing rights and limitations under the
 * NPL.
 *
 * The Initial Developer of this code under the NPL is Netscape
 * Communications Corporation.  Portions created by Netscape are
 * Copyright (C) 1998 Netscape Communications Corporation.  All Rights
 * Reserved.
 */
/* ** */
/*
 * JS reflection of Java objects and vice versa
 *
 *   javapackage is a java namespace
 *   java is a java class or object
 *   javaarray is a java array
 *   javaslot represents an object+name that may resolve to
 *     either a field or a method depending on later usage.
 *
 *   netscape.javascript.JSObject is a java reflection of a JS object
 */

#ifdef JAVA

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

/* java joy */
#include "oobj.h"
#include "interpreter.h"
#include "tree.h"
#include "opcodes.h"
#include "javaString.h"
#include "exceptions.h"
#include "jri.h"

/* nspr stuph */
#ifndef NSPR20
#include "prhash.h"
#else
#include "plhash.h"
#endif
#include "prmon.h"      /* for PR_XLock and PR_XUNlock */
#include "prlog.h"
#include "prprf.h"
#include "prgc.h"

#include "jsapi.h"
#include "jsjava.h"
#include "jscntxt.h"    /* for cx->savedErrors */
#include "jsscript.h"   /* for script->javaData */
#include "jsobj.h"      /* for OBJ_GET_SLOT and JSSLOT_PRIVATE */
#include "jsfun.h"      /* for JSFUN_BOUND_METHOD */
#include "jslock.h"     /* for JS_LOCK_RUNTIME/JS_UNLOCK_RUNTIME */

#ifdef MKLINUX
#define VARARGS_ASSIGN(foo, bar) foo[0] = bar[0]
#else
#define VARARGS_ASSIGN(foo, bar) (foo) = (bar)
#endif /*MKLINUX*/

#ifndef NSPR20
#define WAIT_FOREVER LL_MAXINT
#else
#define WAIT_FOREVER PR_INTERVAL_NO_TIMEOUT
/* PR_LOG hacks */
#define debug PR_LOG_MAX
#define error PR_LOG_ERROR
#define warn PR_LOG_WARNING
#endif

/*
 * "exception" is defined by some Windows OS headers.
 * Redefine it for our own purposes.
 */
#ifdef exception
#undef exception
#endif

/*
 * we don't have access to the classJavaLangObject from the java
 * runtime dll, so we need a different version of this macro
 */
static ClassClass *js_classJavaLangObject = 0;
#undef obj_array_classblock
#define obj_array_classblock(obj) \
    ((obj_flags((obj)) == T_NORMAL_OBJECT) ? (obj)->methods->classdescriptor \
                                           : ObjectClassBlock)

/* JSObject generated header */
#define IMPLEMENT_netscape_javascript_JSObject
#include "netscape_javascript_JSObject.h"
#define IMPLEMENT_netscape_javascript_JSException
#ifdef XP_MAC
#include "n_javascript_JSException.h"
#else
#include "netscape_javascript_JSException.h"
#endif
#include "java_lang_Throwable.h"

/*
 * types of reflected java objects
 */

/* a package is basically just a name, since the jdk doesn't
 * allow you to find out whether a particular name represents
 * a package or not */
typedef struct JSJavaPackage JSJavaPackage;

/* either a java class or a java object.
 *   class.field is a static field or method
 *   class(...) constructs an object that is an instance of the class
 *   object.field is a field or method */
typedef struct JSJava JSJava;

/* type associated with a JSJava structure */
/* the JSClass java_class uses OBJECT and CLASS
 * javaarray_class uses ARRAY */
typedef enum JSJavaType {
    JAVA_UNDEF,
    JAVA_OBJECT,
    JAVA_CLASS,
    JAVA_ARRAY
} JSJavaType;

/* JSJClassData holds the info necessary for js to represent
 * itself to java.  When js calls java, it leaves a call to
 * the method referred to by mb on the java stack.  Each
 * JSScript that calls java contains its own copy of this,
 * though the classloader and class may be shared. */
typedef struct {
    int nrefs;
    jglobal loader;
    jglobal clazz;
    struct methodblock *mb;
} JSJClassData;

/* fields and methods are initially represented with a slot object:
 * this allows us to cope with fields and methods that have the same
 * name.  if the slot is used in a function context it will call the
 * appropriate method (dynamically choosing between overloaded methods).
 * if it is used in any other context it will convert itself to the
 * value of the field at the time it was looked up. */
typedef struct JSJavaSlot JSJavaSlot;

/*
 * globals for convenience, set up in the init routine
 */
static ClassClass * ObjectClassBlock = 0;
static ClassClass * JSObjectClassBlock = 0;
static ClassClass * JSExceptionClassBlock = 0;
static ClassClass * StringClassBlock = 0;
static ClassClass * BooleanClassBlock = 0;
static ClassClass * DoubleClassBlock = 0;
static ClassClass * ThrowableClassBlock = 0;
static JRIFieldID JSObjectInternalField;

/* note that these hash tables are only stable because we
 * use a non-moving garbage collector! */

/*
 * this is used to ensure that there is at most one reflection
 * of any java object.	objects are keyed by handle, classes are
 * keyed by classblock pointer.  the value for each is the JS
 * object reflection.  there is a root finder registered with the
 * gc that marks all the java objects (keys).  the JS objects
 * are not in the GC's root set, and are responsible for removing
 * themselves from the table upon finalization.
 */
static PRHashTable *javaReflections = NULL;
static PRMonitor *javaReflectionsMonitor = 0;
/* this jsContext is used when scanning the reflection table */

/*
 * similarly for java reflections of JS objects - in this case
 * the keys are JS objects.  when the corresponding java instance
 * is finalized, the entry is removed from the table, and a GC
 * root for the JS object is removed.
 */
static PRHashTable *jsReflections = NULL;
static PRMonitor *jsReflectionsMonitor = 0;

/*
 * Because of the magic of windows DLLs we need to get this passed in
 */
static JSJCallbacks *jsj_callbacks = NULL;

/*
 * we keep track of the "main" runtime in order to use it for
 * finalization (see js_RemoveReflection).  this depends on there
 * being only one JSRuntime which can run java, which may not be
 * true eventually.
 */
JSRuntime *finalizeRuntime = 0;

#ifndef NSPR20
#ifdef MOZILLA_CLIENT
PR_LOG_DEFINE(MojaSrc);
#else
PRLogModuleInfo *MojaSrc;
#endif
#else
PRLogModuleInfo *MojaSrc = NULL;
#endif

/* lazy initialization */
static void jsj_FinishInit(JSContext *cx, JRIEnv *env);

/* check if a field is static/nonstatic */
/* this now accepts static fields for non-static references */
#define CHECK_STATIC(isStatic, fb) (((fb)->access & ACC_STATIC) \
				    ? (isStatic) : !(isStatic))

/* forward declarations */
typedef void (*JSJCallback)(void*);

static JSBool
js_CallJava(JSContext *cx, JSJCallback doit, void *d, JSBool pushSafeFrame);

static JSBool
js_FindSystemClass(JSContext *cx, char *name, ClassClass **clazz);

static ClassClass *
js_FindJavaClass(JSContext *cx, char *name, ClassClass *from);

static JSBool
js_ExecuteJavaMethod(JSContext *cx, void *raddr, size_t rsize,
		     HObject *ho, char *name, char *sig,
		     struct methodblock *mb, JSBool isStaticCall, ...);

static HObject *
js_ConstructJava(JSContext *cx, char *name, ClassClass *cb, char *sig, ...);

static HObject *
js_ConstructJavaPrivileged(JSContext *cx, char *name, ClassClass *cb,
			   char *sig, ...);

static JSObject *
js_ReflectJava(JSContext *cx, JSJavaType type, HObject *handle,
	       ClassClass *cb, char *sig, JSObject *useMe);

static JSBool
js_reflectJavaSlot(JSContext *cx, JSObject *obj, JSString *str, jsval *vp);

static JSBool
js_javaMethodWrapper(JSContext *cx, JSObject *obj,
		     PRUintn argc, jsval *argv, jsval *rval);

static JSBool
js_javaConstructorWrapper(JSContext *cx, JSObject *obj,
			  PRUintn argc, jsval *argv, jsval *rval);

static JSBool
js_JArrayElementType(HObject *handle, char **sig, ClassClass **classp);

static JSBool
js_convertJSValueToJSObject(JSContext *cx, JSObject *mo, ClassClass *paramcb,
			    JSBool checkOnly, HObject **objp);

static JSBool
js_convertJSValueToJElement(JSContext *cx, jsval v,
			    char *addr, char *sig, ClassClass *fromclass,
			    char **sigRest);

static JSBool
js_convertJSValueToJValue(JSContext *cx, jsval v,
			  OBJECT *addr, char *sig, ClassClass *fromclass,
			  JSBool checkOnly, char **sigRestPtr,
			  int *cost);

static JSBool
js_convertJSValueToJField(JSContext *cx, jsval v,
			  HObject *ho, struct fieldblock *fb);

static JSBool
js_convertJElementToJSValue(JSContext *cx, char *addr, char *sig,
			    jsval *v, JSType desired);

static JSBool
js_convertJValueToJSValue(JSContext *cx, OBJECT *addr, char *sig,
			  jsval *vp, JSType desired);

static JSBool
js_convertJFieldToJSValue(JSContext *cx, HObject *ho, struct fieldblock *fb,
			  jsval *vp, JSType desired);

static JSBool
js_convertJObjectToJSString(JSContext *cx, HObject *ho, JSBool isClass,
			    jsval *vp);

static JSBool
js_convertJObjectToJSNumber(JSContext *cx, HObject *ho, JSBool isClass,
			    jsval *vp);

static JSBool
js_convertJObjectToJSBoolean(JSContext *cx, HObject *ho, JSBool isClass,
			     jsval *vp);

static JSJClassData *
jsj_MakeClassData(JSContext *cx);

static void
jsj_DestroyClassData(JSContext *cx, JSJClassData *data);

static JSBool
js_pushSafeFrame(JSContext *cx, ExecEnv *ee, JSJClassData **classData);

static void
js_popSafeFrame(JSContext *cx, ExecEnv *ee, JSJClassData *classData);

/* handy macro from agent.c */
#define obj_getoffset(o, off) (*(OBJECT *)((char *)unhand(o)+(off)))

/* Java threads call this to find the JSContext to use for
 * executing JavaScript */
PR_IMPLEMENT(int)
JSJ_IsEnabled()
{
    return jsj_callbacks->isEnabled();
}

/* Java threads call this to find the JSContext to use for
 * executing JavaScript */
PR_IMPLEMENT(JSContext *)
JSJ_CurrentContext(JRIEnv *env, char **errp)
{
    return jsj_callbacks->currentContext(env, errp);
}

/* Java threads call this before executing JS code (to preserve
 * run-to-completion in the client */
PR_IMPLEMENT(void)
JSJ_EnterJS(void)
{
    jsj_callbacks->enterJS();
}

/* Java threads call this when finished executing JS code */
PR_IMPLEMENT(void)
JSJ_ExitJS(void)
{
    jsj_callbacks->exitJS();
}

/* Java threads call this when finished executing JS code */
PR_IMPLEMENT(JSObject *)
JSJ_GetDefaultObject(JRIEnv *env, jobject hint)
{
    return jsj_callbacks->getDefaultObject(env, hint);
}

static ExecEnv *
jsj_GetCurrentEE(JSContext *cx)
{
    JRIEnv *env = 0;
    static int js_java_initialized = 0;

    if (js_java_initialized != 2) {
	/* use a cached monitor to make sure that we only
	 * initialize once */
	PR_CEnterMonitor(&js_java_initialized);
	switch (js_java_initialized) {
          case 0:   /* we're first */
	    js_java_initialized = 1;
	    PR_CExitMonitor(&js_java_initialized);

	    /* force both Java and JS to be initialized */
	    env = jsj_callbacks->currentEnv(cx);

            if (!env)
                goto out;

	    /* initialize the hash tables and classes we need */
	    jsj_FinishInit(cx, env);

	    PR_CEnterMonitor(&js_java_initialized);
	    js_java_initialized = 2;
	    PR_CNotifyAll(&js_java_initialized);
	    break;
	  case 1:   /* in progress */
	    PR_CWait(&js_java_initialized, WAIT_FOREVER);
	    break;
	  case 2:   /* done */
	    break;
	}
	PR_CExitMonitor(&js_java_initialized);
    }

    if (!env)
	env = jsj_callbacks->currentEnv(cx);

  out:
    if (!env)
        JS_ReportError(cx, "unable to get Java execution context for JavaScript");

    return (ExecEnv *) env;
}

/****	****	****	****	****	****	****	****	****
 *
 *   java packages are just strings
 *
 ****	****	****	****	****	****	****	****	****/

struct JSJavaPackage {
    char *name;         /* e.g. "java/lang" or 0 if it's the top level */
};

/* javapackage uses standard getProperty */

static JSBool
javapackage_setProperty(JSContext *cx, JSObject *obj, jsval slot, jsval *vp)
{
    JSJavaPackage *package = JS_GetPrivate(cx, obj);

    JS_ReportError(cx, "%s doesn't refer to any Java value",
		   package->name);
    return JS_FALSE;
}

static JSBool
javapackage_enumerate(JSContext *cx, JSObject *obj)
{
    /* FIXME can't do this without reading directories... */
    return JS_TRUE;
}

/* forward declaration */
static JSBool
javapackage_resolve(JSContext *cx, JSObject *obj, jsval id);

static JSBool
javapackage_convert(JSContext *cx, JSObject *obj, JSType type, jsval *vp)
{
    JSJavaPackage *package = JS_GetPrivate(cx, obj);
    JSString *str;
    char *name, *cp;

    switch (type) {
    case JSTYPE_STRING:
        /* convert '/' to '.' so it looks like the entry syntax */
	if (!package->name)
	    break;
        name = PR_smprintf("[JavaPackage %s]", package->name);
	if (!name) {
	    JS_ReportOutOfMemory(cx);
	    return JS_FALSE;
	}
        for (cp = name; *cp != '\0'; cp++)
            if (*cp == '/')
                *cp = '.';
	str = JS_NewString(cx, name, strlen(name));
	if (!str) {
	    free(name);
	    JS_ReportOutOfMemory(cx);
	    return JS_FALSE;
	}

	*vp = STRING_TO_JSVAL(str);
	break;
    case JSTYPE_OBJECT:
	*vp = OBJECT_TO_JSVAL(obj);
	break;
    default:
	break;
    }
    return JS_TRUE;
}

static void
javapackage_finalize(JSContext *cx, JSObject *obj)
{
    JSJavaPackage *package = JS_GetPrivate(cx, obj);

    /* get rid of the private data */
    if (package->name)
	JS_free(cx, package->name);
    JS_free(cx, package);
}

static JSClass javapackage_class = {
    "JavaPackage",
    JSCLASS_HAS_PRIVATE,
    JS_PropertyStub, JS_PropertyStub,
    JS_PropertyStub, javapackage_setProperty, javapackage_enumerate,
    javapackage_resolve, javapackage_convert, javapackage_finalize
};

/* needs pointer to javapackage_class */
static JSBool
javapackage_resolve(JSContext *cx, JSObject *obj, jsval id)
{
    JSJavaPackage *package = JS_GetPrivate(cx, obj);
    char *fullname;
    char *name;
    int namelen;
    ClassClass *cb;
    JSObject *mo;
    JSJClassData *classData;
    JRIEnv *env;

    if (!JSVAL_IS_STRING(id))
	return JS_TRUE;

    name = JS_GetStringBytes(JSVAL_TO_STRING(id));
    namelen = JS_GetStringLength(JSVAL_TO_STRING(id));

    if (package->name) {
	int packagelen = strlen(package->name);
	fullname = JS_malloc(cx, packagelen + namelen + 2);
	if (!fullname)
	    return JS_FALSE;
	strcpy(fullname, package->name);
        fullname[packagelen] = '/';
	strcpy(fullname + packagelen + 1, name);
    } else {
	fullname = JS_malloc(cx, namelen + 1);
	if (!fullname)
	    return JS_FALSE;
	strcpy(fullname, name);
    }

    PR_LOG(MojaSrc, debug, ("looking for java class \"%s\"", fullname));

    /* get the class whose classloader we use to find more classes */
    if (!jsj_callbacks->jsClassLoader) {
        classData = NULL;
    } else if (!(classData = jsj_MakeClassData(cx))) {
    	JS_free(cx, fullname);
	return JS_FALSE;
    }

    env = (JRIEnv *) jsj_GetCurrentEE(cx);
    if (!env) {
	JS_free(cx, fullname);
	return JS_FALSE;
    }

    /* see if the name is a class */
    cb = js_FindJavaClass(cx, fullname,
			  classData
                          ? JRI_GetGlobalRef(env, classData->clazz)
                          : NULL);

    if (jsj_callbacks->jsClassLoader)
        jsj_DestroyClassData(cx, classData);

    /* if not, it's a package */
    if (!cb) {
	JSJavaPackage *newpackage = JS_malloc(cx, sizeof(JSJavaPackage));
	if (!newpackage) {
	    JS_free(cx, fullname);
	    return JS_FALSE;
	}
        PR_LOG(MojaSrc, debug, ("creating package %s", fullname));
	newpackage->name = fullname;
	mo = JS_NewObject(cx, &javapackage_class, 0, 0); /* FIXME proto and parent ok? */
	if (!mo) {
	    JS_free(cx, fullname);
	    JS_free(cx, newpackage);
	    return JS_FALSE;
	}
	JS_SetPrivate(cx, mo, newpackage);
    } else {
	/* reflect the Class object */
	mo = js_ReflectJClassToJSObject(cx, cb);
	if (!mo) {
	    /* FIXME error message? */
	    return JS_FALSE;
	}
	JS_free(cx, fullname);
    }

    return JS_DefineProperty(cx, obj, name, OBJECT_TO_JSVAL(mo),
			     0, 0, JSPROP_READONLY);
}

/****	****	****	****	****	****	****	****	****/

struct JSJava {
    JSJavaType	type;			/* object / array / class */
    HObject	*handle;		/* handle to the java Object */
    ClassClass	*cb;			/* classblock, or element cb if array */
    char	*signature;		/* array element signature */
};

static struct fieldblock *
java_lookup_field(JSContext *cx, ClassClass *cb, JSBool isStatic,
		  const char *name)
{
    while (cb) {
	int i;
	for (i = 0; i < (int)cbFieldsCount(cb); i++) {
	    struct fieldblock *fb = cbFields(cb) + i;
	    if (CHECK_STATIC(isStatic, fb)
		&& !strcmp(fieldname(fb), name)) {
		return fb;
	    }
	}
	/* check the parent */
	if (cbSuperclass(cb))
	    cb = cbSuperclass(cb);
	else
	    cb = 0;
    }

    return 0;
}

static struct fieldblock *
java_lookup_name(JSContext *cx, ClassClass *cb, JSBool isStatic,
		 const char *name)
{
    while (cb) {
	int i;
	for (i = 0; i < (int)cbFieldsCount(cb); i++) {
	    struct fieldblock *fb = cbFields(cb) + i;
	    if (CHECK_STATIC(isStatic, fb)
		&& !strcmp(fieldname(fb), name)) {
		return fb;
	    }
	}
	for (i = cbMethodsCount(cb); i--;) {
	    struct methodblock *mb = cbMethods(cb) + i;
	    if (CHECK_STATIC(isStatic, &mb->fb)
		&& !strcmp(fieldname(&mb->fb), name)) {
		return &mb->fb;
	    }
	}
	/* check the parent */
	if (cbSuperclass(cb))
	    cb = cbSuperclass(cb);
	else
	    cb = 0;
    }
    return 0;
}

static JSBool
java_getProperty(JSContext *cx, JSObject *obj, jsval slot, jsval *vp)
{
    JSString *str;
    JSJava *java = JS_GetPrivate(cx, obj);

    if (!java) {
        JS_ReportError(cx, "illegal operation on Java prototype object");
	return JS_FALSE;
    }

    /* FIXME reflect a getter/setter pair as a property! */

    if (!JSVAL_IS_STRING(slot)) {
        JS_ReportError(cx, "invalid Java property expression");
	return JS_FALSE;
    }

    str = JSVAL_TO_STRING(slot);
    PR_LOG(MojaSrc, debug, ("looked up slot \"%s\"", JS_GetStringBytes(str)));

    /* utter hack so that the assign hack doesn't kill us */
    if (slot == STRING_TO_JSVAL(ATOM_TO_STRING(cx->runtime->atomState.assignAtom))) {
	*vp = JSVAL_VOID;
	return JS_TRUE;
    }

    return js_reflectJavaSlot(cx, obj, str, vp);
}

static JSBool
java_setProperty(JSContext *cx, JSObject *obj, jsval slot, jsval *vp)
{
    JSJava *java = JS_GetPrivate(cx, obj);
    ClassClass *cb;
    struct fieldblock *fb = 0;
    JSString *str;
    const char *name;

    if (!java) {
        JS_ReportError(cx, "illegal operation on Java prototype object");
	return JS_FALSE;
    }

    cb = java->cb;

    if (!JSVAL_IS_STRING(slot)) {
        JS_ReportError(cx, "invalid Java property assignment");
	return JS_FALSE;
    }

    str = JSVAL_TO_STRING(slot);
    name = JS_GetStringBytes(str);
    PR_LOG(MojaSrc, debug, ("looked up slot \"%s\"", name));

    fb = java_lookup_field(cx, cb, java->type == JAVA_CLASS, name);
    if (!fb) {
        JS_ReportError(cx, "no Java %sfield found with name %s",
                          (java->type == JAVA_CLASS ? "static " : ""),
			  name);
	return JS_FALSE;
    }

    if (!js_convertJSValueToJField(cx, *vp, java->handle, fb)) {
        JS_ReportError(cx, "can't set Java field %s", name);
	return JS_FALSE;
    }
    return JS_TRUE;
}

static JSBool
java_enumerate_property(JSContext *cx, JSObject *obj, struct fieldblock *fb)
{
    JSJava *java = JS_GetPrivate(cx, obj);
    jsval junk;

    PR_ASSERT(java->type == JAVA_OBJECT || java->type == JAVA_CLASS);
    if (!(fb->access & ACC_PUBLIC) ||
	!CHECK_STATIC(java->type == JAVA_CLASS, fb)) {
	return JS_TRUE;
    }

    return JS_GetProperty(cx, obj, fieldname(fb), &junk);
/*  FIXME this is what i would prefer, but it seems the get is necessary
    return JS_DefineProperty(cx, obj, fieldname(fb), JSVAL_VOID,
			     0, 0, JSPROP_ENUMERATE);
 */
}

static JSBool
java_enumerate(JSContext *cx, JSObject *obj)
{
    JSJava *java = JS_GetPrivate(cx, obj);
    ClassClass *cb;

    if (!java)
	return JS_TRUE;

    cb = java->cb;

    while (cb) {
	int i;
	for (i = 0; i < (int)cbFieldsCount(cb); i++) {
	    struct fieldblock *fb, *outerfb;

	    fb = cbFields(cb) + i;
	    outerfb = java_lookup_name(cx, java->cb, java->type == JAVA_CLASS,
					fieldname(fb));
	    if (outerfb && outerfb != fb)
		continue;

	    if (!java_enumerate_property(cx, obj, fb))
		return JS_FALSE;
	}
	for (i = cbMethodsCount(cb); i--;) {
	    struct methodblock *mb;
	    struct fieldblock *outerfb;

	    mb = cbMethods(cb) + i;
	    outerfb = java_lookup_name(cx, java->cb, java->type == JAVA_CLASS,
				       fieldname(&mb->fb));
	    if (outerfb && outerfb != &mb->fb)
		continue;

	    if (!java_enumerate_property(cx, obj, &mb->fb))
		return JS_FALSE;
	}
	/* check the parent */
	if (cbSuperclass(cb))
	    cb = cbSuperclass(cb);
	else
	    cb = 0;
    }
    return JS_TRUE;
}

PR_STATIC_CALLBACK(JSBool)
java_resolve(JSContext *cx, JSObject *obj, jsval id)
{
    JSJava *java = JS_GetPrivate(cx, obj);
    char *name;
    jsval v;
    JSErrorReporter oldReporter;
    JSBool hasProperty;

    if (!java || !JSVAL_IS_STRING(id))
	return JS_TRUE;

    name = JS_GetStringBytes(JSVAL_TO_STRING(id));
    PR_LOG(MojaSrc, debug, ("resolve(0x%x, %s) (handle 0x%x)",
			 obj, name, java->handle));

    oldReporter = JS_SetErrorReporter(cx, NULL);
    hasProperty = java_getProperty(cx, obj, id, &v);
    JS_SetErrorReporter(cx, oldReporter);
    if (!hasProperty)
        return JS_TRUE;

    return JS_DefineProperty(cx, obj, name, v, 0, 0, JSPROP_ENUMERATE);
}

PR_STATIC_CALLBACK(PRHashNumber)
java_hashHandle(void *key)
{
    PRHashNumber num = (PRHashNumber) key ;	/* help lame MSVC1.5 on Win16 */
    /* win16 compiler can't shift right by 2, but it will do it by 1 twice. */
    num = num >> 1;
    return num >> 1;
}

static int
java_pointerEq(void *v1, void *v2)
{
    return v1 == v2;
}

PR_STATIC_CALLBACK(void)
java_finalize(JSContext *cx, JSObject *obj)
{
    JSJava *java = JS_GetPrivate(cx, obj);
    void *key;
    PRHashEntry *he, **hep;

    if (!java)
	return;

    /* remove it from the reflection table */
    if (java->type == JAVA_CLASS) {
        PR_LOG(MojaSrc, debug, ("removing class 0x%x from table", java->cb));
	key = cbName(java->cb);
    } else {
        PR_LOG(MojaSrc, debug, ("removing handle 0x%x from table", java->handle));
	key = java->handle;
    }
    PR_EnterMonitor(javaReflectionsMonitor);
    if (javaReflections) {
	hep = PR_HashTableRawLookup(javaReflections,
                                    java_hashHandle(key), key);
	he = *hep;
	if (he) {
	    PR_HashTableRawRemove(javaReflections, hep, he);
	}
    }
    PR_ExitMonitor(javaReflectionsMonitor);

    /* get rid of the private data */
    JS_free(cx, java);
}

PR_STATIC_CALLBACK(JSBool)
java_convert(JSContext *cx, JSObject *obj, JSType type, jsval *vp)
{
    JSJava *java = JS_GetPrivate(cx, obj);

    if (!java) {
	if (type == JSTYPE_OBJECT) {
	    *vp = OBJECT_TO_JSVAL(obj);
	    return JS_TRUE;
	}

        JS_ReportError(cx, "illegal operation on Java prototype object");
	return JS_FALSE;
    }

    PR_LOG(MojaSrc, debug, ("java_convert to %d", type));

    switch (type) {
    case JSTYPE_OBJECT:
	*vp = OBJECT_TO_JSVAL(obj);
	break;
    case JSTYPE_FUNCTION:
	/* only classes convert to functions (constructors) */
	if (java->type != JAVA_CLASS) {
	    /* random java objects do not convert to functions */
            JS_ReportError(cx, "can't convert Java object to function");
	    return JS_FALSE;
	} else {
	    JSString *str;
	    JSFunction *fun;
	    JSObject *funobj;
	    JSObject *globalobj;
	    jsval javaPrototype;

            PR_LOG(MojaSrc, debug, ("making a constructor\n"));

	    str = JS_NewStringCopyZ(cx, cbName(java->cb));
	    if (!str)
		return JS_FALSE;

	    fun = JS_NewFunction(cx, js_javaConstructorWrapper, 0, 0, 0,
				 JS_GetStringBytes(str));

	    /* FIXME a private property of the function object points to the
	     * classblock: gc problem? */
	    funobj = JS_GetFunctionObject(fun);
            if (!JS_DefineProperty(cx, funobj, "#javaClass",
				   PRIVATE_TO_JSVAL(java->cb), 0, 0,
				   JSPROP_READONLY)) {
		return JS_FALSE;
	    }

	    /* set the prototype so that objects constructed with
             * "new" have the right JSClass */
	    if (!(globalobj = JS_GetGlobalObject(cx))
		|| !JS_GetProperty(cx, globalobj,
                                   "#javaPrototype", &javaPrototype)
		|| !JS_DefineProperty(cx, funobj, "prototype",
				      javaPrototype, 0, 0,
				      JSPROP_READONLY)) {
		return JS_FALSE;
	    }

	    *vp = OBJECT_TO_JSVAL(funobj);
	}
	break;
    case JSTYPE_STRING:
	/* either pull out the string or call toString */
	return js_convertJObjectToJSString(cx, java->handle,
					   java->type==JAVA_CLASS, vp);
    case JSTYPE_NUMBER:
	/* call doubleValue() */
	return js_convertJObjectToJSNumber(cx, java->handle,
					   java->type==JAVA_CLASS, vp);
    case JSTYPE_BOOLEAN:
	/* call booleanValue() */
	return js_convertJObjectToJSBoolean(cx, java->handle,
					    java->type==JAVA_CLASS, vp);
    default:
	break;
    }
    return JS_TRUE;
}

static JSClass java_class = {
    "Java",
    JSCLASS_HAS_PRIVATE,
    JS_PropertyStub, JS_PropertyStub,
    java_getProperty, java_setProperty, java_enumerate,
    java_resolve, java_convert, java_finalize
};

/****	****	****	****	****	****	****	****	****/

static JSBool
javaarray_getProperty(JSContext *cx, JSObject *obj, jsval slot, jsval *vp)
{
    JSJava *array = JS_GetPrivate(cx, obj);
    HObject *ho = array->handle;
    PRInt32 index;
    PRInt32 size;
    char *addr;

    if (JSVAL_IS_STRING(slot)) {
	char *name = JS_GetStringBytes(JSVAL_TO_STRING(slot));
        if (0 == strcmp(name, "length")) {
	    *vp = INT_TO_JSVAL((jsint)obj_length(ho));
	    return JS_TRUE;
	}
        JS_ReportError(cx, "invalid Java array element %s", name);
	return JS_FALSE;
    }

    if (!JSVAL_IS_INT(slot)) {
        JS_ReportError(cx, "invalid Java array index expression");
	return JS_FALSE;
    }
    index = JSVAL_TO_INT(slot);
    if (index < 0 || index >= (PRInt32) obj_length(ho)) {
        JS_ReportError(cx, "Java array index %d out of range", index);
	return JS_FALSE;
    }

    size = sizearray(obj_flags(ho), 1);
    addr = ((char*) unhand(ho)) + index * size;

    if (!js_convertJElementToJSValue(cx, addr, array->signature,
				     vp, JSTYPE_VOID)) {
	JS_ReportError(cx,
            "can't convert Java array element %d to JavaScript value", index);
	return JS_FALSE;
    }

    return JS_TRUE;
}

static JSBool
javaarray_setProperty(JSContext *cx, JSObject *obj, jsval slot, jsval *vp)
{
    JSJava *array = JS_GetPrivate(cx, obj);
    HObject *ho = array->handle;
    PRInt32 index;
    PRInt32 size;
    char *addr;

    if (JSVAL_IS_STRING(slot)) {
	char *name = JS_GetStringBytes(JSVAL_TO_STRING(slot));
        if (0 == strcmp(name, "length")) {
            JS_ReportError(cx, "can't set length of a Java array");
	    return JS_FALSE;
	}
        JS_ReportError(cx, "invalid assignment to Java array element %s", name);
	return JS_FALSE;
    }

    if (!JSVAL_IS_INT(slot)) {
        JS_ReportError(cx, "invalid Java array index expression");
	return JS_FALSE;
    }
    index = JSVAL_TO_INT(slot);
    if (index < 0 || index >= (PRInt32) obj_length(ho)) {
        JS_ReportError(cx, "Java array index %d out of range", index);
	return JS_FALSE;
    }

    size = sizearray(obj_flags(ho), 1);
    addr = ((char*) unhand(ho)) + index * size;

    if (!js_convertJSValueToJElement(cx, *vp, addr,
				     array->signature, array->cb, 0)) {
        JS_ReportError(cx, "invalid assignment to Java array element %d",
		       index);
	return JS_FALSE;
    }

    return JS_TRUE;
}

static JSBool
javaarray_enumerate(JSContext *cx, JSObject *obj)
{
    JSJava *array = JS_GetPrivate(cx, obj);
    HObject *ho = array->handle;
    PRUint32 i;
    jsval v;

    for (i = 0; i < obj_length(ho); i++) {
	PRInt32 size = sizearray(obj_flags(ho), 1);
	char *addr = ((char*) unhand(ho)) + i * size;

	if (!js_convertJElementToJSValue(cx, addr, array->signature,
					 &v, JSTYPE_VOID)) {
	    return JS_FALSE;
	}
	if (!JS_SetElement(cx, obj, (jsint) i, &v)) {
	    return JS_FALSE;
	}
    }
    return JS_TRUE;
}

static JSBool
javaarray_resolve(JSContext *cx, JSObject *obj, jsval id)
{
    JSJava *array = JS_GetPrivate(cx, obj);
    HObject *ho = array->handle;
    jsint index;

    if (!JSVAL_IS_INT(id))
        return JS_TRUE;

    index = JSVAL_TO_INT(id);
    if (index < 0 || index >= (PRInt32) obj_length(ho))
	return JS_TRUE;

    return JS_DefineElement(cx, obj, index, JSVAL_VOID,
			    0, 0, JSPROP_ENUMERATE);
}

static void
javaarray_finalize(JSContext *cx, JSObject *obj)
{
    JSJava *array = JS_GetPrivate(cx, obj);

    /* get rid of the private data */
    /* array->signature is a static string */ ;
    array->signature = 0;

    /* remove it from the reflection table */
    java_finalize(cx, obj);
}

static JSBool
javaarray_convert(JSContext *cx, JSObject *obj, JSType type, jsval *vp)
{
    switch (type) {
    case JSTYPE_OBJECT:
	*vp = OBJECT_TO_JSVAL(obj);
	break;
    case JSTYPE_STRING:
	/* FIXME how should arrays convert to strings? */
    default:
	break;
    }
    return JS_TRUE;
}


static JSClass javaarray_class = {
    "JavaArray",
    JSCLASS_HAS_PRIVATE,
    JS_PropertyStub, JS_PropertyStub,
    javaarray_getProperty, javaarray_setProperty, javaarray_enumerate,
    javaarray_resolve, javaarray_convert, javaarray_finalize
};

/****	****	****	****	****	****	****	****	****/

/* this lameness is brought to you by the decision
 * to use strings for signatures in the JDK */
static char *
getSignatureBase(char *sig)
{
    int len;
    char *ret;
    char end;

    switch (*sig) {
    case SIGNATURE_CLASS:
	end = SIGNATURE_ENDCLASS;
	break;
    case SIGNATURE_FUNC:
	end = SIGNATURE_ENDFUNC;
	break;
    default:
	return 0;
	break;
    }

    len = strchr(sig+1, end) - (sig+1);
    ret = malloc(len+1);
    if (!ret)
	return 0;
    strncpy(ret, sig+1, len);
    ret[len] = '\0';

    return ret;
}

static ClassClass *
getSignatureClass(JSContext *cx, char *sig, ClassClass *from)
{
    char *name;
    ClassClass *cb;

    if (sig[1] == '\0') {
        /* special hack: if the signature is "L" it means
	 * just use the from class */
	return from;
    }
    name = getSignatureBase(sig);
    if (!name) {
	JS_ReportOutOfMemory(cx);
	return 0;
    }
    cb = js_FindJavaClass(cx, name, from);
    free(name);
    return cb;
}

/* this doesn't need to run in a java env, but it may throw an
 * exception so we need a temporary one */
static JSBool
js_isSubclassOf(JSContext *cx, ClassClass *cb1, ClassClass *cb2)
{
    ExecEnv *ee = jsj_GetCurrentEE(cx);
    JSBool ret;

    /* FIXME what to do with the exception? */
    if (!ee) {
	return JS_FALSE;
    }

    exceptionClear(ee);
    ret = (JSBool) is_subclass_of(cb1, cb2, ee);
    if (exceptionOccurred(ee)) {
#ifdef DEBUG
	char *message;

	/* exceptionDescribe(ee); */
        /* FIXME this could fail: we don't check if it's throwable,
	 * but i assume that is_subclass_of is well-behaved */
	HString *hdetail =
	  unhand((Hjava_lang_Throwable *)
		 ee->exception.exc)->detailMessage;
	ClassClass *cb = obj_array_classblock(ee->exception.exc);

	/*
	 * FIXME JRI_GetStringPlatformChars and JRI_GetStringUTFChars both
	 * return a pointer into an otherwise unreferenced, unrooted Java
	 * array's body, without returning the Java array's handle.  This
	 * requires fairly conservative GC, which we have in NSPR1.0, but
	 * if we lose it, this code may break.
	 */
	message = (char *)
	    JRI_GetStringPlatformChars((JRIEnv *) ee,
				       (struct java_lang_String *) hdetail,
                                       NULL, 0);

    /* XXX - temporarily replace arguments so we can compile
       (const jbyte *) cx->charSetName,
       (jint) cx->charSetNameLength);
    */

	PR_LOG(MojaSrc, error,
               ("exception in is_subclass_of %s (\"%s\")",
		cbName(cb), message));
#endif
	exceptionClear(ee);
	return JS_FALSE;
    }
    return ret ? JS_TRUE : JS_FALSE;
}

static JSBool
js_convertJSValueToJSObject(JSContext *cx, JSObject *mo, ClassClass *paramcb,
			    JSBool checkOnly, HObject **objp)
{
    /* check if a JSObject would be an acceptable argument */
    if (!js_isSubclassOf(cx, JSObjectClassBlock, paramcb))
	return JS_FALSE;

    /* JSObject is ok, so convert mo to one.
     * a JS object which is not really a java object
     * converts to JSObject */
    if (!checkOnly) {
	*objp = js_ReflectJSObjectToJObject(cx, mo);
	if (!*objp)
	    return JS_FALSE;
    }
    return JS_TRUE;
}

static JSBool
js_convertJSValueToJArray(HObject **objp, JSContext *cx,
			  jsval v, char *sig, ClassClass *fromclass,
			  JSBool checkOnly, int *cost)
{
    /* FIXME bump the cost counter when necessary */
    JSJava *java;
    HArrayOfObject *harr;
    ClassClass *acb;
    ClassClass *paramcb;
    JSObject *mo;
    char *elementsig;
    ClassClass *elementClazz;

    /* the only legal conversions are from null or from a java array */
    if (!JSVAL_IS_OBJECT(v))
	return JS_FALSE;

    /* can always pass null */
    mo = JSVAL_TO_OBJECT(v);
    if (!mo) {
	if (!checkOnly)
	    *objp = 0;
	return JS_TRUE;
    }

    /* otherwise, it must be a JS reflection of a java array */
    if (! JS_InstanceOf(cx, mo, &javaarray_class, 0))
	return JS_FALSE;

    java = JS_GetPrivate(cx, mo);

    sig++; /* skip to array element type */

    switch (*sig) {
    case SIGNATURE_CLASS:	/* object array */
	paramcb = getSignatureClass(cx, sig, fromclass);
        PR_LOG(MojaSrc, debug, ("desired array element signature \"%s\"(0x%x)",
			     sig, paramcb));
	if (!paramcb) {
	    PR_LOG(MojaSrc, warn,
                   ("couldn't find class for signature \"%s\"\n", sig));
	    return JS_FALSE;
	}
	harr = (HArrayOfObject *) java->handle;
        acb = (ClassClass *)(unhand(harr)->body[class_offset(harr)]);
	if (!acb) {
	    PR_LOG(MojaSrc, warn,
                   ("couldn't find class of array element\n"));
	    return JS_FALSE;
	}

	/* elements must convert */
	if (js_isSubclassOf(cx, acb, paramcb)) {
	    if (!checkOnly)
		*objp = (HObject *) harr;
	    return JS_TRUE;
	}
	break;
    case SIGNATURE_ARRAY:	/* nested array */
        /* FIXME nested arrays can't be supported, because the
	 * jdk runtime treats them all as flat
	 *
	 * This just in... Actually, you can get the dimensions of a
         * java array because they aren't flattened. */
	/* FIXME throw an exception */
	break;
    default:			/* primitive array */
	/* for any other array, the signature must match exactly */
	if (js_JArrayElementType(java->handle, &elementsig, &elementClazz)) {
	    if (elementsig[0] == sig[0]) {
		if (!checkOnly)
		    *objp = java->handle;
		return JS_TRUE;
	    }
	}
	break;
    }

    return JS_FALSE;
}

PR_IMPLEMENT(JSBool)
js_convertJSValueToJObject(HObject **objp, JSContext *cx,
			   jsval v, char *sig, ClassClass *fromclass,
			   JSBool checkOnly, int *cost)
{
    /* FIXME bump the cost counter when necessary */

    ClassClass *paramcb;

    paramcb = sig ? getSignatureClass(cx, sig, fromclass)
        : ObjectClassBlock;

    PR_LOG(MojaSrc, debug, ("desired argument class signature \"%s\"(0x%x)",
	    sig, paramcb));

    if (!paramcb) {
	PR_LOG(MojaSrc, warn,
               ("couldn't find class for signature \"%s\"", sig));
	return JS_FALSE;
    }

    /* JS wrappers around java objects do the same check
     * as the java compiler */
    /* FIXME except classes, which become JSObject (circular problem?) */
    if (JSVAL_IS_OBJECT(v)) {
	JSObject *mo = JSVAL_TO_OBJECT(v);
	/* null is always a valid object */
	if (!mo) {
	    if (!checkOnly)
		*objp = 0;
	    return JS_TRUE;
	}
	if (JS_TypeOfValue(cx, v) == JSTYPE_FUNCTION) {
	    if (js_convertJSValueToJSObject(cx, mo, paramcb, checkOnly, objp))
		return JS_TRUE;
	} else if (JS_InstanceOf(cx, mo, &java_class, 0) ||
		   JS_InstanceOf(cx, mo, &javaarray_class, 0)) {
	    JSJava *java = JS_GetPrivate(cx, mo);
	    HObject *ho = java->handle;
	    ClassClass *cb;

	    /* class reflections convert to JSObject or String only */
	    if (java->type == JAVA_CLASS) {
		if (js_convertJSValueToJSObject(cx, mo, paramcb, checkOnly,
						objp)) {
		    return JS_TRUE;
		}
	    } else {
		/* get the classblock for the java argument type */
		cb = obj_array_classblock(ho);
                PR_LOG(MojaSrc, debug, ("actual argument 0x%x, class 0x%x",
				     ho, cb));

		/* check against the expected class */
		if (js_isSubclassOf(cx, cb, paramcb)) {
		    if (!checkOnly)
			*objp = ho;
		    return JS_TRUE;
		}
	    }
	} else {
	    /* otherwise see if it will take a JSObject */
	    if (js_convertJSValueToJSObject(cx, mo, paramcb, checkOnly, objp))
		return JS_TRUE;
	}
    } else if (JSVAL_IS_NUMBER(v)) {	/* java.lang.Double */
	if (js_isSubclassOf(cx, DoubleClassBlock, paramcb)) {
	    /* Float is ok */
	    if (!checkOnly)
                *objp = js_ConstructJava(cx, "java/lang/Double",
                                            0, "(D)",
					    JSVAL_IS_INT(v)
					    ? (double) JSVAL_TO_INT(v)
					    : *JSVAL_TO_DOUBLE(v));
	    return JS_TRUE;
	}
    } else if (JSVAL_IS_BOOLEAN(v)) {  /* java.lang.Boolean */
	/* FIXME this should return Boolean.JS_TRUE or Boolean.FALSE
	 * instead of constructing a new one? */
	if (js_isSubclassOf(cx, BooleanClassBlock, paramcb)) {
	    if (!checkOnly)
                *objp = js_ConstructJava(cx, "java/lang/Boolean",
                                         0, "(Z)", (long) JSVAL_TO_BOOLEAN(v));
	    return JS_TRUE;
	}
    }

    /* last ditch attempt: is a String acceptable? */
    if (js_isSubclassOf(cx, StringClassBlock, paramcb)) {
	JSString *str;
	/* string is ok, convert to one */

	str = JS_ValueToString(cx, v);
	if (str) {
	    /* make a java String from str */
	    if (!checkOnly) {
		ExecEnv *ee = jsj_GetCurrentEE(cx);
		if (!ee)
		    return JS_FALSE;
		*objp = (JHandle *)
		  JRI_NewStringPlatform((JRIEnv *) ee,
					(const jbyte *) JS_GetStringBytes(str),
					(jint) JS_GetStringLength(str),
                                        NULL, 0);

    /* XXX - temporarily replace arguments so we can compile
       (const jbyte *) cx->charSetName,
       (jint) cx->charSetNameLength);
    */
	    }
	    return JS_TRUE;
	}
    }
    return JS_FALSE;
}

PR_IMPLEMENT(JSBool)
js_convertJObjectToJSValue(JSContext *cx, jsval *vp, HObject *ho)
{
    ExecEnv *ee;
    JRIEnv *env;
    JSObject *mo;

    ee = jsj_GetCurrentEE(cx);
    if (!ee) return JS_FALSE;

    env = (JRIEnv *) ee;

    if (!ho) {
	*vp = OBJECT_TO_JSVAL(0);
	return JS_TRUE;
    }

    /*
     * If it's a JSObject, pull out the original JS object when
     * it comes back into the JS world.
     */
    if (obj_array_classblock(ho) == JSObjectClassBlock) {
	jref jso = (jref) ho /* FIXME */;
	*vp = OBJECT_TO_JSVAL(
	    (JSObject *) JRI_GetFieldInt(env, jso, JSObjectInternalField));
	return JS_TRUE;
    }

    /*
     * Instances of java.lang.String are wrapped so we can
     * call methods on them, but they convert to a JS string
     * if used in a string context.
     */

    /* otherwise, wrap it */
    mo = js_ReflectJObjectToJSObject(cx, ho);
    if (!mo) {
        JS_ReportError(cx, "can't convert Java object to JavaScript object");
	return JS_FALSE;
    }
    *vp = OBJECT_TO_JSVAL(mo);
    return JS_TRUE;
}


#define REFLECTION_IN_PROGRESS ((jref)1)

PR_IMPLEMENT(HObject *)
js_ReflectJSObjectToJObject(JSContext *cx, JSObject *mo)
{
    ExecEnv *ee;
    JRIEnv *env;
    jref jso;
    PRHashEntry *he;

    ee = jsj_GetCurrentEE(cx);
    if (!ee) return 0;

    env = (JRIEnv *) ee;

    /* see if it's already there */
    PR_EnterMonitor(jsReflectionsMonitor);

    while ((jso = PR_HashTableLookup(jsReflections, mo)) != 0) {
	if (jso != REFLECTION_IN_PROGRESS)
	    break;
	PR_Wait(jsReflectionsMonitor, WAIT_FOREVER);
    }

    if (jso) {
	PR_ExitMonitor(jsReflectionsMonitor);
	return (HObject *) jso /*FIXME*/;
    }

    /*
     * Release the monitor temporarily while we call the java constructor,
     * which could contain arbitrary scariness.  Put a placeholder in the
     * hash table so anyone racing to reflect mo waits for us to finish.
     */
    PR_HashTableAdd(jsReflections, mo, REFLECTION_IN_PROGRESS);
    PR_ExitMonitor(jsReflectionsMonitor);

    /* not found, reflect it */
    jso = (jref) /*FIXME*/
        js_ConstructJavaPrivileged(cx, 0, JSObjectClassBlock, "()");

    /* FIXME check for exceptions */
    if (!jso) {
	return 0;
    }

    /* need to check again since we released the monitor */
    PR_EnterMonitor(jsReflectionsMonitor);

#ifdef DEBUG
{
    jref tmp = PR_HashTableLookup(jsReflections, mo);

    PR_ASSERT(!tmp || tmp == REFLECTION_IN_PROGRESS);
    if (tmp && tmp != REFLECTION_IN_PROGRESS) {
	PR_ExitMonitor(jsReflectionsMonitor);

	/* let the one we constructed die in gc and use the one we found */
	return (HObject *) tmp;
    }
}
#endif

    JRI_SetFieldInt(env, jso, JSObjectInternalField, (jint) mo);

    /* add it to the table */
    he = PR_HashTableAdd(jsReflections, mo, jso);
    if (he)
	(void) JS_AddRoot(cx, (void *)&he->key);	/* FIXME be errors? */

    /* awaken anyone racing and exit monitor */
    PR_NotifyAll(jsReflectionsMonitor);
    PR_ExitMonitor(jsReflectionsMonitor);

    return (HObject *) jso;/*FIXME*/
}

PR_IMPLEMENT(void)
js_RemoveReflection(JSContext *cx, JSObject *mo)
{
    PRHashEntry *he, **hep;

    /* remove it from the reflection table */
    PR_LOG(MojaSrc, debug, ("removing JS object 0x%x from table", mo));
    PR_EnterMonitor(jsReflectionsMonitor);
    hep = PR_HashTableRawLookup(jsReflections, java_hashHandle(mo), mo);
    he = *hep;

    if (he) {
	/* FIXME inline JS_RemoveRoot -- this will go away with GC unification */
	JS_LOCK_RUNTIME(finalizeRuntime);
	(void) PR_HashTableRemove(finalizeRuntime->gcRootsHash, (void *)&he->key);
	JS_UNLOCK_RUNTIME(finalizeRuntime);
    }
    PR_HashTableRawRemove(jsReflections, hep, he);
    PR_ExitMonitor(jsReflectionsMonitor);
}



static JSBool
js_convertJSValueToJValue(JSContext *cx, jsval v,
			  OBJECT *addr, char *sig, ClassClass *fromclass,
			  JSBool checkOnly, char **sigRestPtr,
			  int *cost)
{
    Java8 tdub;
    char *p = sig;

    switch (*p) {
    case SIGNATURE_BOOLEAN:
	if (!JSVAL_IS_BOOLEAN(v)) {
	    /* FIXME we could convert other things to boolean too,
	     * but until cost checking is done this will cause us
	     * to choose the wrong method if there is method overloading */
#ifndef USE_COSTS
	    return JS_FALSE;
#else
	    if (!JS_ConvertValue(cx, v, JSTYPE_BOOLEAN, &v))
		return JS_FALSE;
	    (*cost)++;
#endif
	}
	if (!checkOnly)
	    *(long*)addr = (JSVAL_TO_BOOLEAN(v) == JS_TRUE);
	break;
    case SIGNATURE_SHORT:
    case SIGNATURE_BYTE:
    case SIGNATURE_CHAR:
    case SIGNATURE_INT:
	/* FIXME should really do a range check... */
	if (!JSVAL_IS_NUMBER(v)) {
#ifndef USE_COSTS
	    return JS_FALSE;
#else
	    if (!JS_ConvertValue(cx, v, JSTYPE_NUMBER, &v))
		return JS_FALSE;
	    (*cost)++;
#endif
	}
	if (!checkOnly) {
	    if (JSVAL_IS_INT(v))
		*(long*)addr = (long) JSVAL_TO_INT(v);
	    else
		*(long*)addr = (long) *JSVAL_TO_DOUBLE(v);
	}
	break;

    case SIGNATURE_LONG:
	if (!JSVAL_IS_NUMBER(v)) {
#ifndef USE_COSTS
	    return JS_FALSE;
#else
	    if (!JS_ConvertValue(cx, v, JSTYPE_NUMBER, &v))
		return JS_FALSE;
	    (*cost)++;
#endif
	}
	if (!checkOnly) {
	    PRInt64 ll;
	    if (JSVAL_IS_INT(v)) {
		long l = (long) JSVAL_TO_INT(v);
		LL_I2L(ll, l);
	    } else {
		double d = (double) *JSVAL_TO_DOUBLE(v);
		LL_D2L(ll, d);
	    }
	    SET_INT64(tdub, addr, ll);
	}
	break;

    case SIGNATURE_FLOAT:
	if (!JSVAL_IS_NUMBER(v)) {
#ifndef USE_COSTS
	    return JS_FALSE;
#else
	    if (!JS_ConvertValue(cx, v, JSTYPE_NUMBER, &v))
		return JS_FALSE;
	    (*cost)++;
#endif
	}
	if (!checkOnly) {
	    if (JSVAL_IS_INT(v))
		*(float*)addr = (float) JSVAL_TO_INT(v);
	    else
		*(float*)addr = (float) *JSVAL_TO_DOUBLE(v);
	}
	break;

    case SIGNATURE_DOUBLE:
	if (!JSVAL_IS_NUMBER(v)) {
#ifndef USE_COSTS
	    return JS_FALSE;
#else
	    if (!JS_ConvertValue(cx, v, JSTYPE_NUMBER, &v))
		return JS_FALSE;
	    (*cost)++;
#endif
	}
	if (!checkOnly) {
	    double d;
	    if (JSVAL_IS_INT(v))
		d = (double) JSVAL_TO_INT(v);
	    else
		d = (double) *JSVAL_TO_DOUBLE(v);
	    SET_DOUBLE(tdub, addr, d);
	}
	break;

    case SIGNATURE_CLASS:
	/* FIXME cost? */
	if (!js_convertJSValueToJObject((HObject **)/*FIXME*/addr,
					cx, v, p, fromclass,
					checkOnly, cost)) {
	    return JS_FALSE;
	}
	while (*p != SIGNATURE_ENDCLASS) p++;
	break;

    case SIGNATURE_ARRAY:
	/* FIXME cost? */
	if (!js_convertJSValueToJArray((HObject **)/*FIXME*/addr,
				       cx, v, p, fromclass,
				       checkOnly, cost)) {
	    return JS_FALSE;
	}
	while (*p == SIGNATURE_ARRAY) p++; /* skip array beginning */
	/* skip the element type */
	if (*p == SIGNATURE_CLASS) {
	    while (*p != SIGNATURE_ENDCLASS) p++;
	}
	break;

    default:
        PR_LOG(MojaSrc, warn, ("unknown value signature '%s'", p));
	return JS_FALSE;
    }

    if (sigRestPtr)
	*sigRestPtr = p+1;
    return JS_TRUE;
}


/*
 *  this code mimics the method overloading resolution
 *  done in java, for use in JS->java calls
 */


/* push a jsval array onto the java stack for use by
 *  the given method.
 * sigRest gets a pointer to the remainder of the signature (the
 *  rest of the arguments in the list).
 * if checkOnly is true, don't actually convert, just check that it's ok.
 */
static JSBool
js_convertJSToJArgs(JSContext *cx, stack_item *optop,
		    struct methodblock *mb, int argc, jsval *argv,
		    JSBool checkOnly, char **sigRestPtr,
		    int *cost)
{
    struct fieldblock *fb = &mb->fb;
    char *sig = fieldsig(fb);
    jsval *vp = argv;
    int argsleft = argc;
    char *p;
    void *addr;

    *cost = 0;

    for (p = sig + 1; *p != SIGNATURE_ENDFUNC; vp++, argsleft--) {
	if (argsleft == 0)		/* not enough arguments passed */
	    return JS_FALSE;
	if (checkOnly)
	    addr = 0;
	else switch (*p) {
	case SIGNATURE_BOOLEAN:
	case SIGNATURE_SHORT:
	case SIGNATURE_BYTE:
	case SIGNATURE_CHAR:
	case SIGNATURE_INT:
	    addr = &(optop++)->i;
	    break;
	case SIGNATURE_LONG:
	    addr = optop;
	    optop += 2;
	    break;
	case SIGNATURE_FLOAT:
	    addr = &(optop++)->f;
	    break;
	case SIGNATURE_DOUBLE:
	    addr = optop;
	    optop += 2;
	    break;
	case SIGNATURE_CLASS:
	case SIGNATURE_ARRAY:
	    addr = &(optop++)->h;
	    break;
	default:
	    PR_LOG(MojaSrc, warn,
                   ("Invalid method signature '%s' for method '%s'\n",
		    fieldsig(fb), fieldname(fb)));
	    return JS_FALSE;
	    break;
	}
	/* this bumps p to the next argument */
	if (!js_convertJSValueToJValue(cx, *vp, addr, p, fieldclass(&mb->fb),
				       checkOnly, &p, cost)) {
	    return JS_FALSE;
	}
    }
    if (argsleft > 0) {
	/* too many arguments */
	return JS_FALSE;
    }

    if (sigRestPtr)
	*sigRestPtr = p+1 /* go past the SIGNATURE_ENDFUNC */;
    return JS_TRUE;
}

/* java array elements are packed with smaller sizes */
static JSBool
js_convertJSValueToJElement(JSContext *cx, jsval v,
			    char *addr, char *sig, ClassClass *fromclass,
			    char **sigRestPtr)
{
    long tmp[2];
    int cost = 0;

    if (!js_convertJSValueToJValue(cx, v, (OBJECT *)&tmp,
				   sig, fromclass,
				   JS_FALSE, sigRestPtr, &cost))
       return JS_FALSE;

    switch (sig[0]) {
    case SIGNATURE_BOOLEAN:
	*(char*)addr = (char)(*(long*)&tmp);
	break;
    case SIGNATURE_BYTE:
	*(char*)addr = (char)(*(long*)&tmp);
	break;
    case SIGNATURE_CHAR:
	*(unicode*)addr = (unicode)(*(long*)&tmp);
	break;
    case SIGNATURE_SHORT:
	*(signed short*)addr = (signed short)(*(long*)&tmp);
	break;
    case SIGNATURE_INT:
	*(PRInt32*)addr = *(long*)&tmp;
	break;
    case SIGNATURE_LONG:
	*(PRInt64*)addr = *(PRInt64*)&tmp;
	break;
    case SIGNATURE_FLOAT:
	*(float*)addr = *(float*)&tmp;
	break;
    case SIGNATURE_DOUBLE:
	*(double*)addr = *(double*)&tmp;
	break;
    case SIGNATURE_CLASS:
    case SIGNATURE_ARRAY:
	*(HObject**)addr = *(HObject**)&tmp;
	break;
    default:
        PR_LOG(MojaSrc, warn, ("unknown value signature '%s'\n", sig[0]));
	return JS_FALSE;
    }
    return JS_TRUE;
}


static OBJECT *
getJavaFieldAddress(HObject *ho, struct fieldblock *fb)
{
    OBJECT *addr = 0;			/* address of the java value */

    if (fb->access & ACC_PUBLIC) {
	if (fb->access & ACC_STATIC) {
	    char *sig = fieldsig(fb);

	    if (sig[0] == SIGNATURE_LONG || sig[0] == SIGNATURE_DOUBLE)
		addr = (long *)twoword_static_address(fb);
	    else
		addr = (long *)normal_static_address(fb);
	} else {
	    addr = &obj_getoffset(ho, fb->u.offset);
	}
    }
    return addr;
}

static JSBool
js_convertJSValueToJField(JSContext *cx, jsval v, HObject *ho,
			  struct fieldblock *fb)
{
    OBJECT *addr = getJavaFieldAddress(ho, fb);
    int cost = 0;

    if (!addr) {
        JS_ReportError(cx, "can't access field %s", fieldname(fb));
	return JS_FALSE;
    }
    return js_convertJSValueToJValue(cx, v, addr,
				     fieldsig(fb), fieldclass(fb),
				     JS_FALSE, 0, &cost);
}

/*
 * Returns -1 if the given method is not applicable to the arguments,
 * or a cost if it is.
 * if argc == -1, match iff the name matches and don't check the
 *  signature.	this always returns cost 1.
 */
static int
methodIsApplicable(JSContext *cx,
		   struct methodblock *mb, JSBool isStatic,
		   const char *name, int argc, jsval *argv)
{
    struct fieldblock *fb = &mb->fb;
    int cost = 0;

    /* name and access must match */
    if (!CHECK_STATIC(isStatic, fb) || strcmp(fieldname(fb), name))
	return -1;

    if (argc == -1)
	return 1;

    if (!js_convertJSToJArgs(cx, 0, mb, argc, argv,
			     JS_TRUE /* checkOnly */,
			     0 /* &sigRest */, &cost)) {
	return -1;
    }

    return cost;
}

/* FIXME this routine doesn't work yet - its purpose is to choose
 * the best method when java methods are overloaded, or to detect
 * ambiguous method calls */
static JSBool
methodIsMoreSpecific(JSContext *cx,
		     struct methodblock *mb1, struct methodblock *mb2)
{
    char *sig1 = fieldsig(&mb1->fb) + 1;        /* skip '(' */
    char *sig2 = fieldsig(&mb2->fb) + 1;

    /* FIXME fill this in! */
    return JS_TRUE;

    /* go through the args */
    while (*sig1 != SIGNATURE_ENDFUNC && *sig2 != SIGNATURE_ENDFUNC) {
	if (*sig1 == SIGNATURE_CLASS) {
	    if (*sig2 == SIGNATURE_CLASS) {
		ClassClass *cb1 =
		  getSignatureClass(cx, sig1, fieldclass(&mb1->fb));
		ClassClass *cb2 =
		  getSignatureClass(cx, sig2, fieldclass(&mb2->fb));

		if (! js_isSubclassOf(cx, cb1, cb2))
		    return JS_FALSE;

		/* next argument */
		while (*sig1++ != SIGNATURE_ENDCLASS);
		while (*sig2++ != SIGNATURE_ENDCLASS);
	    }
	}
    }
    if (*sig1 != *sig2) {
	return JS_FALSE;
	/* arg number mismatch */
    }
    return JS_TRUE;
}

/* based on sun.tools.java.Environment */
/**
 * Return true if an implicit cast from this type to
 * the given type is allowed.
 */

static JSBool
js_convertJValueToJSValue(JSContext *cx, OBJECT *addr, char *sig, jsval *vp,
			  JSType desired)
{
    Java8 tmp;

    switch (sig[0]) {
    case SIGNATURE_VOID:
	*vp = JSVAL_VOID;
	break;
    case SIGNATURE_BYTE:
    case SIGNATURE_CHAR:
    case SIGNATURE_SHORT:
	*vp = INT_TO_JSVAL((jsint) *addr);
	break;
    case SIGNATURE_INT:
	{
	    PRInt32 val = (PRInt32) *addr;
	    if (INT_FITS_IN_JSVAL(val)) {
		*vp = INT_TO_JSVAL((jsint) *addr);
	    } else {
		if (!JS_NewDoubleValue(cx, val, vp))
		    return JS_FALSE;
	    }
	}
	break;
    case SIGNATURE_BOOLEAN:
	*vp = BOOLEAN_TO_JSVAL((JSBool) *addr);
	break;
    case SIGNATURE_LONG:
	{
	    PRInt64 ll;
	    double d;

	    ll = GET_INT64(tmp, addr);
	    LL_L2D(d, ll);
	    if (!JS_NewDoubleValue(cx, d, vp))
		return JS_FALSE;
	}
	break;
    case SIGNATURE_FLOAT:
	if (!JS_NewDoubleValue(cx, *(float *)addr, vp))
	    return JS_FALSE;
	break;
    case SIGNATURE_DOUBLE:
	if (!JS_NewDoubleValue(cx, GET_DOUBLE(tmp, addr), vp))
	    return JS_FALSE;
	break;
    case SIGNATURE_CLASS:
    case SIGNATURE_ARRAY:
	return js_convertJObjectToJSValue(cx, vp, *(HObject **) addr);
    default:
        JS_ReportError(cx, "unknown Java signature character '%c'", sig[0]);
	return JS_FALSE;
    }

    if (desired != JSTYPE_VOID)
	return JS_ConvertValue(cx, *vp, desired, vp);
    return JS_TRUE;
}

/* java array elements are packed with smaller sizes */
static JSBool
js_convertJElementToJSValue(JSContext *cx, char *addr, char *sig, jsval *vp,
			    JSType desired)
{
    switch (sig[0]) {
    case SIGNATURE_BYTE:
	*vp = INT_TO_JSVAL(*(char*)addr);
	break;
    case SIGNATURE_CHAR:
	*vp = INT_TO_JSVAL(*(unicode*)addr);
	break;
    case SIGNATURE_SHORT:
	*vp = INT_TO_JSVAL(*(signed short*)addr);
	break;
    case SIGNATURE_INT:
	{
	    PRInt32 val = *(PRInt32*)addr;
	    if (INT_FITS_IN_JSVAL(val)) {
		*vp = INT_TO_JSVAL((jsint)val);
	    } else {
		if (!JS_NewDoubleValue(cx, val, vp))
		    return JS_FALSE;
	    }
	}
	break;
    case SIGNATURE_BOOLEAN:
	*vp = BOOLEAN_TO_JSVAL((JSBool) *(PRInt32*)addr);
	break;
    default:
	return js_convertJValueToJSValue(cx, (OBJECT *) addr, sig, vp, desired);
    }

    if (desired != JSTYPE_VOID)
	return JS_ConvertValue(cx, *vp, desired, vp);
    return JS_TRUE;
}

static JSBool
js_convertJFieldToJSValue(JSContext *cx, HObject *ho, struct fieldblock *fb,
			  jsval *vp, JSType desired)
{
    OBJECT *addr = getJavaFieldAddress(ho, fb);
    char *sig = fieldsig(fb);

    if (!addr) {
        JS_ReportError(cx, "can't access Java field %s", fieldname(fb));
	return JS_FALSE;
    }
    return js_convertJValueToJSValue(cx, addr, sig, vp, desired);
}

static JSBool
js_convertJObjectToJSString(JSContext *cx, HObject *ho, JSBool isClass,
			    jsval *vp)
{
    JSString *str;
    char *cstr;

    if (isClass) {
        cstr = PR_smprintf("[JavaClass %s]", cbName((ClassClass*)ho));
    } else {
	HString *hstr;
	ExecEnv *ee;

	if (!ho)
	    return JS_FALSE;

	if (obj_classblock(ho) == StringClassBlock) {
            /* it's a string already */
	    hstr = (HString*) ho;
	} else {
	    /* call toString() to convert to a string */
	    if (!js_ExecuteJavaMethod(cx, &hstr, sizeof(hstr), ho,
                                   "toString", "()Ljava/lang/String;",
				   0, JS_FALSE)) {
		return JS_FALSE;
	    }
	}

	/* FIXME js_ExecuteJavaMethod does this too */
	ee = jsj_GetCurrentEE(cx);
	if (!ee)
	    return JS_FALSE;

	/* convert the java string to a JS string */
	cstr = (char *)
	    JRI_GetStringPlatformChars((JRIEnv *) ee,
				       (struct java_lang_String *) hstr,
                                       NULL, 0);

    /* XXX - temporarily replace arguments so we can compile
       (const jbyte *) cx->charSetName,
       (jint) cx->charSetNameLength);
    */

	if (cstr)
	    cstr = strdup(cstr);
    }
    if (!cstr) {
	JS_ReportOutOfMemory(cx);
	return JS_FALSE;
    }
    str = JS_NewString(cx, cstr, strlen(cstr));
    if (!str) {
	free(cstr);
	return JS_FALSE;
    }
    *vp = STRING_TO_JSVAL(str);
    return JS_TRUE;
}

static JSBool
js_convertJObjectToJSNumber(JSContext *cx, HObject *ho, JSBool isClass,
			    jsval *vp)
{
    long foo[2], swap;	/* FIXME JRI and JDK disagree */
    JRI_JDK_Java8 tmp;
    double d;

    if (isClass || !ho) {
        JS_ReportError(cx, "can't convert Java object to number");
	return JS_FALSE;
    }

    if (!js_ExecuteJavaMethod(cx, foo, sizeof(foo), ho,
                                 "doubleValue", "()D",
				 0, JS_FALSE))
	return JS_FALSE;

    swap = foo[0];
    foo[0] = foo[1];
    foo[1] = swap;
    d = JRI_GET_DOUBLE(tmp, &foo[0]);
    return JS_NewDoubleValue(cx, d, vp);
}

static JSBool
js_convertJObjectToJSBoolean(JSContext *cx, HObject *ho, JSBool isClass,
			     jsval *vp)
{
    long b;

    if (isClass) {
        b = JS_TRUE;
	goto ok;
    }

    if (!ho) {
        b = JS_FALSE;
	goto ok;
    }

    if (!js_ExecuteJavaMethod(cx, &b, sizeof(b), ho,
                              "booleanValue", "()Z",
			      0, JS_FALSE)) {
        /* failed, probably missing the booleanValue() method */
        /* by default we convert to true */
        b = JS_TRUE;
    }
 ok:
    *vp = BOOLEAN_TO_JSVAL(b);
    return JS_TRUE;
}

static JSBool
java_returnAsJSValue(JSContext *cx, jsval *vp, ExecEnv *ee, char *sig)
{
    OBJECT *addr;

    if ((sig[0] == SIGNATURE_DOUBLE || sig[0] == SIGNATURE_LONG)) {
	addr = (OBJECT*) &ee->current_frame->optop[-2];
    } else {
	addr = (OBJECT *) &ee->current_frame->optop[-1];
    }
    return js_convertJValueToJSValue(cx, addr, sig, vp, JSTYPE_VOID);
}

static char*
js_createArgTypeString(JSContext* cx, int argc, jsval* argv)
{
    int i;
    int size = 0;
    char *p;
    char** strs;
    char *str;

    if (argc < 1) {
        str = strdup("");
	return str;
    }

    strs = (char**) malloc(argc * sizeof(char*));
    if (!strs)
	return 0;

    for (i = 0; i < argc; i++) {
	JSType t = JS_TypeOfValue(cx, argv[i]);
	switch (t) {
	  case JSTYPE_VOID:
            strs[i] = "void";
	    break;
	  case JSTYPE_OBJECT:
	    if (JSVAL_IS_NULL(argv[i]))
		strs[i] = "null";
	    else
		strs[i] = JS_GetClass(JSVAL_TO_OBJECT(argv[i]))->name;
	    break;
	  case JSTYPE_FUNCTION:
            strs[i] = "function";
	    break;
	  case JSTYPE_STRING:
            strs[i] = "string";
	    break;
	  case JSTYPE_NUMBER:
            strs[i] = "number";
	    break;
	  case JSTYPE_BOOLEAN:
            strs[i] = "boolean";
	    break;
	  default:
            PR_ASSERT(!"bad arg type");
	}
        size += strlen(strs[i]) + 2; /* +2 for ", " */
    }

    str = (char*) malloc(size - 1); /* -2 for ", " & +1 for '\0' */
    if (!str)
	goto out;

    strcpy(str, strs[0]);
    p = str + strlen(strs[0]);
    for (i = 1; i < argc; i++) {
        *p++ = ',';
        *p++ = ' ';
	strcpy(p, strs[i]);
	p += strlen(strs[i]);
    }
    *p = '\0';

  out:
    free(strs);
    return str;
}

/*
 * find the best method to call for a name and a bunch of JS
 * arguments.  try each possible method, then find the most
 * specific of the applicable methods.  "most specific" is
 * determined without reference to the JS arguments: it is
 * the same as "most specific" in the java sense of the word.
 * FIXME this could cause trouble since more methods will be applicable
 *  to a JS call than for a similar java call!
 * pass argc == -1 to match any method with the correct name
 */
static struct methodblock *
matchMethod(JSContext *cx, ClassClass *clazz, JSBool isStatic,
	    const char *name, int argc, jsval *argv, JSBool reportErrors)
{
    int mindex;
    int *isApplicable = 0;		/* array holding costs */
    struct methodblock *bestmb = 0;
    JSBool isOverloaded = JS_FALSE;
    ClassClass *cb = clazz;

    while (cb) {
	/*
	 * Find all applicable methods.  keep track of which ones are
	 * applicable using the isApplicable array, and the best one
	 * found so far in bestmb.  set isOverloaded if there is more
	 * than one applicable method.
	 */
	isApplicable = JS_malloc(cx, cbMethodsCount(cb) * sizeof(int));
	if (!isApplicable)
	    return 0;
	for (mindex = cbMethodsCount(cb); mindex--;) {
	    struct methodblock *mb = cbMethods(cb) + mindex;
	    struct fieldblock *fb = &mb->fb;

	    isApplicable[mindex] = methodIsApplicable(cx, mb, isStatic,
						      name, argc, argv);
	    if (isApplicable[mindex] == -1)
		continue;

            PR_LOG(MojaSrc, debug, ("found applicable method %s with sig %s",
				 fieldname(fb), fieldsig(fb)));

	    if (!bestmb) {		/* first one found */
		bestmb = mb;
		continue;
	    }

	    isOverloaded = JS_TRUE;;

	    if (methodIsMoreSpecific(cx, mb, bestmb)) {
		bestmb = mb;
	    }
	}

        /* if we've found something applicable in the current class,
	 * no need to go any further */
	/* this is the only exit from the loop in which isApplicable
	 * is live */
	if (bestmb)
	    break;

	/* otherwise, check the parent */
	if (cbSuperclass(cb))
	    cb = cbSuperclass(cb);
	else
	    cb = 0;
	JS_free(cx, isApplicable);
	isApplicable = 0;
    }

    /* second pass: check that bestmb is more specific than all other
     * applicable methods */
/* FIXME if mb is equally specific, this is an ambiguous lookup.
   Hopefully we can disambiguate by the cost */

/*
    if (isOverloaded)
	for (mindex = cb->methods_count; mindex--;) {
	    struct methodblock *mb = cbMethods(cb) + mindex;
	    struct fieldblock *fb = &mb->fb;

	    if (!isApplicable[mindex] || mb == bestmb)
		continue;

	}
*/

    if (isApplicable)
	JS_free(cx, isApplicable);

    if (bestmb && (bestmb->fb.access & ACC_PUBLIC))
	return bestmb;
    else if (reportErrors) {
	char buf[512];
	char *argstr = js_createArgTypeString(cx, argc, argv);
        char *methodtype = "method";

        if (0 == strcmp(name, "<init>")) {
            methodtype = "constructor";
	}

	if (!bestmb) {
	    if (argstr) {
		PR_snprintf(buf, sizeof(buf),
                            "no Java %s %s.%s matching JavaScript arguments (%s)",
			    methodtype, cbName(clazz), name, argstr);
		free(argstr);
	    } else {
		PR_snprintf(buf, sizeof(buf),
                            "no Java %s %s.%s matching JavaScript arguments",
			    methodtype, cbName(clazz), name);
	    }
	} else {
	    if (argstr) {
		PR_snprintf(buf, sizeof(buf),
                            "Java %s %s.%s is not public (%s)",
			    methodtype, cbName(clazz), name, argstr);
		free(argstr);
	    } else {
		PR_snprintf(buf, sizeof(buf),
                            "Java %s %s.%s is not public",
			    methodtype, cbName(clazz), name);
	    }
	}
	JS_ReportError(cx, buf);
    }
    return NULL;
}

/* this is the callback to execute java bytecodes in the
 * appropriate java env */
typedef struct {
    JRIEnv  *env;
    char    *pc;
    JSBool  ok;
} js_ExecuteJava_data;

static void
js_ExecuteJava_stub(void *d)
{
    js_ExecuteJava_data *data = d;
    PR_EXTERN(bool_t) ExecuteJava(unsigned char  *, ExecEnv *ee);

    data->ok = (JSBool) ExecuteJava((unsigned char*) data->pc, /*FIXME*/(ExecEnv*)data->env);
}

/* this is a copy of do_execute_java_method_vararg modified for
 *  a jsval* argument list instead of a va_list
 */
static JSBool
do_js_execute_java_method(JSContext *cx, void *obj,
			     struct methodblock *mb, JSBool isStaticCall,
			     int argc, jsval *argv,
			     jsval *rval)
{
    ExecEnv *ee = jsj_GetCurrentEE(cx);
    char *method_name;
    char *method_signature;
    JavaFrame *current_frame, *previous_frame;
    JavaStack *current_stack;
    char *sigRest;
    int cost = 0;
    stack_item *firstarg;
    JSBool success = JS_TRUE;
    JSJClassData *classData;

    unsigned char pc[6];
    cp_item_type  constant_pool[10];
    unsigned char cpt[10];
    JSBool ok;


    if (!ee) return JS_FALSE;

    /* push the safety frame before the call frame */
    if (!js_pushSafeFrame(cx, ee, &classData))
	return JS_FALSE;

    method_name = fieldname(&mb->fb);
    method_signature = fieldsig(&mb->fb);

    previous_frame = ee->current_frame;
    if (previous_frame == 0) {
	/* bottommost frame on this Exec Env. */
	current_stack = ee->initial_stack;
	current_frame = (JavaFrame *)(current_stack->data); /* no vars */
    } else {
	int args_size = mb->args_size;
	current_stack = previous_frame->javastack; /* assume same stack */
	if (previous_frame->current_method) {
	    int size = previous_frame->current_method->maxstack;
	    current_frame = (JavaFrame *)(&previous_frame->ostack[size]);
	} else {
            /* The only frames that don't have a mb are pseudo frames like
             * this one and they don't really touch their stack. */
	    current_frame = (JavaFrame *)(previous_frame->optop + 3);
	}
	if (current_frame->ostack + args_size > current_stack->end_data) {
            /* Ooops.  The current stack isn't big enough.  */
	    if (current_stack->next != 0) {
		current_stack = current_stack->next;
	    } else {
		current_stack = CreateNewJavaStack(ee, current_stack);
		if (current_stack == 0) {
		    JS_ReportOutOfMemory(cx);
		    success = JS_FALSE;
		    goto done;
		}
	    }
	    /* no vars */
	    current_frame = (JavaFrame *)(current_stack->data);
	}
    }
    current_frame->prev = previous_frame;
    current_frame->javastack = current_stack;
    current_frame->optop = current_frame->ostack;
    current_frame->vars = 0;	/* better not reference any! */
    current_frame->constant_pool = 0; /* Until we set it up */
    current_frame->monitor = 0; /* not monitoring anything */
    current_frame->annotation = 0;
    current_frame->current_method = 0;

    /*
     * Allocate space for all the operands before they are actually
     * converted, because conversion may need to use this stack.
     */
    firstarg = current_frame->optop;
    current_frame->optop += mb->args_size;

    ee->current_frame = current_frame;

    /* Push the target object, if not a static call. */
    if (!isStaticCall)
	(firstarg++)->p = obj;

    /* Now convert the args into the space on the stack. */
    if (!js_convertJSToJArgs(cx, firstarg, mb, argc, argv,
			     JS_FALSE /* actually convert */,
			     &sigRest, &cost)) {
        /* the method shouldn't have matched if this was going to happen! */
        JS_ReportError(cx, "internal error: argument conversion failed");
	success = JS_FALSE;
	goto done;
    }

    /* build the bytecodes and constant table for the call */
    constant_pool[CONSTANT_POOL_TYPE_TABLE_INDEX].p = cpt;
    cpt[0] = CONSTANT_POOL_ENTRY_RESOLVED;

    pc[0] = isStaticCall ? opc_invokestatic_quick
	: opc_invokenonvirtual_quick;
    pc[1] = 0; pc[2] = 1;	/* constant pool entry #1 */
    pc[3] = opc_return;

    constant_pool[1].p = mb;
    cpt[1] = CONSTANT_POOL_ENTRY_RESOLVED | CONSTANT_Methodref;

    current_frame->constant_pool = constant_pool;

    /* Run the byte codes in java-land catch any exceptions. */
    ee->exceptionKind = EXCKIND_NONE;

    {
	js_ExecuteJava_data data;
	data.pc = (char*) pc;
	js_CallJava(cx, js_ExecuteJava_stub, &data,
		       JS_FALSE /* we pushed the safety frame already */);
	ok = data.ok;
    }

    if (ok) {
	JSBool ret;
        PR_LOG(MojaSrc, debug, ("method call succeeded\n"));
	ret = java_returnAsJSValue(cx, rval, ee, sigRest);
	if (!ret) {
	    JS_ReportError(cx,
                           "can't convert Java return value with signature %s",
			   sigRest);
	    success = JS_FALSE;
	}
    } else {
	success = JS_FALSE;
    }

  done:
    /* Our caller can look at ee->exceptionKind and ee->exception. */
    ee->current_frame = previous_frame;
    /* pop the safety frame */
    js_popSafeFrame(cx, ee, classData);

    /* FIXMEold cx->javaEnv = saved; */

    return success;
}

static JSBool
js_javaMethodWrapper(JSContext *cx, JSObject *obj,
		     PRUintn argc, jsval *argv, jsval *rval)
{
    JSFunction *fun;
    const char *name;
    JSJava *java;
    ClassClass *cb;
    struct methodblock *realmb;
    JSBool success;

    PR_ASSERT(JS_TypeOfValue(cx, argv[-2]) == JSTYPE_FUNCTION);
    if (!JS_InstanceOf(cx, obj, &java_class, argv))
	return JS_FALSE;

    fun = JS_GetPrivate(cx, JSVAL_TO_OBJECT(argv[-2]));
    name = JS_GetFunctionName(fun);

    java = JS_GetPrivate(cx, obj);
    cb = java->cb;

    PR_LOG(MojaSrc, debug, ("entered methodwrap, fun=0x%x, name=\"%s\"(0x%x)",
			 fun, name, name));

    /* match argc,argv against the signatures of the java methods */
    realmb = matchMethod(cx, cb, java->type==JAVA_CLASS, name, argc, argv,
			 JS_TRUE /* reportErrors */);

    if (!realmb) {
	return JS_FALSE;
    }

    PR_LOG(MojaSrc, debug, ("calling %sjava method %s with signature %s",
                         realmb->fb.access & ACC_STATIC ? "static " : "",
			 realmb->fb.name, realmb->fb.signature));

    success =
      do_js_execute_java_method(cx,
				realmb->fb.access & ACC_STATIC ? 0 : java->handle,
				realmb,
				realmb->fb.access & ACC_STATIC /* isStaticCall */,
				argc, argv, rval);

    return success;
}


static JSBool
js_javaConstructorWrapper(JSContext *cx, JSObject *obj,
			  PRUintn argc, jsval *argv,
			  jsval *rval)
{
    JSObject *funobj;
    ClassClass *cb;
    struct methodblock *realmb;
    HObject *ho;
    JSBool success;
    jsval tmp;
    ExecEnv *ee;

    PR_LOG(MojaSrc, debug, ("entered java constructor wrapper\n"));

    PR_ASSERT(JS_TypeOfValue(cx, argv[-2]) == JSTYPE_FUNCTION);
    funobj = JSVAL_TO_OBJECT(argv[-2]);
    if (!JS_GetProperty(cx, funobj, "#javaClass", &tmp))
	return JS_FALSE;
    cb = JSVAL_TO_PRIVATE(tmp);

    if (!JS_InstanceOf(cx, obj, &java_class, 0)
	|| JS_GetPrivate(cx, obj)) {
	/* not a fresh object! */
        /* 3.0 allowed you to call a java constructor without using
         * "new" so we do the same.  setting obj to null causes
         * ReflectJava to create a new js object rather than using
         * the "this" argument of the constructor */
        obj = NULL;
    }

    /* FIXME these are copied from interpreter.c without much understanding */
    if (cbAccess(cb) & (ACC_INTERFACE | ACC_ABSTRACT)) {
        JS_ReportError(cx, "can't instantiate Java class");
	return JS_FALSE;
    }
    if (!VerifyClassAccess(0, cb, FALSE)) {
        JS_ReportError(cx, "can't access Java class");
	return JS_FALSE;
    }

    /* match argc,argv against the signatures of the java constructors */
    realmb = matchMethod(cx, cb, JS_FALSE, "<init>", argc, argv,
			 JS_TRUE /* reportErrors */);
    if (!realmb) {
	return JS_FALSE;
    }

    if (!VerifyFieldAccess(0, fieldclass(&realmb->fb),
			   realmb->fb.access, FALSE)) {
        JS_ReportError(cx, "illegal access to Java constructor");
	return JS_FALSE;
    }

    PR_LOG(MojaSrc, debug, ("calling java constructor with signature %s",
	    realmb->fb.signature));

    /*
     * Because newobject can fail, and call SignalError, which calls
     * FindClassFromClass, etc.
     */
    /* Allocate the object */
    ee = jsj_GetCurrentEE(cx);
    if (!ee)
	return JS_FALSE;
    if ((ho = newobject(cb, 0, ee)) == 0) {
	JS_ReportOutOfMemory(cx);
	return JS_FALSE;
    }

    success = do_js_execute_java_method(cx, ho, realmb,
					JS_FALSE /* isStaticCall */,
					argc, argv,
					&tmp  /* gets set to void */);

    /* FIXME the interpreter.c code calls cbDecRef(cb) at the end,
     * why?  should we? */

    if (success) {
	JSObject *jso = js_ReflectJava(cx, JAVA_OBJECT, ho, cb, 0, obj);
	if (jso) {
	    *rval = OBJECT_TO_JSVAL(jso);
	    return JS_TRUE;
	}
    }
    return JS_FALSE;
}

/*   *	 *   *	 *   *	 *   *	 *   *	 *   *	 *   *	 *   *	 *   *	 *
 *
 *   JS/java reflection tables
 *
 *   *	 *   *	 *   *	 *   *	 *   *	 *   *	 *   *	 *   *	 *   *	 */

typedef struct ScanArgs {
    GCInfo    *gcInfo;
} ScanArgs;

PR_STATIC_CALLBACK (int)
scanJSJavaReflectionEntry(PRHashEntry *he, int i, void *arg)
{
    ScanArgs *args = arg;
    JSObject *jso = he->value;

    /* scan the handle */
    if (1 /* jso != JSO_REFLECTION_IN_PROGRESS */) {
      /* XXX - fur - temporary change to allow compilation
         JSJava *java = JSVAL_TO_PRIVATE(OBJ_GET_SLOT(jso, JSSLOT_PRIVATE)); */
	JSJava *java = JSVAL_TO_PRIVATE(jso->slots[JSSLOT_PRIVATE]);

	args->gcInfo->livePointer((void *)java->handle);
    }

    return HT_ENUMERATE_NEXT;
}

PR_STATIC_CALLBACK (void)
scanJSJavaReflections(void *runtime)
{
    ScanArgs args;

/*    PR_PROCESS_ROOT_LOG(("Scanning JS reflections of java objects")); */

    args.gcInfo = PR_GetGCInfo();

    /* FIXME this is kind of scary long-term access inside the
     * monitor - is there any alternative? */
#ifndef NSPR20
    PR_EnterMonitor(javaReflectionsMonitor);
#endif
    if (javaReflections) {
	PR_HashTableEnumerateEntries(javaReflections,
				     scanJSJavaReflectionEntry,
				     &args);
    }
#ifndef NSPR20
    PR_ExitMonitor(javaReflectionsMonitor);
#endif
}

#ifdef NSPR20
void PR_CALLBACK PrepareJSLocksForGC(GCLockHookArg arg1)
{
    PR_ASSERT(arg1 == PR_GCBEGIN || arg1 == PR_GCEND);

    if (arg1 == PR_GCBEGIN)
        PR_EnterMonitor(javaReflectionsMonitor);
    else
        PR_ExitMonitor(javaReflectionsMonitor);
}
#endif

static JSObject *
js_ReflectJava(JSContext *cx, JSJavaType type, HObject *handle,
	       ClassClass *cb, char *sig, JSObject *useMe)
{
    JSObject *mo;
    JSJava *java;
    void *key;
    PRHashEntry *he;

    PR_EnterMonitor(javaReflectionsMonitor);
    if (!javaReflections) goto fail;

    /* see if it's already been reflected */
    switch (type) {
      case JAVA_CLASS:
	key = (void *) cbName(cb);
	break;
      case JAVA_OBJECT:
	key = (void *) handle;
	break;
      case JAVA_ARRAY:
	key = (void *) handle;
	break;
      default:
	break;
    }

    mo = PR_HashTableLookup(javaReflections, key);
    if (mo)
	goto succeed;

    /* if we got this far, it isn't in the hash table.
     * if useMe is non-null, we were called via "new" and should
     * use the object that was already constructed if possible */
    if (useMe) {
	/* make sure we have the right js class and no private
	 * data yet.  if not, ignore the provided object */
	if (JS_GetPrivate(cx, useMe))
	    useMe = NULL;
	else {
	    JSClass *clasp = JS_GetClass(useMe);
	    switch (type) {
	      case JAVA_CLASS:
	      case JAVA_OBJECT:
		if (clasp != &java_class)
		    useMe = NULL;
		break;
	      case JAVA_ARRAY:
		if (clasp != &javaarray_class)
		    useMe = NULL;
		break;
	    }
	}
    }
    mo = useMe;

    /* build the private data for the js reflection */
    java = JS_malloc(cx, sizeof(JSJava));
    if (!java)
	goto fail;
    java->type = type;
    java->handle = handle;
    java->cb = cb;
    java->signature = sig;

    switch (type) {
      case JAVA_CLASS:
      case JAVA_OBJECT:
	if (!mo)
	    mo = JS_NewObject(cx, &java_class, 0, 0);
	if (!mo || ! JS_SetPrivate(cx, mo, java))
	    goto fail_free_java;
	break;
      case JAVA_ARRAY:
	if (!mo)
	    mo = JS_NewObject(cx, &javaarray_class, 0, 0);
	if (!mo || ! JS_SetPrivate(cx, mo, java))
	    goto fail_free_java;
	if (! JS_DefineProperty(cx, mo, "length",
				   INT_TO_JSVAL((jsint)obj_length(handle)),
				   0, 0, JSPROP_READONLY))
	    goto fail;
	break;
      default:
	break;
    }

    /* add it to the table */
    he = PR_HashTableAdd(javaReflections, key, mo);
    if (!he) {
	JS_ReportOutOfMemory(cx);
	mo = 0;
    }

  succeed:
    PR_ExitMonitor(javaReflectionsMonitor);
    return mo;

  fail_free_java:
    JS_free(cx, java);
  fail:
    PR_ExitMonitor(javaReflectionsMonitor);
    return 0;
}

/*
 * Get the element type for a java array.  *classp will be set to null
 * if it's not of object type.
 */
static JSBool
js_JArrayElementType(HObject *handle, char **sig, ClassClass **classp)
{
    /* figure out the signature from the type */
    unsigned long elementtype = obj_flags(handle);
    char *elementsig;

    *sig = 0;
    *classp = 0;

    switch (elementtype) {
      case T_CLASS:
	*classp = (ClassClass*)
          unhand((HArrayOfObject *)handle)->body[class_offset(handle)];
	elementsig = SIGNATURE_CLASS_STRING;
	break;
      case T_BOOLEAN:
	elementsig = SIGNATURE_BOOLEAN_STRING;
	break;
      case T_CHAR:
	elementsig = SIGNATURE_CHAR_STRING;
	break;
      case T_FLOAT:
	elementsig = SIGNATURE_FLOAT_STRING;
	break;
      case T_DOUBLE:
	elementsig = SIGNATURE_DOUBLE_STRING;
	break;
      case T_BYTE:
	elementsig = SIGNATURE_BYTE_STRING;
	break;
      case T_SHORT:
	elementsig = SIGNATURE_SHORT_STRING;
	break;
      case T_INT:
	elementsig = SIGNATURE_INT_STRING;
	break;
      case T_LONG:
	elementsig = SIGNATURE_LONG_STRING;
	break;
      default:
	PR_ASSERT(0);
	return JS_FALSE;
    }

    *sig = elementsig;
    return JS_TRUE;
}

PR_IMPLEMENT(JSObject *)
js_ReflectJObjectToJSObject(JSContext *cx, HObject *handle)
{
    JSObject *mo = 0;
    ExecEnv *ee;

    /* force initialization */
    ee = jsj_GetCurrentEE(cx);
    if (!ee) return 0;

    if (handle) {
	if (obj_flags(handle)) {
	    char *elementSig;
	    ClassClass *elementClazz;

	    if (!js_JArrayElementType(handle, &elementSig, &elementClazz)) {
		/* FIXMEbe report an error! */
		return 0;
	    }

	    mo = js_ReflectJava(cx, JAVA_ARRAY, handle,
				elementClazz, elementSig, 0);
            PR_LOG(MojaSrc, debug, ("reflected array[%s] 0x%x as JSObject* 0x%x",
				 elementSig, handle, mo));

	} else {
	    mo = js_ReflectJava(cx, JAVA_OBJECT, handle,
				obj_classblock(handle), 0, 0);
            PR_LOG(MojaSrc, debug, ("reflected HObject* 0x%x as JSObject* 0x%x",
				 handle, mo));
	}
    }

    return mo;
}

JSObject *
js_ReflectJClassToJSObject(JSContext *cx, ClassClass *cb)
{
    ExecEnv *ee;
    JSObject *mo;

    /* force initialization */
    ee = jsj_GetCurrentEE(cx);
    if (!ee) return 0;

    mo = js_ReflectJava(cx, JAVA_CLASS, (HObject *) cbHandle(cb), cb, 0, 0);

    PR_LOG(MojaSrc, debug, ("reflected ClassClass* 0x%x as JSObject* 0x%x",
			 cb, mo));

    return mo;
}

/*	*	*	*	*	*	*	*	*	*/

/*
 * JSJavaSlot is a java slot which will be resolved as a method
 * or a field depending on context.
 */
struct JSJavaSlot {
    JSObject		*obj;		/* the object or class reflection */
    jsval		value;		/* the field value when created */
    JSString		*name;		/* name of the field or method */
    struct fieldblock	*fb;		/* fieldblock if there is a field */
};

/* none of these should ever be called, since javaslot_convert will
 * first turn the slot into the underlying object if there is one */
PR_STATIC_CALLBACK(JSBool)
javaslot_getProperty(JSContext *cx, JSObject *obj, jsval slot,
		      jsval *vp)
{
    /* evil so that the assign hack doesn't kill us */
    if (slot == STRING_TO_JSVAL(ATOM_TO_STRING(cx->runtime->atomState.assignAtom))) {
	*vp = JSVAL_VOID;
	return JS_TRUE;
    }

    JS_ReportError(cx, "Java slots have no properties");
    return JS_FALSE;
}

PR_STATIC_CALLBACK(JSBool)
javaslot_setProperty(JSContext *cx, JSObject *obj, jsval slot,
		 jsval *vp)
{
    JS_ReportError(cx, "Java slots have no properties");
    return JS_FALSE;
}

PR_STATIC_CALLBACK(JSBool)
javaslot_resolve(JSContext *cx, JSObject *obj, jsval id)
{
    return JS_TRUE;
}

PR_STATIC_CALLBACK(void)
javaslot_finalize(JSContext *cx, JSObject *obj)
{
    JSJavaSlot *slot = JS_GetPrivate(cx, obj);

    /* the value may be holding a reference to something... */
    JS_RemoveRoot(cx, &slot->value);

    /* drop the object of which this is a slot */
    JS_RemoveRoot(cx, &slot->obj);

    /* drop the name */
    JS_UnlockGCThing(cx, slot->name);

    JS_free(cx, slot);

    PR_LOG(MojaSrc, debug, ("finalizing JSJavaSlot 0x%x", obj));
}

PR_STATIC_CALLBACK(JSBool)
javaslot_convert(JSContext *cx, JSObject *obj, JSType type, jsval *vp)
{
    JSJavaSlot *slot = JS_GetPrivate(cx, obj);
    const char *name;
    JSFunction *fun;
    JSString *str;

    name = JS_GetStringBytes(slot->name);
    switch (type) {
      case JSTYPE_FUNCTION:
	fun = JS_NewFunction(cx, js_javaMethodWrapper, 0, JSFUN_BOUND_METHOD,
			     slot->obj, name);
	if (!fun)
	    return JS_FALSE;

	PR_LOG(MojaSrc, debug,
               ("converted slot to function 0x%x with name %s\n",
		fun, name));

	*vp = OBJECT_TO_JSVAL(JS_GetFunctionObject(fun));
	return JS_TRUE;

      case JSTYPE_OBJECT:
        PR_LOG(MojaSrc, debug, ("converting java slot 0x%x to object", obj));
	/* FALL THROUGH */
      case JSTYPE_NUMBER:
      case JSTYPE_BOOLEAN:
      case JSTYPE_STRING:
	if (slot->fb) {
	    return JS_ConvertValue(cx, slot->value, type, vp);
	}
	if (type == JSTYPE_STRING) {
	    JSJava *java = JS_GetPrivate(cx, slot->obj);
	    char *cstr, *cp;

	    PR_ASSERT(java->type == JAVA_OBJECT || java->type == JAVA_CLASS);
            cstr = PR_smprintf("[JavaMethod %s.%s]", cbName(java->cb), name);
	    if (!cstr) {
		JS_ReportOutOfMemory(cx);
		return JS_FALSE;
	    }
            for (cp = cstr; *cp != '\0'; cp++)
                if (*cp == '/')
                    *cp = '.';
	    str = JS_NewString(cx, cstr, strlen(cstr));
	    if (!str) {
                free(cstr);
		return JS_FALSE;
	    }
	    *vp = STRING_TO_JSVAL(str);
	    return JS_TRUE;
	}
	if (type != JSTYPE_OBJECT) {
            JS_ReportError(cx, "no field with name \"%s\"", name);
	    return JS_FALSE;
	}
	*vp = OBJECT_TO_JSVAL(obj);
	break;
      default:
	break;
    }
    return JS_TRUE;
}

static JSClass javaslot_class = {
    "JavaSlot",
    JSCLASS_HAS_PRIVATE,
    JS_PropertyStub, JS_PropertyStub,
    javaslot_getProperty, javaslot_setProperty, JS_EnumerateStub,
    javaslot_resolve, javaslot_convert, javaslot_finalize
};

static JSBool
js_reflectJavaSlot(JSContext *cx, JSObject *obj, JSString *str,
		   jsval *vp)
{
    JSJava *java = JS_GetPrivate(cx, obj);
    ClassClass *cb = java->cb;
    struct fieldblock *fb;
    struct methodblock *mb;
    JSJavaSlot *slot;
    JSObject *mo;
    const char *name = JS_GetStringBytes(str);

    *vp = JSVAL_VOID;

    /* PR_ASSERT(obj->clazz == &java_class); */

    /* if there's a field get its value at reflection time */
    fb = java_lookup_field(cx, cb, java->type == JAVA_CLASS, name);
    if (fb) {
	if (!js_convertJFieldToJSValue(cx, java->handle, fb,
				       vp, JSTYPE_VOID)) {
	    /*
             * If this happens, the field had a value that couldn't
	     * be represented in JS.
	     */
            PR_LOG(MojaSrc, error, ("looking up initial field value failed!"));

	    /* FIXME should really set a flag that will cause an error
	     * only if the slot is accessed as a field.  for now we
             * make it look like there wasn't any field by that name,
	     * which is less informative */
	    fb = 0;
	}
    }

#ifndef REFLECT_ALL_SLOTS_LAZILY
    /* match the name against the signatures of the java methods */
    mb = matchMethod(cx, cb, java->type==JAVA_CLASS, name, -1, 0,
		     JS_FALSE /* reportErrors */);

    if (!fb) {
	JSFunction *fun;

	if (!mb) {
	    /* nothing by that name, report an error */
            JS_ReportError(cx, "Java object has no field or method named %s",
			   name);
	    return JS_FALSE;
	}

        /* if we get here, there's a method but no field by this name */
	fun = JS_NewFunction(cx, js_javaMethodWrapper, 0,
			     JSFUN_BOUND_METHOD, obj, name);
	if (!fun)
	    return JS_FALSE;

	PR_LOG(MojaSrc, debug,
               ("eagerly converted slot to function 0x%x with name %s(0x%x)\n",
		fun, name, name));

	*vp = OBJECT_TO_JSVAL(JS_GetFunctionObject(fun));
	return JS_TRUE;
    } else if (!mb) {
        /* there's a field but no method by this name */
	/* looking up the field already set *vp */
	return JS_TRUE;
    }

    /* if we get here there are both fields and methods by this name,
     * so we create a slot object to delay the binding */
#endif

    slot = (JSJavaSlot *) JS_malloc(cx, sizeof(JSJavaSlot));
    if (!slot) {
	return JS_FALSE;
    }

    /* corresponding removes and unlocks are in javaslot_finalize */
    slot->obj = obj;
    if (!JS_AddRoot(cx, &slot->obj)) {
	JS_free(cx, slot);
	return JS_FALSE;
    }
    slot->value = *vp;
    if (!JS_AddRoot(cx, &slot->value)) {
	JS_RemoveRoot(cx, &slot->obj);
	JS_free(cx, slot);
	return JS_FALSE;
    }

    /* FIXME check return value? */
    JS_LockGCThing(cx, str);
    slot->name = str;
    slot->fb = fb;

    mo = JS_NewObject(cx, &javaslot_class, 0, 0);
    JS_SetPrivate(cx, mo, slot);

    PR_LOG(MojaSrc, debug, ("reflected slot %s of 0x%x as 0x%x",
	    name, java->handle, mo));
    if (!mo) {
	JS_RemoveRoot(cx, &slot->obj);
	JS_RemoveRoot(cx, &slot->value);
	JS_UnlockGCThing(cx, slot->name);
	JS_free(cx, slot);
	return JS_FALSE;
    }
    *vp = OBJECT_TO_JSVAL(mo);
    return JS_TRUE;
}

/***********************************************************************/

/* a saved JS error state */
typedef struct SavedJSError SavedJSError;
struct SavedJSError {
    char *message;
    JSErrorReport report;
    SavedJSError *next;
};

/*
 * capture a JS error that occurred in JS code called by java.
 * makes a copy of the JS error data and hangs it off the JS
 * environment.  when the JS code returns, this is checked and
 * used to generate a JSException.  if the JSException is uncaught
 * and makes it up to another layer of JS, the error will be
 * reinstated with JS_ReportError
 */
PR_IMPLEMENT(void)
js_JavaErrorReporter(JSContext *cx, const char *message, JSErrorReport *report)
{
    /* save the error state */
    SavedJSError *newerr;
    newerr = malloc(sizeof(SavedJSError));
    if (!newerr) {
	/* FIXME not much we can do here, abort? */
	return;
    }
    newerr->message = 0;
    if (message) {
	newerr->message = strdup(message);
	if (!newerr->message) {
	    /* FIXME not much we can do here, abort? */
	    free(newerr);
	    return;
	}
    }
    newerr->report.filename = 0;
    newerr->report.lineno = 0;
    newerr->report.linebuf = 0;
    newerr->report.tokenptr = 0;

    if (report) {
	if (report->filename) {
	    newerr->report.filename = strdup(report->filename);
	    if (!newerr->report.filename) {
		/* FIXME not much we can do here, abort? */
		free(newerr->message);
		free(newerr);
		return;
	    }
	}
	newerr->report.lineno = report->lineno;

	if (report->linebuf) {
	    newerr->report.linebuf = strdup(report->linebuf);
	    if (!newerr->report.linebuf) {
		/* FIXME not much we can do here, abort? */
		free((void*)newerr->report.filename);
		free(newerr->message);
		free(newerr);
		return;
	    }
	    newerr->report.tokenptr = newerr->report.linebuf +
				      (report->tokenptr - report->linebuf);
	}
    }

    /* push this error */
    newerr->next = cx->savedErrors;
    cx->savedErrors = newerr;
}

static SavedJSError *
js_js_FreeError(SavedJSError *err)
{
    SavedJSError *next = err->next;

    free(err->message);
    free((char*)err->report.filename);/*FIXME*/
    free((char*)err->report.linebuf);
    free(err);

    return next;
}

PR_IMPLEMENT(void)
jsj_ClearSavedErrors(JSContext *cx)
{
    while (cx->savedErrors)
        cx->savedErrors = js_js_FreeError(cx->savedErrors);
}

/* this is called upon returning from JS to java.  one possibility
 * is that the JS error was actually triggered by java at some point -
 * if so we throw the original java exception.	otherwise, each JS
 * error will have pushed something on JSContext->savedErrors, so
 * we convert them all to a string and throw a JSException with that
 * info.
 */
PR_IMPLEMENT(void)
js_JSErrorToJException(JSContext *cx, ExecEnv *ee)
{
    SavedJSError *err = 0;

    if (!cx->savedErrors) {
	exceptionClear(ee);
	PR_LOG(MojaSrc, debug,
               ("j-m succeeded with no exception cx=0x%x ee=0x%x", cx, ee));
	return;
    }

    /*
     * If there's a pending exception in the java env, assume it
     * needs to be propagated (since JS couldn't have caught it
     * and done something with it).
     */
    if (exceptionOccurred(ee)) {
	PR_LOG(MojaSrc, debug,
               ("j-m propagated exception through JS cx=0x%x ee=0x%x",
		cx, ee));
	return; 	/* propagating is easy! */
    }

    /* otherwise, throw a JSException */
    /* get the message from the deepest saved JS error */
    err = cx->savedErrors;
    if (err) {
	while (err->next)
	    err = err->next;
    }

    /* propagate any pending JS errors upward with a java exception */
    {
	JRIEnv *env = (JRIEnv*) ee;
	struct java_lang_String* message =
	  JRI_NewStringUTF(env, err->message,
			   strlen(err->message));
	struct java_lang_String* filename = err->report.filename
	  ? JRI_NewStringUTF(env, err->report.filename,
			      strlen(err->report.filename))
	  : NULL;
	int lineno = err->report.lineno;
	struct java_lang_String* source = err->report.linebuf
	  ? JRI_NewStringUTF(env, err->report.linebuf,
			      strlen(err->report.linebuf))
	  : NULL;
	int index = err->report.linebuf
	  ? err->report.tokenptr - err->report.linebuf
	  : 0;
	jref exc = (jref)
	 execute_java_constructor((ExecEnv *)env,
				  NULL, JSExceptionClassBlock,
                                  "(Ljava/lang/String;Ljava/lang/String;ILjava/lang/String;I)",
				  message, filename, (int32_t)lineno, source, (int32_t)index);

	exceptionThrow(ee, (HObject *)exc);
	PR_LOG(MojaSrc, debug,
               ("j-m raised JSException \"%s\" cx=0x%x ee=0x%x",
		err->message, cx, ee));
    }
}

static JSBool
js_isJSException(ExecEnv *ee, HObject *exc)
{
    return strcmp(cbName(obj_array_classblock(exc)),
                  "netscape/javascript/JSException")
      ? JS_TRUE : JS_FALSE;
}

/*
 * This is called after returning from java to JS.  if the exception
 * is actually a JSException, we pull the original JS error state out
 * of the JSContext and use that.  Otherwise we turn the JSException
 * into a string and pass it up as a JS error
 */
static JSBool
js_JExceptionToJSError(JSContext *cx, ExecEnv *ee)
{
    SavedJSError *err = 0;
    char *message;
    JSBool success;
    JHandle *exc;

    /*
     * Get rid of any JS errors so far, but save the deepest one
     * in case this was a JSException and we re-report it.
     */
    /* FIXME the deepest one is the most interesting? */
    err = cx->savedErrors;
    if (err) {
	while (err->next)
	    err = js_js_FreeError(err);
    }

    /* if no exception reached us, continue on our merry way */
    if (!exceptionOccurred(ee)) {
	PR_LOG(MojaSrc, debug,
               ("m-j succeeded, no exceptions cx=0x%x ee=0x%x",
		cx, ee));
	success = JS_TRUE;
	goto done;
    }

    /* if we got this far there was an error for sure */
    success = JS_FALSE;

    switch (ee->exceptionKind) {
      case EXCKIND_THROW:
        /* save the exception while we poke around in java */
        exc = ee->exception.exc;
        exceptionClear(ee);

	if (err && js_isJSException(ee, exc)) {
	    js_ReportErrorAgain(cx, err->message, &err->report);
	    PR_LOG(MojaSrc, debug,
                   ("m-j re-reported error \"%s\" cx=0x%x ee=0x%x",
		    err->message, cx, ee));
	}
	/* otherwise, describe the exception to a string */
	else if (js_isSubclassOf(cx,
				 obj_array_classblock(exc),
				 ThrowableClassBlock)) {
	    HString *hdetail =
	      unhand((Hjava_lang_Throwable *)exc)->detailMessage;
	    ClassClass *cb = obj_array_classblock(exc);

	    message = (char *)
		JRI_GetStringPlatformChars((JRIEnv *) ee,
					   (struct java_lang_String *) hdetail,
                                           NULL, 0);

    /* XXX - temporarily replace arguments so we can compile
       (const jbyte *) cx->charSetName,
       (jint) cx->charSetNameLength);
    */

	    PR_LOG(MojaSrc, debug,
                   ("m-j converted exception %s, \"%s\" to error cx=0x%x ee=0x%x",
		    cbName(cb), message, cx, ee));
	    /* pass the string to JS_ReportError */
            JS_ReportError(cx, "uncaught Java exception %s (\"%s\")",
			   cbName(cb), message);
	}

        /* it's not a Throwable, somebody in java-land is being lame */
	else {
	    ClassClass *cb = obj_array_classblock(exc);
            JS_ReportError(cx, "uncaught Java exception of class %s",
			      cbName(cb));
	    PR_LOG(MojaSrc, debug,
                   ("m-j converted exception %s to error cx=0x%x ee=0x%x",
		    cbName(cb), cx, ee));
	}
	break;
      case EXCKIND_STKOVRFLW:
        JS_ReportError(cx, "Java stack overflow, pc=0x%x", ee->exception.addr);
	break;
      default:
	JS_ReportError(cx,
                       "internal error: Java exception of unknown type %d",
		       ee->exceptionKind);
	break;
    }

  done:
    if (err) {
	js_js_FreeError(err);
	cx->savedErrors = 0;
    }
    PR_LOG(MojaSrc, debug, ("m-j cleared JS errors cx=0x%x", cx));
    return success;
}

/***********************************************************************/

static JSBool
js_FindSystemClass(JSContext *cx, char *name, ClassClass **clazz)
{
    ExecEnv *ee;

    ee = jsj_GetCurrentEE(cx);

    if (!ee) return JS_FALSE;

    *clazz = FindClassFromClass(ee, name, TRUE, NULL);

    return js_JExceptionToJSError(cx, ee);
}

/*
 * All js_CallJava calls must use a data pointer that starts like
 * this one:
 */
typedef struct {
    JRIEnv *env;
} js_CallJava_data;

static JSBool
js_CallJava(JSContext *cx, JSJCallback doit, void *d, JSBool pushSafeFrame)
{
    js_CallJava_data *data = d;
    JSBool success = JS_TRUE;
    ExecEnv *ee;
    JSJClassData *classData;

    ee = jsj_GetCurrentEE(cx);

    if (!ee) return JS_FALSE;

    data->env = (JRIEnv *) ee;

    /* security: push the safety frame onto the java stack */
    if (pushSafeFrame)
	if (!js_pushSafeFrame(cx, ee, &classData)) {
	    return JS_FALSE;
	}

    PR_LOG_BEGIN(MojaSrc, debug, ("entering java ee=0x%x cx=0x%x", ee, cx));
    /* FIXME this should be restructured: there could be a prolog and
     * epilog to set up and tear down the JS->java call stuff */
    doit(data);

    PR_LOG_END(MojaSrc, debug, ("left java ee=0x%x cx=0x%x", ee, cx));

    /* it's only safe to call this on the mozilla thread */
    success = js_JExceptionToJSError(cx, ee);

    /* pop the safety frame */
    if (pushSafeFrame)
	js_popSafeFrame(cx, ee, classData);

    return success;
}


/***********************************************************************/

typedef struct {
    JRIEnv *env;
    HObject *self;
    char *name;
    char *sig;
    struct methodblock *mb;
    JSBool isStaticCall;
    va_list args;
    long *raddr;
    size_t rsize;
} js_ExecuteJavaMethod_data;

static void
js_ExecuteJavaMethod_stub(void *d)
{
    js_ExecuteJavaMethod_data *data = (js_ExecuteJavaMethod_data *) d;

    data->raddr[0] =
	do_execute_java_method_vararg(/*FIXME*/(ExecEnv*)data->env,
				      data->self,
				      data->name, data->sig,
				      data->mb, (bool_t) data->isStaticCall,
				      data->args,
				      (data->rsize > sizeof(long))
					  ? &data->raddr[1] : NULL,
				      FALSE);
}

static JSBool
js_ExecuteJavaMethod(JSContext *cx, void *raddr, size_t rsize,
			HObject *ho, char *name, char *sig,
			struct methodblock *mb, JSBool isStaticCall, ...)
{
    js_ExecuteJavaMethod_data data;
    JSBool success;
    va_list args;

#if defined(XP_MAC) && !defined(NSPR20)
 /* Metrowerks va_start() doesn't handle one-byte parameters properly. FIX this when va_start() works again. */
	args = &isStaticCall+1;
#else
    va_start(args, isStaticCall);
#endif

    data.self = ho;
    data.name = name;
    data.sig = sig;
    data.mb = mb;
    data.isStaticCall = isStaticCall;
    VARARGS_ASSIGN(data.args, args);
    data.raddr = raddr;
    data.rsize = rsize;

    success = js_CallJava(cx, js_ExecuteJavaMethod_stub, &data, JS_TRUE);
    va_end(args);

    return success;
}

/***********************************************************************/

typedef struct {
    JRIEnv *env;
    JSContext *cx;
    char *name;
    ClassClass *fromclass;
    ClassClass *ret;
    char *errstr;
} js_FindJavaClass_data;

static void
js_FindJavaClass_stub(void *d)
{
    js_FindJavaClass_data *data = d;
    ExecEnv *ee = /*FIXME*/(ExecEnv*)data->env;

    exceptionClear(ee);

    /* FIXME need to push a stack frame with the classloader
     * of data->fromclass or the security check on opening
     * the url for the class will fail */

    data->ret = FindClassFromClass(ee, data->name, TRUE, data->fromclass);

    /* we clear the exception state, because when JS
     * fails to find a class it assumes it's a package instead
     * of an error. */
    /* FIXME can we report an error if the problem is accessing
     * bogus-codebase? */

    if (exceptionOccurred(ee)) {
	ClassClass *cb = obj_array_classblock(ee->exception.exc);

#ifdef DEBUG
	char *message;
        /* FIXME this could fail: we don't check if it's Throwable,
	 * but i assume that FindClass is well-behaved */
	HString *hdetail =
	  unhand((Hjava_lang_Throwable *)
		 ee->exception.exc)->detailMessage;

	message = (char *)
	    JRI_GetStringPlatformChars((JRIEnv *) ee,
				       (struct java_lang_String *) hdetail,
                                       NULL, 0);

    /* XXX - temporarily replace arguments so we can compile
       (const jbyte *) cx->charSetName,
       (jint) cx->charSetNameLength);
    */

	PR_LOG(MojaSrc, debug,
               ("exception in is_subclass_of %s (\"%s\")",
		cbName(cb), message));
#endif

#ifdef DEBUG_JSJ
	/* take a look at the exception to see if we can narrow
	 * down the kinds of failures that cause a package to
	 * be created? */
	exceptionDescribe(ee);
#endif
        /* FIXME other exceptions don't matter? narrow this down... */
	exceptionClear(ee);
    }
}

/*
 * This can call arbitrary java code in class initialization.
 * pass 0 for the "from" argument for system classes, but if
 * you want to check the applet first pass its class.
 */
static ClassClass *
js_FindJavaClass(JSContext *cx, char *name, ClassClass *from)
{
    js_FindJavaClass_data data;

    data.cx = cx;
    data.name = name;
    data.fromclass = from;
    data.errstr = 0;
    if (!js_CallJava(cx, js_FindJavaClass_stub, &data, JS_TRUE))
	return 0;
    if (data.errstr) {
        JS_ReportError(cx, "%s", data. errstr);
	free(data.errstr);
	/* FIXME need to propagate error condition differently */
	return 0;
    }
    return data.ret;
}

/***********************************************************************/


typedef struct {
    JRIEnv *env;
    JSBool privileged;
    char *name;
    ClassClass *cb;
    char *sig;
    va_list args;
    HObject *ret;
} js_ConstructJava_data;

static void
js_ConstructJava_stub(void *d)
{
    js_ConstructJava_data *data = d;
    ExecEnv *ee = /*FIXME*/(ExecEnv*)data->env;

    if (data->privileged) {
	/* FIXME extremely lame - there should be a security
	 * flag to execute_java_constructor_vararg instead.
	 * the effect of this is that the JSObject constructor may
         * get called on the wrong thread, but this probably won't
	 * do any damage.  JRI will fix this, right? */
	ee = PRIVILEGED_EE;
    }
    data->ret =
      execute_java_constructor_vararg(ee,
				      data->name, data->cb,
				      data->sig, data->args);
}

/* this can call arbitrary java code in class initialization */
static HObject *
js_ConstructJava(JSContext *cx, char *name, ClassClass *cb,
		    char *sig, ...)
{
    js_ConstructJava_data data;
    va_list args;
    JSBool success;

    va_start(args, sig);
    data.name = name;
    data.privileged = JS_FALSE;
    data.cb = cb;
    data.sig = sig;
    VARARGS_ASSIGN(data.args, args);
    data.env = JRI_GetCurrentEnv();

    success = js_CallJava(cx, js_ConstructJava_stub, &data, JS_TRUE);
    va_end(args);

    if (success) return data.ret;
    else return 0;
}

/* for private constructors, i.e. JSObject */
static HObject *
js_ConstructJavaPrivileged(JSContext *cx, char *name, ClassClass *cb,
			      char *sig, ...)
{
    js_ConstructJava_data data;
    va_list args;
    JSBool success;

    va_start(args, sig);
    data.privileged = JS_TRUE;
    data.name = name;
    data.cb = cb;
    data.sig = sig;
    VARARGS_ASSIGN(data.args, args);
    data.env = JRI_GetCurrentEnv();

    success = js_CallJava(cx, js_ConstructJava_stub, &data, JS_TRUE);
    va_end(args);

    if (success) return data.ret;
    else return 0;
}


/***********************************************************************/

static JSBool jsj_enabled = JS_TRUE;

/*
 * most of the initialization is done lazily by this function,
 * which is only called through jsj_GetCurrentEE().  it is
 * only called once - this is enforced in jsj_GetCurrentEE().
 */
static void
jsj_FinishInit(JSContext *cx, JRIEnv *env)
{
    char *name;
    ExecEnv *ee;

    PR_LOG(MojaSrc, debug, ("jsj_FinishInit()\n"));

    /* initialize the reflection tables */
    javaReflections =
      PR_NewHashTable(256, (PRHashFunction) java_hashHandle,
		      (PRHashComparator) java_pointerEq,
		      (PRHashComparator) java_pointerEq, 0, 0);
#ifdef NSPR20
    PR_RegisterGCLockHook((GCLockHookFunc*) PrepareJSLocksForGC, 0);
#endif
    PR_RegisterRootFinder(scanJSJavaReflections,
                          "scan JS reflections of java objects",
			  JS_GetRuntime(cx));
    if (javaReflectionsMonitor == NULL)
	javaReflectionsMonitor = PR_NewNamedMonitor("javaReflections");
    jsReflections =
      PR_NewHashTable(256, (PRHashFunction) java_hashHandle,
		      (PRHashComparator) java_pointerEq,
		      (PRHashComparator) java_pointerEq, 0, 0);
    if (jsReflectionsMonitor == NULL)
	jsReflectionsMonitor = PR_NewNamedMonitor("jsReflections");

    ee = (ExecEnv *) env;

    exceptionClear(ee);

    name = "java/lang/Object";
    if (! (ObjectClassBlock = FindClassFromClass(ee, name, TRUE, 0)))
	goto badclass;
    MakeClassSticky(ObjectClassBlock);

    name = "netscape/javascript/JSObject";
    if (! (JSObjectClassBlock = FindClassFromClass(ee, name, TRUE, 0)))
	goto badclass;
    MakeClassSticky(JSObjectClassBlock);
    JSObjectInternalField = JRI_GetFieldID(env,
					   (struct java_lang_Class*)cbHandle(JSObjectClassBlock),
                                           "internal", "I");

    name = "netscape/javascript/JSException";
    if (! (JSExceptionClassBlock = FindClassFromClass(ee, name, TRUE, 0)))
	goto badclass;
    MakeClassSticky(JSExceptionClassBlock);

    name = "java/lang/String";
    if (! (StringClassBlock = FindClassFromClass(ee, name, TRUE, 0)))
	goto badclass;
    MakeClassSticky(StringClassBlock);

    name = "java/lang/Boolean";
    if (! (BooleanClassBlock = FindClassFromClass(ee, name, TRUE, 0)))
	goto badclass;
    MakeClassSticky(BooleanClassBlock);

    name = "java/lang/Double";
    if (! (DoubleClassBlock = FindClassFromClass(ee, name, TRUE, 0)))
	goto badclass;
    MakeClassSticky(DoubleClassBlock);

    name = "java/lang/Throwable";
    if (! (ThrowableClassBlock = FindClassFromClass(ee, name, TRUE, 0)))
	goto badclass;
    MakeClassSticky(ThrowableClassBlock);

    return;

  badclass:
    PR_LOG(MojaSrc, error, ("couldn't find class \"%s\"\n", name));
    JS_ReportError(cx, "Unable to initialize LiveConnect: missing \"%s\"",
		   name);
    jsj_enabled = JS_FALSE;
    return;
}

PR_STATIC_CALLBACK(int)
jsj_TrashJSReflectionEntry(PRHashEntry *he, int i, void *arg)
{
    JRIEnv *env = arg;
    jref jso = (jref) he->value;

    JRI_SetFieldInt(env, jso, JSObjectInternalField, (jint) 0);
    /* FIXME inline JS_RemoveRoot -- this will go away with GC unification */
    JS_LOCK_RUNTIME(finalizeRuntime);
    (void) PR_HashTableRemove(finalizeRuntime->gcRootsHash, (void *)&he->key);
    JS_UNLOCK_RUNTIME(finalizeRuntime);

    return HT_ENUMERATE_REMOVE;
}

PR_EXTERN(void)
JSJ_Finish(void)
{
    JRIEnv *env = 0;

    /* PR_ASSERT(we_have_libmocha_lock) */

    jsj_enabled = JS_FALSE;

    if (!javaReflectionsMonitor)
	return;
    PR_EnterMonitor(javaReflectionsMonitor);
    if (javaReflections) {
	PR_HashTableDestroy(javaReflections);
	javaReflections = NULL;
    }
    PR_ExitMonitor(javaReflectionsMonitor);

    env = JRI_GetCurrentEnv();

    if (!env) {
#ifdef DEBUG_JSJ
        PR_ASSERT(env);
#else
        return;
#endif
    }

    /* FIXME assert that lm_lock is held */
    PR_EnterMonitor(jsReflectionsMonitor);
    if (jsReflections) {
        PR_HashTableEnumerateEntries(jsReflections,
                                     jsj_TrashJSReflectionEntry,
                                     (void*) env);
	PR_HashTableDestroy(jsReflections);
	jsReflections = NULL;
    }
    PR_ExitMonitor(jsReflectionsMonitor);
}

/*
 * Get the java class associated with an instance, useful for access
 * to static fields and methods of applets.
 */
static JSBool
js_getJavaClass(JSContext *cx, JSObject *obj, PRUintn argc, jsval *argv,
		jsval *rval)
{
    JSObject *mo;
    JSObject *moclass;
    JSJava *java;

    /* FIXME this could accept strings as well i suppose */
    if (argc != 1 ||
	!JSVAL_IS_OBJECT(argv[0]) ||
	!(mo = JSVAL_TO_OBJECT(argv[0])) ||
	!JS_InstanceOf(cx, mo, &java_class, 0) ||
	(java = (JSJava *) JS_GetPrivate(cx, mo))->type != JAVA_OBJECT) {
        JS_ReportError(cx, "getClass expects a Java object argument");
	return JS_FALSE;
    }

    if (!(moclass = js_ReflectJClassToJSObject(cx, java->cb))) {
        JS_ReportError(cx, "getClass can't find Java class reflection");
	return JS_FALSE;
    }

    *rval = OBJECT_TO_JSVAL(moclass);
    return JS_TRUE;
}

static JSObject *
js_DefineJavaPackage(JSContext *cx, JSObject *obj, char *jsname, char *package)
{
    JSJavaPackage *pack;
    JSObject *pobj;

    pack = JS_malloc(cx, sizeof(JSJavaPackage));
    if (!pack)
	return 0;

    if (package) {
	pack->name = JS_strdup(cx, package);
	if (!pack->name) {
	    JS_free(cx, pack);
	    return 0;
	}
    } else {
	pack->name = 0;
    }

    /* FIXME can we make the package read-only? */
    pobj = JS_DefineObject(cx, obj, jsname, &javapackage_class, 0, 0);
    if (pobj && !JS_SetPrivate(cx, pobj, pack))
	(void) JS_DeleteProperty(cx, obj, jsname);
    return pobj;
}

/* hook from js_DestroyContext */
static void
jsj_DestroyJSContextHook(JSContext *cx)
{
    /* FIXME do anything with the env? */
    /* FIXMEold cx->javaEnv = 0; */
}

/* FIXME make sure this is called from jsscript.c:js_DestroyScript !*/
static void
jsj_DestroyScriptHook(JSContext *cx, JSScript *script)
{
    JSJClassData *data = script->javaData;

    if (!data)
	return;

    jsj_DestroyClassData(cx, data);

    script->javaData = 0;
}

static void
jsj_DestroyFrameHook(JSContext *cx, JSStackFrame *frame) {
    if (frame->annotation) {
        JRIEnv *env = (JRIEnv *) jsj_GetCurrentEE(cx);
        if (!env) return;

        JRI_DisposeGlobalRef(env, frame->annotation);
    }
}

/* we need to hook into the js interpreter in a few special places */
JSInterpreterHooks js_Hooks = {
    jsj_DestroyJSContextHook,
    jsj_DestroyScriptHook,
    jsj_DestroyFrameHook
};

extern void _java_javascript_init(void);

/*
 * Initialize JS<->Java glue
 */
PR_IMPLEMENT(JSBool)
JSJ_Init(JSJCallbacks *callbacks)
{
    static JSBool initialized = JS_FALSE;

    /* JSJ_Init may be called twice: it is called with default
     * hooks when the netscape.javascript.JSObject class is
     * initialized, but it also may be called by the client
     * or server.  in this case, the first to call it gets
     * to set the hooks */
    if (initialized)
        return JS_FALSE;
    initialized = JS_TRUE;

    _java_javascript_init();	/* stupid linker tricks */

    js_SetInterpreterHooks(&js_Hooks);

    if (!callbacks)
	return JS_TRUE;

#ifdef NSPR20
    if (MojaSrc == NULL)
        MojaSrc = PR_NewLogModule("MojaSrc");
#endif

    jsj_callbacks = callbacks;

    return JS_TRUE;
}

/*
 * Initialize a javascript context and toplevel object for use with JSJ
 */
PR_IMPLEMENT(JSBool)
JSJ_InitContext(JSContext *cx, JSObject *obj)
{
    JSObject *mo;

    /* grab the main runtime from the context if we haven't yet */
    if (!finalizeRuntime)
        finalizeRuntime = JS_GetRuntime(cx);

    /* define the top of the java package namespace as "Packages" */
    mo = js_DefineJavaPackage(cx, obj, "Packages", 0);

    /* some convenience packages */
    /* FIXME these should be properties of the top-level package
     * too.  as it is there will be two different objects for
     * "java" and "Packages.java" which is unfortunate but mostly
     * invisible */

    /* FIXMEbe Use new JS_AliasProperty API call.
     * have to lookup Packages.java first... */

    js_DefineJavaPackage(cx, obj, "java", "java");
    js_DefineJavaPackage(cx, obj, "sun", "sun");
    js_DefineJavaPackage(cx, obj, "netscape", "netscape");

    JS_DefineFunction(cx, obj, "getClass", js_getJavaClass, 0, JSPROP_READONLY);
    /* create the prototype object for java objects and classes */
    mo = JS_DefineObject(cx, obj, "#javaPrototype", &java_class, 0,
			   JSPROP_READONLY | JSPROP_PERMANENT);
    /* FIXME set up the private data? */

    /* any initialization that depends on java running is done in
     * js_FinishInitJava */

    return JS_TRUE;
}

#ifdef NEVER
/* This is really a big comment describing the JavaScript class file,
   never intended to be compiled. */
/* FIXME should this be in a package? */

/**
 * Internal class for secure JavaScript->Java calls.
 * We load this class with a crippled ClassLoader, and make sure
 * that one of the methods from this class is on the Java stack
 * when JavaScript calls into Java.
 * The call to System.err.println is necessary because otherwise
 * sj doesn't account for the arguments in mb->maxstack, and
 * the jsContext argument gets wiped out when the next stack
 * frame is constructed.
 */

package netscape.javascript;

class JavaScript {
    /* can't be constructed */
    private JavaScript() {};

    /**
     * this is the method that will be on the Java stack when
     * JavaScript calls Java.  it is never actually called,
     * but we put a reference to it on the stack as if it had
     * been.  the mochaContext argument is a native pointer to
     * the current JavaScript context so that we can figure
     * that out from Java.
     */
    static void callJava(int jsContext) {
	/* this call is here to make sure the compiler
	 * allocates enough stack for us - previously we
	 * were getting mb->maxstack == 0, which would
	 * cause the jsContext argument to be overwritten */
	System.err.println(jsContext);
    };
}

#endif /* NEVER */

static unsigned char JavaScript_class_bytes[] = {
 "\312\376\272\276\000\003\000-\000\035\007\000\026\007\000\030\007\000"
 "\020\007\000\033\012\000\003\000\012\012\000\002\000\010\011\000\004\000"
 "\011\014\000\031\000\034\014\000\015\000\032\014\000\013\000\014\001\000"
 "\007println\001\000\004(I)V\001\000\003err\001\000\015ConstantValue\001"
 "\000\010callJava\001\000\023java/io/PrintStream\001\000\012Exceptions"
 "\001\000\017LineNumberTable\001\000\012SourceFile\001\000\016LocalVar"
 "iables\001\000\004Code\001\000\036netscape/javascript/JavaScript\001\000"
 "\017JavaScript.java\001\000\020java/lang/Object\001\000\006<init>\001"
 "\000\025Ljava/io/PrintStream;\001\000\020java/lang/System\001\000\003"
 "()V\000\000\000\001\000\002\000\000\000\000\000\002\000\002\000\031\000"
 "\034\000\001\000\025\000\000\000\035\000\001\000\001\000\000\000\005*"
 "\267\000\006\261\000\000\000\001\000\022\000\000\000\006\000\001\000\000"
 "\000\020\000\010\000\017\000\014\000\001\000\025\000\000\000$\000\002"
 "\000\001\000\000\000\010\262\000\007\032\266\000\005\261\000\000\000\001"
 "\000\022\000\000\000\012\000\002\000\000\000\037\000\007\000\032\000\001"
 "\000\023\000\000\000\002\000\027"
};

/* now something to make a classloader and get a JavaScript object
 * from it. */

/* FIXME garbage collection of these classes?  how should they be
 * protected? */

#include "java_lang_ClassLoader.h"

#define USELESS_CODEBASE_URL "http://javascript-of-unknown-origin.netscape.com/"

static void
jsj_DestroyClassData(JSContext *cx, JSJClassData *data)
{
    ExecEnv *ee;
    JRIEnv *env;
    HObject *loader;

    /* FIXME locking! */
    if (--data->nrefs > 0)
	return;

    ee = jsj_GetCurrentEE(cx);
    if (!ee) {
        /* FIXME memory leak - if we can't find a java execution
         * env we can't free the classloader and class */
        PR_ASSERT(!"can't find java env to free JSScript.javaData");
	return;
    }

    env = (JRIEnv *) ee;

    if (data->clazz) {
	JRI_DisposeGlobalRef(env, data->clazz);
	data->clazz = NULL;
    }

    if (data->loader) {
	loader = JRI_GetGlobalRef(env, data->loader);
	/* comment out call to releaseClassLoader because it is obsolete.
	execute_java_dynamic_method(ee, loader,
                                    "releaseClassLoader", "()V");
	if (exceptionOccurred(ee)) {
	*/
                /* FIXME memory leak - if we can't find a java execution
                 * env we can't free the classloader and class */
    /*
            PR_ASSERT(!"failed releasing javascript classloader");
	    goto done;
	}
	*/
	JRI_DisposeGlobalRef(env, data->loader);
	data->loader = NULL;
    }

    data->mb = NULL;

  done:
    free(data);
}

/* security: this method returns a methodblock which can be
 * used for secure JS->java calls.  the methodblock is
 * guaranteed to have an associated AppletClassLoader which will
 * allow the SecurityManager to determine what permissions to
 * give to the Java code being called.
 */
static JSJClassData *
jsj_MakeClassData(JSContext *cx)
{
    int i;
    ExecEnv *ee;
    JRIEnv *env;
    JSScript *script;
    JSStackFrame *fp;
    PRInt32 length;
    HArrayOfByte *bytes;
    const char *origin_url;
    struct Hjava_lang_ClassLoader *loader;
    ClassClass *clazz;
    struct methodblock *mb;
    JSJClassData *data;

    ee = jsj_GetCurrentEE(cx);
    if (!ee) return 0;

    /* see if this script already has a classloader */
    script = NULL;
    for (fp = cx->fp; fp; fp = fp->down) {
	script = fp->script;
	if (script)
	    break;
    }
    if (script &&
	(data = (JSJClassData *) script->javaData)) {
	/* FIXME locking! */
	data->nrefs++;
	return data;
    }

    data = (JSJClassData *) malloc(sizeof(JSJClassData));
    if (!data) {
	JS_ReportOutOfMemory(cx);
	return 0;
    }
    data->nrefs = 1;
    data->loader = NULL;
    data->clazz = NULL;
    data->mb = NULL;

    if (script && script->filename
        && strcmp("[unknown origin]", script->filename)) {
	origin_url = script->filename;
    } else {
	origin_url = USELESS_CODEBASE_URL;
    }

    loader = jsj_callbacks->jsClassLoader(cx, origin_url);

    if (exceptionOccurred(ee) || !loader) {
        JS_ReportError(cx, "couldn't create ClassLoader for JavaScript");
	free(data);
	return 0;
    }

    env = (JRIEnv *) ee;
    data->loader = JRI_NewGlobalRef(env, loader);

    /* FIXME should save and re-use this array in a jglobal */
    /* make the array of bytes for class JavaScript */
    length = sizeof(JavaScript_class_bytes);
    bytes = (HArrayOfByte *) ArrayAlloc(T_BYTE, length);
    if (!bytes) {
	jsj_DestroyClassData(cx, data);
	JS_ReportError(cx,
                       "couldn't allocate bytes for JavaScript.class");
	return 0;
    }
    memmove(unhand(bytes)->body, JavaScript_class_bytes, (size_t)length);

    /* FIXME lock to avoid race condition between this and defineClass */
    clazz = FindLoadedClass("netscape/javascript/JavaScript", loader);

    if (!clazz) {
	/* make class JavaScript */
	clazz = (ClassClass*)
	    do_execute_java_method(ee, loader,
                                   "defineClass", "(Ljava/lang/String;[BII)Ljava/lang/Class;",
				   0, FALSE, NULL /* no required name */, bytes, 0L, length);

	if (exceptionOccurred(ee) || !clazz) {
	    jsj_DestroyClassData(cx, data);
            JS_ReportError(cx, "couldn't load class JavaScript");
	    return 0;
	}

        (void)
            do_execute_java_method(ee, loader,
                                   "setPrincipalAry",
                                   "(Ljava/lang/Class;Ljava/lang/String;)Z",
                                   0, FALSE,
                                   clazz, NULL /* don't lookup */);

	if (exceptionOccurred(ee) || !clazz) {
	    jsj_DestroyClassData(cx, data);
            JS_ReportError(cx, "couldn't set principal for class JavaScript");
	    return 0;
	}
    }

    data->clazz = JRI_NewGlobalRef(env, cbHandle(clazz));

    /* FIXME set up the signatures on the class here */

    /* find the static method callJava() for the class */
    for (i = 0; i < (int)cbMethodsCount(clazz); i++) {
	mb = cbMethods(clazz) + i;
        if (!strcmp(fieldname(&mb->fb), "callJava")
            && !strcmp(fieldsig(&mb->fb), "(I)V")) {
	    /* found it... */
	    data->mb = mb;
	    if (script) {
		/* FIXME locking! */
		data->nrefs++;
		script->javaData = data;
	    }
	    return data;
	}
    }

    jsj_DestroyClassData(cx, data);
    JS_ReportError(cx, "can't find method JavaScript.callJava");

    return 0;
}

/*
 * tell whether a partiular methodblock is part of a safety frame
 *
 * the security here depends on the fact that users are forbidden
 * to define packages in netscape.javascript.  the consequence
 * of breaking this security would be that the user could get
 * a java int dereferenced as a pointer, most likely crashing
 * the program.
 *
 * embedding some sort of secret into the JavaScript class
 *  (pointer to itself?) would also secure this.
 */
PR_IMPLEMENT(JSBool)
JSJ_IsSafeMethod(struct methodblock *mb)
{
    ClassClass *cb;

    if (!mb)
	return JS_FALSE;

    cb = fieldclass(&mb->fb);

    if (!cb ||
        strcmp(cbName(cb), "netscape/javascript/JavaScript"))
	return JS_FALSE;

    return JS_TRUE;
}

/*
 * push a frame onto the java stack which does nothing except
 * provide a classloader for the security manager
 */
static JSBool
js_pushSafeFrame(JSContext *cx, ExecEnv *ee, JSJClassData **classData)
{
    JavaFrame *current_frame, *previous_frame;
    JavaStack *current_stack;
    struct methodblock *mb;

    if (!jsj_callbacks->jsClassLoader) {
        *classData = NULL;
        return JS_TRUE;
    }

    if (!(*classData = jsj_MakeClassData(cx)))
	return JS_FALSE;
    mb = (*classData)->mb;

    previous_frame = ee->current_frame;
    if (previous_frame == 0) {
	/* bottommost frame on this Exec Env. */
	current_stack = ee->initial_stack;
	current_frame = (JavaFrame *)(current_stack->data); /* no vars */
    } else {
	int args_size = mb->args_size;
	current_stack = previous_frame->javastack; /* assume same stack */
	if (previous_frame->current_method) {
	    int size = previous_frame->current_method->maxstack;
	    current_frame = (JavaFrame *)(&previous_frame->ostack[size]);
	} else {
            /* The only frames that don't have a mb are pseudo frames like
             * this one and they don't really touch their stack. */
	    current_frame = (JavaFrame *)(previous_frame->optop + 3);
	}
	if (current_frame->ostack + args_size > current_stack->end_data) {
            /* Ooops.  The current stack isn't big enough.  */
	    if (current_stack->next != 0) {
		current_stack = current_stack->next;
	    } else {
		current_stack = CreateNewJavaStack(ee, current_stack);
		if (current_stack == 0) {
		    JS_ReportOutOfMemory(cx);
		    return JS_FALSE;
		}
	    }
	    /* no vars */
	    current_frame = (JavaFrame *)(current_stack->data);
	}
    }
    current_frame->prev = previous_frame;
    current_frame->javastack = current_stack;
    current_frame->optop = current_frame->ostack;
    current_frame->vars = 0;	/* better not reference any! */
    current_frame->monitor = 0; /* not monitoring anything */
    current_frame->annotation = 0;

    /* make this be a method with the JS classloader */
    current_frame->current_method = mb;

    /* FIXME push the current mochaContext as an integer */
    current_frame->optop[0].i = (int32_t) cx;
    current_frame->optop += current_frame->current_method->args_size;
    current_frame->constant_pool = 0;

    ee->current_frame = current_frame;

    return JS_TRUE;
}

/*
 * pop the safety frame from the java stack
 */
static void
js_popSafeFrame(JSContext *cx, ExecEnv *ee, JSJClassData *classData)
{
    if (!classData)
        return;

    ee->current_frame = ee->current_frame->prev;
    jsj_DestroyClassData(cx, classData);
}

/*
 * look up the stack for the most recent safety frame and extract
 * the JSContext from it.  return NULL if no such frame was found.
 */
PR_IMPLEMENT(void)
JSJ_FindCurrentJSContext(JRIEnv *env, JSContext **cxp, void **loaderp)
{
    ExecEnv *ee = (ExecEnv *) env;
    JavaFrame *frame, frame_buf;
    ClassClass	*cb;

#define NEXT_FRAME(frame) \
    (((frame)->current_method && (frame)->current_method->fb.access & ACC_MACHINE_COMPILED) ? \
     CompiledFramePrev(frame, &frame_buf) \
     : frame->prev)

    *cxp = 0;
    *loaderp = 0;

    /* search the stack upward */
    for (frame = ee->current_frame; frame; frame = NEXT_FRAME(frame)) {
	struct methodblock *mb = frame->current_method;
	if (mb) {
	    cb = fieldclass(&frame->current_method->fb);
	    *loaderp = cbLoader(cb);
	    if (*loaderp) {
		if (JSJ_IsSafeMethod(mb)) {
			/* extract the mochacontext here */
		    *cxp = (JSContext *)frame->ostack[0].i;
		} else
		    *cxp = 0;
		return;
	    }
	}
    }
    return;
}

PR_IMPLEMENT(JSBool)
JSJ_IsCalledFromJava(JSContext *cx)
{
    ExecEnv *ee;

    ee = (ExecEnv *) JRI_GetCurrentEnv();
    if (ee == NULL)
        return JS_FALSE;

    return ee->current_frame != NULL;
}

/* extract the javascript object from a netscape.javascript.JSObject */
PR_IMPLEMENT(JSObject *)
JSJ_ExtractInternalJSObject(JRIEnv *env, HObject* jso)
{
    PR_ASSERT(obj_array_classblock((HObject*)jso) == JSObjectClassBlock);
    
    return (JSObject *) JRI_GetFieldInt(env, (netscape_javascript_JSObject*)jso,
                                        JSObjectInternalField);
}

PR_IMPLEMENT(JSPrincipals *)
js_GetJSPrincipalsFromJavaCaller(JSContext *cx, int callerDepth)
{
    return jsj_callbacks->getJSPrincipalsFromJavaCaller(cx, callerDepth);
}


#endif /* defined(JAVA) */
