/* -*- Mode: C; c-file-style: "gnu" -*-
   classloader.c -- native methods for java/lang/ClassLoader.
   Created: Chris Toshok <toshok@hungry.com>, 25-Aug-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 <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <assert.h>

extern PRLogModuleInfo *nativeLm;

/* JDK1.2 */
JNIEXPORT jobject JNICALL
Java_java_lang_ClassLoader_findBootstrapClass(JNIEnv *env,
					      jobject clsloader,
					      jstring name)
{
  HungryEnv *henv = HVM_ThreadGetEnv();
  ClazzFile *cf;
  char *class_name = HVM_StringToCString(henv, name);

  cf = HVM_ClassFindLoaded(henv, class_name);

  if (cf)
    {
      free(class_name);
      return clazzfile_to_jclass(henv, cf);
    }
  else
    {
      (*env)->ThrowNew(env,
		       (*env)->FindClass(env, "java/lang/ClassNotFoundException"),
		       class_name);
      free(class_name);
      return NULL;
    }
}

/* JDK1.2 */
JNIEXPORT jclass JNICALL
Java_java_lang_ClassLoader_findLoadedClass(JNIEnv *env,
					   jobject clsloader,
					   jstring name)
{
  HungryEnv *henv = HVM_ThreadGetEnv();
  ClazzFile *cf;
  char *class_name = HVM_StringToCString(henv, name);

  cf = HVM_ClassFindLoaded(henv, class_name);

  free(class_name);

  if (cf)
    return clazzfile_to_jclass(henv, cf);
  else
    return NULL;
}

/* JDK1.2 */
JNIEXPORT jobject JNICALL
Java_java_lang_ClassLoader_getCallerClassLoader(JNIEnv *env,
						jobject clsloader)
{
  HungryEnv *henv = HVM_ThreadGetEnv();
  StackFrame *parent_frame;

  parent_frame = henv->top_frame->parent;

  return parent_frame->method->clazz->classloader;
}

/* JDK1.1 */
JNIEXPORT void JNICALL
Java_java_lang_ClassLoader_init(JNIEnv *env,
				jobject obj)
{
  /* anything to do here? */
}

JNIEXPORT jobject JNICALL
Java_java_lang_ClassLoader_defineClass0(JNIEnv *env,
					jobject obj,
					jobject name_str,
					jbyteArray data,
					jint offset,
					jint length)
{
  jclass cls;
  jbyte *elements;

  elements = (*env)->GetByteArrayElements(env, data, NULL);

  cls = (*env)->DefineClass(env, obj, elements + offset, length);

  (*env)->ReleaseByteArrayElements(env, data, elements, 0);

  return cls;
}

/*
 * For Classpath, not JDK
 */
JNIEXPORT jclass JNICALL
Java_java_lang_VMClassLoader_defineClass(JNIEnv *env,
                                         jclass this_class,
                                         jobject class_loader,
                                         jstring name,
                                         jbyteArray data,
                                         jint offset,
                                         jint length)
{
  return(Java_java_lang_ClassLoader_defineClass0(env, class_loader, name,
                                                 data, offset, length));
}

JNIEXPORT void JNICALL
Java_java_lang_ClassLoader_resolveClass0(JNIEnv *env,
					 jobject obj,
					 jobject cls)
{
  /* nothing to do here, since we resolve classes when we define them (I think...). */
}

/*
 * For Classpath, not JDK
 */
JNIEXPORT void JNICALL
Java_java_lang_VMClassLoader_resolveClass(JNIEnv *env, jclass this_class,
                                          jclass to_resolve)
{
  Java_java_lang_ClassLoader_resolveClass0(env, this_class, to_resolve);
} 

JNIEXPORT jobject JNICALL
Java_java_lang_ClassLoader_findSystemClass0(JNIEnv *env,
					    jobject obj,
					    jobject name_str)
{
  jclass cls;
  const jbyte *name_bytes = (*env)->GetStringUTFChars(env, name_str, NULL);

  cls = (*env)->FindClass(env, (char*)name_bytes);

  if (cls == NULL)
    {
      jclass class_not_found;

      (*env)->ExceptionClear(env); /* eat the previous exception */

      class_not_found = (*env)->FindClass(env, 
					  "java/lang/ClassNotFoundException");
      (*env)->ThrowNew(env, class_not_found, (char*)name_bytes);

      (*env)->ReleaseStringUTFChars(env, name_str, name_bytes);

      return NULL;
    }
  else
    {
      (*env)->ReleaseStringUTFChars(env, name_str, name_bytes);

      return cls;
    }
}

JNIEXPORT jobject JNICALL
Java_java_lang_ClassLoader_getSystemResourceAsStream0(JNIEnv *env,
						      jclass cls,
						      jobject name_str)
{
  jmethodID getSystemResource;
  jobject url;
  jclass url_class = (*env)->FindClass(env, "java/net/URL");

  assert(NULL != url_class);
  getSystemResource = (*env)->GetStaticMethodID(env, cls, "getSystemResource",
						"(Ljava/lang/String;)Ljava/net/URL;");
  assert(NULL != getSystemResource);
  url = (*env)->CallStaticObjectMethod(env, cls, getSystemResource,
				       name_str);
  
  if (url == NULL)
    {
      return NULL;
    }
  else
    {
      jobject stream;
      jmethodID openStream;

      openStream = (*env)->GetMethodID(env, url_class, "openStream",
				       "()Ljava/io/InputStream;");

      stream = (*env)->CallObjectMethod(env, url, openStream);
	  
      return stream;
    }
}

static char *
locate_resource_on_classpath(const char *resource_name,
			     HVMClasspath *cp)
{
  char *full_resource_name;
  int i;

  for (i = 0; i < cp->num_entries; i ++)
    {
      if (cp->entries[i].type == eClasspathEntryZIP ||
	  cp->entries[i].type == eClasspathEntryJAR)
	{
	  ZipDirectory *dir;
	  int j;

	  /* if there was an error reading the archive, pass over it. */
	  if (NULL == cp->entries[i].zip.fd)
	    continue;

	  dir = (ZipDirectory*)cp->entries[i].zip.central_directory;

	  for (j = 0; j < cp->entries[i].zip.count; j ++, dir = ZIPDIR_NEXT(dir))
	    {
	      if (!strcmp(resource_name, ZIPDIR_FILENAME(dir)))
		{
		  full_resource_name = PR_smprintf ("systemresource:/ZIP%d/+/%s",
						    i,
						    resource_name);
				  
		  return full_resource_name;
		}
	    }
	}
      else if (cp->entries[i].type == eClasspathEntryDirectory)
	{
	  PRFileInfo info;

	  full_resource_name = PR_smprintf ("systemresource:/FILE/%s/%s",
					    cp->entries[i].path,
					    resource_name);

	  if (PR_FAILURE == PR_GetFileInfo(full_resource_name 
					   + PL_strlen("systemresource:/FILE"),
					   &info))
	    {
	      free(full_resource_name);
	      full_resource_name = NULL;
	      continue;
	    }
	  else
	    return full_resource_name;
	}
    }

  return NULL;
}

JNIEXPORT jstring JNICALL
Java_java_lang_ClassLoader_getSystemResourceAsName0(JNIEnv *env,
						    jclass cls,
						    jobject name_str)
{
  const jbyte *name_bytes = (*env)->GetStringUTFChars(env, name_str, NULL);
  HungryEnv *henv = HVM_ThreadGetEnv();
  HungryVM *hvm = henv->vm;
  char *resource_location;
  jstring resource_str;

  PR_LOG(nativeLm, PR_LOG_DEBUG,
	 ("java.lang.ClassLoader.getSystemResourceAsName0(%s)\n", name_bytes));

  resource_location = locate_resource_on_classpath((char*)name_bytes,
						   hvm->_cp);

  (*env)->ReleaseStringUTFChars(env, name_str, name_bytes);

  if (resource_location)
    {
      PR_LOG (nativeLm, PR_LOG_DEBUG,
	      ("Found %s\n", resource_location));
      resource_str = (*env)->NewStringUTF(env, resource_location);
      free(resource_location);
    }
  else
    resource_str = (*env)->NewStringUTF(env, "");

  return resource_str;
}

/* JDK1.2 specific inner class native methods */

JNIEXPORT jlong JNICALL
Java_java_lang_ClassLoader_00024NativeLibrary_find(JNIEnv *env,
						   jobject lib,
						   jstring name)
{
  HungryEnv *henv = HVM_ThreadGetEnv();
  char *sym_name = HVM_StringToCString(henv, name);
  HVMDllFuncHandle func_handle;
  jclass native_lib_cls = (*env)->GetObjectClass(env, lib);
  jfieldID handle_field;
  HVMDllLibHandle lib_handle;

  handle_field = (*env)->GetFieldID(env, native_lib_cls, "handle", "J");

  lib_handle = (HVMDllLibHandle)(jint)(*env)->GetLongField(env, lib, handle_field);

  func_handle = HVM_DllFindFunctionInLib(sym_name, lib_handle);

  free(sym_name);

  assert(sizeof(jlong) >= sizeof(HVMDllFuncHandle));

  return (jlong)func_handle;
}

/* can't use the NSA_* routines here, since the java code checks the "handle" field to make sure
   it isn't zero after "load" is called. */
JNIEXPORT void JNICALL
Java_java_lang_ClassLoader_00024NativeLibrary_load(JNIEnv *env,
						   jobject lib,
						   jstring name)
{
  HungryEnv *henv = HVM_ThreadGetEnv();
  char *lib_name = HVM_StringToCString(henv, name);
  char *lib_suffix;
  char *long_lib_name;
  HVMDllLibHandle handle;
  jclass native_lib_cls = (*env)->GetObjectClass(env, lib);
  jfieldID handle_field;
  HungryVM *hvm = henv->vm;

  handle_field = (*env)->GetFieldID(env, native_lib_cls, "handle", "J");

  if (hvm->_version == JNI_VERSION_1_1)
    lib_suffix = "-1.1";
  else if (hvm->_version == JNI_VERSION_1_2)
    lib_suffix = "-1.2";
  else
    lib_suffix = "";

  long_lib_name = (char*)malloc(strlen(lib_name) + strlen(lib_suffix) + 1);
  strcpy(long_lib_name, lib_name);
  strcat(long_lib_name, lib_suffix);

  handle = HVM_DllLoad(long_lib_name);
  if (!handle)
    handle = HVM_DllLoad(lib_name);

  (*env)->SetLongField(env, lib, handle_field, (jlong)(jint)handle);

  free(long_lib_name);
  free(lib_name);
}

JNIEXPORT void JNICALL
Java_java_lang_ClassLoader_00024NativeLibrary_unload(JNIEnv *env,
						     jobject lib)
{
  jclass native_lib_cls = (*env)->GetObjectClass(env, lib);
  jfieldID handle_field;
  HVMDllLibHandle handle;

  handle_field = (*env)->GetFieldID(env, native_lib_cls, "handle", "J");

  handle = (HVMDllLibHandle)(jint)(*env)->GetLongField(env, lib, handle_field);

  HVM_DllUnload(handle);
}
