/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is the MRJ Carbon OJI Plugin.
 *
 * The Initial Developer of the Original Code is
 * Netscape Communications Corp.
 * Portions created by the Initial Developer are Copyright (C) 2001
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Patrick C. Beard <beard@netscape.com>
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

#include "CSecureEnv.h"
#include "nsISecurityContext.h"

#include "MRJPlugin.h"
#include "MRJSession.h"
#include "nsIThreadManager.h"
#include "nsIJVMManager.h"
#include "nsIScriptSecurityManager.h"
#include "nsIPrincipal.h"

#include "MRJMonitor.h"
#include "NativeMonitor.h"
#include "JavaMessageQueue.h"

#define PROXY_JNI_CALLS 1
#define USE_LIVECONNECT_PROXY 1
#define LOCAL_REFS_ARE_GLOBAL USE_LIVECONNECT_PROXY

inline jobject ToGlobalRef(JNIEnv* env, jobject localRef)
{
    jobject globalRef = env->NewGlobalRef(localRef);
    // env->DeleteLocalRef(localRef);   // not necessary from native methods. done en-masse.
    return globalRef;
}

JavaMessageQueue::JavaMessageQueue(Monitor* monitor)
	:	mFirst(NULL), mLast(NULL), mMonitor(monitor)
{
}

void JavaMessageQueue::putMessage(JavaMessage* msg)
{
	if (mFirst == NULL) {
		mFirst = mLast = msg;
	} else {
		mLast->setNext(msg);
		mLast = msg;
	}
	msg->setNext(NULL);
}

JavaMessage* JavaMessageQueue::getMessage()
{
	JavaMessage* msg = mFirst;
	if (msg != NULL) {
		mFirst = mFirst->getNext();
		if (mFirst == NULL) mLast = NULL;
	}
	return msg;
}

void JavaMessageQueue::enter()
{
	mMonitor->enter();
}

void JavaMessageQueue::exit()
{
	mMonitor->exit();
}

void JavaMessageQueue::wait()
{
	mMonitor->wait();
}

void JavaMessageQueue::wait(long long millis)
{
	mMonitor->wait(millis);
}

void JavaMessageQueue::notify()
{
	mMonitor->notify();
}

/**
 * Native run method that communicates with LiveConnect from a Java thread.
 */
static void netscape_oji_JNIThread_run(JNIEnv* env, jobject self)
{
	CSecureEnv* secureEnv = NULL;
	jmethodID yieldMethod = NULL;
	jmethodID sleepMethod = NULL;
	
	jclass clazz = env->GetObjectClass(self);
	if (clazz != NULL) {
		// the field fSecureEnv contains a pointer to a CSecureEnv.
		jfieldID fSecureEnvField = env->GetFieldID(clazz, "fSecureEnv", "I");
		if (fSecureEnvField != NULL) {
			secureEnv = (CSecureEnv*) env->GetIntField(self, fSecureEnvField);
		}
		yieldMethod = env->GetStaticMethodID(clazz, "yield", "()V");
		sleepMethod = env->GetStaticMethodID(clazz, "sleep", "(J)V");
	}
	
	// notify the secure JNI that we are here, and wait for messages to arrive.
	if (secureEnv != NULL) {
		jboolean isRunning = true;
		MRJSession* session = secureEnv->getSession();
		MRJMonitor requestMonitor(session, self);
		MRJMonitor replyMonitor(session);
		// NativeMonitor replyMonitor(secureEnv->getThreadManager());
		JavaMessageQueue requests(&requestMonitor), replies(&replyMonitor);
		secureEnv->initialize(env, &isRunning, &requests, &replies);
		
		// when this thread is running, no other thread can enter the request queue monitor.
		requests.enter();
		
		while (isRunning) {
			// the protocol for now is dead simple:  get a message from the
			// requests message queue, process it, and then put it back in
			// the replies queue. This will get more elaborate to handle
			// upcall requests. 
			JavaMessage* msg = requests.getMessage();
			if (msg != NULL) {
				msg->execute(env);
				secureEnv->savePendingException(env);
				replies.putMessage(msg);
				replies.notify();
			} else {
				// should we do sleep, timed wait, or what?
				// env->CallStaticVoidMethod(clazz, yieldMethod);
				// env->CallStaticVoidMethod(clazz, sleepMethod, jlong(kDefaultJMTime));
				requests.wait();
			}
		}
		
		requests.exit();
	}
}

static jclass netscape_oji_JNIRunnable;
static jmethodID netscape_oji_JNIRunnable_constructorID;
static jfieldID netscape_oji_JNIRunnable_mJavaMessageID;

static void netscape_oji_JNIRunnable_run(JNIEnv* env, jobject self)
{
    JavaMessage* msg = (JavaMessage*) env->GetIntField(self, netscape_oji_JNIRunnable_mJavaMessageID);
    if (msg) {
        msg->execute(env);
        // what about upcalls?
    }
}

static bool check_exceptions(JNIEnv* env)
{
    jthrowable exc = env->ExceptionOccurred();
	if (exc) {
	    env->ExceptionDescribe();
	    env->ExceptionClear();
	    env->DeleteLocalRef(exc);
	    return true;
	}
	return false;
}

/**
 * Called from browser side, starts the Java thread that calls from LiveConnect to Java
 * are processed in.
 */
static void CreateJNIThread(CSecureEnv* secureEnv)
{
	nsIThreadManager* manager = secureEnv->getThreadManager();
	MRJSession* session = secureEnv->getSession();
	JNIEnv* env = session->getCurrentEnv();
	
    check_exceptions(env);

	jclass JNIThreadClass = env->FindClass("netscape.oji.JNIThread");
	if (JNIThreadClass != NULL) {
		JNINativeMethod method = { "run", "()V", &netscape_oji_JNIThread_run };
		env->RegisterNatives(JNIThreadClass, &method, 1);
		jmethodID constructorID = env->GetMethodID(JNIThreadClass, "<init>", "(I)V");
		if (constructorID != NULL) {
			jobject javaThread = env->NewObject(JNIThreadClass, constructorID, secureEnv);
			for (;;) {
				// was there some kind of exception? bail if so.
				if (check_exceptions(env))
				    break;
				// give time to Java, to allow the thread to come up.
				session->idle();
				// has the thread made contact?
				if (secureEnv->isInitialized())
					break;
				// give time to NSPR, to avoid hanging too long.
				manager->Sleep();
			}
		}
		env->DeleteLocalRef(JNIThreadClass);
	}
}

static jobject CreateJNIRunnable(JNIEnv* env, JavaMessage* msg)
{
    // XXX:  does this have to be thread safe?
    if (!netscape_oji_JNIRunnable) {
    	jclass clazz = env->FindClass("netscape.oji.JNIRunnable");
    	if (!clazz) return NULL;
		JNINativeMethod method = { "run", "()V", &netscape_oji_JNIRunnable_run };
		env->RegisterNatives(clazz, &method, 1);
		netscape_oji_JNIRunnable_constructorID = env->GetMethodID(clazz, "<init>", "(I)V");
        netscape_oji_JNIRunnable_mJavaMessageID = env->GetFieldID(clazz, "mJavaMessage", "I");
		netscape_oji_JNIRunnable = (jclass) env->NewGlobalRef(clazz);
		env->DeleteLocalRef(clazz);
    }
    check_exceptions(env);
    return env->NewObject(netscape_oji_JNIRunnable, netscape_oji_JNIRunnable_constructorID, msg);
}

static jclass GetLiveConnectProxy(JNIEnv* env, nsIPrincipal* codebasePrincipal)
{
    jclass liveConnectProxy = NULL;
    jclass netscape_oji_ProxyClassLoaderFactory = env->FindClass("netscape/oji/ProxyClassLoaderFactory");
    if (netscape_oji_ProxyClassLoaderFactory) {
        jmethodID createClassLoaderID = env->GetStaticMethodID(netscape_oji_ProxyClassLoaderFactory,
                                                               "createClassLoader", 
                                                               "(Ljava/lang/String;)Ljava/lang/ClassLoader;");
        if (createClassLoaderID) {
            jstring codebaseUTF = NULL;
            char* codebase;
            nsresult rv = codebasePrincipal->GetOrigin(&codebase);
            if (NS_SUCCEEDED(rv)) {
                codebaseUTF = env->NewStringUTF(codebase);
                delete[] codebase;
            }
            if (codebaseUTF) {
                jobject classLoader = env->CallStaticObjectMethod(netscape_oji_ProxyClassLoaderFactory,
                                                                  createClassLoaderID, codebaseUTF);
                if (classLoader) {
                    jclass clazz = env->GetObjectClass(classLoader);
                    jmethodID loadClassID = env->GetMethodID(clazz, "loadClass",
                                                             "(Ljava/lang/String;)Ljava/lang/Class;");
                    if (loadClassID) {
                        jstring className = env->NewStringUTF("netscape.oji.LiveConnectProxy");
                        if (className) {
                            liveConnectProxy = (jclass) env->CallObjectMethod(classLoader, loadClassID, className);
                            env->DeleteLocalRef(className);
                        }
                    }
                    env->DeleteLocalRef(clazz);
                }
                env->DeleteLocalRef(codebaseUTF);
            }
        }
        env->DeleteLocalRef(netscape_oji_ProxyClassLoaderFactory);
    }
    check_exceptions(env);
    return liveConnectProxy;
}

/**
 * Creates a new native thread in MRJ's main thread, to avoid deadlock problems.
 */
class CreateNativeThreadMessage : public NativeMessage {
	nsresult* mResult;
	PRUint32* mThreadID;
	CSecureEnv* mSecureEnv;
public:
	CreateNativeThreadMessage(nsresult* outResult, PRUint32* outThreadID, CSecureEnv* secureEnv)
		:	mResult(outResult), mThreadID(outThreadID), mSecureEnv(secureEnv)
	{
	}

	virtual void execute()
	{
		nsIThreadManager* manager = mSecureEnv->getThreadManager();
		*mResult = manager->CreateThread(mThreadID, mSecureEnv);
	}
};

/**
 * Called from a Java thread that wants to communicate with the browser.
 */
static void CreateNativeThread(CSecureEnv* secureEnv)
{
	nsresult result;
	PRUint32 threadID;
	MRJSession* session = secureEnv->getSession();
	
	// cause a native thread to be created on our behalf. Perhaps this should be a message we send to the
	// session itself. otherwise we could have reentrancy problems.
	CreateNativeThreadMessage message(&result, &threadID, secureEnv);
	session->sendMessage(&message);

	if (session->onMainThread()) {
		// give time to other native threads, so the new thread can come up.
		nsIThreadManager* manager = secureEnv->getThreadManager();
		while (!secureEnv->isInitialized()) {
			manager->Sleep();
		}
	} else {
		// sleep the current Java thread until we rendezvous with the Native thread.
		JNIEnv* env = session->getCurrentEnv();
		jclass threadClass = env->FindClass("java/lang/Thread");
		if (threadClass != NULL) {
			jmethodID sleepMethod = env->GetStaticMethodID(threadClass, "sleep", "(J)V");
			if (sleepMethod != NULL) {
				while (!secureEnv->isInitialized())
					env->CallStaticVoidMethod(threadClass, sleepMethod, jlong(1024));
			}
			env->DeleteLocalRef(threadClass);
		}
	}
}

/**
 * Runs the server thread for LiveConnect upcalls from spontaneous Java threads.
 */
NS_IMETHODIMP CSecureEnv::Run()
{
	jboolean isRunning = true;
	NativeMonitor requestMonitor(mSession, mThreadManager);
	MRJMonitor replyMonitor(mSession);
	JavaMessageQueue requests(&requestMonitor), replies(&replyMonitor);
	// initialize(env, self, &isRunning, &requests, &replies);
	
	// we have to create the Proxy JNI here, so it associated with this thread.
	nsIJVMManager* manager = mPlugin->getManager();
	manager->CreateProxyJNI(this, &mProxyEnv);
	
	mIsRunning = &isRunning;
	mNativeQueue = &requests;
	mJavaQueue = &replies;
	
	// when this thread is running, no other thread can enter the request queue monitor.
	requests.enter();
	
	while (isRunning) {
		// the protocol for now is dead simple:  get a message from the
		// requests message queue, process it, and then put it back in
		// the replies queue. This will get more elaborate to handle
		// upcall requests. 
		JavaMessage* msg = requests.getMessage();
		if (msg != NULL) {
			msg->execute(mProxyEnv);
			replies.putMessage(msg);
			replies.notify();
		} else {
			// should we do sleep, timed wait, or what?
			// env->CallStaticVoidMethod(clazz, yieldMethod);
			// env->CallStaticVoidMethod(clazz, sleepMethod, jlong(kDefaultJMTime));
			requests.wait();
		}
	}
	
	requests.exit();

	return NS_OK;
}

/**
 * Used to send a message from Native to Java threads.
 */
void CSecureEnv::sendMessageToJava(JavaMessage* msg)
{
#if USE_LIVECONNECT_PROXY
    JNIEnv* env = mSession->getCurrentEnv();
    // XXX this needs to be optimized up the wazoo.
    // somehow, get the associated class loader for the codebase of the currently running
    // script. doing this here would require us to use lots of unfrozen interfaces.
    nsresult rv = NS_OK;
    if (!mScriptSecurityManager) {
        rv = MRJPlugin::GetService("@mozilla.org/scriptsecuritymanager;1",
                                   NS_GET_IID(nsIScriptSecurityManager),
                                   (void**)&mScriptSecurityManager);
    }
    // XXX Simple principal caching mechanism. If the principal is the same as
    // last time, then keep using the same LiveConnect proxy class. This should
    // be adequate for the simple cases of a single document using LiveConnect.
    // There should also be some caching going on inside ProxyClassLoaderFactory,
    // which should return the same class loader for the same URL, until this
    // prinicipal changes. Idea:  could use a weak reference dictionary that
    // only holds the class loader while there are other strong references to it.
    if (NS_SUCCEEDED(rv)) {
        nsIPrincipal* scriptPrincipal;
        rv = mScriptSecurityManager->GetSubjectPrincipal(&scriptPrincipal);
        if (NS_SUCCEEDED(rv)) {
            if (scriptPrincipal != mScriptPrincipal) {
                // invalidate our cached LiveConnectProxy class.
                NS_IF_RELEASE(mScriptPrincipal);
                mScriptPrincipal = scriptPrincipal;
                if (mLiveConnectProxy) {
                    env->DeleteGlobalRef(mLiveConnectProxy);
                    mLiveConnectProxy = NULL;
                }
            }
            if (!mLiveConnectProxy && scriptPrincipal) {
                jclass liveConnectProxy = GetLiveConnectProxy(env, scriptPrincipal);
                if (liveConnectProxy) {
                    mLiveConnectProxy = (jclass) env->NewGlobalRef(liveConnectProxy);
                    env->DeleteLocalRef(liveConnectProxy);
                }
            }
        }
    }
    if (mLiveConnectProxy) {
        jobject runnable = CreateJNIRunnable(env, msg);
        if (runnable) {
            jmethodID runID = env->GetStaticMethodID(mLiveConnectProxy, "run", "(Ljava/lang/Runnable;)V");
            if (runID) {
                env->CallStaticVoidMethod(mLiveConnectProxy, runID, runnable);
                savePendingException(env);
            }
            env->DeleteLocalRef(runnable);
        }
    } else {
        msg->execute(env);
        savePendingException(env);
    }
#else
	messageLoop(mProxyEnv, msg, mJavaQueue, mNativeQueue, true);
#endif
}

/**
 * Used to send a message from Java to Native threads.
 */
void CSecureEnv::sendMessageFromJava(JNIEnv* javaEnv, JavaMessage* msg, Boolean busyWaiting)
{
	messageLoop(javaEnv, msg, mNativeQueue, mJavaQueue, busyWaiting);
}

////////////////////////////////////////////////////////////////////////////
// Table-driven nsISupports data.

const InterfaceInfo CSecureEnv::sInterfaces[] = {
	{ NS_ISECUREENV_IID, INTERFACE_OFFSET(CSecureEnv, nsISecureEnv) },
	{ NS_IRUNNABLE_IID, INTERFACE_OFFSET(CSecureEnv, nsIRunnable) },
};
const UInt32 CSecureEnv::kInterfaceCount = sizeof(sInterfaces) / sizeof(InterfaceInfo);

CSecureEnv::CSecureEnv(MRJPlugin* plugin, JNIEnv* proxyEnv, JNIEnv* javaEnv)
	:	SupportsMixin(this, sInterfaces, kInterfaceCount),
		mPlugin(plugin), mProxyEnv(proxyEnv), mJavaEnv(javaEnv),
		mSession(plugin->getSession()), mThreadManager(plugin->getThreadManager()),
		mIsRunning(NULL), mJavaQueue(NULL), mNativeQueue(NULL),
		mPendingException(NULL),
		mScriptSecurityManager(NULL), mScriptPrincipal(NULL), mLiveConnectProxy(NULL)
{
	// need to create the JNIThread for communicating with Java.
	if (mJavaEnv != NULL)
        CreateNativeThread(this);
	else
	    CreateJNIThread(this);
}


CSecureEnv::~CSecureEnv()  
{
	// Tell the Java thread to die.
	if (mIsRunning != NULL) {
		*mIsRunning = false;
		mJavaQueue->notify();
	}

    JNIEnv* env = mSession->getCurrentEnv();
    if (mPendingException) {
        env->DeleteGlobalRef(mPendingException);
        mPendingException = NULL;
    }
    
    if (mLiveConnectProxy) {
        env->DeleteGlobalRef(mLiveConnectProxy);
        mLiveConnectProxy = NULL;
    }
    
    NS_IF_RELEASE(mScriptPrincipal);
    NS_IF_RELEASE(mScriptSecurityManager);
}

void CSecureEnv::initialize(JNIEnv* javaEnv, jboolean* isRunning, JavaMessageQueue* javaQueue, JavaMessageQueue* nativeQueue)
{
	mJavaEnv = javaEnv;
	mIsRunning = isRunning;
	mJavaQueue = javaQueue;
	mNativeQueue = nativeQueue;
}

///=--------------------------------------------------------------------------=
// CSecureEnv::Create
///=--------------------------------------------------------------------------=
// Create the CSecureEnv object for creating object, invoking method, 
// getting/setting field in JNI with security context.
//
// parameters :
//
// return :
// 
// notes :
//
NS_METHOD
CSecureEnv::Create(MRJPlugin* plugin, JNIEnv* proxyEnv, const nsIID& aIID, void* *aInstancePtr)
{
	CSecureEnv* secureEnv = new CSecureEnv(plugin, proxyEnv);
	if (secureEnv == NULL)
	    return NS_ERROR_OUT_OF_MEMORY;
	NS_ADDREF(secureEnv);
	nsresult rv = secureEnv->QueryInterface(aIID, aInstancePtr);
	NS_RELEASE(secureEnv);
	return rv;
}

///=--------------------------------------------------------------------------=
// CSecureEnv::NewObject
///=--------------------------------------------------------------------------=
// Create new Java object in LiveConnect.
//
// @param env        -- JNIEnv pointer.
// @param clazz      -- Java Class object.
// @param methodID   -- Method id
// @param args       -- arguments for invoking the constructor.
// @param result     -- return new Java object.
// @param ctx        -- security context 
//

class NewObjectMessage : public JavaMessage {
	jclass clazz;
	jmethodID methodID;
	jvalue* args;
	jobject* result;

public:
	NewObjectMessage(jclass clazz,  jmethodID methodID, jvalue *args, jobject* result)
	{
		this->clazz = clazz;
		this->methodID = methodID;
		this->args = args;
		this->result = result;
	}
	
	virtual void execute(JNIEnv* env)
	{
		*result = env->NewObjectA(clazz, methodID, args);
#if LOCAL_REFS_ARE_GLOBAL
        *result = ToGlobalRef(env, *result);
#endif
	}
};

NS_IMETHODIMP CSecureEnv::NewObject(/*[in]*/  jclass clazz, 
                                    /*[in]*/  jmethodID methodID, 
                                    /*[in]*/  jvalue *args, 
                                    /*[out]*/ jobject* result,
                                    /*[in]*/  nsISecurityContext* ctx)
{
    if (clazz == NULL || methodID == NULL)
        return NS_ERROR_NULL_POINTER;

#if PROXY_JNI_CALLS
    // Call method on Java side
    NewObjectMessage msg(clazz, methodID, args, result);
    sendMessageToJava(&msg);
#else
	*result = mJavaEnv->NewObjectA(clazz, methodID, args);
#endif
	
	return NS_OK;
}


///=--------------------------------------------------------------------------=
// CSecureEnv::CallMethod
///=--------------------------------------------------------------------------=
// Invoke method on Java object in LiveConnect.
//
// @param type       -- Return type
// @param obj        -- Java object.
// @param methodID   -- Method id
// @param result     -- return result of invocation.
// @param ctx        -- security context 
//

class CallMethodMessage : public JavaMessage {
	jni_type return_type;
	jobject obj;
	jmethodID methodID;
	jvalue* args;
	jvalue* result;

public:
	CallMethodMessage(jni_type return_type, jobject obj, jmethodID methodID, jvalue *args, jvalue* result)
	{
		this->return_type = return_type;
		this->obj = obj;
		this->methodID = methodID;
		this->args = args;
		this->result = result;
	}
	
	virtual void execute(JNIEnv* env)
	{
		switch (return_type) {
		case jobject_type:
			result->l = env->CallObjectMethodA(obj, methodID, args);
#if LOCAL_REFS_ARE_GLOBAL
            result->l = ToGlobalRef(env, result->l);
#endif
			break;
		case jboolean_type:
			result->z = env->CallBooleanMethodA(obj, methodID, args);
			break;
		case jbyte_type:
			result->b = env->CallByteMethodA(obj, methodID, args);
			break;
		case jchar_type:
			result->c = env->CallCharMethodA(obj, methodID, args);
			break;
		case jshort_type:
			result->s = env->CallShortMethodA(obj, methodID, args);
			break;
		case jint_type:
			result->i = env->CallIntMethodA(obj, methodID, args);
			break;
		case jlong_type:
			result->j = env->CallLongMethodA(obj, methodID, args);
			break;
		case jfloat_type:
			result->f = env->CallFloatMethodA(obj, methodID, args);
			break;
		case jdouble_type:
			result->d = env->CallDoubleMethodA(obj, methodID, args);
			break;
		case jvoid_type:
			env->CallVoidMethodA(obj, methodID, args);
			break;
		}
	}
};

NS_IMETHODIMP CSecureEnv::CallMethod(/*[in]*/  jni_type return_type,
                                      /*[in]*/  jobject obj, 
                                      /*[in]*/  jmethodID methodID, 
                                      /*[in]*/  jvalue *args, 
                                      /*[out]*/ jvalue* result,
                                      /*[in]*/  nsISecurityContext* ctx)
{
    if (obj == NULL || methodID == NULL)
        return NS_ERROR_NULL_POINTER;

#if PROXY_JNI_CALLS
	// Call method on Java side
	// return CallJavaMethod(obj, method, args, ctx, result);
	CallMethodMessage msg(return_type, obj, methodID, args, result);
	sendMessageToJava(&msg);
#else
    JNIEnv* env = mJavaEnv;
	switch (return_type) {
	case jobject_type:
		result->l = env->CallObjectMethodA(obj, methodID, args);
		break;
	case jboolean_type:
		result->z = env->CallBooleanMethodA(obj, methodID, args);
		break;
	case jbyte_type:
		result->b = env->CallByteMethodA(obj, methodID, args);
		break;
	case jchar_type:
		result->c = env->CallCharMethodA(obj, methodID, args);
		break;
	case jshort_type:
		result->s = env->CallShortMethodA(obj, methodID, args);
		break;
	case jint_type:
		result->i = env->CallIntMethodA(obj, methodID, args);
		break;
	case jlong_type:
		result->j = env->CallLongMethodA(obj, methodID, args);
		break;
	case jfloat_type:
		result->f = env->CallFloatMethodA(obj, methodID, args);
		break;
	case jdouble_type:
		result->d = env->CallDoubleMethodA(obj, methodID, args);
		break;
	case jvoid_type:
		env->CallVoidMethodA(obj, methodID, args);
		break;
	}
#endif
	
	return NS_OK;
}


///=--------------------------------------------------------------------------=
// CSecureEnv::CallNonvirtualMethod
///=--------------------------------------------------------------------------=
// Invoke non-virtual method on Java object in LiveConnect.
//
// @param obj        -- Java object.
// @param methodID   -- Method id
// @param args       -- arguments for invoking the constructor.
// @param result     -- return result of invocation.
// @param ctx        -- security context 
//

class CallNonvirtualMethodMessage : public JavaMessage {
	jni_type type;
	jobject obj;
	jclass clazz;
	jmethodID methodID;
	jvalue* args;
	jvalue* result;

public:
	CallNonvirtualMethodMessage(jni_type type, jobject obj, jclass clazz, jmethodID methodID, jvalue *args, jvalue* result)
	{
		this->type = type;
		this->obj = obj;
		this->clazz = clazz;
		this->methodID = methodID;
		this->args = args;
		this->result = result;
	}
	
	virtual void execute(JNIEnv* env)
	{
		switch (type) {
		case jobject_type:
			result->l = env->CallNonvirtualObjectMethodA(obj, clazz, methodID, args);
#if LOCAL_REFS_ARE_GLOBAL
            result->l = ToGlobalRef(env, result->l);
#endif
			break;
		case jboolean_type:
			result->z = env->CallNonvirtualBooleanMethodA(obj, clazz, methodID, args);
			break;
		case jbyte_type:
			result->b = env->CallNonvirtualByteMethodA(obj, clazz, methodID, args);
			break;
		case jchar_type:
			result->c = env->CallNonvirtualCharMethodA(obj, clazz, methodID, args);
			break;
		case jshort_type:
			result->s = env->CallNonvirtualShortMethodA(obj, clazz, methodID, args);
			break;
		case jint_type:
			result->i = env->CallNonvirtualIntMethodA(obj, clazz, methodID, args);
			break;
		case jlong_type:
			result->j = env->CallNonvirtualLongMethodA(obj, clazz, methodID, args);
			break;
		case jfloat_type:
			result->f = env->CallNonvirtualFloatMethodA(obj, clazz, methodID, args);
			break;
		case jdouble_type:
			result->d = env->CallNonvirtualDoubleMethodA(obj, clazz, methodID, args);
			break;
		case jvoid_type:
			env->CallNonvirtualVoidMethodA(obj, clazz, methodID, args);
			break;
		}
	}
};

NS_IMETHODIMP CSecureEnv::CallNonvirtualMethod(/*[in]*/  jni_type type,
                                                /*[in]*/  jobject obj, 
                                                /*[in]*/  jclass clazz,
                                                /*[in]*/  jmethodID methodID,
                                                /*[in]*/  jvalue *args, 
                                                /*[out]*/ jvalue* result,
                                                /*[in]*/  nsISecurityContext* ctx)
{
    if (obj == NULL || clazz == NULL || methodID == NULL)
        return NS_ERROR_NULL_POINTER;

#if PROXY_JNI_CALLS
	// Call non-virtual method on Java side
	// return CallJavaMethod(obj, method, args, ctx, result);
	CallNonvirtualMethodMessage msg(type, obj, clazz, methodID, args, result);
	sendMessageToJava(&msg);
#else
    #error "Implement me!"
#endif

	return NS_OK;
}


///=--------------------------------------------------------------------------=
// CSecureEnv::GetField
///=--------------------------------------------------------------------------=
// Get a field on Java object in LiveConnect.
//
// @param obj        -- Java object.
// @param fieldID    -- field id
// @param result     -- return field value
// @param ctx        -- security context 
//

class GetFieldMessage : public JavaMessage {
	jni_type type;
	jobject obj;
	jfieldID fieldID;
	jvalue* result;
public:
	GetFieldMessage(jni_type type, jobject obj, jfieldID fieldID, jvalue* result)
	{
		this->type = type;
		this->obj = obj;
		this->fieldID = fieldID;
		this->result = result;
	}
	
	virtual void execute(JNIEnv* env)
	{
		switch (type) {
		case jobject_type:
			result->l = env->GetObjectField(obj, fieldID);
#if LOCAL_REFS_ARE_GLOBAL
            result->l = ToGlobalRef(env, result->l);
#endif
			break;
		case jboolean_type:
			result->z = env->GetBooleanField(obj, fieldID);
			break;
		case jbyte_type:
			result->b = env->GetByteField(obj, fieldID);
			break;
		case jchar_type:
			result->c = env->GetCharField(obj, fieldID);
			break;
		case jshort_type:
			result->s = env->GetShortField(obj, fieldID);
			break;
		case jint_type:
			result->i = env->GetIntField(obj, fieldID);
			break;
		case jlong_type:
			result->j = env->GetLongField(obj, fieldID);
			break;
		case jfloat_type:
			result->f = env->GetFloatField(obj, fieldID);
			break;
		case jdouble_type:
			result->d = env->GetDoubleField(obj, fieldID);
			break;
		}
	}
};

NS_IMETHODIMP CSecureEnv::GetField(/*[in]*/  jni_type type,
                                   /*[in]*/  jobject obj, 
                                   /*[in]*/  jfieldID fieldID,
                                   /*[out]*/ jvalue* result,
                                   /*[in]*/  nsISecurityContext* ctx)
{
    if (mJavaEnv == NULL || obj == NULL || fieldID == NULL)
        return NS_ERROR_NULL_POINTER;

#if PROXY_JNI_CALLS
    // Get field on Java side
    GetFieldMessage msg(type, obj, fieldID, result);
    sendMessageToJava(&msg);
#else    
	JNIEnv* env = mJavaEnv;
	switch (type) {
	case jobject_type:
		result->l = env->GetObjectField(obj, fieldID);
		break;
	case jboolean_type:
		result->z = env->GetBooleanField(obj, fieldID);
		break;
	case jbyte_type:
		result->b = env->GetByteField(obj, fieldID);
		break;
	case jchar_type:
		result->c = env->GetCharField(obj, fieldID);
		break;
	case jshort_type:
		result->s = env->GetShortField(obj, fieldID);
		break;
	case jint_type:
		result->i = env->GetIntField(obj, fieldID);
		break;
	case jlong_type:
		result->j = env->GetLongField(obj, fieldID);
		break;
	case jfloat_type:
		result->f = env->GetFloatField(obj, fieldID);
		break;
	case jdouble_type:
		result->d = env->GetDoubleField(obj, fieldID);
		break;
	}
#endif

	return NS_OK;
}


///=--------------------------------------------------------------------------=
// CSecureEnv::SetField
///=--------------------------------------------------------------------------=
//
// Set a field on Java object in LiveConnect.
//
// @param obj        -- Java object.
// @param fieldID    -- field id
// @param result     -- field value to set
// @param ctx        -- security context 
//

class SetFieldMessage : public JavaMessage {
	jni_type type;
	jobject obj;
	jfieldID fieldID;
	jvalue val;
public:
	SetFieldMessage(jni_type type, jobject obj, jfieldID fieldID, jvalue val)
	{
		this->type = type;
		this->obj = obj;
		this->fieldID = fieldID;
		this->val = val;
	}
	
	virtual void execute(JNIEnv* env)
	{
    	switch (type) {
    	case jobject_type:
    		env->SetObjectField(obj, fieldID, val.l);
    		break;
    	case jboolean_type:
    		env->SetBooleanField(obj, fieldID, val.z);
    		break;
    	case jbyte_type:
    		env->SetByteField(obj, fieldID, val.b);
    		break;
    	case jchar_type:
    		env->SetCharField(obj, fieldID, val.c);
    		break;
    	case jshort_type:
    		env->SetShortField(obj, fieldID, val.s);
    		break;
    	case jint_type:
    		env->SetIntField(obj, fieldID, val.i);
    		break;
    	case jlong_type:
    		env->SetLongField(obj, fieldID, val.j);
    		break;
    	case jfloat_type:
    		env->SetFloatField(obj, fieldID, val.f);
    		break;
    	case jdouble_type:
    		env->SetDoubleField(obj, fieldID, val.d);
    		break;
    	}
	}
};

NS_IMETHODIMP CSecureEnv::SetField(/*[in]*/ jni_type type,
                                    /*[in]*/ jobject obj, 
                                    /*[in]*/ jfieldID fieldID,
                                    /*[in]*/ jvalue val,
                                    /*[in]*/ nsISecurityContext* ctx)
{
    if (mJavaEnv == NULL || obj == NULL || fieldID == NULL)
        return NS_ERROR_NULL_POINTER;

#if PROXY_JNI_CALLS
    // Set field on Java side
    SetFieldMessage msg(type, obj, fieldID, val);
    sendMessageToJava(&msg);
#else
	JNIEnv* env = mJavaEnv;
	switch (type) {
	case jobject_type:
		env->SetObjectField(obj, fieldID, val.l);
		break;
	case jboolean_type:
		env->SetBooleanField(obj, fieldID, val.z);
		break;
	case jbyte_type:
		env->SetByteField(obj, fieldID, val.b);
		break;
	case jchar_type:
		env->SetCharField(obj, fieldID, val.c);
		break;
	case jshort_type:
		env->SetShortField(obj, fieldID, val.s);
		break;
	case jint_type:
		env->SetIntField(obj, fieldID, val.i);
		break;
	case jlong_type:
		env->SetLongField(obj, fieldID, val.j);
		break;
	case jfloat_type:
		env->SetFloatField(obj, fieldID, val.f);
		break;
	case jdouble_type:
		env->SetDoubleField(obj, fieldID, val.d);
		break;
	}
#endif

	return NS_OK;
}


///=--------------------------------------------------------------------------=
// CSecureEnv::CallStaticMethod
///=--------------------------------------------------------------------------=
//
// Invoke static method on Java object in LiveConnect.
//
// @param obj        -- Java object.
// @param methodID   -- method id
// @param args       -- arguments for invoking the constructor.
// @param result     -- return result of invocation.
// @param ctx        -- security context 
//

class CallStaticMethodMessage : public JavaMessage {
	jni_type type;
	jclass clazz;
	jmethodID methodID;
	jvalue* args;
	jvalue* result;
public:
	CallStaticMethodMessage(jni_type type, jclass clazz, jmethodID methodID, jvalue *args, jvalue* result)
	{
		this->type = type;
		this->clazz = clazz;
		this->methodID = methodID;
		this->args = args;
		this->result = result;
	}
	
	virtual void execute(JNIEnv* env)
	{
		switch (type) {
		case jobject_type:
			result->l = env->CallStaticObjectMethodA(clazz, methodID, args);
#if LOCAL_REFS_ARE_GLOBAL
            result->l = ToGlobalRef(env, result->l);
#endif
			break;
		case jboolean_type:
			result->z = env->CallStaticBooleanMethodA(clazz, methodID, args);
			break;
		case jbyte_type:
			result->b = env->CallStaticByteMethodA(clazz, methodID, args);
			break;
		case jchar_type:
			result->c = env->CallStaticCharMethodA(clazz, methodID, args);
			break;
		case jshort_type:
			result->s = env->CallStaticShortMethodA(clazz, methodID, args);
			break;
		case jint_type:
			result->i = env->CallStaticIntMethodA(clazz, methodID, args);
			break;
		case jlong_type:
			result->j = env->CallStaticLongMethodA(clazz, methodID, args);
			break;
		case jfloat_type:
			result->f = env->CallStaticFloatMethodA(clazz, methodID, args);
			break;
		case jdouble_type:
			result->d = env->CallStaticDoubleMethodA(clazz, methodID, args);
			break;
		case jvoid_type:
			env->CallStaticVoidMethodA(clazz, methodID, args);
			break;
		}
	}
};

NS_IMETHODIMP CSecureEnv::CallStaticMethod(/*[in]*/  jni_type type,
                                            /*[in]*/  jclass clazz,
                                            /*[in]*/  jmethodID methodID,
                                            /*[in]*/  jvalue *args, 
                                            /*[out]*/ jvalue* result,
                                            /*[in]*/  nsISecurityContext* ctx)
{
    if (clazz == NULL || methodID == NULL)
        return NS_ERROR_NULL_POINTER;

#if PROXY_JNI_CALLS
	// Call method on Java side
	// return CallJavaMethod(NULL, method, args, ctx, result);
	CallStaticMethodMessage msg(type, clazz, methodID, args, result);
	sendMessageToJava(&msg);
#else
    #error "Implement me!"
#endif

	return NS_OK;
}


///=--------------------------------------------------------------------------=
// CSecureEnv::GetStaticField
///=--------------------------------------------------------------------------=
// Get a static field on Java object in LiveConnect.
//
// @param obj        -- Java object.
// @param fieldID    -- field id
// @param result     -- return field value
// @param ctx        -- security context 
//

class GetStaticFieldMessage : public JavaMessage {
	jni_type type;
	jclass clazz;
	jfieldID fieldID;
	jvalue* result;
public:
	GetStaticFieldMessage(jni_type type, jclass clazz, jfieldID fieldID, jvalue* result)
	{
		this->type = type;
		this->clazz = clazz;
		this->fieldID = fieldID;
		this->result = result;
	}
	
	virtual void execute(JNIEnv* env)
	{
		switch (type) {
		case jobject_type:
			result->l = env->GetStaticObjectField(clazz, fieldID);
#if LOCAL_REFS_ARE_GLOBAL
            result->l = ToGlobalRef(env, result->l);
#endif
			break;
		case jboolean_type:
			result->z = env->GetStaticBooleanField(clazz, fieldID);
			break;
		case jbyte_type:
			result->b = env->GetStaticByteField(clazz, fieldID);
			break;
		case jchar_type:
			result->c = env->GetStaticCharField(clazz, fieldID);
			break;
		case jshort_type:
			result->s = env->GetStaticShortField(clazz, fieldID);
			break;
		case jint_type:
			result->i = env->GetStaticIntField(clazz, fieldID);
			break;
		case jlong_type:
			result->j = env->GetStaticLongField(clazz, fieldID);
			break;
		case jfloat_type:
			result->f = env->GetStaticFloatField(clazz, fieldID);
			break;
		case jdouble_type:
			result->d = env->GetStaticDoubleField(clazz, fieldID);
			break;
		}
	}
};

NS_IMETHODIMP CSecureEnv::GetStaticField(/*[in]*/  jni_type type,
                                          /*[in]*/  jclass clazz, 
                                          /*[in]*/  jfieldID fieldID,
                                          /*[out]*/ jvalue* result,
                                          /*[in]*/  nsISecurityContext* ctx)
{
    if (mJavaEnv == NULL || clazz == NULL || fieldID == NULL)
        return NS_ERROR_NULL_POINTER;

#if PROXY_JNI_CALLS
    // Get static field on Java side
	GetStaticFieldMessage msg(type, clazz, fieldID, result);
	sendMessageToJava(&msg);
#else
    // This doesn't work in Mac OS X.
	// should be able to perform in Java env.
	JNIEnv* env = mJavaEnv;
	switch (type) {
	case jobject_type:
		result->l = env->GetStaticObjectField(clazz, fieldID);
		break;
	case jboolean_type:
		result->z = env->GetStaticBooleanField(clazz, fieldID);
		break;
	case jbyte_type:
		result->b = env->GetStaticByteField(clazz, fieldID);
		break;
	case jchar_type:
		result->c = env->GetStaticCharField(clazz, fieldID);
		break;
	case jshort_type:
		result->s = env->GetStaticShortField(clazz, fieldID);
		break;
	case jint_type:
		result->i = env->GetStaticIntField(clazz, fieldID);
		break;
	case jlong_type:
		result->j = env->GetStaticLongField(clazz, fieldID);
		break;
	case jfloat_type:
		result->f = env->GetStaticFloatField(clazz, fieldID);
		break;
	case jdouble_type:
		result->d = env->GetStaticDoubleField(clazz, fieldID);
		break;
	}
#endif

	return NS_OK;
}


///=--------------------------------------------------------------------------=
// CSecureEnv::SetStaticField
///=--------------------------------------------------------------------------=
// Set a static field on Java object in LiveConnect.
//
// @param obj        -- Java object.
// @param fieldID    -- field id
// @param result     -- field value to set
// @param ctx        -- security context 
//

class SetStaticFieldMessage : public JavaMessage {
	jni_type type;
	jclass clazz;
	jfieldID fieldID;
	jvalue val;
public:
	SetStaticFieldMessage(jni_type type, jclass clazz, jfieldID fieldID, jvalue val)
	{
		this->type = type;
		this->clazz = clazz;
		this->fieldID = fieldID;
		this->val = val;
	}
	
	virtual void execute(JNIEnv* env)
	{
    	switch (type) {
    	case jobject_type:
    		env->SetStaticObjectField(clazz, fieldID, val.l);
    		break;
    	case jboolean_type:
    		env->SetStaticBooleanField(clazz, fieldID, val.z);
    		break;
    	case jbyte_type:
    		env->SetStaticByteField(clazz, fieldID, val.b);
    		break;
    	case jchar_type:
    		env->SetStaticCharField(clazz, fieldID, val.c);
    		break;
    	case jshort_type:
    		env->SetStaticShortField(clazz, fieldID, val.s);
    		break;
    	case jint_type:
    		env->SetStaticIntField(clazz, fieldID, val.i);
    		break;
    	case jlong_type:
    		env->SetStaticLongField(clazz, fieldID, val.j);
    		break;
    	case jfloat_type:
    		env->SetStaticFloatField(clazz, fieldID, val.f);
    		break;
    	case jdouble_type:
    		env->SetStaticDoubleField(clazz, fieldID, val.d);
    		break;
    	}
	}
};

NS_IMETHODIMP CSecureEnv::SetStaticField(/*[in]*/  jni_type type,
                                          /*[in]*/ jclass clazz, 
                                          /*[in]*/ jfieldID fieldID,
                                          /*[in]*/ jvalue val,
                                          /*[in]*/ nsISecurityContext* ctx)
{
    if (mJavaEnv == NULL || clazz == NULL || fieldID == NULL)
        return NS_ERROR_NULL_POINTER;

#if PROXY_JNI_CALLS
	// Set static field on Java side
	SetStaticFieldMessage msg(type, clazz, fieldID, val);
	sendMessageToJava(&msg);
#else
	JNIEnv* env = mJavaEnv;
	switch (type) {
	case jobject_type:
		env->SetStaticObjectField(clazz, fieldID, val.l);
		break;
	case jboolean_type:
		env->SetStaticBooleanField(clazz, fieldID, val.z);
		break;
	case jbyte_type:
		env->SetStaticByteField(clazz, fieldID, val.b);
		break;
	case jchar_type:
		env->SetStaticCharField(clazz, fieldID, val.c);
		break;
	case jshort_type:
		env->SetStaticShortField(clazz, fieldID, val.s);
		break;
	case jint_type:
		env->SetStaticIntField(clazz, fieldID, val.i);
		break;
	case jlong_type:
		env->SetStaticLongField(clazz, fieldID, val.j);
		break;
	case jfloat_type:
		env->SetStaticFloatField(clazz, fieldID, val.f);
		break;
	case jdouble_type:
		env->SetStaticDoubleField(clazz, fieldID, val.d);
		break;
	}
#endif

	return NS_OK;
}


NS_IMETHODIMP CSecureEnv::GetVersion(/*[out]*/ jint* version) 
{
    if (mJavaEnv == NULL || version == NULL)
        return NS_ERROR_NULL_POINTER;
    
 	JNIEnv* env = mJavaEnv;
	*version = env->GetVersion();

    return NS_OK;
}

/**
 * To give proper "local" refs, need to run this in the Java thread.
 */
class DefineClassMessage : public JavaMessage {
	const char* name;
	jobject loader;
	const jbyte *buf;
	jsize len;
	jclass* clazz;
public:
	DefineClassMessage(const char* name, jobject loader, const jbyte *buf, jsize len, jclass* clazz)
	{
		this->name = name;
		this->loader = loader;
		this->buf = buf;
		this->len = len;
		this->clazz = clazz;
	}
	
	virtual void execute(JNIEnv* env)
	{
    	*clazz = env->DefineClass(name, loader, buf, len);
	}
};

NS_IMETHODIMP CSecureEnv::DefineClass(/*[in]*/  const char* name,
                                       /*[in]*/  jobject loader,
                                       /*[in]*/  const jbyte *buf,
                                       /*[in]*/  jsize len,
                                       /*[out]*/ jclass* clazz) 
{
    if (mJavaEnv == NULL || clazz == NULL)
        return NS_ERROR_NULL_POINTER;

#if PROXY_JNI_CALLS
    DefineClassMessage msg(name, loader, buf, len, clazz);
    sendMessageToJava(&msg);
#else
    *clazz = mJavaEnv->DefineClass(name, loader, buf, len);
#endif

    return NS_OK;
}

/**
 * To give proper "local" refs, need to run this in the Java thread.
 */
class FindClassMessage : public JavaMessage {
	const char* name;
	jclass* result;
public:
	FindClassMessage(const char* name, jclass* result)
	{
		this->name = name;
		this->result = result;
	}

	virtual void execute(JNIEnv* env)
	{
		*result = env->FindClass(name);
#if LOCAL_REFS_ARE_GLOBAL
        *result = (jclass) ToGlobalRef(env, *result);
#endif
	}
};

NS_IMETHODIMP CSecureEnv::FindClass(/*[in]*/  const char* name, 
                                     /*[out]*/ jclass* clazz) 
{
    if (clazz == NULL)
        return NS_ERROR_NULL_POINTER;

#if PROXY_JNI_CALLS
	FindClassMessage msg(name, clazz);
	sendMessageToJava(&msg);
#else
	*clazz = mJavaEnv->FindClass(name);
#endif

    return NS_OK;
}

/**
 * To give proper "local" refs, need to run this in the Java thread.
 */
class GetSuperclassMessage : public JavaMessage {
	jclass sub;
	jclass* super;
public:
	GetSuperclassMessage(jclass sub, jclass* super)
	{
		this->sub = sub;
		this->super = super;
	}

	virtual void execute(JNIEnv* env)
	{
    	*super = env->GetSuperclass(sub);
	}
};

NS_IMETHODIMP CSecureEnv::GetSuperclass(/*[in]*/  jclass sub,
                                         /*[out]*/ jclass* super) 
{
    if (mJavaEnv == NULL || super == NULL)
        return NS_ERROR_NULL_POINTER;

#if PROXY_JNI_CALLS
	GetSuperclassMessage msg(sub, super);
	sendMessageToJava(&msg);
#else
	*super = mJavaEnv->GetSuperclass(sub);
#endif

    return NS_OK;
}


class IsAssignableFromMessage : public JavaMessage {
    jclass sub;
    jclass super;
    jboolean* result;
public:
	IsAssignableFromMessage(jclass sub, jclass super, jboolean* result)
	{
		this->sub = sub;
		this->super = super;
		this->result = result;
	}

	virtual void execute(JNIEnv* env)
	{
        *result = env->IsAssignableFrom(sub, super);
	}
};

NS_IMETHODIMP CSecureEnv::IsAssignableFrom(/*[in]*/  jclass sub,
                                            /*[in]*/  jclass super,
                                            /*[out]*/ jboolean* result) 
{
    if (mJavaEnv == NULL || result == NULL)
        return NS_ERROR_NULL_POINTER;

#if PROXY_JNI_CALLS    
	IsAssignableFromMessage msg(sub, super, result);
	sendMessageToJava(&msg);
#else
	// JNIEnv* env = mSession->getCurrentEnv();
	*result = mJavaEnv->IsAssignableFrom(sub, super);
#endif

    return NS_OK;
}


class ThrowMessage : public JavaMessage {
    jthrowable obj;
    jint* result;
public:
	ThrowMessage(jthrowable obj, jint* result)
	{
		this->obj = obj;
		this->result = result;
	}

	virtual void execute(JNIEnv* env)
	{
        *result = env->Throw(obj);
	}
};

NS_IMETHODIMP CSecureEnv::Throw(/*[in]*/  jthrowable obj,
                                 /*[out]*/ jint* result) 
{
	if (mJavaEnv == NULL || result == NULL)
        return NS_ERROR_NULL_POINTER;
    
#if PROXY_JNI_CALLS    
	ThrowMessage msg(obj, result);
	sendMessageToJava(&msg);
#else
    *result = mJavaEnv->Throw(obj);
#endif

    return NS_OK;
}

class ThrowNewMessage : public JavaMessage {
    jclass clazz;
    const char* message;
    jint* result;
public:
	ThrowNewMessage(jclass clazz, const char* message, jint* result)
	{
		this->clazz = clazz;
		this->message = message;
		this->result = result;
	}

	virtual void execute(JNIEnv* env)
	{
        *result = env->ThrowNew(clazz, message);
	}
};

NS_IMETHODIMP CSecureEnv::ThrowNew(/*[in]*/  jclass clazz,
                                    /*[in]*/  const char *message,
                                    /*[out]*/ jint* result) 
{
    if (mJavaEnv == NULL || result == NULL)
        return NS_ERROR_NULL_POINTER;
    
#if PROXY_JNI_CALLS    
	ThrowNewMessage msg(clazz, message, result);
	sendMessageToJava(&msg);
#else
    *result = mJavaEnv->ThrowNew(clazz, msg);
#endif

    return NS_OK;
}


class ExceptionOccurredMessage : public JavaMessage {
    CSecureEnv* secureEnv;
    jthrowable* result;
public:
	ExceptionOccurredMessage(CSecureEnv* secureEnv, jthrowable* result)
	{
	    this->secureEnv = secureEnv;
		this->result = result;
	}

	virtual void execute(JNIEnv* env)
	{
        *result = secureEnv->getPendingException(env);
#if LOCAL_REFS_ARE_GLOBAL
        *result = (jthrowable) ToGlobalRef(env, *result);
#endif
	}
};

NS_IMETHODIMP CSecureEnv::ExceptionOccurred(/*[out]*/ jthrowable* result)
{
	if (mJavaEnv == NULL || result == NULL)
		return NS_ERROR_NULL_POINTER;
    
#if PROXY_JNI_CALLS
	ExceptionOccurredMessage msg(this, result);
	sendMessageToJava(&msg);
#else
    *result = mJavaEnv->ExceptionOccurred();
#endif

    return NS_OK;
}

NS_IMETHODIMP CSecureEnv::ExceptionDescribe(void)
{
	if (mJavaEnv == NULL)
		return NS_ERROR_NULL_POINTER;

#if PROXY_JNI_CALLS    
	class ExceptionDescribeMessage : public JavaMessage {
    public:
    	virtual void execute(JNIEnv* env) { env->ExceptionDescribe(); }
    } msg;
	sendMessageToJava(&msg);
#else
    mJavaEnv->ExceptionDescribe();
#endif

    return NS_OK;
}

class ExceptionClearMessage : public JavaMessage {
    CSecureEnv* secureEnv;
public:
    ExceptionClearMessage(CSecureEnv* secureEnv)
    {
        this->secureEnv = secureEnv;
    }
	virtual void execute(JNIEnv* env) { secureEnv->clearPendingException(env); }
};

NS_IMETHODIMP CSecureEnv::ExceptionClear(void)
{
#if PROXY_JNI_CALLS
    ExceptionClearMessage msg(this); 
	sendMessageToJava(&msg);
#else
    mJavaEnv->ExceptionClear();
#endif

    return NS_OK;
}


NS_IMETHODIMP CSecureEnv::FatalError(/*[in]*/ const char* msg)
{
    mJavaEnv->FatalError(msg);
    return NS_OK;
}


/**
 * To give proper "local" refs, need to run this in the true thread.
 */

class NewGlobalRefMessage : public JavaMessage {
	jobject localRef;
	jobject* result;
public:
	NewGlobalRefMessage(jobject localRef, jobject* result)
	{
		this->localRef = localRef;
		this->result = result;
	}

	virtual void execute(JNIEnv* env)
	{
		*result = env->NewGlobalRef(localRef);
	}
};

NS_IMETHODIMP CSecureEnv::NewGlobalRef(/*[in]*/  jobject localRef, 
                                        /*[out]*/ jobject* result)
{
    if (result == NULL)
        return NS_ERROR_NULL_POINTER;

#if PROXY_JNI_CALLS
    NewGlobalRefMessage msg(localRef, result);
 	sendMessageToJava(&msg);
#else
	*result = mJavaEnv->NewGlobalRef(localRef);
#endif

    return NS_OK;
}

/**
 * This should be safe to call from any thread, since global refs
 * are thread independent.
 */
NS_IMETHODIMP CSecureEnv::DeleteGlobalRef(/*[in]*/ jobject globalRef) 
{
    mJavaEnv->DeleteGlobalRef(globalRef);
    return NS_OK;
}

/**
 * To give proper "local" refs, need to run this in the true thread.
 */
class DeleteLocalRefMessage : public JavaMessage {
	jobject localRef;
public:
	DeleteLocalRefMessage(jobject localRef)
	{
		this->localRef = localRef;
	}

	virtual void execute(JNIEnv* env)
	{
#if LOCAL_REFS_ARE_GLOBAL
		env->DeleteGlobalRef(localRef);
#else
		env->DeleteLocalRef(localRef);
#endif
	}
};

NS_IMETHODIMP CSecureEnv::DeleteLocalRef(/*[in]*/ jobject localRef)
{
#if PROXY_JNI_CALLS
    DeleteLocalRefMessage msg(localRef);
 	sendMessageToJava(&msg);
#else
    mJavaEnv->DeleteLocalRef(localRef);
#endif
    return NS_OK;
}

class IsSameObjectMessage : public JavaMessage {
	jobject obj1;
	jobject obj2;
	jboolean* result;
public:
	IsSameObjectMessage(jobject obj1, jobject obj2, jboolean* result)
	{
		this->obj1 = obj1;
		this->obj2 = obj2;
		this->result = result;
	}

	virtual void execute(JNIEnv* env)
	{
		*result = env->IsSameObject(obj1, obj2);
	}
};

NS_IMETHODIMP CSecureEnv::IsSameObject(/*[in]*/  jobject obj1,
                                        /*[in]*/  jobject obj2,
                                        /*[out]*/ jboolean* result) 
{
    if (mJavaEnv == NULL || result == NULL)
        return NS_ERROR_NULL_POINTER;
    
#if PROXY_JNI_CALLS
    IsSameObjectMessage msg(obj1, obj2, result);
 	sendMessageToJava(&msg);
#else
    *result = mJavaEnv->IsSameObject(obj1, obj2);
#endif

    return NS_OK;
}


class AllocObjectMessage : public JavaMessage {
	jclass clazz;
	jobject* result;
public:
	AllocObjectMessage(jclass clazz, jobject* result)
	{
		this->clazz = clazz;
		this->result = result;
	}

	virtual void execute(JNIEnv* env)
	{
		*result = env->AllocObject(clazz);
#if LOCAL_REFS_ARE_GLOBAL
        *result = ToGlobalRef(env, *result);
#endif
	}
};

NS_IMETHODIMP CSecureEnv::AllocObject(/*[in]*/  jclass clazz,
                                      /*[out]*/ jobject* result) 
{
    if (mJavaEnv == NULL || result == NULL)
        return NS_ERROR_NULL_POINTER;
    
#if PROXY_JNI_CALLS
    AllocObjectMessage msg(clazz, result);
 	sendMessageToJava(&msg);
#else
    *result = mJavaEnv->AllocObject(clazz);
#endif

    return NS_OK;
}

class GetObjectClassMessage : public JavaMessage {
    jobject obj;
	jclass* result;
public:
	GetObjectClassMessage(jobject obj, jclass* result)
	{
		this->obj = obj;
		this->result = result;
	}

	virtual void execute(JNIEnv* env)
	{
		*result = env->GetObjectClass(obj);
#if LOCAL_REFS_ARE_GLOBAL
        *result = (jclass) ToGlobalRef(env, *result);
#endif
	}
};

NS_IMETHODIMP CSecureEnv::GetObjectClass(/*[in]*/  jobject obj,
                                          /*[out]*/ jclass* result) 
{
    if (mJavaEnv == NULL || result == NULL)
        return NS_ERROR_NULL_POINTER;
    
#if PROXY_JNI_CALLS
    GetObjectClassMessage msg(obj, result);
 	sendMessageToJava(&msg);
#else
    *result = mJavaEnv->GetObjectClass(obj);
#endif

    return NS_OK;
}


class IsInstanceOfMessage : public JavaMessage {
    jobject obj;
    jclass clazz;
	jboolean* result;
public:
	IsInstanceOfMessage(jobject obj, jclass clazz, jboolean* result)
	{
		this->obj = obj;
		this->clazz = clazz;
		this->result = result;
	}

	virtual void execute(JNIEnv* env)
	{
		*result = env->IsInstanceOf(obj, clazz);
	}
};

NS_IMETHODIMP CSecureEnv::IsInstanceOf(/*[in]*/  jobject obj,
                                        /*[in]*/  jclass clazz,
                                        /*[out]*/ jboolean* result) 
{
    if (mJavaEnv == NULL || result == NULL)
        return NS_ERROR_NULL_POINTER;
    
#if PROXY_JNI_CALLS
    IsInstanceOfMessage msg(obj, clazz, result);
 	sendMessageToJava(&msg);
#else
    *result = mJavaEnv->IsInstanceOf(obj, clazz);
#endif

    return NS_OK;
}

class GetMethodIDMessage : public JavaMessage {
    jclass clazz;
    const char* name;
    const char* sig;
    jboolean isStatic;
	jmethodID* result;
public:
	GetMethodIDMessage(jclass clazz, const char* name, const char* sig,
	                   jboolean isStatic, jmethodID* result)
	{
		this->clazz = clazz;
		this->name = name;
		this->sig = sig;
		this->isStatic = isStatic;
		this->result = result;
	}

	virtual void execute(JNIEnv* env)
	{
		*result = (isStatic ? env->GetStaticMethodID(clazz, name, sig)
		                    : env->GetMethodID(clazz, name, sig));
	}
};

class GetFieldIDMessage : public JavaMessage {
    jclass clazz;
    const char* name;
    const char* sig;
    jboolean isStatic;
	jfieldID* result;
public:
	GetFieldIDMessage(jclass clazz, const char* name, const char* sig,
	                   jboolean isStatic, jfieldID* result)
	{
		this->clazz = clazz;
		this->name = name;
		this->sig = sig;
		this->isStatic = isStatic;
		this->result = result;
	}

	virtual void execute(JNIEnv* env)
	{
		*result = (isStatic ? env->GetStaticFieldID(clazz, name, sig)
		                    : env->GetFieldID(clazz, name, sig));
	}
};

NS_IMETHODIMP CSecureEnv::GetMethodID(/*[in]*/  jclass clazz, 
                                       /*[in]*/  const char* name,
                                       /*[in]*/  const char* sig,
                                       /*[out]*/ jmethodID* result) 
{
    if (result == NULL)
        return NS_ERROR_NULL_POINTER;

#if PROXY_JNI_CALLS
    GetMethodIDMessage msg(clazz, name, sig, JNI_FALSE, result);
 	sendMessageToJava(&msg);
#else
    *result = mJavaEnv->GetMethodID(clazz, name, sig);
#endif

    return NS_OK;
}


NS_IMETHODIMP CSecureEnv::GetFieldID(/*[in]*/  jclass clazz, 
                                      /*[in]*/  const char* name,
                                      /*[in]*/  const char* sig,
                                      /*[out]*/ jfieldID* result) 
{
    if (result == NULL)
        return NS_ERROR_NULL_POINTER;
    
#if PROXY_JNI_CALLS
    GetFieldIDMessage msg(clazz, name, sig, JNI_FALSE, result);
 	sendMessageToJava(&msg);
#else
    *result = mJavaEnv->GetFieldID(clazz, name, sig);
#endif

    return NS_OK;
}


NS_IMETHODIMP CSecureEnv::GetStaticMethodID(/*[in]*/  jclass clazz, 
                                             /*[in]*/  const char* name,
                                             /*[in]*/  const char* sig,
                                             /*[out]*/ jmethodID* result)
{
    if (result == NULL)
        return NS_ERROR_NULL_POINTER;
    
#if PROXY_JNI_CALLS
    GetMethodIDMessage msg(clazz, name, sig, JNI_TRUE, result);
 	sendMessageToJava(&msg);
#else
    *result = mJavaEnv->GetStaticMethodID(clazz, name, sig);
#endif

    return NS_OK;
}


NS_IMETHODIMP CSecureEnv::GetStaticFieldID(/*[in]*/  jclass clazz, 
                                            /*[in]*/  const char* name,
                                            /*[in]*/  const char* sig,
                                            /*[out]*/ jfieldID* result) 
{
    if (result == NULL)
        return NS_ERROR_NULL_POINTER;
    
#if PROXY_JNI_CALLS
    GetFieldIDMessage msg(clazz, name, sig, JNI_TRUE, result);
 	sendMessageToJava(&msg);
#else
    *result = mJavaEnv->GetStaticFieldID(clazz, name, sig);
#endif

    return NS_OK;
}

/**
 * To give proper "local" refs, need to run this in the true thread.
 */

class NewStringMessage : public JavaMessage {
	const jchar* unicode;
	jsize len;
	jstring* result;
public:
	NewStringMessage(const jchar* unicode, jsize len, jstring* result)
	{
		this->unicode = unicode;
		this->len = len;
		this->result = result;
	}

	virtual void execute(JNIEnv* env)
	{
    	*result = env->NewString(unicode, len);
#if LOCAL_REFS_ARE_GLOBAL
        *result = (jstring) ToGlobalRef(env, *result);
#endif
	}
};

NS_IMETHODIMP CSecureEnv::NewString(/*[in]*/  const jchar* unicode,
                                     /*[in]*/  jsize len,
                                     /*[out]*/ jstring* result) 
{
    if (result == NULL)
        return NS_ERROR_NULL_POINTER;

#if PROXY_JNI_CALLS    
    NewStringMessage msg(unicode, len, result);
    sendMessageToJava(&msg);
#else
	*result = mJavaEnv->NewString(unicode, len);
#endif

    return NS_OK;
}

class GetStringLengthMessage : public JavaMessage {
	jstring str;
	jsize* result;
public:
	GetStringLengthMessage(jstring str, jsize* result)
	{
		this->str = str;
		this->result = result;
	}

	virtual void execute(JNIEnv* env)
	{
    	*result = env->GetStringLength(str);
	}
};

NS_IMETHODIMP CSecureEnv::GetStringLength(/*[in]*/  jstring str,
                                           /*[out]*/ jsize* result) 
{
    if (mJavaEnv == NULL || result == NULL)
        return NS_ERROR_NULL_POINTER;

#if PROXY_JNI_CALLS    
    GetStringLengthMessage msg(str, result);
    sendMessageToJava(&msg);
#else
    *result = mJavaEnv->GetStringLength(str);
#endif

    return NS_OK;
}

class GetStringCharsMessage : public JavaMessage {
	jstring str;
	jboolean* isCopy;
	const jchar** result;
public:
	GetStringCharsMessage(jstring str, jboolean* isCopy, const jchar** result)
	{
		this->str = str;
		this->isCopy = isCopy;
		this->result = result;
	}

	virtual void execute(JNIEnv* env)
	{
    	*result = env->GetStringChars(str, isCopy);
	}
};

NS_IMETHODIMP CSecureEnv::GetStringChars(/*[in]*/  jstring str,
                                          /*[in]*/  jboolean *isCopy,
                                          /*[out]*/ const jchar** result) 
{
    if (mJavaEnv == NULL || result == NULL)
        return NS_ERROR_NULL_POINTER;
    
#if PROXY_JNI_CALLS    
    GetStringCharsMessage msg(str, isCopy, result);
    sendMessageToJava(&msg);
#else
    *result = mJavaEnv->GetStringChars(str, isCopy);
#endif

    return NS_OK;
}


class ReleaseStringCharsMessage : public JavaMessage {
	jstring str;
	const jchar* chars;
public:
	ReleaseStringCharsMessage(jstring str, const jchar* chars)
	{
		this->str = str;
		this->chars = chars;
	}

	virtual void execute(JNIEnv* env)
	{
    	env->ReleaseStringChars(str, chars);
	}
};

NS_IMETHODIMP CSecureEnv::ReleaseStringChars(/*[in]*/  jstring str,
                                              /*[in]*/  const jchar *chars) 
{
    if (mJavaEnv == NULL)
        return NS_ERROR_NULL_POINTER;
    
#if PROXY_JNI_CALLS    
    ReleaseStringCharsMessage msg(str, chars);
    sendMessageToJava(&msg);
#else
    mJavaEnv->ReleaseStringChars(str, chars);
#endif

    return NS_OK;
}

class NewStringUTFMessage : public JavaMessage {
	const char *utf;
	jstring* result;
public:
	NewStringUTFMessage(const char *utf, jstring* result)
	{
		this->utf = utf;
		this->result = result;
	}

	virtual void execute(JNIEnv* env)
	{
    	*result = env->NewStringUTF(utf);
#if LOCAL_REFS_ARE_GLOBAL
        *result = (jstring) ToGlobalRef(env, *result);
#endif
	}
};

NS_IMETHODIMP CSecureEnv::NewStringUTF(/*[in]*/  const char *utf,
                                        /*[out]*/ jstring* result) 
{
    if (mJavaEnv == NULL || result == NULL)
        return NS_ERROR_NULL_POINTER;
    
#if PROXY_JNI_CALLS    
	NewStringUTFMessage msg(utf, result);
    sendMessageToJava(&msg);
#else
	*result = mJavaEnv->NewStringUTF(utf);
#endif

    return NS_OK;
}


class GetStringUTFLengthMessage : public JavaMessage {
	jstring str;
	jsize* result;
public:
	GetStringUTFLengthMessage(jstring str, jsize* result)
	{
		this->str = str;
		this->result = result;
	}

	virtual void execute(JNIEnv* env)
	{
    	*result = env->GetStringUTFLength(str);
	}
};

NS_IMETHODIMP CSecureEnv::GetStringUTFLength(/*[in]*/  jstring str,
                                              /*[out]*/ jsize* result) 
{
    if (mJavaEnv == NULL || result == NULL)
        return NS_ERROR_NULL_POINTER;
    
#if PROXY_JNI_CALLS    
	GetStringUTFLengthMessage msg(str, result);
    sendMessageToJava(&msg);
#else
    *result = mJavaEnv->GetStringUTFLength(str);
#endif

    return NS_OK;
}

    
class GetStringUTFCharsMessage : public JavaMessage {
	jstring str;
	jboolean* isCopy;
	const char** result;
public:
	GetStringUTFCharsMessage(jstring str, jboolean* isCopy, const char** result)
	{
		this->str = str;
		this->isCopy = isCopy;
		this->result = result;
	}

	virtual void execute(JNIEnv* env)
	{
    	*result = env->GetStringUTFChars(str, isCopy);
	}
};

NS_IMETHODIMP CSecureEnv::GetStringUTFChars(/*[in]*/  jstring str,
                                             /*[in]*/  jboolean *isCopy,
                                             /*[out]*/ const char** result) 
{
    if (mJavaEnv == NULL || result == NULL)
        return NS_ERROR_NULL_POINTER;
    
#if PROXY_JNI_CALLS    
	GetStringUTFCharsMessage msg(str, isCopy, result);
    sendMessageToJava(&msg);
#else
    *result = mJavaEnv->GetStringUTFChars(str, isCopy);
#endif

    return NS_OK;
}


class ReleaseStringUTFCharsMessage : public JavaMessage {
	jstring str;
	const char* chars;
public:
	ReleaseStringUTFCharsMessage(jstring str, const char* chars)
	{
		this->str = str;
		this->chars = chars;
	}

	virtual void execute(JNIEnv* env)
	{
    	env->ReleaseStringUTFChars(str, chars);
	}
};

NS_IMETHODIMP CSecureEnv::ReleaseStringUTFChars(/*[in]*/  jstring str,
                                                 /*[in]*/  const char *chars) 
{
    if (mJavaEnv == NULL)
        return NS_ERROR_NULL_POINTER;
    
#if PROXY_JNI_CALLS    
	ReleaseStringUTFCharsMessage msg(str, chars);
    sendMessageToJava(&msg);
#else
    mJavaEnv->ReleaseStringUTFChars(str, chars);
#endif

    return NS_OK;
}


class GetArrayLengthMessage : public JavaMessage {
	jarray array;
	jsize* result;
public:
	GetArrayLengthMessage(jarray array, jsize* result)
	{
		this->array = array;
		this->result = result;
	}

	virtual void execute(JNIEnv* env)
	{
    	*result = env->GetArrayLength(array);
	}
};

NS_IMETHODIMP CSecureEnv::GetArrayLength(/*[in]*/  jarray array,
                                         /*[out]*/ jsize* result) 
{
    if (mJavaEnv == NULL || result == NULL)
        return NS_ERROR_NULL_POINTER;
    
#if PROXY_JNI_CALLS    
	GetArrayLengthMessage msg(array, result);
    sendMessageToJava(&msg);
#else
    *result = mJavaEnv->GetArrayLength(array);
#endif

    return NS_OK;
}

class NewObjectArrayMessage : public JavaMessage {
    jsize len;
	jclass clazz;
	jobject init;
	jobjectArray* result;
public:
	NewObjectArrayMessage(jsize len, jclass clazz, jobject init, jobjectArray* result)
	{
	    this->len = len;
		this->clazz = clazz;
		this->init = init;
		this->result = result;
	}

	virtual void execute(JNIEnv* env)
	{
		*result = env->NewObjectArray(len, clazz, init);
#if LOCAL_REFS_ARE_GLOBAL
        *result = (jobjectArray) ToGlobalRef(env, *result);
#endif
	}
};

NS_IMETHODIMP CSecureEnv::NewObjectArray(/*[in]*/  jsize len,
										 /*[in]*/  jclass clazz,
					                     /*[in]*/  jobject init,
					                     /*[out]*/ jobjectArray* result)
{
    if (mJavaEnv == NULL || result == NULL)
        return NS_ERROR_NULL_POINTER;

#if PROXY_JNI_CALLS    
	NewObjectArrayMessage msg(len, clazz, init, result);
    sendMessageToJava(&msg);
#else
    *result = mJavaEnv->NewObjectArray(len, clazz, init);
#endif

    return NS_OK;
}

class GetObjectArrayElementMessage : public JavaMessage {
	jobjectArray array;
	jsize index;
	jobject* result;
public:
	GetObjectArrayElementMessage(jobjectArray array, jsize index, jobject* result)
	{
		this->array = array;
		this->index = index;
		this->result = result;
	}
	
	virtual void execute(JNIEnv* env)
	{
        *result = env->GetObjectArrayElement(array, index);
#if LOCAL_REFS_ARE_GLOBAL
        *result = ToGlobalRef(env, *result);
#endif
	}
};

NS_IMETHODIMP CSecureEnv::GetObjectArrayElement(/*[in]*/  jobjectArray array,
                                                 /*[in]*/  jsize index,
                                                 /*[out]*/ jobject* result)
{
    if (mJavaEnv == NULL || result == NULL)
        return NS_ERROR_NULL_POINTER;

#if PROXY_JNI_CALLS    
	GetObjectArrayElementMessage msg(array, index, result);
    sendMessageToJava(&msg);
#else
    *result = mJavaEnv->GetObjectArrayElement(array, index);
#endif

    return NS_OK;
}


class SetObjectArrayElementMessage : public JavaMessage {
	jobjectArray array;
	jsize index;
	jobject val;
public:
	SetObjectArrayElementMessage(jobjectArray array, jsize index, jobject val)
	{
		this->array = array;
		this->index = index;
		this->val = val;
	}
	
	virtual void execute(JNIEnv* env)
	{
        env->SetObjectArrayElement(array, index, val);
	}
};

NS_IMETHODIMP CSecureEnv::SetObjectArrayElement(/*[in]*/  jobjectArray array,
                                                 /*[in]*/  jsize index,
                                                 /*[in]*/  jobject val) 
{
    if (mJavaEnv == NULL)
        return NS_ERROR_NULL_POINTER;

#if PROXY_JNI_CALLS    
	SetObjectArrayElementMessage msg(array, index, val);
    sendMessageToJava(&msg);
#else
    mJavaEnv->SetObjectArrayElement(array, index, val);
#endif

    return NS_OK;
}

class NewArrayMessage : public JavaMessage {
	jni_type element_type;
	jsize len;
	jarray* result;
public:
	NewArrayMessage(jni_type element_type, jsize len, jarray* result)
	{
		this->element_type = element_type;
		this->len = len;
		this->result = result;
	}

	virtual void execute(JNIEnv* env)
	{
        switch (element_type) {
        case jboolean_type:
            *result = env->NewBooleanArray(len);
            break;
        case jbyte_type:
            *result = env->NewByteArray(len);
            break;
        case jchar_type:
            *result = env->NewCharArray(len);
            break;
        case jshort_type:
            *result = env->NewShortArray(len);
            break;
        case jint_type:
            *result = env->NewIntArray(len);
            break;
        case jlong_type:
            *result = env->NewLongArray(len);
            break;
        case jfloat_type:
            *result = env->NewFloatArray(len);
            break;
        case jdouble_type:
            *result = env->NewDoubleArray(len);
            break;
        default:
            *result = NULL;
        }
#if LOCAL_REFS_ARE_GLOBAL
        *result = (jarray) ToGlobalRef(env, *result);
#endif
	}
};

NS_IMETHODIMP CSecureEnv::NewArray(/*[in]*/ jni_type element_type,
                        			/*[in]*/  jsize len,
                        			/*[out]*/ jarray* result) 
{
    if (mJavaEnv == NULL || result == NULL)
        return NS_ERROR_NULL_POINTER;

#if PROXY_JNI_CALLS    
	NewArrayMessage msg(element_type, len, result);
    sendMessageToJava(&msg);
#else
    JNIEnv* env = mJavaEnv;
    switch (element_type) {
    case jboolean_type:
        *result = env->NewBooleanArray(len);
        break;
    case jbyte_type:
        *result = env->NewByteArray(len);
        break;
    case jchar_type:
        *result = env->NewCharArray(len);
        break;
    case jshort_type:
        *result = env->NewShortArray(len);
        break;
    case jint_type:
        *result = env->NewIntArray(len);
        break;
    case jlong_type:
        *result = env->NewLongArray(len);
        break;
    case jfloat_type:
        *result = env->NewFloatArray(len);
        break;
    case jdouble_type:
        *result = env->NewDoubleArray(len);
        break;
    default:
        *result = NULL;
    }
#endif

    return NS_OK;
}

class GetArrayElementsMessage : public JavaMessage {
	jni_type element_type;
	jarray array;
	jboolean* isCopy;
	void* result;
public:
	GetArrayElementsMessage(jni_type element_type, jarray array, jboolean* isCopy, void* result)
	{
		this->element_type = element_type;
		this->array = array;
		this->isCopy = isCopy;
		this->result = result;
	}

	virtual void execute(JNIEnv* env)
	{
        switch (element_type) {
    	case jboolean_type:
    	    *(jboolean**) result = env->GetBooleanArrayElements((jbooleanArray)array, isCopy);
    	    break;
    	case jbyte_type:
    	    *(jbyte**) result = env->GetByteArrayElements((jbyteArray)array, isCopy);
    	    break;
    	case jchar_type:
    	    *(jchar**) result = env->GetCharArrayElements((jcharArray)array, isCopy);
    	    break;
    	case jshort_type:
    	    * (jshort**) result = env->GetShortArrayElements((jshortArray)array, isCopy);
    	    break;
    	case jint_type:
    	    * (jint**) result = env->GetIntArrayElements((jintArray)array, isCopy);
    	    break;
    	case jlong_type:
    	    * (jlong**) result = env->GetLongArrayElements((jlongArray)array, isCopy);
    	    break;
    	case jfloat_type:
    	    * (jfloat**) result = env->GetFloatArrayElements((jfloatArray)array, isCopy);
    	    break;
    	case jdouble_type:
    	    * (jdouble**) result = env->GetDoubleArrayElements((jdoubleArray)array, isCopy);
    	    break;
    	default:
    	    *(void**)result = NULL;
    	}
	}
};

NS_IMETHODIMP CSecureEnv::GetArrayElements(/*[in]*/  jni_type element_type,
                                           /*[in]*/  jarray array,
                                           /*[in]*/  jboolean *isCopy,
                                           /*[out]*/ void* result)
{
    if (mJavaEnv == NULL || result == NULL)
        return NS_ERROR_NULL_POINTER;

#if PROXY_JNI_CALLS
    GetArrayElementsMessage msg(element_type, array, isCopy, result);
    sendMessageToJava(&msg);
#else
    JNIEnv* env = mJavaEnv;
    switch (element_type) {
	case jboolean_type:
	    *(jboolean**) result = env->GetBooleanArrayElements((jbooleanArray)array, isCopy);
	    break;
	case jbyte_type:
	    *(jbyte**) result = env->GetByteArrayElements((jbyteArray)array, isCopy);
	    break;
	case jchar_type:
	    *(jchar**) result = env->GetCharArrayElements((jcharArray)array, isCopy);
	    break;
	case jshort_type:
	    * (jshort**) result = env->GetShortArrayElements((jshortArray)array, isCopy);
	    break;
	case jint_type:
	    * (jint**) result = env->GetIntArrayElements((jintArray)array, isCopy);
	    break;
	case jlong_type:
	    * (jlong**) result = env->GetLongArrayElements((jlongArray)array, isCopy);
	    break;
	case jfloat_type:
	    * (jfloat**) result = env->GetFloatArrayElements((jfloatArray)array, isCopy);
	    break;
	case jdouble_type:
	    * (jdouble**) result = env->GetDoubleArrayElements((jdoubleArray)array, isCopy);
	    break;
	default:
	    *(void**)result = NULL;
	}
#endif

    return NS_OK;
}


class ReleaseArrayElementsMessage : public JavaMessage {
	jni_type element_type;
	jarray array;
	void* elems;
	jint mode;
public:
	ReleaseArrayElementsMessage(jni_type element_type, jarray array, void* elems, jint mode)
	{
		this->element_type = element_type;
		this->array = array;
		this->elems = elems;
		this->mode = mode;
	}

	virtual void execute(JNIEnv* env)
	{
    	switch (element_type) {
    	case jboolean_type:
    		env->ReleaseBooleanArrayElements((jbooleanArray)array, (jboolean*)elems, mode);
    		break;
    	case jbyte_type:
    		env->ReleaseByteArrayElements((jbyteArray)array, (jbyte*)elems, mode);
    		break;
    	case jchar_type:
    		env->ReleaseCharArrayElements((jcharArray)array, (jchar*)elems, mode);
    		break;
    	case jshort_type:
    		env->ReleaseShortArrayElements((jshortArray)array, (jshort*)elems, mode);
    		break;
    	case jint_type:
    		env->ReleaseIntArrayElements((jintArray)array, (jint*)elems, mode);
    		break;
    	case jlong_type:
    		env->ReleaseLongArrayElements((jlongArray)array, (jlong*)elems, mode);
    		break;
    	case jfloat_type:
    		env->ReleaseFloatArrayElements((jfloatArray)array, (jfloat*)elems, mode);
    		break;
    	case jdouble_type:
    		env->ReleaseDoubleArrayElements((jdoubleArray)array, (jdouble*)elems, mode);
    		break;
    	}
	}
};

NS_IMETHODIMP CSecureEnv::ReleaseArrayElements(/*[in]*/ jni_type element_type,
                                                /*[in]*/ jarray array,
                                                /*[in]*/ void *elems,
                                                /*[in]*/ jint mode) 
{
	if (mJavaEnv == NULL)
		return NS_ERROR_NULL_POINTER;
	
#if PROXY_JNI_CALLS
    ReleaseArrayElementsMessage msg(element_type, array, elems, mode);
    sendMessageToJava(&msg);
#else
	JNIEnv* env = mJavaEnv;
	switch (element_type) {
	case jboolean_type:
		env->ReleaseBooleanArrayElements((jbooleanArray)array, (jboolean*)elems, mode);
		break;
	case jbyte_type:
		env->ReleaseByteArrayElements((jbyteArray)array, (jbyte*)elems, mode);
		break;
	case jchar_type:
		env->ReleaseCharArrayElements((jcharArray)array, (jchar*)elems, mode);
		break;
	case jshort_type:
		env->ReleaseShortArrayElements((jshortArray)array, (jshort*)elems, mode);
		break;
	case jint_type:
		env->ReleaseIntArrayElements((jintArray)array, (jint*)elems, mode);
		break;
	case jlong_type:
		env->ReleaseLongArrayElements((jlongArray)array, (jlong*)elems, mode);
		break;
	case jfloat_type:
		env->ReleaseFloatArrayElements((jfloatArray)array, (jfloat*)elems, mode);
		break;
	case jdouble_type:
		env->ReleaseDoubleArrayElements((jdoubleArray)array, (jdouble*)elems, mode);
		break;
	}
#endif

    return NS_OK;
}

class GetArrayRegionMessage : public JavaMessage {
	jni_type element_type;
	jarray array;
	jsize start;
	jsize len;
	void* buf;
public:
	GetArrayRegionMessage(jni_type element_type, jarray array, jsize start, jsize len, void* buf)
	{
		this->element_type = element_type;
		this->array = array;
		this->start = start;
		this->len = len;
		this->buf = buf;
	}

	virtual void execute(JNIEnv* env)
	{
        switch (element_type) {
        case jboolean_type:
            env->GetBooleanArrayRegion((jbooleanArray)array, start, len, (jboolean*)buf);
            break;
        case jbyte_type:
            env->GetByteArrayRegion((jbyteArray)array, start, len, (jbyte*)buf);
            break;
        case jchar_type:
            env->GetCharArrayRegion((jcharArray)array, start, len, (jchar*)buf);
            break;
        case jshort_type:
            env->GetShortArrayRegion((jshortArray)array, start, len, (jshort*)buf);
            break;
        case jint_type:
            env->GetIntArrayRegion((jintArray)array, start, len, (jint*)buf);
            break;
        case jlong_type:
            env->GetLongArrayRegion((jlongArray)array, start, len, (jlong*)buf);
            break;
        case jfloat_type:
            env->GetFloatArrayRegion((jfloatArray)array, start, len, (jfloat*)buf);
            break;
        case jdouble_type:
            env->GetDoubleArrayRegion((jdoubleArray)array, start, len, (jdouble*)buf);
            break;
        }
	}
};

NS_IMETHODIMP CSecureEnv::GetArrayRegion(/*[in]*/  jni_type element_type,
                                         /*[in]*/  jarray array,
                                         /*[in]*/  jsize start,
                                         /*[in]*/  jsize len,
                                         /*[out]*/ void* buf)
{
    if (mJavaEnv == NULL || buf == NULL)
        return NS_ERROR_NULL_POINTER;

#if PROXY_JNI_CALLS
    GetArrayRegionMessage msg(element_type, array, start, len, buf);
    sendMessageToJava(&msg);
#else
    JNIEnv* env = mJavaEnv;
    switch (element_type) {
    case jboolean_type:
        env->GetBooleanArrayRegion((jbooleanArray)array, start, len, (jboolean*)buf);
        break;
    case jbyte_type:
        env->GetByteArrayRegion((jbyteArray)array, start, len, (jbyte*)buf);
        break;
    case jchar_type:
        env->GetCharArrayRegion((jcharArray)array, start, len, (jchar*)buf);
        break;
    case jshort_type:
        env->GetShortArrayRegion((jshortArray)array, start, len, (jshort*)buf);
        break;
    case jint_type:
        env->GetIntArrayRegion((jintArray)array, start, len, (jint*)buf);
        break;
    case jlong_type:
        env->GetLongArrayRegion((jlongArray)array, start, len, (jlong*)buf);
        break;
    case jfloat_type:
        env->GetFloatArrayRegion((jfloatArray)array, start, len, (jfloat*)buf);
        break;
    case jdouble_type:
        env->GetDoubleArrayRegion((jdoubleArray)array, start, len, (jdouble*)buf);
        break;
    default:
        return NS_ERROR_FAILURE;
    }
#endif

    return NS_OK;
}


class SetArrayRegionMessage : public JavaMessage {
	jni_type element_type;
	jarray array;
	jsize start;
	jsize len;
	void* buf;
public:
	SetArrayRegionMessage(jni_type element_type, jarray array, jsize start, jsize len, void* buf)
	{
		this->element_type = element_type;
		this->array = array;
		this->start = start;
		this->len = len;
		this->buf = buf;
	}

	virtual void execute(JNIEnv* env)
	{
        switch (element_type) {
        case jboolean_type:
            env->SetBooleanArrayRegion((jbooleanArray)array, start, len, (jboolean*)buf);
            break;
        case jbyte_type:
            env->SetByteArrayRegion((jbyteArray)array, start, len, (jbyte*)buf);
            break;
        case jchar_type:
            env->SetCharArrayRegion((jcharArray)array, start, len, (jchar*)buf);
            break;
        case jshort_type:
            env->SetShortArrayRegion((jshortArray)array, start, len, (jshort*)buf);
            break;
        case jint_type:
            env->SetIntArrayRegion((jintArray)array, start, len, (jint*)buf);
            break;
        case jlong_type:
            env->SetLongArrayRegion((jlongArray)array, start, len, (jlong*)buf);
            break;
        case jfloat_type:
            env->SetFloatArrayRegion((jfloatArray)array, start, len, (jfloat*)buf);
            break;
        case jdouble_type:
            env->SetDoubleArrayRegion((jdoubleArray)array, start, len, (jdouble*)buf);
            break;
        }
	}
};

NS_IMETHODIMP CSecureEnv::SetArrayRegion(/*[in]*/  jni_type element_type,
                                         /*[in]*/  jarray array,
                                         /*[in]*/  jsize start,
                                         /*[in]*/  jsize len,
                                         /*[in]*/  void* buf)
{
    if (mJavaEnv == NULL || buf == NULL)
        return NS_ERROR_NULL_POINTER;

#if PROXY_JNI_CALLS
    SetArrayRegionMessage msg(element_type, array, start, len, buf);
    sendMessageToJava(&msg);
#else
    JNIEnv* env = mJavaEnv;
    switch (type) {
    case jboolean_type:
        env->SetBooleanArrayRegion((jbooleanArray)array, start, len, (jboolean*)buf);
        break;
    case jbyte_type:
        env->SetByteArrayRegion((jbyteArray)array, start, len, (jbyte*)buf);
        break;
    case jchar_type:
        env->SetCharArrayRegion((jcharArray)array, start, len, (jchar*)buf);
        break;
    case jshort_type:
        env->SetShortArrayRegion((jshortArray)array, start, len, (jshort*)buf);
        break;
    case jint_type:
        env->SetIntArrayRegion((jintArray)array, start, len, (jint*)buf);
        break;
    case jlong_type:
        env->SetLongArrayRegion((jlongArray)array, start, len, (jlong*)buf);
        break;
    case jfloat_type:
        env->SetFloatArrayRegion((jfloatArray)array, start, len, (jfloat*)buf);
        break;
    case jdouble_type:
        env->SetDoubleArrayRegion((jdoubleArray)array, start, len, (jdouble*)buf);
        break;
    default:
        return NS_ERROR_FAILURE;
    }
#endif

    return NS_OK;
}


class RegisterNativesMessage : public JavaMessage {
	jclass clazz;
	const JNINativeMethod *methods;
	jint nMethods;
	jint* result;
public:
	RegisterNativesMessage(jclass clazz, const JNINativeMethod *methods, jint nMethods, jint* result)
	{
		this->clazz = clazz;
		this->methods = methods;
		this->nMethods = nMethods;
		this->result = result;
	}
	
	virtual void execute(JNIEnv* env)
	{
    	*result = env->RegisterNatives(clazz, methods, nMethods);
	}
};

NS_IMETHODIMP CSecureEnv::RegisterNatives(/*[in]*/  jclass clazz,
                                           /*[in]*/  const JNINativeMethod *methods,
                                           /*[in]*/  jint nMethods,
                                           /*[out]*/ jint* result) 
{
    if (mJavaEnv == NULL || result == NULL)
        return NS_ERROR_NULL_POINTER;

#if PROXY_JNI_CALLS
    RegisterNativesMessage msg(clazz, methods, nMethods, result);
    sendMessageToJava(&msg);
#else
    *result = mJavaEnv->RegisterNatives(clazz, methods, nMethods);
#endif

    return NS_OK;
}


class UnregisterNativesMessage : public JavaMessage {
	jclass clazz;
	jint* result;
public:
	UnregisterNativesMessage(jclass clazz, jint* result)
	{
		this->clazz = clazz;
		this->result = result;
	}
	
	virtual void execute(JNIEnv* env)
	{
    	*result = env->UnregisterNatives(clazz);
	}
};

NS_IMETHODIMP CSecureEnv::UnregisterNatives(/*[in]*/  jclass clazz,
                                             /*[out]*/ jint* result) 
{
    if (mJavaEnv == NULL || result == NULL)
        return NS_ERROR_NULL_POINTER;

#if PROXY_JNI_CALLS
    UnregisterNativesMessage msg(clazz, result);
    sendMessageToJava(&msg);
#else
    *result = mJavaEnv->UnregisterNatives(clazz);
#endif

    return NS_OK;
}


class MonitorEnterMessage : public JavaMessage {
	jobject obj;
	jint* result;
public:
	MonitorEnterMessage(jobject obj, jint* result)
	{
		this->obj = obj;
		this->result = result;
	}

	virtual void execute(JNIEnv* env)
	{
		*result = env->MonitorEnter(obj);
	}
};

NS_IMETHODIMP CSecureEnv::MonitorEnter(/*[in]*/  jobject obj,
                                        /*[out]*/ jint* result) 
{
    if (mJavaEnv == NULL || result == NULL)
        return NS_ERROR_NULL_POINTER;

#if PROXY_JNI_CALLS
	MonitorEnterMessage msg(obj, result);
	sendMessageToJava(&msg);
#else
	*result = mJavaEnv->MonitorEnter(obj);
#endif

    return NS_OK;
}

class MonitorExitMessage : public JavaMessage {
	jobject obj;
	jint* result;
public:
	MonitorExitMessage(jobject obj, jint* result)
	{
		this->obj = obj;
		this->result = result;
	}

	virtual void execute(JNIEnv* env)
	{
		*result = env->MonitorExit(obj);
	}
};
 
NS_IMETHODIMP CSecureEnv::MonitorExit(/*[in]*/  jobject obj,
                                       /*[out]*/ jint* result)
{
    if (mJavaEnv == NULL || result == NULL)
        return NS_ERROR_NULL_POINTER;

#if PROXY_JNI_CALLS
	MonitorExitMessage msg(obj, result);
	sendMessageToJava(&msg);
#else
	*result = mJavaEnv->MonitorExit(obj);
#endif

    return NS_OK;
}

NS_IMETHODIMP CSecureEnv::GetJavaVM(/*[in]*/  JavaVM **vm,
                                     /*[out]*/ jint* result)
{
    if (mJavaEnv == NULL || result == NULL)
        return NS_ERROR_NULL_POINTER;

    *result = mJavaEnv->GetJavaVM(vm);

    return NS_OK;
}

/**
 * Runs a message loop. Puts the message in the sendQueue, then waits on the receiveQueue for the reply.
 */

void CSecureEnv::messageLoop(JNIEnv* env, JavaMessage* msg, JavaMessageQueue* sendQueue, JavaMessageQueue* receiveQueue, Boolean busyWaiting)
{
	// put the message in the send queue, and wait for a reply.
	sendQueue->putMessage(msg);
	sendQueue->notify();
	JavaMessage* replyMsg = receiveQueue->getMessage();
	for (;;) {
		// Fully synchronized approach.
		if (replyMsg != NULL) {
			if (replyMsg == msg)
				break;
			// must be a message that needs executing.
			replyMsg->execute(env);
			sendQueue->putMessage(replyMsg);
			sendQueue->notify();
			// notify can cause a yield, so look again.
			replyMsg = receiveQueue->getMessage();
			if (replyMsg != NULL)
				continue;
		}
		// when busy waiting, must give time to browser between timed waits.
		if (busyWaiting) {
			receiveQueue->wait(1024);
			replyMsg = receiveQueue->getMessage();
			if (replyMsg != NULL)
				continue;
			// mSession->lock();
			mThreadManager->Sleep();
			// mSession->unlock();
		} else {
			// wait for a message to arrive.
			receiveQueue->wait();
		}
		replyMsg = receiveQueue->getMessage();
	}
}

void CSecureEnv::savePendingException(JNIEnv* env)
{
    // first off, always restore the env to a known state.
    jthrowable pendingException = env->ExceptionOccurred();
    if (pendingException) {
#if USE_LIVECONNECT_PROXY    
        env->ExceptionDescribe();
#endif
        env->ExceptionClear();
        
        if (mPendingException)
            env->DeleteGlobalRef(mPendingException);

        mPendingException = (jthrowable) env->NewGlobalRef(pendingException);
        env->DeleteLocalRef(pendingException);
    }
}

jthrowable CSecureEnv::getPendingException(JNIEnv* env)
{
    if (mPendingException)
        return (jthrowable) env->NewLocalRef(mPendingException);
    return NULL;
}

void CSecureEnv::clearPendingException(JNIEnv* env)
{
    if (mPendingException) {
        env->DeleteGlobalRef(mPendingException);
        mPendingException = NULL;
    }
}

CSecureEnv* GetSecureJNI(JNIEnv* env, jobject thread)
{
	CSecureEnv* secureJNI = NULL;
	
	jclass threadClass = env->GetObjectClass(thread);
	if (threadClass != NULL) {
		jfieldID fSecureEnvField = env->GetFieldID(threadClass, "fSecureEnv", "I");
		if (fSecureEnvField != NULL) {
			secureJNI = (CSecureEnv*) env->GetIntField(thread, fSecureEnvField);
		} else {
			env->ExceptionClear();
		}
		env->DeleteLocalRef(threadClass);
	}
	
	return secureJNI;
}
