/**********************************************************************
 * Copyright (c) 2005, 2009 IBM Corporation and others.
 * All rights reserved.   This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 * $Id: java.c,v 1.6 2009/07/10 00:11:30 jwest Exp $
 *
 * Contributors:
 * IBM - Initial API and implementation
 **********************************************************************/

#include "java.h"
#include "RAComm.h"

#include <stdlib.h>

/* Bug 62342 begins */
#ifdef _WIN32
	#define NUM_JVM_OPTIONS 2
#elif defined(__OS400__) /* Bug 95493 */
	#define NUM_JVM_OPTIONS 2
#else
	#define NUM_JVM_OPTIONS 1
#endif
/* Bug 62342 ends */

#ifdef __cplusplus
extern "C" {
#endif

/**
 * GLOBALS
 */

/* The global JVM handle*/
JavaVM *_java_VM = NULL;
JNIEnv *_mainThread;
int _currentJavaJobId = 0;
BOOL _isJvmCreated = FALSE;

/**
 * PROTOTYPES
 */
#ifdef _WIN32
DWORD WINAPI win32JavaJobProxy(LPVOID args);
#endif

void *JavaJob(void *args);

JavaVM* ra_getJvm() {
	return _java_VM;
}

BOOL ra_isJvmCreated() {
	return _isJvmCreated;
}

int ra_createJavaVitualMachine(char *dllPath) {
	return ra_createJavaVitualMachineWithClasspath(dllPath, NULL);
}

int ra_createJavaVitualMachineWithClasspath(char *dllPath, char *classpath) {
    jint result; /* the result of JVM creation */
    JavaVMInitArgs vmArgs; /* the JVM arguments */
    JavaVMOption options[NUM_JVM_OPTIONS]; /* the options inside the JVM argument structure */ /* Bug 62342 */
    char *classpathBuffer; /* the pointer to the class path to be loaded with the JVM */
#if defined(__OS400__) /* Bug 95493 */
    char *versionBuffer;  /* Bug 95493 the pointer to the version to be loaded with the JVM */
#endif
    int size, offset; /* the size and offset of the string buffer to be allocated for the class path */
#ifdef _WIN32
	char *jvmArgBuffer; /* Bug 62342 */
#endif

#ifndef __OS400__
    ra_CreateJavaVM vmCreationFunction; /* the entry point of JNI_CreateJavaVM() call */
    DLL_REFERENCE dllRef; /* the pointer to the dynamic library */

    /* Load the dynamic library. */
    if (!(dllRef = LOAD_LIBRARY(dllPath))) {
        printf("Couldn't load JVM library %s\n", dllPath);
        return -1;
    }
	else {
#if _DEBUG
        printf("Loaded JVM library %s\n", dllPath);
#endif
	}

    /* Resolve the entry point. */
    if (!(vmCreationFunction = (ra_CreateJavaVM) RESOLVE_ENTRY_POINT(dllRef, "JNI_CreateJavaVM"))) {
        printf("Couldn't resolve entrypoint for JNI_CreateJavaVM\n");
        return -2;
    }
	else {
#if _DEBUG
        printf("Resolved entrypoint for JNI_CreateJavaVM\n");
#endif
	}
#endif

    /* Construct the class path for this JVM.  This path can be of an arbitrary length. */
	offset = strlen(JAVA_PROPERTY_CLASSPATH);
    for (size = 1024; ; size <<= 1) {

        /* Make sure there is enough memory. */
        if (!(classpathBuffer = (char *)ra_malloc(size))) {
            return -1;
        }
		BZERO(classpathBuffer, size);

        result = ra_getEnvironmentVariable("CLASSPATH", classpathBuffer + offset, size - offset);
		if(classpath == NULL) {
			if (result + offset <= size) {
				break;
			}
		}
		else {
			if (result + offset + (int)strlen(classpath) + 1 <= size) {
				break;
			}
		}

        ra_free(classpathBuffer);
    }

#if _DEBUG
	printf("System CLASSPATH = %s, rc = %d\n", classpathBuffer + offset, result);
#endif

	if(classpath != NULL) {
		strcat(classpathBuffer + offset, FILE_PATH_SEPARATOR_STR);
		strcat(classpathBuffer + offset, classpath);
		result = 1; /* force this to be non-zero if environment has nothing */
	}

#if _DEBUG
		printf("Agent Controller CLASSPATH = %s\n",  classpathBuffer + offset);
#endif

	/* Bug 62342 begins */
	vmArgs.nOptions = 0;

#ifdef _WIN32
	/* Add the -Xrs option to prevent JVM shutdown upon user logoff */
	jvmArgBuffer = (char*)ra_malloc(sizeof(char) * (strlen(JAVA_OPTION_REDUCESIGNAL) + 1));
	BZERO(jvmArgBuffer, strlen(JAVA_OPTION_REDUCESIGNAL) + 1);
	memcpy(jvmArgBuffer, JAVA_OPTION_REDUCESIGNAL, strlen(JAVA_OPTION_REDUCESIGNAL));
	options[vmArgs.nOptions].optionString = jvmArgBuffer;
	vmArgs.nOptions++;
#endif

	/* Set all options for the JVM. */
	if (result) {
#if defined(__OS400__) /* Bug 95493 */
		versionBuffer = (char*)ra_malloc(sizeof(char) * 19);
		BZERO(versionBuffer, 19);
		memcpy(versionBuffer, "-Djava.version=1.4", 18);
		options[vmArgs.nOptions].optionString = as400_etoa(versionBuffer);
		vmArgs.nOptions++;
#endif

		memcpy(classpathBuffer, "-Djava.class.path=", offset);
#if _DEBUG
		printf("CLASSPATH used for starting Java job is: %s\n", classpathBuffer + offset);
#endif
#ifdef __OS400__
		/* Set the class path using what is in the CLASSPATH variable. The encoding conversion must be done at run-time. */
		options[vmArgs.nOptions].optionString = as400_etoa(classpathBuffer);
#elif defined MVS
		__etoa(classpathBuffer);
		options[vmArgs.nOptions].optionString = classpathBuffer;
#else
		options[vmArgs.nOptions].optionString = classpathBuffer;
#endif

		vmArgs.nOptions++;
	}

	if(vmArgs.nOptions == 0) {
		vmArgs.options = NULL;
	}
	else {
		vmArgs.options = options;
	}

	/* Bug 62342 ends */

    vmArgs.version = JNI_VERSION_1_2;
    vmArgs.ignoreUnrecognized = JNI_FALSE;

    /* Instantiate the JVM, and clean up. */
#ifdef __OS400__
    result = JNI_CreateJavaVM(&_java_VM, (void**)&_mainThread, (void*)&vmArgs);
    ra_free(options[0].optionString);
#else
    result = (*vmCreationFunction)(&_java_VM, (void**)&_mainThread, (void*)&vmArgs);
#endif

    ra_free(classpathBuffer);
#ifdef _WIN32
	ra_free(jvmArgBuffer); /* Bug 62342 */
#elif defined(__OS400__)
	ra_free(versionBuffer); /* Bug 95493 */
#endif

	if(result == 0) { /* Success */
		_isJvmCreated = TRUE;
		return 0;
	}
	else {
		_isJvmCreated = FALSE;
		return -1;
	}
}

int ra_submitJavaJob(ra_javaJobImplementation *jobImpl, BOOL asynchronous, TID *tid) {
	return ra_submitJavaJobReuseJVMThread(jobImpl, asynchronous, tid, NULL);
}

int ra_submitJavaJobReuseJVMThread(ra_javaJobImplementation *jobImpl, BOOL asynchronous, TID *tid, JNIEnv *jenv) {
	int rc = 0;
	HANDLE handle;
#ifdef __OS400__
    fcn_ptr_wrp* ptr; /* the pointer to the wrapper for the function pointer */

    /* On AS/400, function and variable pointers are not interchangable.  This wrapper is therefore needed
       to create threads properly. This pointer is dynamically allocated to avoid a race condition.  It is
       to be freed after the function pointer is passed properly.  Currently, this is done in JavaJob(). */
    ptr = ra_malloc(sizeof(fcn_ptr_wrp));
    ptr -> fcn_ptr = jobImpl;
#endif

#if _DEBUG
	printf("Inside ra_submitJavaJobReuseJVMThread()\n");
#endif

    if (asynchronous) {
#if _DEBUG
		printf("Asynchronous java job\n");
#endif
        /* Create a thread to run the job on */
#ifdef _WIN32
        handle = CreateThread(NULL,              /* default security attributes */
							 0,                 /* same stack size as current thread */
							 win32JavaJobProxy, /* Thread entry point */
							 (LPVOID) jobImpl,  /* params */
							 0,                 /* start executing immediately */
							 tid);              /* the thread ID */
		CloseHandle(handle);
#elif defined __OS400__
        int result =pthread_create(tid, NULL, JavaJob, ptr);
        if (result!=0) {
			#if _DEBUG
				printf("Failed to create thread for asynchronous java job\n");
			#endif
            rc = -1;
        }
#elif defined(_AIX)
    	pthread_attr_t thread_attr;

    	pthread_attr_init(&thread_attr);
    	pthread_attr_setstacksize( &thread_attr, 4194304 );
        int result = pthread_create(tid, &thread_attr, JavaJob, jobImpl);
        if (result!=0) {
		#if _DEBUG
			printf("Failed to create thread for asynchronous java job\n");
		#endif
            rc = -1;
        }
#else
        int result = pthread_create(tid, NULL, JavaJob, jobImpl);
        if (result!=0) {
			#if _DEBUG
				printf("Failed to create thread for asynchronous java job\n");
			#endif
            rc = -1;
        }
#endif
    } else {
        ra_javaJob *job;

#if _DEBUG
		printf("Synchronous java job\n");
#endif
		/* Allocate memory for the job. */
		job = (ra_javaJob*) ra_malloc(sizeof(ra_javaJob));
		BZERO(job, sizeof(ra_javaJob));

		if(jenv == NULL) {
			rc = ATTACH_THREAD(job->env);
#if _DEBUG
			printf("Attached to running jvm thread\n");
#endif
		}
		else {
			job->env = jenv;
			rc = 0;
#if _DEBUG
			printf("Reuse existing attached jvm thread\n");
#endif
		}

		if(!rc) {
#if _DEBUG
			printf("Running the java job\n");
#endif
			/* Run the job */
			rc = jobImpl(job);
#if _DEBUG
			printf("Returned from the java job, rc = %d\n", rc);
#endif

            /* Detach the thread from the JVM */
			if(jenv == NULL) {
				DETACH_THREAD();
#if _DEBUG
				printf("Detached from the jvm thread\n");
#endif
			}

			/* cleanup */
			ra_free(job);
		} else {
			/* couldn't attach thread to jvm */
			/* cleanup */
#if _DEBUG
			printf("Cannot attach to the jvm thread\n");
#endif
			ra_free(job);
			rc = -1;
		}
	}
	return rc;
}


int ra_destroyJavaVirtualMachine() {
	/* TODO: Currently, JVMs on most platforms do not handle the JVM destruction gracefully, e.g., process crash upon the DestroyJavaVM().
	 *	As a result, this call cannot be made until better support is available.
	 */
	jint rc = 0;

#if 0 /* disable the whole block */
	if(_isJvmCreated) {
#if defined(_HPUX)
		rc = (_java_VM)->DestroyJavaVM();
#else
		rc = (*_java_VM)->DestroyJavaVM(_java_VM);
#endif
		_java_VM = NULL;
		_isJvmCreated = FALSE;
	}
#endif

    return (int)rc;
}


void javaJobCleanup(void *arg) {
    ra_javaJob *job=(ra_javaJob*)arg;
    /* Notify the listener we have exited */

    ra_free(job);
}



void *JavaJob(void *args) {
    ra_javaJobImplementation *jobImpl; /* the entry point to the startup method */
    ra_javaJob *job; /* the job object */

#ifdef __OS400__
    /* The pointer args is allocated in ra_submitJavaJob() as a wrapper to pass the function pointer
       on AS/400 properly.  Freeing it here the correct behaviour.  */
    jobImpl = ((fcn_ptr_wrp *) args) -> fcn_ptr;
    ra_free(args);
#else
    /* On other platforms, the entry point is stored directly in the paremeter. */
    jobImpl = (ra_javaJobImplementation *) args;
#endif

    /* Allocate the job. */
    job = (ra_javaJob *) ra_malloc(sizeof(ra_javaJob));
    BZERO(job, sizeof(ra_javaJob));

#ifndef _WIN32
    /* Prepare for cleanup. */
    pthread_cleanup_push(javaJobCleanup, job);
#endif

    /* Start the job. */
    if (!ATTACH_THREAD(job -> env)) {
#if _DEBUG
			printf("Attached to running jvm thread\n");
#endif
        jobImpl(job);
        DETACH_THREAD();
#if _DEBUG
			printf("Detached from the jvm thread\n");
#endif
    } else {
        printf("Couldn't run job because couldn't attach thread to JVM\n");
    }

#ifndef _WIN32
    pthread_cleanup_pop(1);
#else
    javaJobCleanup(job);
#endif
    return (void*) NULL;
}


#ifdef _WIN32
DWORD WINAPI win32JavaJobProxy(LPVOID args) {
	DWORD returnVal = 0;
	JavaJob(args);
	return returnVal;
}
#endif

/* Creates a native representation of a Java String */
char* copyJavaStringToNative(JNIEnv *env, jstring str) {
	char *utf8Bytes;
	jsize utf8Length;
	char *nativeStr = NULL;
	int nativeLength;

	utf8Length = (jsize)ENV(env)->GetStringUTFLength(ENVPARM(env) str);
	utf8Bytes = (char*)ENV(env)->GetStringUTFChars(ENVPARM(env) str, NULL);
	if(utf8Length > 0) {
		nativeLength = unicode2native(&nativeStr, utf8Bytes, utf8Length);
	}
	else {
		nativeStr = (char*)ra_malloc(sizeof(char)); /* Bug 103601 */
		nativeStr[0] = '\0'; /* Bug 103601 */
	}
	ENV(env)->ReleaseStringUTFChars(ENVPARM(env) str, utf8Bytes);

	return nativeStr;
}

/** fndclsid **************************************************************************************
  * This function finds the class ID corresponding to the class name given, taking care of
  * platform-dependent encoding on AS/400 and OS/390.
  *
  * @param env          the environment in which the search is to be performed
  * @param clsname      the name of the class in native encoding
  * @returns the Java class ID corresponding to the requested class if any.
  */
jclass fndclsid(JNIEnv *env, char *clsname) {
    jclass clsid; /* the class ID for the class name */
    char *asciicls; /* the class name in ASCII format */

/* On AS/400 and OS/390, convert the class name at run-time into the ASCII format for the lookup. */
#ifdef __OS400__
    asciicls = as400_etoa(clsname);
#elif defined MVS
    asciicls = (char *) ra_malloc(strlen(clsname) + 1);
    strcpy(asciicls, clsname);
    __etoa(asciicls);
#else
    asciicls = clsname;
#endif

    /* Look up the class ID, after appropriate transformation. */
    clsid = ENV(env) -> FindClass(ENVPARM(env) asciicls);

/* On AS/400 and OS/390, free the ASCII class name before the class ID is returned. */
#if defined __OS400__ || defined MVS
    ra_free(asciicls);
#endif
    return clsid;
}


/** getnewstrutf **********************************************************************************
  * This function creates a new UTF8 string using the string given, taking care of platform-
  * dependent encoding on AS/400 and OS/390.
  *
  * @param env  the environment in which the search is to be performed
  * @param str  the content of the string to be created
  * @returns the Java string corresponding to the string given.
  */
jstring getnewstrutf(JNIEnv *env, char *str) {
#if defined __OS400__ || defined MVS
    char *asciistr; /* the temporary ASCII string */
    jstring javastr; /* the new Java string */

	if (!str) { 
		return 0;
	}
 #ifdef __OS400__
    asciistr = as400_etoa(str);
 #else
    asciistr = (char *) ra_malloc(strlen(str) + 1);
    strcpy(asciistr, str);
    __etoa(asciistr);
 #endif
    javastr = ENV(env) -> NewStringUTF(ENVPARM(env) asciistr);
    ra_free(asciistr);
    return javastr;
#else
	if (!str) {
		return 0; 
	}
    return ENV(env) -> NewStringUTF(ENVPARM(env) str);
#endif
}


/** fndmthdid *************************************************************************************
  * This function finds the method ID corresponding to the method given, taking care of platform-
  * dependent encoding on AS/400 and OS/390.
  *
  * @param env        the environment in which the search is to be performed
  * @param clsid      the class ID to which the method belongs
  * @param mthd       the name of the method in native encoding
  * @param sig        the signature of the method in native encoding
  * @returns the Java method ID corresponding to the requested method if any.
  */
jmethodID getmthdid(JNIEnv *env, jclass clsid, char *mthd, char *sig) {
    jmethodID mthdid; /* the resulting method ID */
    char *asciimthd; /* the method name in ASCII format */
    char *asciisig; /* the method signature in ASCII format */

    /* On each platform, obtain the ASCII forms of the method name and signature. */
#ifdef __OS400__
    asciimthd = as400_etoa(mthd);
    asciisig = as400_etoa(sig);
#elif defined MVS
    asciimthd = (char *) ra_malloc(strlen(mthd) + 1);
    asciisig = (char *) ra_malloc(strlen(sig) + 1);
    strcpy(asciimthd, mthd);
    strcpy(asciisig, sig);
    __etoa(asciimthd);
    __etoa(asciisig);
#else
    asciimthd = mthd;
    asciisig = sig;
#endif

    /* Look up the method ID. */
    mthdid = ENV(env) -> GetMethodID(ENVPARM(env) clsid, asciimthd, asciisig);

    /* free memory before the methid ID is returned. */
#if defined __OS400__ || defined MVS
    ra_free(asciimthd);
    ra_free(asciisig);
#endif
    return mthdid;
}

#ifdef __cplusplus
}
#endif
