/* -*- Mode: C; c-file-style: "gnu" -*-
   jniinvoc.c -- Java Native Interface Invocation API.
   Created: Chris Toshok <toshok@hungry.com>, 26-Jul-1997
*/
/*
  This file is part of Japhar, the GNU Virtual Machine for Java Bytecodes.
  Japhar is a project of The Hungry Programmers, GNU, and OryxSoft.

  Copyright (C) 1997, 1998, 1999 The Hungry Programmers

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

  This library 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
  Library General Public License for more details.

  You should have received a copy of the GNU Library General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "jni.h"
#include "jniint.h"
#include "init.h"
#include "gc.h"
#include "qop.h"
#include "log.h"
#include "native-threads.h"
#include "signals.h"
#include "dynamic_loading.h"
#include "resolve.h"
#include "exceptions.h"

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

#define MYLOG "JNI"

/* our list of vms, and the monitor to lock when mucking with the list. */
static HungryJavaVM *vms;
static HMonitor vms_monitor = NULL;

/* the sun vtable */
extern struct JNINativeInterface _hungryJNIInterface;

static void
free_java_vm(JavaVM *vm)
{
  HungryJavaVM *hvm = (HungryJavaVM*)vm;

  /* free the vm's classpath */
  CLASSPATH_destroy(hvm->_cp_entries, hvm->_num_cp_entries);

  /* it monitor */
  MONITOR_destroy(hvm->_mon);

  /* and it initial env */
  /* XXX does something else need to happen here? */
  JNIFUNC(DeallocHungryJNIEnv)(hvm->_initial_env);
  THREAD_setEnv(NULL);
  THREAD_setVM(NULL);

  JNIFUNC(DeallocHungryJavaVM)(hvm);
}

static jint
JNIFUNC(DestroyJavaVM)(JavaVM *vm)
{
  JNIEnv *env = THREAD_getEnv();
  HungryJavaVM *hvm = (HungryJavaVM*)vm;
  jclass thr_class = (*env)->FindClass(env, "java/lang/Thread");
  jmethodID isDaemon = (*env)->GetMethodID(env, thr_class, "isDaemon", "()Z");

  MONITOR_enter(hvm->_mon);

  if (hvm->_version == JNI_VERSION_1_1
      && THREAD_getCurrent() != hvm->_initial_env->_thr)
    {
      MONITOR_exit(hvm->_mon);
  
      return -1;
    }
  else
    {
      /* check to see if there are non-daemon thread. */
      HungryJNIEnv* envs = hvm->_envs;

      while (envs)
	{
	  jobject thr = envs->_java_info->java_thread;

	  if ((JNIEnv*)envs != env && thr != NULL)
	    if (!(*env)->CallBooleanMethod(env, thr, isDaemon))
	      MONITOR_wait(hvm->_mon);
	    
	  envs = envs->next;
	}

      UNQUEUE(hvm, vms);
      
      /* Since we're deleting this vm, collect its garbage. */
      JGC_runCollector(vm);
      JGC_runFinalizers(vm);

#if 0
      if (hvm->_args.enableVerboseGC)
#endif
	JGC_printStats(vm, stdout);
      
      MONITOR_exit(hvm->_mon);
      
      free_java_vm(vm);
    }

  return 0;
}

static jint
JNIFUNC(AttachCurrentThread)(JavaVM *vm,
			     JNIEnv **p_env,
			     void *thr_args)
{
  JNIEnv *env = THREAD_getEnv();
  HungryJavaVM *hvm = (HungryJavaVM*)vm;
  HungryJNIEnv *henv;
  JavaVMAttachArgs *args = (JavaVMAttachArgs*)thr_args;
  char *thread_name = "<jthread>";
  jobject threadgroup = NULL;


  MONITOR_enter(hvm->_mon);

  /* if the thread has an env, it's been attached already, so just quit */
  if (env != NULL) goto err;

  /* otherwise, we allocate an env */
  henv = JNIFUNC(AllocHungryJNIEnv)();
  if (!henv) goto err;

  env = (JNIEnv*)henv;

  henv->_vm = (HungryJavaVM*)vm;
  henv->_thr = THREAD_getCurrent();
  THREAD_setEnv(env);
  THREAD_setVM(vm);

  if (args && args->version == JNI_VERSION_1_2)
    {
      if (args->name)
	thread_name = args->name;

      if (args->group)
	threadgroup = args->group;
    }

  lowlevel_thread_init(env, JNI_FALSE,
		       thread_name,
		       threadgroup,
		       -1 /* XXX */);

  ENQUEUE(henv, ((HungryJavaVM*)vm)->_envs);

  *p_env = env;
  MONITOR_exit(hvm->_mon);
  return 0;

 err:
  MONITOR_exit(hvm->_mon);
  return -1;
}

static
JNIFUNC(DetachCurrentThread)(JavaVM *vm)
{
  JNIEnv *env = THREAD_getEnv();
  HungryJNIEnv *henv = (HungryJNIEnv*)env;
  HungryJavaVM *hvm = (HungryJavaVM*)vm;

  MONITOR_enter(hvm->_mon);

  /* if it isn't an attached thread */
  if (!henv)
    goto err;    
  
  /* if it's the initial thread -- the one that created the java vm */
  if (henv == hvm->_initial_env)
    goto err;    
  
  /* if it's an attached thread, but attached to a different vm */
  if (henv->_vm != hvm)
    goto err;    
  
  UNQUEUE(henv, hvm->_envs);

  JNIFUNC(DeallocHungryJNIEnv)(henv);
  THREAD_setEnv(NULL);
  THREAD_setVM(NULL);

  MONITOR_notifyAll(hvm->_mon);
  MONITOR_exit(hvm->_mon);
  return 0;

 err:
  MONITOR_notifyAll(hvm->_mon);
  MONITOR_exit(hvm->_mon);
  return -1;
}

/* New for JDK 1.2 */
static jint
JNIFUNC(GetEnv)(JavaVM *vm, void **env,
		jint version)
{
  return -1; /* XXX */
}

const struct JNIInvokeInterface _hungryJNIInvokeInterface = {
  NULL,
  NULL,
  NULL,
  JNIFUNC(DestroyJavaVM),
  JNIFUNC(AttachCurrentThread),
  JNIFUNC(DetachCurrentThread),
  JNIFUNC(GetEnv),
};

JNIEXPORT jint JNICALL
JNI_GetDefaultJavaVMInitArgs(void *vm_args)
{
  JDK1_1InitArgs *args = (JDK1_1InitArgs*)vm_args;
  char *classpath_env;

  if (args->version != JNI_VERSION_1_1)
    return -1;

  args->version = JNI_VERSION_1_1;

  args->properties = NULL;

  args->checkSource = 0;
  args->nativeStackSize = 0; /* should be 16 megs? */
  args->javaStackSize = 0;
  args->minHeapSize = 0; /* should be 16 megs? */
  args->maxHeapSize = 0; /* should be what? */
  args->verifyMode = VERIFY_ALL;

  /*
  ** setup the classpath.  semantics are as follows:
  ** if CLASSPATH is not set, we use the system classpath.
  ** if CLASSPATH is set, we use the value of CLASSPATH, and then append
  **   the system classpath.
  */
  classpath_env = getenv("CLASSPATH");
  if (!classpath_env)
    args->classpath = CLASSPATH_getSystemClasspath();
  else 
    {
      char *sys_path = CLASSPATH_getSystemClasspath();
      args->classpath = (char *) malloc(strlen(classpath_env) +
					strlen(sys_path) + 2);
      strcpy((char*)args->classpath, classpath_env);
      strcat((char*)args->classpath, ":");
      strcat((char*)args->classpath, sys_path);
      free(sys_path);
    }

  args->vfprintf = NULL;

  args->exit = NULL;
  args->abort = NULL;
  args->enableClassGC = JNI_FALSE; /* don't enable this yet... it's buggy. */
  args->enableVerboseGC = JNI_FALSE;
  /* XXX hungry added field... */
  args->enableVerboseClassLoading = JNI_TRUE;
  args->disableAsyncGC = JNI_TRUE;

  return 0;
}

JNIEXPORT jint JNICALL
JNI_GetCreatedJavaVMs(JavaVM **vmBuf,
		      jsize bufLen,
		      jsize *nVMs)

{
  HungryJavaVM *cur;
  int count = 0;

  MONITOR_enter(vms_monitor);

  /* we loop through the entire list of vm's to get the count
     right, but we only assign things into vmBuf if it's not
     null and if its long enough (bufLen is big enough.) */
  for (cur = (HungryJavaVM*)vms;
       cur != NULL;
       cur = cur->next)
    {
      if (vmBuf && bufLen > count)
	vmBuf[count] = (JavaVM*)cur;
      count ++;
    }
  
  if (nVMs)
    *nVMs = count;

  MONITOR_exit(vms_monitor);

  return 0;
}

static void
add_user_properties(JNIEnv *env,
		    JavaVMOption *options,
		    int nOptions)
{
  jobject props;
  jclass system_cls = (*env)->FindClass(env, "java/lang/System");
  jfieldID props_field = (*env)->GetStaticFieldID(env, system_cls,
						  "props",
						  "Ljava/util/Properties;");
  jclass props_cls;
  jmethodID hash_put;
  int i;

  props = (*env)->GetStaticObjectField(env, system_cls, props_field);
  props_cls = (*env)->GetObjectClass(env, props);
  hash_put = (*env)->GetMethodID(env, props_cls, "put",
				 "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");


  for (i = 0; i < nOptions; i++)
    {
      char *eq;
      jstring key = NULL;
      jstring value = NULL;
      char *optionString = strdup(options[i].optionString);
      
      if (optionString[1] != 'D')
	{
	  free(optionString);
	  continue;
	}

      optionString += 2; /* skip the -D */
      eq = strchr(optionString, '=');
      if (eq)
	{
	  *eq = 0;
	  value = (*env)->NewStringUTF(env, eq + 1);
	}
      else
	value = (*env)->NewStringUTF(env, ""); /* XXX is this right? */
      
      key = (*env)->NewStringUTF(env, optionString);
      
      if (eq)
	*eq = '=';
      
      (*env)->CallVoidMethod(env, props, hash_put, key, value);

      free(optionString - 2);
    }
}

static int
parse_verbose_flags(char *flags)
{
  char *tmp_flags = strdup(flags);
  char *comma;
  char *p = tmp_flags;
  int ret_val = 0;

  comma = strchr(tmp_flags, ',');
  do {
    if (comma)
      *comma = 0;

    if (!strcmp(p, "gc"))
      {
	ret_val |= VERBOSE_GC;
      }      
    else if (!strcmp(p, "jni"))
      {
	ret_val |= VERBOSE_JNI;
      }
    else if (!strcmp(p, "class"))
      {
	ret_val |= VERBOSE_CLASS;
      }
    else if (!strcmp(p, "method"))
      {
	ret_val |= VERBOSE_METHOD;
      }
    else
      {
	free (tmp_flags);
	return -1;
      }

    if (comma)
      {
	p = comma + 1;
	comma = strchr(p, ',');
      }
    else
      p = NULL;
  } while (p);

  return ret_val;
}

static jboolean
parse_jvm_options(HungryJavaVM *hvm,
		  JavaVMInitArgs *args)
{
  int i;
  char *gc_name = NULL;
  char *classpath = NULL;
  char *libpath = NULL;

  for (i = 0; i < args->nOptions; i++)
    {
      char *optionString = args->options[i].optionString;

      if (optionString[0] == '-')
	{
	  if (optionString[1] == 'D')
	    {
	      /* catch the setting of classpath */
	      if (!strncmp(optionString, "-Djava.class.path=",
			   18 /* strlen("-Djava.class.path")*/)) 
		classpath = optionString + 18;
	      /* and of the LD_LIBRARY_PATH equivalent. */
	      else if (!strncmp(optionString, "-Djava.library.path=",
				19 /* strlen("-Djava.library.path")*/)) 
		libpath = optionString + 19;
	    }
	  else if (!strncmp(optionString, "-verbose:", 9 /* strlen(-verbose:) */))
	    {
	      int flags = parse_verbose_flags(optionString + 9);

	      if (flags == -1)
		{
		  fprintf(stderr, "Unrecognized verbose option: %s\n", optionString);
		  return JNI_FALSE;
		}
	      else
		hvm->_verbose_flags = flags;
	    }
	  else if (!strncmp(optionString, "-Xgc=", 5 /* strlen(-Xgc=) */))
	    {
	      gc_name = optionString + 5;
	    }
	  else
	    {
	      if (!args->ignoreUnrecognized)
		{
		  /* give some error message, probably. */
		  return JNI_FALSE;
		}
	    }
	}
      else
	{
	  if (!strcmp(optionString, "vfprintf"))
	    hvm->vfprintf = (jint(*)(FILE*,const char*,va_list))args->options[i].extraInfo;
	  else if (!strcmp(optionString, "exit"))
	    hvm->exit = (void(*)(jint))args->options[i].extraInfo;
	  else if (!strcmp(optionString, "abort"))
	    hvm->abort = (void(*)(void))args->options[i].extraInfo;
	  else
	    {
	      if (!args->ignoreUnrecognized)
		{
		  /* give some error message, probably. */
		  return JNI_FALSE;
		}
	    }
	}
    }

  if (gc_name)
    hvm->_gc = JGC_getNamed(gc_name);

  if (classpath)
    hvm->_classpath = classpath;

  return JNI_TRUE;
}

JNIEXPORT jint JNICALL
JNI_CreateJavaVM(JavaVM **p_vm,
		 JNIEnv **p_env,
		 void *vm_args)
{
  HungryJavaVM *new_vm;
  HungryJNIEnv *new_env;
  JavaVM *vm;
  JNIEnv *env;

  LOG_init();

  if (vms_monitor == NULL)
    vms_monitor = MONITOR_create();

  /* allocate our larger vm struct, and memcpy the sun vtable into
     new_vm->_vm */
  new_vm = JNIFUNC(AllocHungryJavaVM)();

  if (new_vm == NULL)
    {
      JAVARLOG0(MYLOG, 1, "Unable to alloc HungryJavaVM");
      return -1;
    }
  vm = (JavaVM*)new_vm;

  new_vm->_mon = MONITOR_create();
  if (NULL == new_vm->_mon)
    {
      JAVARLOG0(MYLOG, 1, "Unable to create monitor");
      free(new_vm);
      return -1;
    }

  /* allocate an env to tie to this thread, and memcpy the sun vtable into
     new_env->_env */
  new_env = JNIFUNC(AllocHungryJNIEnv)();
  if (new_env == NULL)
    {
      JAVARLOG0(MYLOG, 1, "Unable to alloc HungryJNIEnv");
      MONITOR_exit(new_vm->_mon);
      MONITOR_destroy(new_vm->_mon);
      free(new_vm);
      return -1;
    }
  env = (JNIEnv*)new_env;

  /* add the env to the vm's list of envs, and make it the special, "initial"
     env */
  new_vm->_initial_env = new_env;
  ENQUEUE(new_env, new_vm->_envs);

  /* add the backpointer to the env's vm, and let the env know what its
     native thread is */
  new_env->_vm = new_vm;
  new_env->_thr = THREAD_getCurrent();

  if (NULL == new_env->_thr)
    fprintf(stderr, "Warning: THREAD_getCurrent() == NULL\n");

  /* save off the env in the native thread's per thread info so we can get
     it back later */
  THREAD_setEnv(env);
  THREAD_setVM(vm);

  /* make sure our native symbols are loading before we go trying to execute java code. */
  if ( ! initialize_system_libraries() )
    {
      JAVARLOG0(MYLOG, 1, "Unable to load system libraries");
      MONITOR_destroy(new_vm->_mon);
      JNIFUNC(DeallocHungryJavaVM)(new_vm);
      JNIFUNC(DeallocHungryJNIEnv)(new_env);
      THREAD_setEnv(NULL);
      return -1;
    }
  /* make sure that the env is actually there. */
  assert(THREAD_getEnv() != NULL);

  new_vm->_version = *(int*)vm_args;

  if (new_vm->_version == JNI_VERSION_1_1)
    {
      /* XXX more needs to be done here */
      /* save off the args with which this vm was created */
      memcpy(&new_vm->_args, vm_args, sizeof(JDK1_1InitArgs));

      new_vm->_classpath = new_vm->_args.classpath;
    }
  else
    {
      if (parse_jvm_options(new_vm, (JavaVMInitArgs*)vm_args) == JNI_FALSE)
	{
	  MONITOR_destroy(new_vm->_mon);
	  JNIFUNC(DeallocHungryJavaVM)(new_vm);
	  JNIFUNC(DeallocHungryJNIEnv)(new_env);
	  THREAD_setEnv(NULL);
	  return -1;
	}
    }

  if (!new_vm->_gc)
    new_vm->_gc = JGC_getNamed("");
  JGC_initCollector((JavaVM*)new_vm, 0, 8192, new_vm->_verbose_flags & VERBOSE_GC);

  /* create our classpath */
  CLASSPATH_create(new_vm->_classpath, &new_vm->_cp_entries, &new_vm->_num_cp_entries);

  /* create our class repository (hash table of ClazzFile*'s and class descriptors. */
  initialize_class_repository((JavaVM*)new_vm);

  new_vm->_string_cf = find_class(env, "java/lang/String");
  new_vm->_object_cf = find_class(env, "java/lang/Object");
  new_vm->_class_cf = find_class(env, "java/lang/Class");
  if (!new_vm->_string_cf)
    {
      MONITOR_destroy(new_vm->_mon);
      JNIFUNC(DeallocHungryJavaVM)(new_vm);
      JNIFUNC(DeallocHungryJNIEnv)(new_env);
      THREAD_setEnv(NULL);
      return -1;
    }

  /* initialize some java.lang.Class specific stuff. */
  initialize_class_class((JNIEnv*)new_env);

  /* get the thread machinery going. */
  lowlevel_thread_init((JNIEnv*)new_env, JNI_TRUE,
		       "main",
		       NULL, -1);

  if ((*env)->ExceptionOccurred(env))
    {
      (*env)->ExceptionDescribe(env);
      (*env)->FatalError(env, "Exception occurred creating a java vm.");;
    }

  /* Handle initial properties */
  add_user_properties(env,
		      ((JavaVMInitArgs*)vm_args)->options,
		      ((JavaVMInitArgs*)vm_args)->nOptions);

  SIGNAL_install(fatal_signal_handler);

  /* return the vm and env to the caller */
  *p_vm = vm;
  *p_env = env;

  return 0;
}
