/* -*- Mode: C; c-file-style: "gnu"; indent-tabs-mode:nil -*-
   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 "arch.h"
#include "jniint.h"
#include "qop.h"

extern PRLogModuleInfo* jniLm;

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

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

static void
free_java_vm(JavaVM *vm)
{
#if 0
  HungryVM *hvm = (HungryVM*)vm;

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

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

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

  JNIFUNC(DeallocHungryJavaVM)(hvm);
#endif
}

static jint
JNIFUNC(DestroyJavaVM)(JavaVM *vm)
{
  HungryEnv *henv = HVM_ThreadGetEnv();
  HungryVM *hvm = henv->vm;
  JNIEnv *env = henv->thread_env;
  jclass thr_class = (*env)->FindClass(env, java_lang_Thread);
  jmethodID isDaemon = (*env)->GetMethodID(env, thr_class, "isDaemon", "()Z");

  PR_EnterMonitor(hvm->_mon);

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

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

          if ( envs != henv && thr != NULL &&
               JNI_FALSE == (*env)->CallBooleanMethod(env, thr, isDaemon) )
            {
              PR_Wait(hvm->_mon, PR_INTERVAL_NO_TIMEOUT);
              /* Restart list parsing as we gave up VM lock */
              envs = hvm->_envs;
            }
          else
            envs = envs->next;
        }

      PR_EnterMonitor(vms_monitor);
      UNQUEUE(hvm, vms);
      PR_ExitMonitor(vms_monitor);

      HVM_VMShutdown(hvm);

      PR_ExitMonitor(hvm->_mon);
      
      free_java_vm(vm);
    }

  HVM_DllExit();

  return 0;
}

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

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

  henv = PR_CALLOC(sizeof(HungryEnv));
  if (henv == NULL)
    return -1;

  /* find the VM we're attaching to */
  hvm = JavaVMToHVM(vm);

  /* otherwise, we allocate an env */
  env = JNIFUNC(AllocJNIEnv)();

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

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

  if (thread_name == NULL) thread_name = "(java thread)";

  henv->thread_env = (void*)env;
  JNIEnvToHEnv(env) = henv;

  HVM_ThreadInit(hvm, henv,
                 JNI_FALSE,
                 thread_name,
                 threadgroup,
                 -1 /* XXX */);

  PR_EnterMonitor(hvm->_mon);
  ENQUEUE(henv, hvm->_envs);
  PR_ExitMonitor(hvm->_mon);

  *p_env = env;

  return 0;

 err:
  return -1;
}

static jint
JNIFUNC(DetachCurrentThread)(JavaVM *vm)
{
  HungryEnv *henv = HVM_ThreadGetEnv();
  HungryVM *hvm = henv->vm;

  PR_EnterMonitor(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(DeallocJNIEnv)(henv->thread_env);
  PR_DELETE(henv);
  HVM_ThreadSetEnv(NULL);

  PR_NotifyAll(hvm->_mon);
  PR_ExitMonitor(hvm->_mon);
  return 0;

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

/* New for JDK 1.2 */
static jint
JNIFUNC(GetEnv)(JavaVM *vm, void **env,
                jint version)
{
  HungryEnv *henv = HVM_ThreadGetEnv();

  if (henv == NULL)
    {
      return -1;
    }
  else
    {
      *env = henv->thread_env;
      return 0;
    }
}

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;
  HVMClasspath *sys_classpath;
  HVMClasspath *user_classpath;

  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 user classpath is not set, we use the system classpath.
  ** if user classpath is set, we use the value of the user classpath, and then append
  **   the system classpath.
  */
  user_classpath = HVM_ClasspathGetUserClasspath();
  sys_classpath = HVM_ClasspathGetSystemClasspath();

  user_classpath = HVM_ClasspathConcat(user_classpath, sys_classpath);
  args->classpath = HVM_ClasspathToString(user_classpath);
  HVM_ClasspathDestroy(user_classpath);
  HVM_ClasspathDestroy(sys_classpath);

  args->vfprintf = NULL;

  args->exit = NULL;
  args->abort = NULL;
  args->enableClassGC = JNI_FALSE;
  args->enableVerboseGC = JNI_FALSE;
  args->disableAsyncGC = JNI_TRUE;

  return 0;
}

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

{
  HungryVM *cur;
  int count = 0;

  PR_EnterMonitor(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 = vms;
       cur != NULL;
       cur = cur->next)
    {
      if (vmBuf && bufLen > count)
        vmBuf[count] = (JavaVM*)cur;
      count ++;
    }
  
  if (nVMs)
    *nVMs = count;

  PR_ExitMonitor(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;
  jclass props_cls;
  jmethodID hash_put;
  int i;

  props_field = (*env)->GetStaticFieldID(env, system_cls,
                                         "props",
                                         "Ljava/util/Properties;");
  /* classpath calls the props field something different.  deal with it */
  if (!props_field)
    {
      (*env)->ExceptionClear(env);
      props_field = (*env)->GetStaticFieldID(env, system_cls,
                                             "properties",
                                             "Ljava/util/Properties;");
    }

  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 = PL_strdup(options[i].optionString);
      
      if (optionString[1] != 'D')
        {
          PR_DELETE(optionString);
          continue;
        }

      optionString += 2; /* skip the -D */
      eq = PL_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);

      PR_Free(optionString - 2);
    }
}

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

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

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

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

  return ret_val;
}

static jboolean
parse_jvm_options(HungryVM *hvm,
                  JavaVMInitArgs *args)
{
  int i;
  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 (!PL_strncmp(optionString, "-Djava.class.path=",
                           18 /* strlen("-Djava.class.path")*/)) 
                classpath = optionString + 18;
              /* and of the LD_LIBRARY_PATH equivalent. */
              else if (!PL_strncmp(optionString, "-Djava.library.path=",
                                19 /* strlen("-Djava.library.path")*/)) 
                libpath = optionString + 19;
            }
          else if (!PL_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 (!args->ignoreUnrecognized)
                {
                  /* give some error message, probably. */
                  return JNI_FALSE;
                }
            }
        }
      else
        {
          if (!PL_strcmp(optionString, "vfprintf"))
            hvm->vfprintf = (jint(*)(FILE*,const char*,va_list))args->options[i].extraInfo;
          else if (!PL_strcmp(optionString, "exit"))
            hvm->exit = (void(*)(jint))args->options[i].extraInfo;
          else if (!PL_strcmp(optionString, "abort"))
            hvm->abort = (void(*)(void))args->options[i].extraInfo;
          else
            {
              if (!args->ignoreUnrecognized)
                {
                  /* give some error message, probably. */
                  return JNI_FALSE;
                }
            }
        }
    }

  if (classpath)
    hvm->_classpath = classpath;

  return JNI_TRUE;
}

JNIEXPORT jint JNICALL
JNI_CreateJavaVM(JavaVM **p_vm,
                 JNIEnv **p_env,
                 void *vm_args)
{
  HungryVM *new_vm = NULL;
  HungryEnv *new_env = NULL;
  JavaVM *vm = NULL;
  JNIEnv *env = NULL;

  if (PR_FALSE == HVM_VMAllocate(&new_vm, &new_env))
    goto error;

  new_vm->_version = *(int*)vm_args;
  
  if (new_vm->_version == JNI_VERSION_1_1)
    {
      JDK1_1InitArgs *vm_args11 = (JDK1_1InitArgs*)vm_args;

      /* XXX more needed here, probably. */
      new_vm->vfprintf = vm_args11->vfprintf;
      new_vm->exit = vm_args11->exit;
      new_vm->abort = vm_args11->abort;
      
      if (vm_args11->enableVerboseGC) new_vm->_verbose_flags = VERBOSE_GC;

      new_vm->_classpath = PL_strdup(((JDK1_1InitArgs*)vm_args)->classpath);
    }
  else
    {
      if (parse_jvm_options(new_vm, (JavaVMInitArgs*)vm_args) == JNI_FALSE)
        {
          PR_LOG (jniLm, PR_LOG_DEBUG, ("parse_jvm_options == JNI_FALSE\n"));
          goto error;
        }
    }

  vm = JNIFUNC(AllocJavaVM)();
  new_vm->vm = (void*)vm;

  if (NULL == vm)
    {
      PR_LOG (jniLm, PR_LOG_DEBUG, ("AllocJavaVM return NULL\n"));
      goto error;
    }

  JavaVMToHVM(vm) = new_vm;

  env = JNIFUNC(AllocJNIEnv)();
  new_env->thread_env = (void*)env;

  if (NULL == env)
    {
      PR_LOG (jniLm, PR_LOG_DEBUG, ("AllocJNIEnv return NULL\n"));
      goto error;
    }

  JNIEnvToHEnv(env) = new_env;

  if (PR_FALSE == HVM_VMInitialize(new_vm, new_env))
    {
      PR_LOG (jniLm, PR_LOG_DEBUG, ("HVM_VMInitialize returned PR_FALSE\n"));
      goto error;
    }

  if (vms_monitor == NULL)
    vms_monitor = PR_NewMonitor();

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

  SIGNAL_install(fatal_signal_handler);

  PR_EnterMonitor(vms_monitor);
  ENQUEUE(new_vm, vms);
  PR_ExitMonitor(vms_monitor);

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

  return 0;

 error:
  HVM_ThreadSetEnv(NULL);
  if (new_env) PR_DELETE(new_env);
  if (env) JNIFUNC(DeallocJNIEnv)(env);
  if (new_vm->_mon) PR_DestroyMonitor(new_vm->_mon);
  if (new_vm) PR_DELETE(new_vm);
  if (vm) JNIFUNC(DeallocJavaVM)(vm);
  return -1;
}
