/* -*- Mode: C; c-file-style: "gnu" -*-
   system.c -- native methods for java/lang/System.
   Created: Chris Toshok <toshok@hungry.com>, 28-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 "ClazzFile.h"

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

#ifdef HAVE_SYS_TIME_H
#  include <sys/time.h>
#elif defined( HAVE_TIME_H )
#  include <time.h>
#endif
#ifdef HAVE_SYS_TIMEB_H
#  include <sys/timeb.h>
#endif
#ifdef HAVE_UNISTD_H
#  include <unistd.h>
#endif
#ifdef HAVE_DIRECT_H
#  include <direct.h>
#endif
#if HAVE_SYS_UTSNAME_H
#  include <sys/utsname.h>
#endif
#if defined( FREEBSD )
#  include <sys/param.h> /* for MAXPATHLEN */
#elif defined( IRIX )
#  include <nl_types.h>
#  define MAXPATHLEN NL_MAXPATHLEN
#elif defined( HURD )
#  include <limits.h>
#  define MAXPATHLEN _POSIX_PATH_MAX
#elif defined( _WINDOWS )
#  define MAXPATHLEN _MAX_PATH
#else
#  include <limits.h>
#  ifndef MAXPATHLEN
#    define MAXPATHLEN PATH_MAX
#  endif
#endif

#if TIME_WITH_SYS_TIME
# include <sys/time.h>
# include <time.h>
#else
# if HAVE_SYS_TIME_H
#  include <sys/time.h>
# else
#  include <time.h>
# endif
#endif

extern PRLogModuleInfo *nativeLm;

JNIEXPORT jlong JNICALL
Java_java_lang_System_currentTimeMillis(JNIEnv *env,
					jclass cls)
{
  PRTime result = PR_Now();
  return (jlong)result / 1000;
}

JNIEXPORT void JNICALL
Java_java_lang_System_arraycopy(JNIEnv *env,
				jclass cls,
				jobject src,
				jint src_position,
				jobject dest,
				jint dest_position,
				jint length)
{
  HungryEnv *henv = HVM_ThreadGetEnv();
  jint el_size;
  jclass dest_class, src_class;
  ClazzFile *dest_cls, *src_cls;
  FieldStruct *dest_body_field, *src_body_field;
  FieldStruct *dest_length_field, *src_length_field;
  FieldStruct *el_size_field;
  InterpValue val;
  char *dest_body, *src_body;
  jint src_length, dest_length;
  jint k;
  char *src_clsname, *dest_clsname;
  ClazzFile *dest_el_cls;

  if (dest == NULL || src == NULL)
    {
      HVM_ExceptionThrow(henv, "java/lang/NullPointerException", NULL);
      return;
    }

  /*
   * If any of the following is true, an 
   * <code>ArrayStoreException</code> is thrown and the destination is 
   * not modified: 
   */
  dest_class = (*env)->GetObjectClass(env, dest);
  src_class = (*env)->GetObjectClass(env, src);

  dest_cls = jclass_to_clazzfile(henv, dest_class);
  src_cls = jclass_to_clazzfile(henv, src_class);

  /* 
   * <li>The <code>src</code> argument refers to an object that is not an 
   *     array.
   */
  if (!HVM_ClassIsArray(henv, dest_cls))
    {
      HVM_ExceptionThrow(henv, "java/lang/ArrayStoreException", "destination isn't an array");
      return;
    }
  /*
   * <li>The <code>dst</code> argument refers to an object that is not an 
   *     array. 
   */
  if (!HVM_ClassIsArray(henv, src_cls))
    {
      HVM_ExceptionThrow(henv, "java/lang/ArrayStoreException", "source isn't an array");
      return;
    }

  dest_clsname = getClassName(henv, dest_cls);
  src_clsname = getClassName(henv, src_cls);

  /*
   * <li>The <code>src</code> argument refers to an array with a primitive 
   *     component type and the <code>dst</code> argument refers to an array 
   *     with a reference component type. 
   * <li>The <code>src</code> argument refers to an array with a reference 
   *     component type and the <code>dst</code> argument refers to an array 
   *     with a primitive component type. 
   */
  if (((dest_clsname[1] == 'L' || dest_clsname[1] == '[') &&
       (src_clsname[1] != 'L' && src_clsname[1] != '[')) ||
      ((src_clsname[1] == 'L' || src_clsname[1] == '[') &&
       (dest_clsname[1] != 'L' && dest_clsname[1] != '[')))
    {
      HVM_ExceptionThrow(henv, "java/lang/ArrayStoreException",
		      "both source and destination must be either "
		      "primitive or non primitive arrays.");
      return;
    }

  if (dest_clsname[1] != 'L' && dest_clsname[1] != '['
      && dest_clsname[1] != src_clsname[1])
    {
      HVM_ExceptionThrow(henv, "java/lang/ArrayStoreException",
		      "both source and destination must be have "
		      "the same primitive types.");
      return;
    }

  /*
   * Otherwise, if any of the following is true, an 
   * <code>ArrayIndexOutOfBoundsException</code> is 
   * thrown and the destination is not modified: 
   */

  dest_length_field = dest_cls->fields[ARRAY_LENGTH_INDEX];
  src_length_field = src_cls->fields[ARRAY_LENGTH_INDEX];

  HVM_FieldGet(dest, dest_length_field, &val);
  dest_length = val.i;

  HVM_FieldGet(src, src_length_field, &val);
  src_length = val.i;

  
  if (/* <li>The <code>srcOffset</code> argument is negative. */
      src_position < 0 ||
      /* <li>The <code>dstOffset</code> argument is negative. */
      dest_position < 0 ||
      /* <li>The <code>length</code> argument is negative. */
      length < 0 ||
      /*
       * <li><code>srcOffset+length</code> is greater than 
       *     <code>src.length</code>, the length of the source array. 
       */
      src_position + length > src_length ||
      /*
       * <li><code>dstOffset+length</code> is greater than 
       *     <code>dst.length</code>, the length of the destination array. 
       */
      dest_position + length > dest_length)
    {
      HVM_ExceptionThrow(henv, "java/lang/IndexOutOfBoundsException", NULL);
      return;
    }

  dest_body_field = dest_cls->fields[ARRAY_BODY_INDEX];
  src_body_field = src_cls->fields[ARRAY_BODY_INDEX];

  el_size_field = src_cls->fields[ARRAY_ELSIZE_INDEX];
  
  HVM_FieldGet(src, el_size_field, &val);
  el_size = val.i;

  HVM_FieldGet(dest, dest_body_field, &val);
  dest_body = (char*)val.l;

  HVM_FieldGet(src, src_body_field, &val);
  src_body = (char*)val.l;

  if (src_clsname[1] != '['
      && src_clsname[1] != 'L')
    {
      k = length;
    }
  else
    {
      dest_el_cls = HVM_ClassFind(henv, dest_clsname + 1);

      for (k = 0; k < length; k ++)
	{
	  if (((jobject*)src_body)[k + src_position] == NULL)
	    continue;
	  
	  if (!HVM_ObjectIsInstanceOf(henv, ((jobject*)src_body)[k + src_position], dest_el_cls))
	    {
	      break;
	    }
	}
    }

  memcpy(dest_body + dest_position * el_size,
         src_body + src_position * el_size,
         k * el_size);

  if (k < length - 1)
    HVM_ExceptionThrow(henv, "java/lang/ArrayStoreException", NULL);
}

/* For Classpath, not JDK */
JNIEXPORT void JNICALL
Java_java_lang_VMSystem_arraycopy(JNIEnv *env,
				  jclass cls,
  				  jobject src,
				  jint src_position,
				  jobject dest,
				  jint dest_position,
				  jint length)
{
  Java_java_lang_System_arraycopy(env, cls, src, src_position, dest,
                                  dest_position, length);
}

JNIEXPORT jint JNICALL
Java_java_lang_System_identityHashCode(JNIEnv *env,
				       jclass cls,
				       jobject obj)
{
  /* XXX cut and pasted from java.lang.Object.hashCode */
  japhar_object* jobj = (japhar_object*)obj;

  if (obj == NULL)
    return 0;

  return jobj->hash_code;
}

/* For Classpath, not JDK */
JNIEXPORT jint JNICALL
Java_java_lang_VMSystem_identityHashCode(JNIEnv *env,
				         jclass cls,
				         jobject obj)
{
  return(Java_java_lang_System_identityHashCode(env, cls, obj));
}

/**
 * System properties. The following properties are guaranteed to be defined:
 * <dl>
 * <dt>java.version			<dd>Java version number
 * <dt>java.vendor			<dd>Java vendor specific string
 * <dt>java.vendor.url		<dd>Java vendor URL
 * <dt>java.home			<dd>Java installation directory
 * <dt>java.class.version	<dd>Java class version number
 * <dt>java.class.path		<dd>Java classpath
 * <dt>os.name				<dd>Operating System Name
 * <dt>os.arch				<dd>Operating System Architecture
 * <dt>os.version			<dd>Operating System Version
 * <dt>file.separator		<dd>File separator ("/" on Unix)
 * <dt>path.separator		<dd>Path separator (":" on Unix)
 * <dt>line.separator		<dd>Line separator ("\n" on Unix)
 * <dt>user.name			<dd>User account name
 * <dt>user.home			<dd>User home directory
 * <dt>user.dir				<dd>User's current working directory
 * <dt>user.timezone            <dd>The current timezone
 * </dl>
 * It will also define the java.compiler if env(JAVA_COMPILER) is defined
 */

typedef struct {
  char *key;
  char *value;
} propertySpec;

static propertySpec defaults[] = {
  { "user.language",		"en" },
  { "file.encoding.pkg",	"sun.io" },
  { "file.encoding",		"8859_1" },
  { "java.version", 		"1.1" },
  { "java.vendor",		"The Hungry Programmers" },
  { "java.vendor.url",		"http://www.hungry.com" },
  { "java.home",		INSTALL_DIR },
  { "java.class.version",	"45.3" },
  { "file.separator",		"/" },
  { "path.separator",		":" },
  { "line.separator",		"\n" },
  { "\0" }
};

JNIEXPORT jobject JNICALL
Java_java_lang_System_initProperties(JNIEnv *env,
				     jclass cls,
				     jobject props)
{
  jclass props_cls = (*env)->GetObjectClass(env, props);
  jmethodID hash_put;
  jstring key, value;
  char *home, *user, *jit;
  char cwd[MAXPATHLEN + 1];
  int i;
#if HAVE_SYS_UTSNAME_H
  struct utsname utsname;
#endif
  time_t t;
  struct tm *tminfo;

  PR_LOG(nativeLm, PR_LOG_DEBUG, ("in java_lang_System_initProperties()\n"));

  if ((*env)->ExceptionOccurred(env)) return NULL;

  hash_put = (*env)->GetMethodID(env, props_cls, "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
  if (hash_put == 0)
    hash_put = (*env)->GetMethodID(env, props_cls, "setProperty",
                                   "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");

  i = 0;

  while (defaults[ i ].key[0] != '\0')
    {
      PR_LOG(nativeLm, PR_LOG_DEBUG,
	     ("  putting property  key(%s), value(%s)\n", 
	      defaults[i].key, defaults[i].value));

      key = (*env)->NewStringUTF(env, defaults[i].key);
      value = (*env)->NewStringUTF(env, defaults[i].value);
      (*env)->CallVoidMethod(env, props, hash_put, key, value);

      if ((*env)->ExceptionOccurred(env))
	{
	  (*env)->ExceptionDescribe(env);
	  return NULL;
	}

      i++;
    }

  /* add the user's name. */
  key = (*env)->NewStringUTF(env, "user.name");
  user = getenv("LOGNAME");
  if (user == NULL)
    user = "";
  value = (*env)->NewStringUTF(env, user);
  (*env)->CallVoidMethod(env, props, hash_put, key, value);

  /* add the user's home directory. */
  key = (*env)->NewStringUTF(env, "user.home");
  home = getenv("HOME");
  if (home == NULL)
    home = "";
  value = (*env)->NewStringUTF(env, home);
  (*env)->CallVoidMethod(env, props, hash_put, key, value);
  
  /* add the JIT compiler name if env(JAVA_COMPILER) exists. */
  jit = getenv("JAVA_COMPILER");
  if (jit == NULL)
    jit = "NONE";
  key = (*env)->NewStringUTF(env, "java.compiler");
  value = (*env)->NewStringUTF(env, jit);
  (*env)->CallVoidMethod(env, props, hash_put, key, value);
  
  /* add the present working directory. */
  key = (*env)->NewStringUTF(env, "user.dir");
  getcwd(cwd, MAXPATHLEN);
  value = (*env)->NewStringUTF(env, cwd);
  (*env)->CallVoidMethod(env, props, hash_put, key, value);

  /* add the current timezone */
  key = (*env)->NewStringUTF(env, "user.timezone");
  value = 0;
  t = time(0);
  tminfo = localtime(&t);
#if HAVE_TM_ZONE
  value = (*env)->NewStringUTF(env, tminfo->tm_zone);
#elif defined(HAVE_TZNAME)
  value = (*env)->NewStringUTF(env, tzname[tminfo->tm_isdst]);   
#else
#error No known method to determine current timezone!
#endif

  if (value)
    (*env)->CallVoidMethod(env, props, hash_put, key, value);

#if HAVE_SYS_UTSNAME_H
  uname(&utsname);
#endif

  /* add the OS name and version*/
  key = (*env)->NewStringUTF(env, "os.name");
#if HAVE_SYS_UTSNAME_H
  value = (*env)->NewStringUTF(env, utsname.sysname);
#else
  value = (*env)->NewStringUTF(env, OS_NAME);
#endif
  (*env)->CallVoidMethod(env, props, hash_put, key, value);

  /* add the OS name and version*/
  key = (*env)->NewStringUTF(env, "os.version");
#if HAVE_SYS_UTSNAME_H
  value = (*env)->NewStringUTF(env, utsname.release);
#else
  value = (*env)->NewStringUTF(env, OS_REV);
#endif
  (*env)->CallVoidMethod(env, props, hash_put, key, value);

  return props;
}

/* For Classpath, not JDK */
JNIEXPORT void JNICALL
Java_java_lang_VMSystem_insertSystemProperties(JNIEnv *env,
					       jclass cls,
					       jobject props)
{
  Java_java_lang_System_initProperties(env, cls, props);
}

void
Java_java_lang_System_setIn0(JNIEnv *env,
			     jclass cls,
			     jobject in)
{
  jfieldID in_field;

  in_field = (*env)->GetStaticFieldID(env, cls, "in", "Ljava/io/InputStream;");

  (*env)->SetStaticObjectField(env, cls, in_field, in);
}

void
Java_java_lang_System_setOut0(JNIEnv *env,
			      jclass cls,
			      jobject out)
{
  jfieldID out_field;

  out_field = (*env)->GetStaticFieldID(env, cls, "out", "Ljava/io/PrintStream;");

  (*env)->SetStaticObjectField(env, cls, out_field, out);
}

void
Java_java_lang_System_setErr0(JNIEnv *env,
			      jclass cls,
			      jobject err)
{
  jfieldID err_field;

  err_field = (*env)->GetStaticFieldID(env, cls, "err", "Ljava/io/PrintStream;");

  (*env)->SetStaticObjectField(env, cls, err_field, err);
}

jstring
Java_java_lang_System_mapLibraryName(JNIEnv *env,
				     jclass cls,
				     jstring lib_name)
{
  HungryEnv *henv = HVM_ThreadGetEnv();
  char *lib_name_str = HVM_StringToCString(henv, lib_name);
  char *mapped_library_name = HVM_DllMapLibName(lib_name_str);
  jstring mapped_libname;

  mapped_libname = (*env)->NewStringUTF(env, mapped_library_name);

  free(lib_name_str);
  free(mapped_library_name);

  return mapped_libname;
}

jclass
Java_java_lang_System_getCallerClass(JNIEnv *env,
				     jclass cls)
{
  HungryEnv *henv = HVM_ThreadGetEnv();
  StackFrame *parent_frame;

  parent_frame = henv->top_frame->parent;

  return clazzfile_to_jclass(henv, parent_frame->method->clazz);
}

/* JDK 1.2 uses registration of native methods... */
static JNINativeMethod sys_native_methods[] = {
  { "arraycopy",	"(Ljava/lang/Object;ILjava/lang/Object;II)V",	(void*) Java_java_lang_System_arraycopy },
  { "currentTimeMillis", "()J",	(void*) Java_java_lang_System_currentTimeMillis },
  { "getCallerClass",	"()Ljava/lang/Class;",	(void*) Java_java_lang_System_getCallerClass },
  { "identityHashCode",	"(Ljava/lang/Object;)I", (void*) Java_java_lang_System_identityHashCode },
  { "initProperties",	"(Ljava/util/Properties;)Ljava/util/Properties;", (void*) Java_java_lang_System_initProperties },
  { "mapLibraryName",	"(Ljava/lang/String;)Ljava/lang/String;", (void*) Java_java_lang_System_mapLibraryName },
  { "setErr0",		"(Ljava/io/PrintStream;)V", (void*) Java_java_lang_System_setErr0 },
  { "setIn0",		"(Ljava/io/InputStream;)V", (void*) Java_java_lang_System_setIn0 },
  { "setOut0",		"(Ljava/io/PrintStream;)V", (void*) Java_java_lang_System_setOut0 }
};
static int num_sys_native_methods = sizeof(sys_native_methods) / sizeof(sys_native_methods[0]);

JNIEXPORT void JNICALL
Java_java_lang_System_registerNatives(JNIEnv *env,
				      jclass cls)
{
#ifdef HAVE_LIBFFI
  (*env)->RegisterNatives(env, cls,
			  sys_native_methods,
			  num_sys_native_methods);
#endif
}
