/* -*- 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 "jniint.h"
#include "objects.h"
#include "classpath.h"
#include "log.h"
#include "interp.h"
#include "class-repository.h"
#include "dynamic_loading.h"

#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <assert.h>

#define MYLOG "NativeClassLoader"

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;
}

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

  cf = find_class_in_repository(env, class_name);


  if (cf)
    {
      free(class_name);
      return clazzfile_to_jclass(env, 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)
{
  ClazzFile *cf;
  char *class_name = jstring2charptr(env, name);

  cf = find_class_in_repository(env, class_name);

  free(class_name);

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

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

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

  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);
	  
      stream = (*env)->NewGlobalRef(env, stream);

      return stream;
    }
}

static char *
locate_resource_on_classpath(const char *resource_name,
			     ClasspathEntry *entries, int num_entries)
{
  char *full_resource_name;
  int i;

  for (i = 0; i < num_entries; i ++)
    {
      if (entries[i].type == CP_ZIP ||
	  entries[i].type == CP_JAR)
	{
	  ZipDirectory *dir;
	  int j;

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

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

	  for (j = 0; j < entries[i].zip.count; j ++, dir = ZIPDIR_NEXT(dir))
	    {
	      if (!strcmp(resource_name, ZIPDIR_FILENAME(dir)))
		{
		  full_resource_name = (char*)malloc(strlen("systemresource:/ZIP/")
						     + strlen(resource_name) 
						     + strlen("/+/")
						     + strlen(entries[i].path) + 2);
				  
		  sprintf (full_resource_name, "systemresource:/ZIP%d/+/%s",
			   i,
			   resource_name);
				  
		  return full_resource_name;
		}
	    }
	}
      else if (entries[i].type == CP_DIR)
	{
	  struct stat stat_buf;

	  full_resource_name = (char*)malloc(strlen("systemresource:/FILE/")
					     + strlen(resource_name) 
					     + strlen(entries[i].path) + 2);

	  sprintf (full_resource_name, "systemresource:/FILE/%s/%s",
		   entries[i].path,
		   resource_name);

	  if (stat(full_resource_name + strlen("systemresource:/FILE"), &stat_buf) == -1)
	    {
	      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);
  HungryJNIEnv *henv = (HungryJNIEnv*)env;
  HungryJavaVM *hvm = henv->_vm;
  ClasspathEntry *entries = hvm->_cp_entries;
  int num_entries = hvm->_num_cp_entries;
  char *resource_location;
  jstring resource_str;

  JAVARLOG1(MYLOG, 2, "java.lang.ClassLoader.getSystemResourceAsName0(%s)\n", name_bytes);

  resource_location = locate_resource_on_classpath((char*)name_bytes,
						   entries, num_entries);

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

  if (resource_location)
    {
      JAVARLOG1 (MYLOG, 2, "Found %s\n", resource_location);
      resource_str = (*env)->NewStringUTF(env, resource_location);
      free(resource_location);
    }
  else
    resource_str = (*env)->NewStringUTF(env, "");

  resource_str = (*env)->NewGlobalRef(env, resource_str);

  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)
{
  char *sym_name = jstring2charptr(env, name);
  DLL_FuncHandle func_handle;
  jclass native_lib_cls = (*env)->GetObjectClass(env, lib);
  jfieldID handle_field;
  DLL_LibHandle lib_handle;

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

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

  func_handle = DLL_findFunctionInLib(sym_name, lib_handle);

  free(sym_name);

  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)
{
  char *lib_name = jstring2charptr(env, name);
  char *lib_suffix;
  char *long_lib_name;
  DLL_LibHandle handle;
  jclass native_lib_cls = (*env)->GetObjectClass(env, lib);
  jfieldID handle_field;
  HungryJavaVM *hvm = ((HungryJNIEnv*)env)->_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 = DLL_load(long_lib_name);
  if (!handle)
    handle = DLL_load(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;
  DLL_LibHandle handle;

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

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

  DLL_unload(handle);
}
