/*
 * %W% %E%
 * 
 * Copyright (c) 2006, Oracle and/or its affiliates. All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 
 * -Redistribution of source code must retain the above copyright notice, this
 *  list of conditions and the following disclaimer.
 * 
 * -Redistribution in binary form must reproduce the above copyright notice, 
 *  this list of conditions and the following disclaimer in the documentation
 *  and/or other materials provided with the distribution.
 * 
 * Neither the name of Oracle or the names of contributors may 
 * be used to endorse or promote products derived from this software without 
 * specific prior written permission.
 * 
 * This software is provided "AS IS," without a warranty of any kind. ALL 
 * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
 * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
 * OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN")
 * AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE
 * AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
 * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST 
 * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, 
 * INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY 
 * OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, 
 * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
 * 
 * You acknowledge that this software is not designed, licensed or intended
 * for use in the design, construction, operation or maintenance of any
 * nuclear facility.
 */

/* Table of class information. 
 *
 *   Each element in this table is identified with a ClassIndex.
 *   Each element is uniquely identified by it's signature and loader.
 *   Every class load has a unique class serial number.
 *   While loaded, each element will have a cache of a global reference
 *     to it's jclass object, plus jmethodID's as needed.
 *   Method signatures and names are obtained via BCI.
 *   Methods can be identified with a ClassIndex and MethodIndex pair,
 *     where the MethodIndex matches the index of the method name and
 *     signature arrays obtained from the BCI pass.
 *   Strings are stored in the string table and a StringIndex is used.
 *   Class Loaders are stored in the loader table and a LoaderIndex is used.
 *   Since the jclass object is an object, at some point an object table
 *      entry may be allocated for the jclass as an ObjectIndex.
 */

#include "hprof.h"

/* Effectively represents a jclass object. */

/* These table elements are made unique by and sorted by signature name. */

typedef struct ClassKey {
    StringIndex    sig_string_index;    /* Signature of class */
    LoaderIndex    loader_index;	/* Index for class loader */
} ClassKey;

/* Each class could contain method information, gotten from BCI callback */

typedef struct MethodInfo {
    StringIndex  name_index;    /* Method name, index into string table */
    StringIndex  sig_index;     /* Method signature, index into string table */
    jmethodID    method_id;	/* Method ID, possibly NULL at first */
} MethodInfo;

/* The basic class information we save */

typedef struct ClassInfo {
    jclass         classref;	        /* Global ref to jclass */
    MethodInfo    *method;		/* Array of method data */
    int            method_count;	/* Count of methods */
    ObjectIndex    object_index;	/* Optional object index for jclass */
    SerialNumber   serial_num; 		/* Unique to the actual class load */
    ClassStatus    status;		/* Current class status (bit mask) */
    ClassIndex     super;		/* Super class in this table */
    StringIndex    name;                /* Name of class */
    jint           inst_size;           /* #bytes needed for instance fields */
    jint           field_count;         /* Number of all fields */
    FieldInfo     *field;		/* Pointer to all FieldInfo's */
} ClassInfo;

/* Private interfaces */

static ClassKey*
get_pkey(ClassIndex index)
{
    void *key_ptr;
    int   key_len;

    table_get_key(gdata->class_table, index, (void*)&key_ptr, &key_len);
    HPROF_ASSERT(key_len==sizeof(ClassKey));
    HPROF_ASSERT(key_ptr!=NULL);
    return (ClassKey*)key_ptr;
}

static void
fillin_pkey(const char *sig, LoaderIndex loader_index, ClassKey *pkey)
{
    static ClassKey empty_key;
    
    HPROF_ASSERT(loader_index!=0);
    *pkey                  = empty_key;
    pkey->sig_string_index = string_find_or_create(sig);
    pkey->loader_index     = loader_index;
}

static ClassInfo *
get_info(ClassIndex index)
{
    ClassInfo *info;

    info = (ClassInfo*)table_get_info(gdata->class_table, index);
    return info;
}

static void
fill_info(TableIndex index, ClassKey *pkey)
{
    ClassInfo *info;
    char      *sig;
    
    info = get_info(index);
    info->serial_num = gdata->class_serial_number_counter++;
    info->method_count = 0;
    info->inst_size = -1;
    info->field_count = -1;
    info->field = NULL;
    sig = string_get(pkey->sig_string_index);
    if ( sig[0] != JVM_SIGNATURE_CLASS ) {
	info->name = pkey->sig_string_index;
    } else {
	int        len;

	len = string_get_len(pkey->sig_string_index);
	if ( len > 2  ) {
	    char      *name;
	    
	    /* Class signature looks like "Lname;", we want "name" here. */
	    name = HPROF_MALLOC(len-1);
	    (void)memcpy(name, sig+1, len-2);
	    name[len-2] = 0;
	    info->name = string_find_or_create(name);
	    HPROF_FREE(name);
	} else {
	    /* This would be strange, a class signature not in "Lname;" form? */
	    info->name = pkey->sig_string_index;
	}
   }
}

static ClassIndex
find_entry(ClassKey *pkey)
{
    ClassIndex index;
    
    index = table_find_entry(gdata->class_table, 
				(void*)pkey, (int)sizeof(ClassKey));
    return index;
}

static ClassIndex
create_entry(ClassKey *pkey)
{
    ClassIndex index;
    
    index = table_create_entry(gdata->class_table, 
				(void*)pkey, (int)sizeof(ClassKey), NULL);
    fill_info(index, pkey);
    return index;
}

static ClassIndex
find_or_create_entry(ClassKey *pkey)
{
    ClassIndex      index;
    
    HPROF_ASSERT(pkey!=NULL);
    HPROF_ASSERT(pkey->loader_index!=0);
    index = find_entry(pkey);
    if ( index == 0 ) {
	index = create_entry(pkey);
    }
    return index;
}

static void
delete_classref(JNIEnv *env, ClassInfo *info, jclass klass)
{
    jclass ref;
    int    i;
    
    HPROF_ASSERT(env!=NULL);
    HPROF_ASSERT(info!=NULL);
    
    for ( i = 0 ; i < info->method_count ; i++ ) {
	info->method[i].method_id  = NULL;
    }
    ref = info->classref;
    if ( klass != NULL ) {
        info->classref = newGlobalReference(env, klass);
    } else {
        info->classref = NULL;
    }
    if ( ref != NULL ) {
        deleteGlobalReference(env, ref);
    }
}

static void
cleanup_item(TableIndex index, void *key_ptr, int key_len, 
				void *info_ptr, void *arg)
{
    ClassInfo *info;

    /* Cleanup any information in this ClassInfo structure. */
    HPROF_ASSERT(key_ptr!=NULL);
    HPROF_ASSERT(key_len==sizeof(ClassKey));
    HPROF_ASSERT(info_ptr!=NULL);
    info = (ClassInfo *)info_ptr;
    if ( info->method_count > 0 ) {
        HPROF_FREE((void*)info->method);
	info->method_count = 0;
        info->method       = NULL;
    }
    if ( info->field != NULL ) {
        HPROF_FREE((void*)info->field);
	info->field_count = 0;
        info->field      = NULL;
    }
}

static void
delete_ref_item(TableIndex index, void *key_ptr, int key_len, 
				void *info_ptr, void *arg)
{
    delete_classref((JNIEnv*)arg, (ClassInfo*)info_ptr, NULL);
}

static void
list_item(TableIndex index, void *key_ptr, int key_len, 
				void *info_ptr, void *arg)
{
    ClassInfo *info;
    ClassKey   key;
    char      *sig;
    int        i;

    HPROF_ASSERT(key_ptr!=NULL);
    HPROF_ASSERT(key_len==sizeof(ClassKey));
    HPROF_ASSERT(info_ptr!=NULL);
    key = *((ClassKey*)key_ptr);
    sig = string_get(key.sig_string_index);
    info = (ClassInfo *)info_ptr;
    debug_message( 
	     "0x%08x: Class %s, SN=%u, status=0x%08x, ref=%p,"
	     " method_count=%d\n",
             index, 
             (const char *)sig, 
	     info->serial_num,
             info->status, 
	     (void*)info->classref,
	     info->method_count);
    if ( info->method_count > 0 ) {
	for ( i = 0 ; i < info->method_count ; i++ ) {
            debug_message( 
		"    Method %d: \"%s\", sig=\"%s\", method=%p\n",
		i,
		string_get(info->method[i].name_index),
		string_get(info->method[i].sig_index),
		(void*)info->method[i].method_id);
	}
    }
}

static void
all_status_remove(TableIndex index, void *key_ptr, int key_len, 
				void *info_ptr, void *arg)
{
    ClassInfo   *info;
    ClassStatus  status;

    HPROF_ASSERT(info_ptr!=NULL);
    /*LINTED*/
    status = (ClassStatus)(long)(ptrdiff_t)arg;
    info = (ClassInfo *)info_ptr;
    info->status &= (~status);
}

static void
unload_walker(TableIndex index, void *key_ptr, int key_len, 
				void *info_ptr, void *arg)
{
    ClassInfo        *info;

    HPROF_ASSERT(info_ptr!=NULL);
    info = (ClassInfo *)info_ptr;
    if ( ! ( info->status & CLASS_IN_LOAD_LIST ) ) {
	if ( ! (info->status & (CLASS_SPECIAL|CLASS_SYSTEM|CLASS_UNLOADED)) ) {
            io_write_class_unload(info->serial_num, info->object_index);
            info->status |= CLASS_UNLOADED;
            delete_classref((JNIEnv*)arg, info, NULL);
	}
    }
}

/* External interfaces */

void
class_init(void)
{
    HPROF_ASSERT(gdata->class_table==NULL);
    gdata->class_table = table_initialize("Class", 512, 512, 511,
                                    (int)sizeof(ClassInfo));
}

ClassIndex
class_find_or_create(const char *sig, LoaderIndex loader_index)
{
    ClassKey key;
    
    fillin_pkey(sig, loader_index, &key);
    return find_or_create_entry(&key);
}

ClassIndex
class_create(const char *sig, LoaderIndex loader_index)
{
    ClassKey key;
    
    fillin_pkey(sig, loader_index, &key);
    return create_entry(&key);
}

void
class_prime_system_classes(void)
{
    /* Prime System classes? Anything before VM_START is System class.
     *   Or classes loaded before env arg is non-NULL.
     *   Or any of the classes listed below.
     */
    static const char * signatures[] =      
        {
            "Ljava/lang/Object;",
            "Ljava/io/Serializable;",
            "Ljava/lang/String;",
            "Ljava/lang/Class;",
            "Ljava/lang/ClassLoader;",
            "Ljava/lang/System;",
            "Ljava/lang/Thread;",
            "Ljava/lang/ThreadGroup;",
        };
    int n_signatures;
    int i;
    LoaderIndex loader_index;
   
    n_signatures = (int)sizeof(signatures)/(int)sizeof(signatures[0]);
    loader_index = loader_find_or_create(NULL, NULL);
    for ( i = 0 ; i < n_signatures ; i++ ) {
        ClassInfo  *info;
        ClassIndex  index;
	ClassKey    key;
	
	fillin_pkey(signatures[i], loader_index, &key);
        index = find_or_create_entry(&key);
        info = get_info(index);
        info->status |= CLASS_SYSTEM;
    }
}

void
class_add_status(ClassIndex index, ClassStatus status)
{
    ClassInfo *info;

    info = get_info(index);
    info->status |= status;
}

ClassStatus
class_get_status(ClassIndex index)
{
    ClassInfo *info;

    info = get_info(index);
    return info->status;
}

StringIndex
class_get_signature(ClassIndex index)
{
    ClassKey *pkey;

    pkey = get_pkey(index);
    return pkey->sig_string_index;
}

SerialNumber
class_get_serial_number(ClassIndex index)
{
    ClassInfo *info;

    if ( index == 0 ) {
	return 0;
    }
    info = get_info(index);
    return info->serial_num;
}

void
class_all_status_remove(ClassStatus status)
{
    table_walk_items(gdata->class_table, &all_status_remove, 
		(void*)(ptrdiff_t)(long)status);
}

void
class_do_unloads(JNIEnv *env)
{
    table_walk_items(gdata->class_table, &unload_walker, (void*)env);
}

void
class_list(void)
{
    debug_message( 
        "--------------------- Class Table ------------------------\n");
    table_walk_items(gdata->class_table, &list_item, NULL);
    debug_message( 
        "----------------------------------------------------------\n");
}

void
class_cleanup(void)
{
    table_cleanup(gdata->class_table, &cleanup_item, NULL);
    gdata->class_table = NULL;
}

void
class_delete_global_references(JNIEnv* env)
{
    table_walk_items(gdata->class_table, &delete_ref_item, (void*)env);
}

void
class_set_methods(ClassIndex index, const char **name, const char **sig, 
			int count)
{
    ClassInfo *info;
    int        i;

    info               = get_info(index);
    if ( info->method_count > 0 ) {
        HPROF_FREE((void*)info->method);
	info->method_count = 0;
        info->method       = NULL;
    }
    info->method_count = count;
    if ( count > 0 ) {
	info->method = (MethodInfo *)HPROF_MALLOC(count*(int)sizeof(MethodInfo));
	for ( i = 0 ; i < count ; i++ ) {
	    info->method[i].name_index = string_find_or_create(name[i]); 
	    info->method[i].sig_index  = string_find_or_create(sig[i]);
	    info->method[i].method_id  = NULL;
	}
    }
}

jclass
class_new_classref(JNIEnv *env, ClassIndex index, jclass classref)
{
    ClassInfo *info;
    
    HPROF_ASSERT(classref!=NULL);
    info = get_info(index);
    if ( ! isSameObject(env, classref, info->classref) ) {
        delete_classref(env, info, classref);
    }
    return info->classref;
}

jclass
class_get_class(JNIEnv *env, ClassIndex index)
{
    ClassInfo *info;
    jclass     clazz;
    
    info 	= get_info(index);
    clazz 	= info->classref;
    if ( env != NULL && clazz == NULL ) {
	WITH_LOCAL_REFS(env, 1) {
	    jclass   new_clazz;
	    char    *class_name;
	    
	    class_name = string_get(info->name);
	    /* This really only makes sense for the bootclass classes, 
	     *   since FindClass doesn't provide a way to load a class in
	     *   a specific class loader.
	     */
	    new_clazz = findClass(env, class_name);
	    if ( new_clazz == NULL ) {
		HPROF_ERROR(JNI_TRUE, "Cannot load class with findClass");
	    }
	    HPROF_ASSERT(new_clazz!=NULL);
	    clazz = class_new_classref(env, index, new_clazz);
	} END_WITH_LOCAL_REFS;
        HPROF_ASSERT(clazz!=NULL);
    }
    return clazz;
}

jmethodID
class_get_methodID(JNIEnv *env, ClassIndex index, MethodIndex mnum)
{
    ClassInfo *info;
    jmethodID  method;
    
    info = get_info(index);
    HPROF_ASSERT(mnum < info->method_count);
    method = info->method[mnum].method_id;
    if ( method == NULL ) {
        char * name;
        char * sig;
        jclass clazz;
        
        name  = (char *)string_get(info->method[mnum].name_index);
        HPROF_ASSERT(name!=NULL);
        sig   = (char *)string_get(info->method[mnum].sig_index);
        HPROF_ASSERT(sig!=NULL);
        clazz = class_get_class(env, index);
	if ( clazz != NULL ) {
	    method = getMethodID(env, clazz, name, sig);
	    HPROF_ASSERT(method!=NULL);
	    info = get_info(index);
	    info->method[mnum].method_id = method;
	}
    }
    return method;
}

void
class_set_inst_size(ClassIndex index, jint inst_size)
{
    ClassInfo *info;
    
    info = get_info(index);
    info->inst_size = inst_size;
}

jint
class_get_inst_size(ClassIndex index)
{
    ClassInfo *info;
    
    info = get_info(index);
    return info->inst_size;
}

void
class_set_object_index(ClassIndex index, ObjectIndex object_index)
{
    ClassInfo *info;
    
    info = get_info(index);
    info->object_index = object_index;
}

ObjectIndex
class_get_object_index(ClassIndex index)
{
    ClassInfo *info;
    
    info = get_info(index);
    return info->object_index;
}

ClassIndex	
class_get_super(ClassIndex index)
{
    ClassInfo *info;
    
    info = get_info(index);
    return info->super;
}

void	
class_set_super(ClassIndex index, ClassIndex super)
{
    ClassInfo *info;
    
    info = get_info(index);
    info->super = super;
}

LoaderIndex     
class_get_loader(ClassIndex index)
{
    ClassKey *pkey;

    pkey = get_pkey(index);
    HPROF_ASSERT(pkey->loader_index!=0);
    return pkey->loader_index;
}

/* Get ALL class fields (supers too), return 1 on error, 0 if ok */
jint
class_get_all_fields(JNIEnv *env, ClassIndex index,
                jint *pfield_count, FieldInfo **pfield)
{
    ClassInfo  *info;
    FieldInfo  *finfo;
    jint        count;
    jint        ret;
    
    count = 0;
    finfo = NULL;
    ret   = 1;       /* Default is to return an error condition */
    
    info = get_info(index);
    if ( info != NULL ) {
        if ( info->field_count >= 0 ) {
	    /* Get cache */
	    count = info->field_count;
	    finfo = info->field;
	    ret   = 0;                 /* Return of cache data, no error */
        } else {
	    jclass     klass;
    	
	    klass = info->classref;
	    if ( klass == NULL || isSameObject(env, klass, NULL) ) {
	        /* This is probably an error because this will cause the field
	         *    index values to be off, but I'm hesitant to generate a
	         *    fatal error here, so I will issue something and continue.
	         *    I should have been holding a global reference to all the
	         *    jclass, so I'm not sure how this could happen.
		 *    Issuing a FindClass() here is just asking for trouble
		 *    because if the class went away, we aren't even sure
		 *    what ClassLoader to use.
	         */
	        HPROF_ERROR(JNI_FALSE, "Missing jclass when fields needed");
	    } else {
		jint status;

		status = getClassStatus(klass);
		if ( status & 
		    (JVMTI_CLASS_STATUS_PRIMITIVE|JVMTI_CLASS_STATUS_ARRAY) ) {
		    /* Set cache */
		    info->field_count = count;
		    info->field       = finfo;
		    ret               = 0;      /* Primitive or array ok */
		} else if ( status & JVMTI_CLASS_STATUS_PREPARED ) {
		    /* Call JVMTI to get them */
		    getAllClassFieldInfo(env, klass, &count, &finfo);
		    /* Set cache */
		    info->field_count = count;
		    info->field       = finfo;
		    ret               = 0;
		}
	    }
        }
    }
    *pfield_count = count;
    *pfield       = finfo;
    return ret;
}

