/* -*- Mode: C; c-file-style: "gnu"; indent-tabs-mode:nil -*-
   jnirefl.c -- Java Native Interface methods relating reflection
   Created: Hernan Otero <hernan_otero@bigfoot.com>, 10-Jul-1998
 */
/*
  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"

jmethodID
JNIFUNC(FromReflectedMethod)(JNIEnv *env, jobject method)
{
  /* Since a Method is only valid for a single Class and its
   * ancestors and descendants which have that method (and I'm
   * not even sure about the ancestors part), the jmethodID
   * returned will always be the same (jmethodIDs are likewise
   * valid for ancestors and descendants of a single Class).
   * Thus it is safe to cache the jmethodID.
   * Same argument is valid for Field/jfieldID.
   */
  return (jmethodID) HVM_ObjectGetNativeState(method);
}

jfieldID
JNIFUNC(FromReflectedField)(JNIEnv *env, jobject field)
{
  return (jfieldID) HVM_ObjectGetNativeState(field);
}

PR_IMPLEMENT(jclass)
sig_to_jclass(HungryEnv *henv, char *sig_str)
{
  JNIEnv *env = henv->thread_env;
  ClazzFile *cf;
  jclass sig_cls = NULL;

  switch (*sig_str)
    {
    case JSIG_BOOLEAN:
      cf = HVM_ClassFindPrimitive(henv, "boolean");
      sig_cls = clazzfile_to_jclass(henv, cf);
      break;
    case JSIG_BYTE:
      cf = HVM_ClassFindPrimitive(henv, "byte");
      sig_cls = clazzfile_to_jclass(henv, cf);
      break;
    case JSIG_CHAR:
      cf = HVM_ClassFindPrimitive(henv, "char");
      sig_cls = clazzfile_to_jclass(henv, cf);
      break;
    case JSIG_SHORT:
      cf = HVM_ClassFindPrimitive(henv, "short");
      sig_cls = clazzfile_to_jclass(henv, cf);
      break;
    case JSIG_INT:
      cf = HVM_ClassFindPrimitive(henv, "int");
      sig_cls = clazzfile_to_jclass(henv, cf);
      break;
    case JSIG_LONG:
      cf = HVM_ClassFindPrimitive(henv, "long");
      sig_cls = clazzfile_to_jclass(henv, cf);
      break;
    case JSIG_FLOAT:
      cf = HVM_ClassFindPrimitive(henv, "float");
      sig_cls = clazzfile_to_jclass(henv, cf);
      break;
    case JSIG_DOUBLE:
      cf = HVM_ClassFindPrimitive(henv, "double");
      sig_cls = clazzfile_to_jclass(henv, cf);
      break;
    case JSIG_VOID:
      cf = HVM_ClassFindPrimitive(henv, "void");
      sig_cls = clazzfile_to_jclass(henv, cf);
      break;
    case JSIG_ARRAY:
      {
        char *typ;
        char *p;
        typ = PL_strdup(sig_str);
        
        p = typ;
        while (*p != '[')
          p++;

        if (*p == 'L')
          {
            while (*p != ';')
              p++;
            p++; /* skip the colon */
            *p = 0;
          }
        else
          {
            p++; /* skip over the interesting char */
            *p = 0;
          }

        sig_cls = (*env)->FindClass(env, typ);

        break;
      }
    case JSIG_OBJECT:
      {
        char *java_sig;
        char *semi;

        semi = PL_strchr(sig_str, ';');
        java_sig = PR_MALLOC(semi - sig_str);
        PL_strncpy(java_sig, sig_str + 1, semi - sig_str - 1);
        java_sig[semi - sig_str - 1] = 0;
        
        sig_cls = (*env)->FindClass(env, java_sig);
        
        PR_DELETE(java_sig);
        break;
      }
    default:
      (*env)->FatalError(env, "Illegal primitive type.");
      break;
    }
  
  return sig_cls;
}

static jclass class_class = NULL;
static jclass method_class = NULL;
static jclass ctor_class = NULL;
static jmethodID method_ctor = NULL;
static jmethodID ctor_ctor = NULL;
static jboolean method_ctor_args = JNI_FALSE;
static jboolean ctor_ctor_args = JNI_FALSE;

static jfieldID method_clazz_field = NULL;
static jfieldID method_slot_field = NULL;
static jfieldID method_name_field = NULL;
static jfieldID method_returnType_field = NULL;
static jfieldID method_parameterTypes_field = NULL;
static jfieldID method_exceptionTypes_field = NULL;

static jfieldID constructor_clazz_field = NULL;
static jfieldID constructor_slot_field = NULL;
static jfieldID constructor_parameterTypes_field = NULL;
static jfieldID constructor_exceptionTypes_field = NULL;

static void
init_reflect_ids(JNIEnv *env)
{
  class_class = (*env)->FindClass(env, java_lang_Class);

  method_class = (*env)->FindClass(env,
                                   java_lang_reflect_Method);
  method_ctor = (*env)->GetMethodID(env, method_class,
                                    "<init>", "()V");
  /* classpath doesn't have this constructor.  so, we find its counterpart. */
  if (!method_ctor)
    {
      (*env)->ExceptionClear(env);
      method_ctor = (*env)->GetMethodID(env, method_class,
                                        "<init>", "(Ljava/lang/Class;Ljava/lang/String;I)V");
      method_ctor_args = JNI_TRUE;
    }

  if (!method_ctor_args)
    {
      method_clazz_field = (*env)->GetFieldID(env, method_class,
                                              "clazz", "Ljava/lang/Class;");
      method_slot_field = (*env)->GetFieldID(env, method_class,
                                             "slot", "I");
      method_name_field = (*env)->GetFieldID(env, method_class,
                                             "name", "Ljava/lang/String;");
      method_returnType_field = (*env)->GetFieldID(env, method_class,
                                                   "returnType", "Ljava/lang/Class;");
      method_parameterTypes_field = (*env)->GetFieldID(env, method_class,
                                                       "parameterTypes", "[Ljava/lang/Class;");
      method_exceptionTypes_field = (*env)->GetFieldID(env, method_class,
                                                       "exceptionTypes", "[Ljava/lang/Class;");
    }

  ctor_class = (*env)->FindClass(env,
                                 java_lang_reflect_Constructor);

  ctor_ctor = (*env)->GetMethodID(env, ctor_class,
                                  "<init>", "()V");

  constructor_clazz_field = (*env)->GetFieldID(env, ctor_class,
                                               "clazz", "Ljava/lang/Class;");
  constructor_slot_field = (*env)->GetFieldID(env, ctor_class,
                                              "slot", "I");
  constructor_parameterTypes_field = (*env)->GetFieldID(env, ctor_class,
                                                        "parameterTypes", "[Ljava/lang/Class;");
  constructor_exceptionTypes_field = (*env)->GetFieldID(env, ctor_class,
                                                        "exceptionTypes", "[Ljava/lang/Class;");
}

static jobject
toReflectedMethod(JNIEnv *env, jclass cls, jmethodID method)
{
  HungryEnv *henv = JNIEnvToHEnv(env);
  jint slot;
  jobject rmethod;
  jstring mname;
  ClazzFile *clazz = method->clazz;

  mname = (*env)->NewStringUTF(env, method->name);

  if (!class_class)
    init_reflect_ids(env);

  /* Determine method's slot inside class */
  for (slot = 0; slot < clazz->num_methods; ++slot) 
    {
      if (&clazz->methods[slot] == method) 
        {
          break;
        }
    }

  if (method_ctor_args)
    {
      rmethod = (*env)->NewObject(env, method_class, method_ctor,
                                  clazzfile_to_jclass(henv, clazz),
                                  mname, slot);
    }
  else
    {
      HVMSignature *sig = HVM_SigParseFromJavaSig(henv, method->sig_str);
      jobjectArray exception_array;
      jobjectArray param_array;
      jclass returntype;
      jint num_exceptions = method->num_throwable_exceptions;
      jint p;
      jint num_params = sig->method.num_params;

      returntype = sig_to_jclass(henv,
                                 sig->method.return_type);

      rmethod = (*env)->NewObject(env, method_class, method_ctor);
      
      /* Create the declared exceptions array */
      exception_array = (*env)->NewObjectArray(env, num_exceptions,
                                               class_class, NULL);
      for (p = 0; p < num_exceptions; p ++)
        {
          jobject exc;
          ClazzFile *exc_type;
          
          exc_type = getThrowableException(henv, method, p);
          
          exc = clazzfile_to_jclass(henv, exc_type);
          
          (*env)->SetObjectArrayElement(env, exception_array, p, exc);
        }
      
      /* Create the parameter types array */
      param_array = (*env)->NewObjectArray(env, num_params,
                                           class_class, NULL);
      for (p = 0; p < num_params; p ++)
        {
          jclass param;
          
          param = sig_to_jclass(henv, sig->method.params[p]);
          
          (*env)->SetObjectArrayElement(env, param_array, p, param);
        }

      (*env)->SetObjectField(env, rmethod, method_name_field, mname);
      (*env)->SetIntField(env, rmethod, method_slot_field, slot);

      (*env)->SetObjectField(env, rmethod, method_clazz_field,
                             clazzfile_to_jclass(henv, clazz));
      (*env)->SetObjectField(env, rmethod, method_exceptionTypes_field,
                             exception_array);
      (*env)->SetObjectField(env, rmethod, method_parameterTypes_field,
                             param_array);
      (*env)->SetObjectField(env, rmethod, method_returnType_field,
                             returntype);
      HVM_SigFree(henv, sig);
    }

  HVM_ObjectSetNativeState(rmethod, method);

  return rmethod;
}

static jobject
toReflectedCtor(JNIEnv *env, jclass cls, jmethodID method)
{
  jint slot;
  jobject rmethod;
  ClazzFile *clazz = method->clazz;
  HungryEnv *henv = JNIEnvToHEnv(env);
  HVMSignature *sig = HVM_SigParseFromJavaSig(henv, method->sig_str);
  jobjectArray exception_array;
  jobjectArray param_array;
  jint num_exceptions = method->num_throwable_exceptions;
  jint p;
  jint num_params = sig->method.num_params;
  
  if (!class_class)
    init_reflect_ids(env);

  /* Determine method's slot inside class */
  for (slot = 0; slot < clazz->num_methods; ++slot) 
    {
      if (&clazz->methods[slot] == method) 
        {
          break;
        }
    }
  
  rmethod = (*env)->NewObject(env, ctor_class, ctor_ctor);
  
  /* Create the declared exceptions array */
  exception_array = (*env)->NewObjectArray(env, num_exceptions,
                                           class_class, NULL);
  for (p = 0; p < num_exceptions; p ++)
    {
      jobject exc;
      ClazzFile *exc_type;
      
      exc_type = getThrowableException(henv, method, p);
      
      exc = clazzfile_to_jclass(henv, exc_type);
      
      (*env)->SetObjectArrayElement(env, exception_array, p, exc);
    }
  
  /* Create the parameter types array */
  param_array = (*env)->NewObjectArray(env, num_params,
                                       class_class, NULL);
  for (p = 0; p < num_params; p ++)
    {
      jclass param;
      
      param = sig_to_jclass(henv, sig->method.params[p]);
      
      (*env)->SetObjectArrayElement(env, param_array, p, param);
    }
  
  (*env)->SetIntField(env, rmethod, constructor_slot_field, slot);
  
  (*env)->SetObjectField(env, rmethod, constructor_clazz_field,
                         clazzfile_to_jclass(henv, clazz));
  (*env)->SetObjectField(env, rmethod, constructor_exceptionTypes_field,
                         exception_array);
  (*env)->SetObjectField(env, rmethod, constructor_parameterTypes_field,
                         param_array);
  HVM_SigFree(henv, sig);

  HVM_ObjectSetNativeState(rmethod, method);

  return rmethod;
}

jobject
JNIFUNC(ToReflectedMethod)(JNIEnv *env, jclass cls, jmethodID method)
{
  if (!PL_strcmp(method->name, "<init>"))
    return toReflectedCtor(env, cls, method);
  else
    return toReflectedMethod(env, cls, method);
}

jobject
JNIFUNC(ToReflectedField)(JNIEnv *env, jclass cls, jfieldID field)
{
  HungryEnv *henv = JNIEnvToHEnv(env);
  static jclass field_class = NULL;
  static jfieldID clazz_field = NULL;
  static jfieldID slot_field = NULL;
  static jfieldID name_field = NULL;
  static jfieldID type_field = NULL;
  static jmethodID field_ctor = NULL;
  int slot;
  ClazzFile *clazz = field->clazz;
  jobject rfield;
  jobject type;
  jstring name;

  if (!field_class)
    {
      field_class = (*env)->FindClass(env,
                                      java_lang_reflect_Field);
      clazz_field = (*env)->GetFieldID(env, field_class,
                                       "clazz", "Ljava/lang/Class;");
      slot_field = (*env)->GetFieldID(env, field_class,
                                      "slot", "I");
      name_field = (*env)->GetFieldID(env, field_class,
                                      "name", "Ljava/lang/String;");
      type_field = (*env)->GetFieldID(env, field_class,
                                      "type", "Ljava/lang/Class;");
      field_ctor = (*env)->GetMethodID(env, field_class,
                                       "<init>", "()V");
    }

  /* Determine field's slot inside class */
  for (slot = 0; slot < clazz->num_fields; ++slot) 
    {
      if (clazz->fields[slot] == field) 
        {
          break;
        }
    }
  
  rfield = (*env)->NewObject(env, field_class, field_ctor);
  name = (*env)->NewStringUTF(env, field->name);

  type = sig_to_jclass(henv, field->sig_str);

  name = (*env)->NewGlobalRef(env, name);
  (*env)->SetObjectField(env, rfield, name_field, name);
  (*env)->SetIntField(env, rfield, slot_field, slot);
  (*env)->SetObjectField(env, rfield, clazz_field,
                         clazzfile_to_jclass(henv, clazz));
  (*env)->SetObjectField(env, rfield, type_field, type);

  HVM_ObjectSetNativeState(rfield, field);

  return (*env)->NewGlobalRef(env, rfield);
}
