/* IcedTeaJavaRequestProcessor.cc

   Copyright (C) 2009, 2010  Red Hat

This file is part of IcedTea.

IcedTea is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.

IcedTea is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
General Public License for more details.

You should have received a copy of the GNU General Public License
along with IcedTea; see the file COPYING.  If not, write to the
Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301 USA.

Linking this library statically or dynamically with other modules is
making a combined work based on this library.  Thus, the terms and
conditions of the GNU General Public License cover the whole
combination.

As a special exception, the copyright holders of this library give you
permission to link this library with independent modules to produce an
executable, regardless of the license terms of these independent
modules, and to copy and distribute the resulting executable under
terms of your choice, provided that you also meet, for each linked
independent module, the terms and conditions of the license of that
module.  An independent module is a module which is not derived from
or based on this library.  If you modify this library, you may extend
this exception to your version of the library, but you are not
obligated to do so.  If you do not wish to do so, delete this
exception statement from your version. */

#include <typeinfo>

#include "IcedTeaJavaRequestProcessor.h"
#include "IcedTeaScriptablePluginObject.h"

/*
 * This class processes LiveConnect requests from JavaScript to Java.
 *
 * It sends the requests to Java, gets the return information, and sends it
 * back to the browser/JavaScript
 */

/**
 * Processes return information from JavaSide (return messages of requests)
 *
 * @param message The message request to process
 * @return boolean indicating whether the message is serviceable by this object
 */

bool
JavaRequestProcessor::newMessageOnBus(const char* message)
{

	// Anything we are waiting for _MUST_ have and instance id and reference #
	std::vector<std::string*>* message_parts = IcedTeaPluginUtilities::strSplit(message, " ");

	IcedTeaPluginUtilities::printStringPtrVector("JavaRequest::newMessageOnBus:", message_parts);

	if (*(message_parts->at(0)) == "context" && *(message_parts->at(2)) == "reference")
		if (atoi(message_parts->at(1)->c_str()) == this->instance && atoi(message_parts->at(3)->c_str()) == this->reference)
		{
			// Gather the results

			// Let's get errors out of the way first
			if (!message_parts->at(4)->find("Error"))
			{
				for (int i=5; i < message_parts->size(); i++)
				{
					result->error_msg->append(*(message_parts->at(i)));
					result->error_msg->append(" ");
				}

				PLUGIN_ERROR("Error on Java side: %s\n", result->error_msg->c_str());

				result->error_occurred = true;
				result_ready = true;
			}
			else if (!message_parts->at(4)->find("GetStringUTFChars") ||
			         !message_parts->at(4)->find("GetToStringValue"))
			{
				// first item is length, and it is radix 10
				int length = strtol(message_parts->at(5)->c_str(), NULL, 10);

				IcedTeaPluginUtilities::getUTF8String(length, 6 /* start at */, message_parts, result->return_string);
				result_ready = true;
			}
			else if (!message_parts->at(4)->find("GetStringChars")) // GetStringChars (UTF-16LE/UCS-2)
			{
				// first item is length, and it is radix 10
				int length = strtol(message_parts->at(5)->c_str(), NULL, 10);

				IcedTeaPluginUtilities::getUTF16LEString(length, 6 /* start at */, message_parts, result->return_wstring);
				result_ready = true;
			} else if (!message_parts->at(4)->find("FindClass") ||
			           !message_parts->at(4)->find("GetClassName") ||
			           !message_parts->at(4)->find("GetClassID") ||
			           !message_parts->at(4)->find("GetMethodID") ||
			           !message_parts->at(4)->find("GetStaticMethodID") ||
			           !message_parts->at(4)->find("GetObjectClass") ||
			           !message_parts->at(4)->find("NewObject") ||
			           !message_parts->at(4)->find("NewStringUTF") ||
			           !message_parts->at(4)->find("HasPackage") ||
			           !message_parts->at(4)->find("HasMethod") ||
			           !message_parts->at(4)->find("HasField") ||
			           !message_parts->at(4)->find("GetStaticFieldID") ||
			           !message_parts->at(4)->find("GetFieldID") ||
			           !message_parts->at(4)->find("GetJavaObject") ||
			           !message_parts->at(4)->find("IsInstanceOf") ||
			           !message_parts->at(4)->find("NewArray"))
			{
				result->return_identifier = atoi(message_parts->at(5)->c_str());
				result->return_string->append(*(message_parts->at(5))); // store it as a string as well, for easy access
				result_ready = true;
			}  else if (!message_parts->at(4)->find("DeleteLocalRef") ||
			            !message_parts->at(4)->find("NewGlobalRef"))
			{
			    result_ready = true; // nothing else to do
			} else if (!message_parts->at(4)->find("CallMethod") ||
			           !message_parts->at(4)->find("CallStaticMethod") ||
			           !message_parts->at(4)->find("GetField") ||
			           !message_parts->at(4)->find("GetStaticField") ||
			           !message_parts->at(4)->find("GetValue") ||
			           !message_parts->at(4)->find("GetObjectArrayElement"))
			{

			    if (!message_parts->at(5)->find("literalreturn") || !message_parts->at(5)->find("jsobject"))
                {
			        // literal returns don't have a corresponding jni id
			        result->return_identifier = 0;
			        result->return_string->append(*(message_parts->at(5)));
			        result->return_string->append(" ");
			        result->return_string->append(*(message_parts->at(6)));

                } else
			    {
                    // Else it is a complex object

			        result->return_identifier = atoi(message_parts->at(5)->c_str());
			        result->return_string->append(*(message_parts->at(5))); // store it as a string as well, for easy access
			    }

				result_ready = true;
			} else if (!message_parts->at(4)->find("GetArrayLength"))
            {
			    result->return_identifier = 0; // length is not an "identifier"
			    result->return_string->append(*(message_parts->at(5)));
			    result_ready = true;
            } else if (!message_parts->at(4)->find("SetField") ||
                       !message_parts->at(4)->find("SetObjectArrayElement"))
            {

                // nothing to do

                result->return_identifier = 0;
                result_ready = true;
            }

            IcedTeaPluginUtilities::freeStringPtrVector(message_parts);
			return true;
		}

    IcedTeaPluginUtilities::freeStringPtrVector(message_parts);
	return false;
}

/**
 * Constructor.
 *
 * Initializes the result data structure (heap)
 */

JavaRequestProcessor::JavaRequestProcessor()
{
    PLUGIN_DEBUG("JavaRequestProcessor constructor\n");

	// caller frees this
	result = new JavaResultData();
	result->error_msg = new std::string();
	result->return_identifier = 0;
	result->return_string = new std::string();
	result->return_wstring = new std::wstring();
	result->error_occurred = false;

	result_ready = false;
}

/**
 * Destructor
 *
 * Frees memory used by the result struct
 */

JavaRequestProcessor::~JavaRequestProcessor()
{
    PLUGIN_DEBUG("JavaRequestProcessor::~JavaRequestProcessor\n");

	if (result)
	{
		if (result->error_msg)
			delete result->error_msg;

		if (result->return_string)
			delete result->return_string;

		if (result->return_wstring)
			delete result->return_wstring;

		delete result;
	}
}

/**
 * Resets the results
 */
void
JavaRequestProcessor::resetResult()
{
	// caller frees this
	result->error_msg->clear();
	result->return_identifier = 0;
	result->return_string->clear();
	result->return_wstring->clear();
	result->error_occurred = false;

	result_ready = false;
}

void
JavaRequestProcessor::postAndWaitForResponse(std::string message)
{
    struct timespec t;
    clock_gettime(CLOCK_REALTIME, &t);
    t.tv_sec += REQUESTTIMEOUT; // 1 minute timeout

    // Clear the result
    resetResult();

    java_to_plugin_bus->subscribe(this);
    plugin_to_java_bus->post(message.c_str());

    // Wait for result to be filled in.
    struct timespec curr_t;

    bool isPluginThread = false;

    if (pthread_self() == itnp_plugin_thread_id)
    {
        isPluginThread = true;
        PLUGIN_DEBUG("JRP is in plug-in thread...\n");
    }

    do
    {
        clock_gettime(CLOCK_REALTIME, &curr_t);

        if (!result_ready && (curr_t.tv_sec < t.tv_sec))
        {
            if (isPluginThread)
            {
                processAsyncCallQueue(NULL);

                // Let the browser run its pending events too
                if (g_main_context_pending(NULL))
                {
                    g_main_context_iteration(NULL, false);
                } else
                {
                    usleep(1000); // 1ms
                }
            } else
            {
                usleep(1000); // 1ms
            }
        }
        else
        {
            break;
        }
    } while (1);

    if (curr_t.tv_sec >= t.tv_sec)
    {
    	result->error_occurred = true;
    	result->error_msg->append("Error: Timed out when waiting for response");

    	// Report error
    	PLUGIN_DEBUG("Error: Timed out when waiting for response to %s\n", message.c_str());
    }

    java_to_plugin_bus->unSubscribe(this);
}

/**
 * Given an object id, fetches the toString() value from Java
 *
 * @param object_id The ID of the object
 * @return A JavaResultData struct containing the result of the request
 */

JavaResultData*
JavaRequestProcessor::getToStringValue(std::string object_id)
{
	std::string message = std::string();

	this->instance = 0; // context is always 0 (needed for java-side backwards compat.)
	this->reference = IcedTeaPluginUtilities::getReference();

	IcedTeaPluginUtilities::constructMessagePrefix(0, reference, &message);

    message.append(" GetToStringValue "); // get it in UTF8
    message.append(object_id);

    postAndWaitForResponse(message);

	IcedTeaPluginUtilities::releaseReference();

	return result;
}

/**
 * Given an object id, fetches the value of that ID from Java
 *
 * @param object_id The ID of the object
 * @return A JavaResultData struct containing the result of the request
 */

JavaResultData*
JavaRequestProcessor::getValue(std::string object_id)
{
    std::string message = std::string();

    this->instance = 0; // context is always 0 (needed for java-side backwards compat.)
    this->reference = IcedTeaPluginUtilities::getReference();

    IcedTeaPluginUtilities::constructMessagePrefix(0, reference, &message);

    message.append(" GetValue "); // get it in UTF8
    message.append(object_id);

    postAndWaitForResponse(message);

    IcedTeaPluginUtilities::releaseReference();

    return result;
}

/**
 * Given a string id, fetches the actual string from Java side
 *
 * @param string_id The ID of the string
 * @return A JavaResultData struct containing the result of the request
 */

JavaResultData*
JavaRequestProcessor::getString(std::string string_id)
{
    std::string message = std::string();

	this->instance = 0; // context is always 0 (needed for java-side backwards compat.)
	this->reference = IcedTeaPluginUtilities::getReference();

	IcedTeaPluginUtilities::constructMessagePrefix(0, reference, &message);

    message.append(" GetStringUTFChars "); // get it in UTF8
    message.append(string_id);

    postAndWaitForResponse(message);

	IcedTeaPluginUtilities::releaseReference();

	return result;
}

/**
 * Decrements reference count by 1
 *
 * @param object_id The ID of the object
 */

void
JavaRequestProcessor::deleteReference(std::string object_id)
{
    std::string message = std::string();

    this->instance = 0; // context is always 0 (needed for java-side backwards compat.)
    this->reference = IcedTeaPluginUtilities::getReference();

    IcedTeaPluginUtilities::constructMessagePrefix(0, reference, &message);

    message.append(" DeleteLocalRef ");
    message.append(object_id);

    postAndWaitForResponse(message);

    IcedTeaPluginUtilities::releaseReference();
}

/**
 * Increments reference count by 1
 *
 * @param object_id The ID of the object
 */

void
JavaRequestProcessor::addReference(std::string object_id)
{
    std::string message = std::string();

    this->instance = 0; // context is always 0 (needed for java-side backwards compat.)
    this->reference = IcedTeaPluginUtilities::getReference();

    IcedTeaPluginUtilities::constructMessagePrefix(0, reference, &message);

    message.append(" NewGlobalRef ");
    message.append(object_id);

    postAndWaitForResponse(message);

    IcedTeaPluginUtilities::releaseReference();

}

JavaResultData*
JavaRequestProcessor::findClass(int plugin_instance_id,
                                std::string name)
{
    std::string message = std::string();
    std::string plugin_instance_id_str = std::string();

    IcedTeaPluginUtilities::itoa(plugin_instance_id, &plugin_instance_id_str);

	this->instance = 0; // context is always 0 (needed for java-side backwards compat.)
	this->reference = IcedTeaPluginUtilities::getReference();

	IcedTeaPluginUtilities::constructMessagePrefix(0, reference, &message);

    message.append(" FindClass ");
    message.append(plugin_instance_id_str);
    message.append(" ");
    message.append(name);

    postAndWaitForResponse(message);

	return result;
}

JavaResultData*
JavaRequestProcessor::getClassName(std::string objectID)
{
    std::string message = std::string();

	this->instance = 0; // context is always 0 (needed for java-side backwards compat.)
	this->reference = IcedTeaPluginUtilities::getReference();

	IcedTeaPluginUtilities::constructMessagePrefix(0, reference, &message);

    message.append(" GetClassName ");
    message.append(objectID);

    postAndWaitForResponse(message);

	return result;
}

JavaResultData*
JavaRequestProcessor::getClassID(std::string objectID)
{
    std::string message = std::string();

    this->instance = 0; // context is always 0 (needed for java-side backwards compat.)
    this->reference = IcedTeaPluginUtilities::getReference();

    IcedTeaPluginUtilities::constructMessagePrefix(0, reference, &message);

    message.append(" GetClassID ");
    message.append(objectID);

    postAndWaitForResponse(message);

    return result;
}

JavaResultData*
JavaRequestProcessor::getArrayLength(std::string objectID)
{
    std::string message = std::string();

    this->instance = 0; // context is always 0 (needed for java-side backwards compat.)
    this->reference = IcedTeaPluginUtilities::getReference();

    IcedTeaPluginUtilities::constructMessagePrefix(0, reference, &message);

    message.append(" GetArrayLength ");
    message.append(objectID);

    postAndWaitForResponse(message);

    return result;
}

JavaResultData*
JavaRequestProcessor::getSlot(std::string objectID, std::string index)
{
    std::string message = std::string();
    this->instance = 0; // context is always 0 (needed for java-side backwards compat.)
    this->reference = IcedTeaPluginUtilities::getReference();

    IcedTeaPluginUtilities::constructMessagePrefix(0, reference, &message);

    message.append(" GetObjectArrayElement ");
    message.append(objectID);
    message.append(" ");
    message.append(index);

    postAndWaitForResponse(message);

    return result;
}

JavaResultData*
JavaRequestProcessor::setSlot(std::string objectID,
                              std::string index,
                              std::string value_id)
{
    std::string message = std::string();

    this->instance = 0; // context is always 0 (needed for java-side backwards compat.)
    this->reference = IcedTeaPluginUtilities::getReference();

    IcedTeaPluginUtilities::constructMessagePrefix(0, reference, &message);

    message.append(" SetObjectArrayElement ");
    message.append(objectID);
    message.append(" ");
    message.append(index);
    message.append(" ");
    message.append(value_id);

    postAndWaitForResponse(message);

    IcedTeaPluginUtilities::releaseReference();

    return result;
}

JavaResultData*
JavaRequestProcessor::newArray(std::string array_class,
                               std::string length)
{
    std::string message = std::string();

    this->instance = 0; // context is always 0 (needed for java-side backwards compat.)
    this->reference = IcedTeaPluginUtilities::getReference();

    IcedTeaPluginUtilities::constructMessagePrefix(0, reference, &message);

    message.append(" NewArray ");
    message.append(array_class);
    message.append(" ");
    message.append(length);

    postAndWaitForResponse(message);

    IcedTeaPluginUtilities::releaseReference();

    return result;
}

JavaResultData*
JavaRequestProcessor::getFieldID(std::string classID, std::string fieldName)
{
	JavaResultData* java_result;
	JavaRequestProcessor* java_request = new JavaRequestProcessor();
	std::string message = std::string();

	java_result = java_request->newString(fieldName);

	this->instance = 0; // context is always 0 (needed for java-side backwards compat.)
	this->reference = IcedTeaPluginUtilities::getReference();

	IcedTeaPluginUtilities::constructMessagePrefix(0, reference, &message);
	message.append(" GetFieldID ");
	message.append(classID);
	message.append(" ");
	message.append(java_result->return_string->c_str());

	postAndWaitForResponse(message);

	IcedTeaPluginUtilities::releaseReference();

	delete java_request;

	return result;
}

JavaResultData*
JavaRequestProcessor::getStaticFieldID(std::string classID, std::string fieldName)
{
    JavaResultData* java_result;
    JavaRequestProcessor* java_request = new JavaRequestProcessor();
    std::string message = std::string();

    java_result = java_request->newString(fieldName);

    this->instance = 0; // context is always 0 (needed for java-side backwards compat.)
    this->reference = IcedTeaPluginUtilities::getReference();

    IcedTeaPluginUtilities::constructMessagePrefix(0, reference, &message);
    message.append(" GetStaticFieldID ");
    message.append(classID);
    message.append(" ");
    message.append(java_result->return_string->c_str());

    postAndWaitForResponse(message);

    IcedTeaPluginUtilities::releaseReference();

    delete java_request;

    return result;
}

JavaResultData*
JavaRequestProcessor::getField(std::string source,
                               std::string classID,
                               std::string objectID,
                               std::string fieldName)
{
    JavaResultData* java_result;
    JavaRequestProcessor* java_request = new JavaRequestProcessor();
    std::string message = std::string();

    java_result = java_request->getFieldID(classID, fieldName);

    this->instance = 0; // context is always 0 (needed for java-side backwards compat.)
    this->reference = IcedTeaPluginUtilities::getReference();

    IcedTeaPluginUtilities::constructMessagePrefix(0, reference, source, &message);
    message.append(" GetField ");
    message.append(objectID);
    message.append(" ");
    message.append(java_result->return_string->c_str());

    postAndWaitForResponse(message);

    IcedTeaPluginUtilities::releaseReference();

    delete java_request;

    return result;
}

JavaResultData*
JavaRequestProcessor::getStaticField(std::string source, std::string classID,
                                     std::string fieldName)
{
    JavaResultData* java_result;
    JavaRequestProcessor* java_request = new JavaRequestProcessor();
    std::string message = std::string();

    java_result = java_request->getStaticFieldID(classID, fieldName);

    this->instance = 0; // context is always 0 (needed for java-side backwards compat.)
    this->reference = IcedTeaPluginUtilities::getReference();

    IcedTeaPluginUtilities::constructMessagePrefix(0, reference, source, &message);
    message.append(" GetStaticField ");
    message.append(classID);
    message.append(" ");
    message.append(java_result->return_string->c_str());

    postAndWaitForResponse(message);

    IcedTeaPluginUtilities::releaseReference();

    delete java_request;

    return result;
}


JavaResultData*
JavaRequestProcessor::set(std::string source,
                          bool isStatic,
                          std::string classID,
                          std::string objectID,
                          std::string fieldName,
                          std::string value_id)
{
    JavaResultData* java_result;
    JavaRequestProcessor java_request = JavaRequestProcessor();
    std::string message = std::string();

    java_result = java_request.getFieldID(classID, fieldName);

    this->instance = 0; // context is always 0 (needed for java-side backwards compat.)
    this->reference = IcedTeaPluginUtilities::getReference();

    IcedTeaPluginUtilities::constructMessagePrefix(0, reference, source, &message);

    if (isStatic)
    {
        message.append(" SetStaticField ");
        message.append(classID);
    } else
    {
        message.append(" SetField ");
        message.append(objectID);
    }

    message.append(" ");
    message.append(java_result->return_string->c_str());
    message.append(" ");
    message.append(value_id);

    postAndWaitForResponse(message);

    IcedTeaPluginUtilities::releaseReference();

    return result;
}

JavaResultData*
JavaRequestProcessor::setStaticField(std::string source,
                                     std::string classID,
                                     std::string fieldName,
                                     std::string value_id)
{
    return set(source, true, classID, "", fieldName, value_id);
}

JavaResultData*
JavaRequestProcessor::setField(std::string source,
                               std::string classID,
                               std::string objectID,
                               std::string fieldName,
                               std::string value_id)
{
    return set(source, false, classID, objectID, fieldName, value_id);
}

JavaResultData*
JavaRequestProcessor::getMethodID(std::string classID, NPIdentifier methodName,
                                  std::vector<std::string> args)
{
	std::string message, signature = "(";

    // FIXME: Need to determine how to extract array types and complex java objects
    for (int i=0; i < args.size(); i++)
    {
    	signature += args[i];
    }

    signature += ")";

	this->instance = 0; // context is always 0 (needed for java-side backwards compat.)
	this->reference = IcedTeaPluginUtilities::getReference();

	IcedTeaPluginUtilities::constructMessagePrefix(0, reference, &message);
	message += " GetMethodID " + classID + " ";
	message += IcedTeaPluginUtilities::NPIdentifierAsString(methodName) + " ";
	message += signature;

	postAndWaitForResponse(message);

	IcedTeaPluginUtilities::releaseReference();

	return result;
}

JavaResultData*
JavaRequestProcessor::getStaticMethodID(std::string classID, NPIdentifier methodName,
                                  std::vector<std::string> args)
{
    std::string message, signature = "(";

    // FIXME: Need to determine how to extract array types and complex java objects
    for (int i=0; i < args.size(); i++)
    {
        signature += args[i];
    }

    signature += ")";

    this->instance = 0; // context is always 0 (needed for java-side backwards compat.)
    this->reference = IcedTeaPluginUtilities::getReference();

    IcedTeaPluginUtilities::constructMessagePrefix(0, reference, &message);
    message += " GetStaticMethodID " + classID + " ";
    message += IcedTeaPluginUtilities::NPIdentifierAsString(methodName) + " ";
    message += signature;

    postAndWaitForResponse(message);

    IcedTeaPluginUtilities::releaseReference();

    return result;
}

void
getArrayTypeForJava(NPP instance, NPVariant element, std::string* type)
{

    if (NPVARIANT_IS_BOOLEAN(element)) {
        type->append("string");
    } else if (NPVARIANT_IS_INT32(element)) {
        type->append("string");
    } else if (NPVARIANT_IS_DOUBLE(element)) {
        type->append("string");
    } else if (NPVARIANT_IS_STRING(element)) {
        type->append("string");
    } else if (NPVARIANT_IS_OBJECT(element)) {

        NPObject* first_element_obj = NPVARIANT_TO_OBJECT(element);
        if (IcedTeaScriptableJavaPackageObject::is_valid_java_object(first_element_obj))
        {
            std::string class_id = std::string(((IcedTeaScriptableJavaObject*) first_element_obj)->getClassID());
            type->append(class_id);
        } else
        {
            type->append("jsobject");
        }
    } else {
        type->append("jsobject"); // Else it is a string
    }
}

void
createJavaObjectFromVariant(NPP instance, NPVariant variant, std::string* id)
{
	JavaResultData* java_result;

	std::string className;
	std::string jsObjectClassID = std::string();
	std::string jsObjectConstructorID = std::string();

	std::string stringArg = std::string();
	std::vector<std::string> args = std::vector<std::string>();

	JavaRequestProcessor java_request = JavaRequestProcessor();
	bool alreadyCreated = false;

    if (NPVARIANT_IS_VOID(variant))
    {
    	PLUGIN_DEBUG("VOID %d\n", variant);
    	id->append("0");
    	return; // no need to go further
    } else if (NPVARIANT_IS_NULL(variant))
    {
    	PLUGIN_DEBUG("NULL\n", variant);
    	id->append("0");
    	return; // no need to go further
    } else if (NPVARIANT_IS_BOOLEAN(variant))
    {
    	className = "java.lang.Boolean";

    	if (NPVARIANT_TO_BOOLEAN(variant))
    		stringArg = "true";
    	else
    		stringArg = "false";

    } else if (NPVARIANT_IS_INT32(variant))
    {
    	className = "java.lang.Integer";

    	char* valueStr = (char*) malloc(sizeof(char)*32);
    	sprintf(valueStr, "%d", NPVARIANT_TO_INT32(variant));
    	stringArg += valueStr;
    	free(valueStr);
    } else if (NPVARIANT_IS_DOUBLE(variant))
    {
    	className = "java.lang.Double";

    	char* valueStr = (char*) malloc(sizeof(char)*1024);
    	sprintf(valueStr, "%f", NPVARIANT_TO_DOUBLE(variant));
    	stringArg += valueStr;
    	free(valueStr);
    } else if (NPVARIANT_IS_STRING(variant))
    {
    	className = "java.lang.String";
    	stringArg = IcedTeaPluginUtilities::NPVariantAsString(variant);
    } else if (NPVARIANT_IS_OBJECT(variant))
    {

        NPObject* obj = NPVARIANT_TO_OBJECT(variant);
        if (IcedTeaScriptableJavaPackageObject::is_valid_java_object(obj))
        {
            PLUGIN_DEBUG("NPObject is a Java object\n");
            alreadyCreated = true;
        } else
        {
            PLUGIN_DEBUG("NPObject is not a Java object\n");
            NPIdentifier length_id = browser_functions.getstringidentifier("length");
            bool isJSObjectArray = false;

            // FIXME: We currently only handle <= 2 dim arrays. Do we really need more though?

            // Is it an array?
            if (IcedTeaPluginUtilities::isObjectJSArray(instance, obj)) {
                PLUGIN_DEBUG("NPObject is an array\n");

                std::string array_id = std::string();
                std::string java_array_type = std::string();
                NPVariant length = NPVariant();
                browser_functions.getproperty(instance, obj, length_id, &length);

                std::string length_str = std::string();
                IcedTeaPluginUtilities::itoa(NPVARIANT_TO_INT32(length), &length_str);

                if (NPVARIANT_TO_INT32(length) >= 0)
                {
                    NPIdentifier id_0 = browser_functions.getintidentifier(0);
                    NPVariant first_element = NPVariant();
                    browser_functions.getproperty(instance, obj, id_0, &first_element);

                    // Check for multi-dim array
                    if (NPVARIANT_IS_OBJECT(first_element) &&
                        IcedTeaPluginUtilities::isObjectJSArray(instance, NPVARIANT_TO_OBJECT(first_element))) {

                        NPVariant first_nested_element = NPVariant();
                        browser_functions.getproperty(instance, NPVARIANT_TO_OBJECT(first_element), id_0, &first_nested_element);

                        getArrayTypeForJava(instance, first_nested_element, &java_array_type);

                        length_str.append(" 0"); // secondary array is created on the fly
                    } else
                    {
                        getArrayTypeForJava(instance, first_element, &java_array_type);
                    }
                }

                // For JSObject arrays, we create a regular object (accessible via JSObject.getSlot())
                if (NPVARIANT_TO_INT32(length) < 0 || !java_array_type.compare("jsobject"))
                {
                    isJSObjectArray = true;
                    goto createRegularObject;
                }

                java_result = java_request.newArray(java_array_type, length_str);

                if (java_result->error_occurred) {
                    PLUGIN_ERROR("Unable to create array\n");
                    id->append("-1");
                    return;
                }

                id->append(*(java_result->return_string));

                NPIdentifier index_id = NPIdentifier();
                for (int i=0; i < NPVARIANT_TO_INT32(length); i++)
                {
                    NPVariant value = NPVariant();

                    index_id = browser_functions.getintidentifier(i);
                    browser_functions.getproperty(instance, obj, index_id, &value);

                    std::string value_id = std::string();
                    createJavaObjectFromVariant(instance, value, &value_id);

                    if (value_id == "-1") {
                        PLUGIN_ERROR("Unable to populate array\n");
                        id->clear();
                        id->append("-1");
                        return;
                    }

                    std::string value_str = std::string();
                    IcedTeaPluginUtilities::itoa(i, &value_str);
                    java_result = java_request.setSlot(*id, value_str, value_id);

                }

                // Got here => no errors above. We're good to return!
                return;
            }

            createRegularObject:
            if (!IcedTeaPluginUtilities::isObjectJSArray(instance, obj) || isJSObjectArray) // Else it is not an array
            {

                NPVariant* variant_copy = new NPVariant();
                OBJECT_TO_NPVARIANT(NPVARIANT_TO_OBJECT(variant), *variant_copy);

                className = "netscape.javascript.JSObject";
                IcedTeaPluginUtilities::JSIDToString(variant_copy, &stringArg);
                browser_functions.retainobject(NPVARIANT_TO_OBJECT(variant));

                std::string jsObjectClassID = std::string();
                std::string jsObjectConstructorID = std::string();
                std::vector<std::string> args = std::vector<std::string>();

                java_result = java_request.findClass(0, "netscape.javascript.JSObject");

                // the result we want is in result_string (assuming there was no error)
                if (java_result->error_occurred)
                {
                    PLUGIN_ERROR("Unable to get JSObject class id\n");
                    id->clear();
                    id->append("-1");
                    return;
                }

                jsObjectClassID.append(*(java_result->return_string));
                args.push_back("J");

                java_result = java_request.getMethodID(jsObjectClassID,
                                                       browser_functions.getstringidentifier("<init>"),
                                                       args);

                // the result we want is in result_string (assuming there was no error)
                if (java_result->error_occurred)
                {
                    PLUGIN_ERROR("Unable to get JSObject constructor id\n");
                    id->clear();
                    id->append("-1");
                    return;
                }

                jsObjectConstructorID.append(*(java_result->return_string));

                // We have the method id. Now create a new object.

                args.clear();
                args.push_back(stringArg);
                java_result = java_request.newObjectWithConstructor("",
                                                     jsObjectClassID,
                                                     jsObjectConstructorID,
                                                     args);

                // Store the instance ID for future reference
                IcedTeaPluginUtilities::storeInstanceID(variant_copy, instance);

                // the result we want is in result_string (assuming there was no error)
                // the result we want is in result_string (assuming there was no error)
                if (java_result->error_occurred)
                {
                    PLUGIN_ERROR("Unable to create JSObject\n");
                    id->clear();
                    id->append("-1");
                    return;
                }

                id->append(*(java_result->return_string));
                return;
            }
        }
    }

    if (!alreadyCreated) {
		java_result = java_request.findClass(0, className);

		// the result we want is in result_string (assuming there was no error)
		if (java_result->error_occurred) {
			PLUGIN_ERROR("Unable to find classid for %s\n", className.c_str());
			id->append("-1");
			return;
		}

		jsObjectClassID.append(*(java_result->return_string));

		std::string stringClassName = "Ljava/lang/String;";
		args.push_back(stringClassName);

		java_result = java_request.getMethodID(jsObjectClassID,
				      browser_functions.getstringidentifier("<init>"), args);

		// the result we want is in result_string (assuming there was no error)
		if (java_result->error_occurred) {
			PLUGIN_ERROR("Unable to find string constructor for %s\n", className.c_str());
			id->append("-1");
            return;
		}

		jsObjectConstructorID.append(*(java_result->return_string));

		// We have class id and constructor ID. So we know we can create the
		// object.. now create the string that will be provided as the arg
		java_result = java_request.newString(stringArg);

		if (java_result->error_occurred) {
			PLUGIN_ERROR("Unable to create requested object\n");
			id->append("-1");
            return;
		}

		// Create the object
		args.clear();
		std::string arg = std::string();
		arg.append(*(java_result->return_string));
		args.push_back(arg);
		java_result = java_request.newObjectWithConstructor("[System]", jsObjectClassID, jsObjectConstructorID, args);

        if (java_result->error_occurred) {
            PLUGIN_ERROR("Unable to create requested object\n");
            id->append("-1");
            return;
        }


		id->append(*(java_result->return_string));

	} else {
	    // Else already created

	    std::string classId = std::string(((IcedTeaScriptableJavaObject*) NPVARIANT_TO_OBJECT(variant))->getClassID());
	    std::string instanceId = std::string(((IcedTeaScriptableJavaObject*) NPVARIANT_TO_OBJECT(variant))->getInstanceID());

	    if (instanceId.length() == 0)
	        id->append(classId.c_str());
	    else
	        id->append(instanceId.c_str());
	}

}

JavaResultData*
JavaRequestProcessor::callStaticMethod(std::string source, std::string classID,
                                       std::string methodName,
                                       std::vector<std::string> args)
{
    return call(source, true, classID, methodName, args);
}

JavaResultData*
JavaRequestProcessor::callMethod(std::string source,
                                 std::string objectID, std::string methodName,
                                 std::vector<std::string> args)
{
    return call(source, false, objectID, methodName, args);
}

JavaResultData*
JavaRequestProcessor::call(std::string source,
                           bool isStatic, std::string objectID,
                           std::string methodName,
                           std::vector<std::string> args)
{
    std::string message = std::string();
    this->instance = 0; // context is always 0 (needed for java-side backwards compat.)
    this->reference = IcedTeaPluginUtilities::getReference();

    IcedTeaPluginUtilities::constructMessagePrefix(0, reference, source, &message);

    if (isStatic)
        message += " CallStaticMethod ";
    else
        message += " CallMethod ";

    message += objectID;
    message += " ";
    message += methodName;
    message += " ";

    for (int i=0; i < args.size(); i++)
    {
        message += args[i];
        message += " ";
    }

	postAndWaitForResponse(message);

    IcedTeaPluginUtilities::releaseReference();

    return result;
}

JavaResultData*
JavaRequestProcessor::getObjectClass(std::string objectID)
{
    JavaRequestProcessor* java_request;
    std::string message = std::string();

    this->instance = 0; // context is always 0 (needed for java-side backwards compat.)
    this->reference = IcedTeaPluginUtilities::getReference();

    IcedTeaPluginUtilities::constructMessagePrefix(0, reference, &message);
    message += " GetObjectClass ";
    message += objectID;

    postAndWaitForResponse(message);

    IcedTeaPluginUtilities::releaseReference();

    return result;
}

JavaResultData*
JavaRequestProcessor::newObject(std::string source, std::string classID,
                                std::vector<std::string> args)
{
    JavaRequestProcessor* java_request;
    std::string message = std::string();

    this->instance = 0; // context is always 0 (needed for java-side backwards compat.)
    this->reference = IcedTeaPluginUtilities::getReference();

    IcedTeaPluginUtilities::constructMessagePrefix(0, reference, source, &message);
    message += " NewObject ";
    message += classID;
    message += " ";

    for (int i=0; i < args.size(); i++)
    {
        message += args[i];
        message += " ";
    }

    postAndWaitForResponse(message);

    IcedTeaPluginUtilities::releaseReference();

    return result;
}

JavaResultData*
JavaRequestProcessor::newObjectWithConstructor(std::string source, std::string classID,
                                std::string methodID,
                                std::vector<std::string> args)
{
	JavaRequestProcessor* java_request;
	std::string message = std::string();

	this->instance = 0; // context is always 0 (needed for java-side backwards compat.)
	this->reference = IcedTeaPluginUtilities::getReference();

	IcedTeaPluginUtilities::constructMessagePrefix(0, reference, source, &message);
	message += " NewObjectWithConstructor ";
	message += classID;
	message += " ";
	message += methodID;
	message += " ";

	for (int i=0; i < args.size(); i++)
	{
		message += args[i];
		message += " ";
	}

	postAndWaitForResponse(message);

	IcedTeaPluginUtilities::releaseReference();

	return result;
}

JavaResultData*
JavaRequestProcessor::newString(std::string str)
{
	std::string utf_string = std::string();
	std::string message = std::string();

	IcedTeaPluginUtilities::convertStringToUTF8(&str, &utf_string);

	this->instance = 0; // context is always 0 (needed for java-side backwards compat.)
	this->reference = IcedTeaPluginUtilities::getReference();

	IcedTeaPluginUtilities::constructMessagePrefix(0, reference, &message);
	message.append(" NewStringUTF ");
	message.append(utf_string);

	postAndWaitForResponse(message);

	IcedTeaPluginUtilities::releaseReference();

	return result;
}

JavaResultData*
JavaRequestProcessor::hasPackage(int plugin_instance_id,
                                 std::string package_name)
{
	JavaResultData* java_result;
	JavaRequestProcessor* java_request = new JavaRequestProcessor();
	std::string plugin_instance_id_str = std::string();
	IcedTeaPluginUtilities::itoa(plugin_instance_id, &plugin_instance_id_str);

	java_result = java_request->newString(package_name);

	this->instance = 0; // context is always 0 (needed for java-side backwards compat.)
	this->reference = IcedTeaPluginUtilities::getReference();

	std::string message;
	IcedTeaPluginUtilities::constructMessagePrefix(0, reference, &message);
	message += " HasPackage " + plugin_instance_id_str + " " + *java_result->return_string;

	postAndWaitForResponse(message);

	IcedTeaPluginUtilities::releaseReference();

	delete java_request;

	return result;
}

JavaResultData*
JavaRequestProcessor::hasMethod(std::string classID, std::string method_name)
{
    JavaResultData* java_result;
    JavaRequestProcessor* java_request = new JavaRequestProcessor();
    std::string message = std::string();

    java_result = java_request->newString(method_name);

    this->instance = 0; // context is always 0 (needed for java-side backwards compat.)
    this->reference = IcedTeaPluginUtilities::getReference();

    IcedTeaPluginUtilities::constructMessagePrefix(0, reference, &message);
    message.append(" HasMethod ");
    message.append(classID);
    message.append(" ");
    message.append(java_result->return_string->c_str());

    postAndWaitForResponse(message);

    IcedTeaPluginUtilities::releaseReference();

    delete java_request;

    return result;
}

JavaResultData*
JavaRequestProcessor::hasField(std::string classID, std::string method_name)
{
    JavaResultData* java_result;
    JavaRequestProcessor java_request = JavaRequestProcessor();
    std::string message = std::string();

    java_result = java_request.newString(method_name);

    this->instance = 0; // context is always 0 (needed for java-side backwards compat.)
    this->reference = IcedTeaPluginUtilities::getReference();

    IcedTeaPluginUtilities::constructMessagePrefix(0, reference, &message);
    message.append(" HasField ");
    message.append(classID);
    message.append(" ");
    message.append(java_result->return_string->c_str());

    postAndWaitForResponse(message);

    IcedTeaPluginUtilities::releaseReference();

    return result;
}

JavaResultData*
JavaRequestProcessor::isInstanceOf(std::string objectID, std::string classID)
{
    std::string message = std::string();

    this->instance = 0; // context is always 0 (needed for java-side backwards compat.)
    this->reference = IcedTeaPluginUtilities::getReference();

    IcedTeaPluginUtilities::constructMessagePrefix(0, reference, &message);
    message.append(" IsInstanceOf ");
    message.append(objectID);
    message.append(" ");
    message.append(classID);

    postAndWaitForResponse(message);

    IcedTeaPluginUtilities::releaseReference();

    return result;
}

JavaResultData*
JavaRequestProcessor::getAppletObjectInstance(std::string instanceID)
{
    std::string message = std::string();
    std::string ref_str = std::string();

    this->instance = 0;
    this->reference = IcedTeaPluginUtilities::getReference();
    IcedTeaPluginUtilities::itoa(reference, &ref_str);

    message = "instance ";
    message += instanceID;
    message += " reference ";
    message += ref_str;
    message += " GetJavaObject";

    postAndWaitForResponse(message);

    IcedTeaPluginUtilities::releaseReference();

    return result;
}

