/* -*- 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 "jni.h"
#include "log.h"
#include "objects.h"
#include "array-class.h"
#include "ClazzFile.h"
#include "resolve.h"
#include "dynamic_loading.h"
#include "interp.h"
#include "jniint.h"
#include "exceptions.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

#define MYLOG "Native"

/**
 * WARNING: returned pointer must be freed by caller
 */
static char *
jstring2charptr(JNIEnv *env, jstring jstr)
{
  char *str_copy;
  const jbyte *str_chars;
  int str_length;
  str_chars = (*env)->GetStringUTFChars(env, jstr, NULL);
  str_length = (*env)->GetStringUTFLength(env, jstr) + 1;
  str_copy = (char*) malloc(str_length * sizeof(char));
  strncpy(str_copy, (char*)str_chars, str_length);
  (*env)->ReleaseStringUTFChars(env, jstr, str_chars);
  str_copy[str_length-1] = 0; /* Make sure the string is zero terminated */
  return str_copy;
}

JNIEXPORT jlong JNICALL
Java_java_lang_System_currentTimeMillis(JNIEnv *env,
					jclass cls)
{
  jlong result;
#ifdef HAVE_SYS_TIMEB_H
  /* windows-specific */
  struct _timeb tb;

  _ftime(&tb);

  result = tb.time * 1000;
  result += tb.millitm;
#else
  struct timeval tp;
  
  if (gettimeofday(&tp, NULL) == -1)
    (*env)->FatalError(env, "gettimeofday call failed.");
  
  result = (jlong)tp.tv_sec;
  result *= 1000;
  result += (tp.tv_usec / 1000);
#endif

  return result;
}

JNIEXPORT void JNICALL
Java_java_lang_System_arraycopy(JNIEnv *env,
				jclass cls,
				jobject src,
				jint src_position,
				jobject dest,
				jint dest_position,
				jint length)
{
  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;
  jvalue 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)
    {
      throw_Exception(env, "java/lang/NullPointerException", NULL);
      return;
    }

  dest_class = (*env)->GetObjectClass(env, dest);
  src_class = (*env)->GetObjectClass(env, src);

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

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

  if (!is_assignable_from(env, dest_cls, src_cls))
    {
      throw_Exception(env, "java/lang/ArrayStoreException", NULL);
      return;
    }

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

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

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

  if ((length < 0) ||
      (src_position < 0 || src_position + length > src_length) ||
      (dest_position < 0 || dest_position + length > dest_length))
    {
      throw_Exception(env, "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];
  
  get_instance_field(src, el_size_field, &val);
  el_size = val.i;

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

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

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

      for (k = 0; k < length; k ++)
	{
	  if (((jobject*)src_body)[k + src_position] == NULL)
	    continue;
	  
	  if (!is_instance_of(env, ((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)
    throw_Exception(env, "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;

  if (obj == NULL)
    {
      throw_Exception(env, "java/lang/NullPointerException", NULL);
      return -1;
    }

  obj_to_object(obj, jobj);

  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
 */

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;

  JAVARLOG0(MYLOG, 1, "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')
    {
      JAVARLOG2(MYLOG, 2, "  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)
    {
      /* these need to be global or they'll be collected when we return. */
      key = (*env)->NewGlobalRef(env, key);
      value = (*env)->NewGlobalRef(env, 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)
{
  char *lib_name_str = jstring2charptr(env, lib_name);
  char *mapped_library_name = DLL_mapLibName(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)
{
  HungryJNIEnv *henv = (HungryJNIEnv*)env;
  StackFrame *parent_frame;

  parent_frame = PARENT(TOPFRAME(henv->_java_info));

  return clazzfile_to_jclass(env, 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
}
