/* -*- Mode: C; c-file-style: "gnu" -*-
   objects.c -- runtime code for allocating objects.
   Created: Chris Toshok <toshok@hungry.com>, 10-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 "ClazzFile.h"
#include "class-repository.h"
#include "resolve.h"
#include "log.h"
#include "sig.h"
#include "objects.h"
#include "arrays.h"
#include "method.h"
#include "native-threads.h"
#include "exceptions.h" /* for throw_Exception() */
#include "alloc.h"
#include "method.h"

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

#define MYLOG "ObjectRef"

static int
size_of_type(int sig_type)
{
  switch (sig_type)
    {
    case SIG_JBOOLEAN:
      return sizeof(jboolean);
    case SIG_JBYTE:
      return sizeof(jbyte);
    case SIG_JCHAR:
      return sizeof(jchar);
    case SIG_JSHORT:
      return sizeof(jshort);
    case SIG_JINT:
      return sizeof(jint);
    case SIG_JLONG:
      return sizeof(jlong);
    case SIG_JFLOAT:
      return sizeof(jfloat);
    case SIG_JDOUBLE:
      return sizeof(jdouble);
    case SIG_JOBJECT:
      return sizeof(jobject);
    case VALUE_UNRESOLVED:
      fprintf(stderr, "Unable to determine size of type UNRESOLVED\n");
    default:
      assert(0);
      return -1;
    }
}

static int
alignment_of_type(int sig_type)
{
#ifdef __GNUC__
#define RETURN_ALIGNMENTOF(type) return __alignof__(type)
#else

  /* This should be optimized away by the compiler. [pere] */
#define RETURN_ALIGNMENTOF(type) \
    do { \
      struct alignment_struct_ ## type \
         { char c; type p; } *t = NULL; \
      if ((char*) &(t->c) != (char*)t) \
        {\
          assert(NULL == "Bad alignment"); \
          return -1; \
        } \
      return (int)&(t->p); \
    } while (0)
#endif

  switch (sig_type)
    {
    case SIG_JBOOLEAN:
      RETURN_ALIGNMENTOF(jboolean);
    case SIG_JBYTE:
      RETURN_ALIGNMENTOF(jbyte);
    case SIG_JCHAR:
      RETURN_ALIGNMENTOF(jchar);
    case SIG_JSHORT:
      RETURN_ALIGNMENTOF(jshort);
    case SIG_JINT:
      RETURN_ALIGNMENTOF(jint);
    case SIG_JLONG:
      RETURN_ALIGNMENTOF(jlong);
    case SIG_JFLOAT:
      RETURN_ALIGNMENTOF(jfloat);
    case SIG_JDOUBLE:
      RETURN_ALIGNMENTOF(jdouble);
    case SIG_JOBJECT:
      RETURN_ALIGNMENTOF(jobject);
    case VALUE_UNRESOLVED:
      fprintf(stderr, "Unable to determine size of type UNRESOLVED\n");
    default:
      assert(0);
      return -1;
    }
#undef RETURN_ALIGNMENTOF
}

static size_t
set_field_offset(size_t current_offset, FieldStruct *field)
{
  int field_size = 0;

  assert(NULL != field);
  assert(0 <= current_offset);

  if (field->field_offset != VALUE_UNRESOLVED)
    {
      field_size = size_of_type(field->java_type);
    }
  else
    {
      int field_alignment = 0;

      field_size = size_of_type(field->java_type);
      field_alignment = alignment_of_type(field->java_type);

      /*
       * Calculate first address after current_offset where address %
       * field_aligment is 0.  */
      /* XXX Hm, is there a easier way to calculate this? [pere] */
      field->field_offset = /* Require integer division to be correct */
	(current_offset / field_alignment) * field_alignment +
	(0 == (current_offset % field_alignment) ? 0 : field_alignment);
    }
  return field->field_offset + field_size;
}

void
calculate_instance_field_offsets(JNIEnv *env, ClazzFile *cf)
{
  /* XXX need to handle alignment for platforms that are not strictly
     byte addressable */
  int i;
  int current_offset;
  ClazzFile *parent_class = getSuperClass(env, cf);

  if (parent_class == NULL)
    current_offset = 0;
  else
    current_offset = parent_class->size_of_instance_field_block;

  for (i = 0; i < cf->num_fields; i ++)
    {
      FieldStruct *field = cf->fields[i];

      if (field->access_flags & ACC_STATIC)
	continue;

      if (field->clazz != cf)
	break;

      current_offset = set_field_offset(current_offset, field);
    }

  cf->size_of_instance_field_block = current_offset;
}

static void
calculate_static_field_offsets(JNIEnv *env, ClazzFile *cf)
{
  /* XXX need to handle alignment for platforms that are not strictly
     byte addressable */
  int i;
  int current_offset = 0;

  for (i = 0; i < cf->num_fields; i ++)
    {
      FieldStruct *field = cf->fields[i];

      if (!(field->access_flags & ACC_STATIC))
	continue;

      if (field->clazz != cf)
	break;

      current_offset = set_field_offset(current_offset, field);
    }
}

/* See objects.h to understand how this works. */
static size_t
object_size_without_fields(ClazzFile *class)
{
  /*
   * Make sure to keep this syncronized with
   * interloop.c:{set|get}_instance_field()
   */
  assert(NULL != class);

  return (sizeof(japhar_object)
	  + sizeof(ClazzFile*) * class->nesting_level);
}

japhar_obj
clone_object(JNIEnv *env, japhar_obj old_object)
{
  japhar_object *jobj, *new_jobj;
  japhar_obj new_obj;

  obj_to_object(old_object, jobj);

  new_obj = new_object(env, jobj->_static);

  if (new_obj == NULL)
    return NULL;

  obj_to_object(new_obj, new_jobj);

  /* Copy fields */
  memcpy(((char*)new_jobj + object_size_without_fields(new_jobj->_static)),
	 ((char*)jobj + object_size_without_fields(jobj->_static)),
	 jobj->_static->size_of_instance_field_block);

  return new_obj;
}

static japhar_obj
new_object_no_init(JNIEnv *env, ClazzFile *cf)
{
  int current_static_ptr;
  ClazzFile *current_class = cf;
  japhar_object *new_obj;
  int size_of_object;
#ifdef DEBUG
  /* points to the first field, just after the last ClazzFile * */
  void *field_start;
#endif
  
  size_of_object = (object_size_without_fields(current_class)
		    /* for all the instance fields */
		    + current_class->size_of_instance_field_block);
    
  new_obj = (japhar_object*)JGC_allocObject((JavaVM*)((HungryJNIEnv*)env)->_vm,
					    size_of_object);

  if (new_obj == NULL)
    return NULL;

#ifdef DEBUG
  field_start = (char*)new_obj + object_size_without_fields(current_class);
#endif

  /*
  ** now we step up through the inheritance tree and set the _static
  ** pointers to the right values.  Note that new_obj->_post is NULL
  ** and should remain so -- it's our one post.
  */
  current_static_ptr = 0;
  while (current_class != NULL)
    {
      ClazzFile **ptr = &(new_obj->_static) + current_static_ptr;
#ifdef DEBUG
      assert((void*)ptr < field_start);
#endif
      *ptr = current_class;

      current_static_ptr ++;

      if (current_class->superclass_index == 0)
	current_class = NULL;
      else
	current_class = getSuperClass(env, current_class);
    }

  /* initialize the hashcode for the object, and store it in the japhar_object*
     so we can return it again later in java.lang.Object.hashCode. */
  {
    jvalue foo;

    foo.l = new_obj;
    new_obj->hash_code = foo.i;
  }

  JAVARLOG1(MYLOG, 1, "leaving new_object_no_init(), returning %p\n",
	    (&new_obj->_static));

  return &new_obj->_static;
}

japhar_obj
new_object(JNIEnv *env, ClazzFile *cf)
{
#ifdef LOGGING
  static int num_objects = 0;
#endif

  JAVARLOG2(MYLOG, 1, "in new_object(%s) #%d\n",
	    getClassName(env, cf), ++num_objects);

  /* first we initialize the class. */
  initialize_class(env, cf);
  if ((*env)->ExceptionOccurred(env))
    {
      return NULL;
    }

  return new_object_no_init(env, cf);
}

ClazzFile*
jclass_to_clazzfile(JNIEnv *env, jclass clazz)
{
  ClazzFile *result = (ClazzFile*) NSA_GetNativeState(clazz);

  assert(result != NULL);

  return result;
}

japhar_obj 
clazzfile_to_jclass(JNIEnv *env, ClazzFile *cf)
{
  japhar_obj new_obj;
  static ClazzFile *class_cf = NULL;
  japhar_obj cached_descriptor;

#ifdef LOGGING
  static int num_objects = 0;
#endif

  if (class_cf == NULL)
    class_cf = find_class(env, "java/lang/Class");

  JAVARLOG2(MYLOG, 1, "in clazzfile_to_jclass(cf=%s, %d)\n",
	    getClassName(env,cf), ++num_objects);

  cached_descriptor = cf->jcls;

  if (cached_descriptor)
    return cached_descriptor;
  else
    {
      new_obj = new_object_no_init(env, class_cf);
      assert(NULL != new_obj);

      NSA_SetNativeState(new_obj, cf);

      cf->jcls = new_obj;

      return new_obj;
    }
}

void
object_finalize(japhar_object *obj)
{
  JNIEnv *env = THREAD_getEnv();
  jmethodID finalize;
  
  JAVARLOG1(MYLOG, 2, "Finalizing object %p\n", obj);

  finalize = find_method(env,
			 &obj->_static,
			 "finalize", "()V");

  if (finalize)
    {
      (*env)->CallVoidMethod(env, obj, finalize);
      if ((*env)->ExceptionOccurred(env))
	{
	  /* XXX do something... */
	  return;
	}
    }

  JAVARLOG0(MYLOG, 3, "   freeing memory.\n");

  /* free(obj); */
}

static jboolean
create_static_fields(JNIEnv *env, ClazzFile *cf)
{
  if (cf->num_static_fields == 0)
    {
      cf->static_fields = NULL;
      return JNI_TRUE;
    }
  else
    {
      cf->static_fields = (void*)jcalloc(env, cf->num_static_fields,
					 sizeof(JavaStackItem));
      return (NULL != cf->static_fields) ? JNI_TRUE : JNI_FALSE;
    }
}
static inline jboolean
class_is_array(JNIEnv *env, ClazzFile *cf)
{
  return (NULL != cf && (cf->access_flags & ACC_ARRAY));
}

static inline jboolean
class_is_primitive(JNIEnv *env, ClazzFile *cf)
{
  return (NULL != cf && (cf->access_flags & ACC_PRIMITIVE));
}

static inline jboolean
class_is_interface(JNIEnv *env, ClazzFile *cf)
{
  return (NULL != cf && (cf->access_flags & ACC_INTERFACE));
}

/* Interface or normal class */
static jboolean
is_instance_of_class(JNIEnv *env, ClazzFile *sub, ClazzFile *super)
{
  JAVARLOG2(MYLOG, 1, "Testing %s instanceof class %s\n",
	    getClassName(env, sub), getClassName(env, super));

  assert(NULL != sub);
  assert(NULL != super);

  while (sub)
    {
      u2 interface;

      if (sub == super)
	return JNI_TRUE;

      for (interface = 0; interface < sub->num_interfaces; interface++)
	if ( is_instance_of_class(env, sub->interfaces[ interface ], super) )
	  return JNI_TRUE;

      if (sub->superclass_index == 0)
	return JNI_FALSE;
      else
	sub = getSuperClass(env, sub);
    }

  return JNI_FALSE;
}

static jboolean
is_instance_of_array(JNIEnv *env, ClazzFile *sub, ClazzFile *super)
{

  char *subname = getClassName(env, sub);
  char *supername = getClassName(env, super);

  JAVARLOG2(MYLOG, 1, "Testing %s instanceof array %s\n", subname, supername);

  /* The easy case */
  if (0 == strcmp(subname, supername))
    return JNI_TRUE;

  /*
   * Skip array dimentions until one of the classes are a
   * non-array type.
   */
  while (class_is_array(env, sub) && class_is_array(env, super)) {
    sub =  array_element_type(env, sub);
    super =  array_element_type(env, super);
  }

  assert(NULL != sub);
  assert(NULL != super);

  if (class_is_primitive(env, super)) /* Must match exactly */
    return (sub == super);

  /* The object element class can not be cast to an array */
  if (class_is_array(env, super))
    return JNI_FALSE;

  /* Arrays can only be cast to java.lang.Object */
  if (class_is_array(env, sub))
    return (super == find_class(env, "java/lang/Object"));

  /* Hm, is this the correct test? */
  return is_instance_of_class(env, sub, super);
}

jboolean
is_instance_of(JNIEnv *env, japhar_obj obj, ClazzFile *cf)
{
  assert(NULL != cf);
  assert(NULL != env);
  assert(NULL != obj);

  JAVARLOG2(MYLOG, 1, "Testing %s instanceof %s\n",
	    getClassName(env, *obj), getClassName(env, cf));

  if (class_is_array(env, cf))
    return is_instance_of_array(env, *obj, cf);
  else
    return is_instance_of_class(env, *obj, cf);
}

jboolean
is_assignable_from(JNIEnv *env,
		   ClazzFile *clazz1,
		   ClazzFile *clazz2)
{
  int i;

  if (class_is_array(env, clazz1))
    return is_instance_of_array(env, clazz1, clazz2);
  else
    return is_instance_of_class(env, clazz1, clazz2);
}

/* this is sort of a cross-bred function... it might belong here,
   or it might belong in the same file as get_method_info.  The
   only reason I put it here is because it uses the heap representation
   to do its job. */
MethodStruct *
get_interface_method_info(JNIEnv *env,
			  japhar_obj obj,
			  ClazzFile **cf,
			  char *method_name,
			  char *sig_str)
{
  MethodStruct *method = GetMethodByNameAndSig(env,
					       *obj,
					       method_name, sig_str);
    
  if (method)
    {
      *cf = method->clazz;
      return method;
    }
  else
    {
      throw_Exception(THREAD_getEnv(), "java/lang/RuntimeException", NULL);
      return NULL;
    }
}

/* the guts of the following functions come from
   "The Java Language Specification" by Gosling et. al */

static void
call_initializers(JNIEnv *env, ClazzFile *cf)
{
  jmethodID clinit;
  jclass descriptor;
  ClazzFile *superclass = getSuperClass(env, cf);

  if (superclass)
    call_initializers(env, superclass);

  /* call our static initializers */
  if (cf->access_flags & HAS_CLINIT)
    {
      cf->access_flags &= ~HAS_CLINIT;

      descriptor = clazzfile_to_jclass(env, cf);
      clinit = (*env)->GetStaticMethodID(env, descriptor,
					 "<clinit>", "()V");

      /* if the method isn't defined, clear the exception. */
      if ((*env)->ExceptionOccurred(env))
	{
	  /* XXX there are some we should be legitimately concerned with,
	     like OutOfMemoryError, etc. */
	  (*env)->ExceptionClear(env);
	}

      if (clinit)
	(*env)->CallStaticVoidMethod(env, descriptor, clinit);
    }
}


static void
initialize_class_recurse(JNIEnv *env,
			 ClazzFile *cf,
			 ClazzFile *subclass)
{
  HMonitor clazz_monitor;
  japhar_obj descriptor;

  if (cf == NULL)
    return;

  if (!cf->monitor)
    cf->monitor = MONITOR_create();

  /* create the descriptor and store it in the repository,
     if it's not already there. */
  descriptor = clazzfile_to_jclass(env, cf);

  clazz_monitor = cf->monitor;

  MONITOR_enter(clazz_monitor);

 try_and_initialize:
  switch (cf->initialization_state)
    {
    case CLASS_INIT_FAILED:
      MONITOR_exit(clazz_monitor);
      throw_Exception(env, "java/lang/NoClassDefFoundError", getClassName(env, cf));
      return;
    case CLASS_INIT_FINISHED:
      MONITOR_exit(clazz_monitor);
      return;
    case CLASS_INIT_IN_PROGRESS:
      if (cf->initializing_thread == THREAD_getCurrent())
	{
	  /* we're in the process of initializing it already,
	     so just return. */
	  MONITOR_exit(clazz_monitor);
	  return;
	}
      else
	{
	  /* someone else is initializing it, so wait until they're
	     done and try again. */
	  MONITOR_wait(clazz_monitor);
	  goto try_and_initialize;
	}
    case CLASS_NOT_INITIALIZED:
      {
	cf->initialization_state = CLASS_INIT_IN_PROGRESS;
	cf->initializing_thread = THREAD_getCurrent();

	calculate_static_field_offsets(env, cf);
	if ( ! create_static_fields(env, cf) )
	  /*
	   * Hm, this is probably out of memory error.
	   * Let someone else try again later. [pere]
	   */
	  {
	    cf->initialization_state = CLASS_NOT_INITIALIZED;
	    cf->initializing_thread = NULL;

	    MONITOR_notifyAll(clazz_monitor);
	    MONITOR_exit(clazz_monitor);
	    return;
	  }

	MONITOR_notifyAll(clazz_monitor);
	MONITOR_exit(clazz_monitor);

	if (getSuperClass(env, cf))
	  {
	    initialize_class_recurse(env, getSuperClass(env, cf), subclass);

	    cf->nesting_level = getSuperClass(env, cf)->nesting_level + 1;
	  }
	else
	  cf->nesting_level = 0;

	if ((*env)->ExceptionOccurred(env))
	  {
	    MONITOR_enter(clazz_monitor);
	    cf->initialization_state = CLASS_INIT_FAILED;
	    cf->initializing_thread = NULL;

	    MONITOR_notifyAll(clazz_monitor);

	    MONITOR_exit(clazz_monitor);
	    /* don't clear the exception */
	    return;
	  }

	calculate_instance_field_offsets(env, cf);

	/* we need to only do this next block after we've recursively
	   initialized all the superclasses */
	if (cf == subclass)
	  {
	    call_initializers(env, subclass);
	  }
	
	/* the logic here isn't quite right... We're supposed to throw
	   an ExceptionInInitializerError.... */
	if ((*env)->ExceptionOccurred(env))
	  {
	    MONITOR_enter(clazz_monitor);
	    cf->initialization_state = CLASS_INIT_FAILED;
	    cf->initializing_thread = NULL;
	    
	    MONITOR_notifyAll(clazz_monitor);
	    
	    MONITOR_exit(clazz_monitor);

	    /* don't clear the exception */
	    return;
	  }
	
	/* if we've made it here, things worked. */
	MONITOR_enter(clazz_monitor);
	
	cf->initialization_state = CLASS_INIT_FINISHED;
	cf->initializing_thread = NULL;

	MONITOR_notifyAll(clazz_monitor);
	MONITOR_exit(clazz_monitor);
	return;
      }
    }
}

void
initialize_class(JNIEnv *env, ClazzFile *cf)
{
  /* we race here, but it doesn't matter -- if we get a false negative
     no tremendous loss. */
  if (cf->initialization_state == CLASS_INIT_FINISHED)
    return;
  initialize_class_recurse(env, cf, cf);

  MONITOR_enter(cf->monitor);
  call_initializers(env, cf);
  MONITOR_exit(cf->monitor);
}

japhar_obj
cast_obj(japhar_obj object_ref,
	 ClazzFile *cf)
{
  if (object_ref == NULL)
    {
      throw_Exception(THREAD_getEnv(), "java/lang/NullPointerException", NULL);
      return NULL;
    }

#if 0
  printf ("Casting obj %p (%s) to %s\n",
	  object_ref, getClassName(THREAD_getEnv(), *object_ref),
	  getClassName(THREAD_getEnv(), cf));
#endif

  /* shortcut */
  if ((*object_ref)->nesting_level == cf->nesting_level)
    {
      if (*object_ref == cf) 
	return object_ref;
      else
	return NULL;
    }

  /*
  ** if the object we're casting's class is higher in the inheritance
  ** tree than cf, find the most deeply nested class.
  ** this should only happen in the case where we're downcasting.
  */
  if ((*object_ref)->nesting_level < cf->nesting_level)
    object_ref -= (*object_ref)->nesting_level;

  /*
  ** if at this point the nesting level of the class is still greater
  ** than our object's class, we know the cast can't work. 
  */
  if ((*object_ref)->nesting_level < cf->nesting_level)
    return NULL;
  else
    {
      japhar_obj prospective_obj;

      prospective_obj = object_ref + (*object_ref)->nesting_level - cf->nesting_level;

      if (*prospective_obj == cf)
	return prospective_obj;
      else
	return NULL;
    }
}

void*
NSA_GetNativeState(japhar_obj object_ref)
{
  japhar_object *jobj;

  obj_to_object(object_ref, jobj);

  return jobj->native_state;
}

void
NSA_SetNativeState(japhar_obj object_ref,
                   void *native_state)
{
  japhar_object *jobj;

  obj_to_object(object_ref, jobj);

  jobj->native_state = native_state;
}

/* necessary for JDK1.2 */
jboolean
obj_is_reference(JNIEnv *env,
		 japhar_obj obj)
{
  ClazzFile* ref_class = NULL;
  int checked = 0;

  if (ref_class == NULL && checked == 0)
    {
      ref_class = find_class(env, "java/lang/ref/Reference");
      checked = 1;
    }

  if (!ref_class)
    return JNI_FALSE;

  return is_instance_of(env, obj, ref_class);
}

/* necessary for JDK1.2 */
japhar_obj
get_obj_from_reference(JNIEnv *env,
		       japhar_obj obj)
{
  jclass ref_class = (*env)->GetObjectClass(env, obj);
  jmethodID get;

  get = (*env)->GetMethodID(env, ref_class, "get", "()Ljava/lang/Object;");

  return (*env)->CallObjectMethod(env, obj, get);
}
