/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 *
 * ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Mozilla Communicator client code, released
 * March 31, 1998.
 *
 * The Initial Developer of the Original Code is
 * Netscape Communications Corporation.
 * Portions created by the Initial Developer are Copyright (C) 1998
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */
 
/*
 * This file is part of the Java-vendor-neutral implementation of LiveConnect
 *
 * It contains the code used to reflect Java fields as properties of
 * JavaObject objects and the code to access those fields.
 *
 */

#include <stdlib.h>

#include "jsj_private.h"      /* LiveConnect internals */

/*
 * Add a single field, described by java_field, to the JavaMemberDescriptor
 * named by field_name within the given JavaClassDescriptor.
 *
 * Returns JS_TRUE on success. Otherwise, returns JS_FALSE and reports an error.
 */
static JSBool
add_java_field_to_class_descriptor(JSContext *cx,
                                   JNIEnv *jEnv,
                                   JavaClassDescriptor *class_descriptor, 
                                   jstring field_name_jstr,
                                   jobject java_field,  /* a java.lang.reflect.Field */
                                   jint modifiers)
{
    jclass fieldType;
    jfieldID fieldID;
    jclass java_class;

    JSBool is_static_field;
    JavaMemberDescriptor *member_descriptor = NULL;
    const char *sig_cstr = NULL;
    const char *field_name = NULL;
    JavaSignature *signature = NULL;
    JavaFieldSpec *field_spec = NULL;
        
    is_static_field = modifiers & ACC_STATIC;
    if (is_static_field) {
        member_descriptor = jsj_GetJavaStaticMemberDescriptor(cx, jEnv, class_descriptor, field_name_jstr);
    } else {
        member_descriptor = jsj_GetJavaMemberDescriptor(cx, jEnv, class_descriptor, field_name_jstr);
    }
    if (!member_descriptor)
        goto error;
    
    field_spec = (JavaFieldSpec*)JS_malloc(cx, sizeof(JavaFieldSpec));
    if (!field_spec)
        goto error;

    field_spec->modifiers = modifiers;

    /* Get the Java class corresponding to the type of the field */
    fieldType = (*jEnv)->CallObjectMethod(jEnv, java_field, jlrField_getType);
    if (!fieldType) {
        jsj_UnexpectedJavaError(cx, jEnv,
                                "Unable to determine type of field using"
                                " java.lang.reflect.Field.getType()");
        goto error;
    }
    
    signature = jsj_GetJavaClassDescriptor(cx, jEnv, fieldType);
    (*jEnv)->DeleteLocalRef(jEnv, fieldType);
    if (!signature)
        goto error;
    field_spec->signature = signature;

    field_name = jsj_DupJavaStringUTF(cx, jEnv, field_name_jstr);
    if (!field_name)
        goto error;
    field_spec->name = field_name;

    /* Compute the JNI-style (string-based) signature of the field type */
    sig_cstr = jsj_ConvertJavaSignatureToString(cx, signature);
    if (!sig_cstr)
        goto error;

    /* Compute the JNI fieldID and cache it for quick field access */
    java_class = class_descriptor->java_class;
    if (is_static_field)
        fieldID = (*jEnv)->GetStaticFieldID(jEnv, java_class, field_name, sig_cstr);
    else
        fieldID = (*jEnv)->GetFieldID(jEnv, java_class, field_name, sig_cstr);
    if (!fieldID) {
       jsj_UnexpectedJavaError(cx, jEnv,
                           "Can't get Java field ID for class %s, field %s (sig=%s)",
                           class_descriptor->name, field_name, sig_cstr);
       goto error;
    }
    field_spec->fieldID = fieldID;
    
    JS_free(cx, (char*)sig_cstr);
    
    member_descriptor->field = field_spec;

    /* Success */
    return JS_TRUE;

error:
    if (field_spec) {
        JS_FREE_IF(cx, (char*)field_spec->name);
        JS_free(cx, field_spec);
    }
    JS_FREE_IF(cx, (char*)sig_cstr);
    if (signature)
        jsj_ReleaseJavaClassDescriptor(cx, jEnv, signature);
    return JS_FALSE;
}

/*
 * Free up a JavaFieldSpec and all its resources.
 */
void
jsj_DestroyFieldSpec(JSContext *cx, JNIEnv *jEnv, JavaFieldSpec *field)
{
    JS_FREE_IF(cx, (char*)field->name);
    jsj_ReleaseJavaClassDescriptor(cx, jEnv, field->signature);
    JS_free(cx, field);
}

/*
 * Add a JavaMemberDescriptor to the collection of members in class_descriptor
 * for every public field of the identified Java class.  (A separate collection
 * is kept in class_descriptor for static and instance members.)
 * If reflect_only_static_fields is set, instance fields are not reflected.  If
 * it isn't set, only instance fields are reflected and static fields are not
 * reflected.
 *
 * Returns JS_TRUE on success. Otherwise, returns JS_FALSE and reports an error.
 */
JSBool 
jsj_ReflectJavaFields(JSContext *cx, JNIEnv *jEnv, JavaClassDescriptor *class_descriptor,
                      JSBool reflect_only_static_fields)
{
    int i;
    JSBool ok;
    jint modifiers;
    jobject java_field;
    jstring field_name_jstr;
    jarray joFieldArray;
    jsize num_fields;
    jclass java_class;

    /* Get a java array of java.lang.reflect.Field objects, by calling
       java.lang.Class.getFields(). */
    java_class = class_descriptor->java_class;
    joFieldArray = (*jEnv)->CallObjectMethod(jEnv, java_class, jlClass_getFields);
    if (!joFieldArray) {
        jsj_UnexpectedJavaError(cx, jEnv,
                                "Can't determine Java object's fields "
                                "using java.lang.Class.getFields()");
        return JS_FALSE;
    }

    /* Iterate over the class fields */
    num_fields = (*jEnv)->GetArrayLength(jEnv, joFieldArray);
    for (i = 0; i < num_fields; i++) {
       
        /* Get the i'th reflected field */
        java_field = (*jEnv)->GetObjectArrayElement(jEnv, joFieldArray, i);
        if (!java_field) {
            jsj_UnexpectedJavaError(cx, jEnv, "Can't access a Field[] array");
            return JS_FALSE;
        }

        /* Get the field modifiers, e.g. static, public, private, etc. */
        modifiers = (*jEnv)->CallIntMethod(jEnv, java_field, jlrField_getModifiers);
        if ((*jEnv)->ExceptionOccurred(jEnv)) {
            jsj_UnexpectedJavaError(cx, jEnv,
                                    "Can't access a Field's modifiers using"
                                    "java.lang.reflect.Field.getModifiers()");
            return JS_FALSE;
        }

        /* Don't allow access to private or protected Java fields. */
        if (!(modifiers & ACC_PUBLIC))
            goto no_reflect;

        /* Reflect all instance fields or all static fields, but not both */
        if (reflect_only_static_fields != ((modifiers & ACC_STATIC) != 0))
            goto no_reflect;

        /* Determine the unqualified name of the field */
        field_name_jstr = (*jEnv)->CallObjectMethod(jEnv, java_field, jlrField_getName);
        if (!field_name_jstr) {
            jsj_UnexpectedJavaError(cx, jEnv,
                                    "Can't obtain a Field's name"
                                    "java.lang.reflect.Field.getName()");
            return JS_FALSE;
        }
        
        /* Add a JavaFieldSpec object to the JavaClassDescriptor */
        ok = add_java_field_to_class_descriptor(cx, jEnv, class_descriptor, field_name_jstr,
                                                java_field, modifiers);
        if (!ok)
            return JS_FALSE;

        (*jEnv)->DeleteLocalRef(jEnv, field_name_jstr);
        field_name_jstr = NULL;

no_reflect:
        (*jEnv)->DeleteLocalRef(jEnv, java_field);
        java_field = NULL;
    }

    (*jEnv)->DeleteLocalRef(jEnv, joFieldArray);

    /* Success */
    return JS_TRUE;
}

/*
 * Read the value of a Java field and return it as a JavaScript value.
 * If the field is static, then java_obj is a Java class, otherwise
 * it's a Java instance object.
 *
 * Returns JS_TRUE on success. Otherwise, returns JS_FALSE and reports an error.
 */
JSBool
jsj_GetJavaFieldValue(JSContext *cx, JNIEnv *jEnv, JavaFieldSpec *field_spec,
                      jobject java_obj, jsval *vp)
{
    JSBool is_static_field, success;
    jvalue java_value;
    JavaSignature *signature;
    JavaSignatureChar field_type;
    jfieldID fieldID = field_spec->fieldID;

    is_static_field = field_spec->modifiers & ACC_STATIC;

#define GET_JAVA_FIELD(Type,member)                                          \
    JS_BEGIN_MACRO                                                           \
    if (is_static_field)                                                     \
        java_value.member =                                                  \
            (*jEnv)->GetStatic##Type##Field(jEnv, (*jEnv)->GetObjectClass(jEnv, java_obj), fieldID);        \
    else                                                                     \
        java_value.member =                                                  \
            (*jEnv)->Get##Type##Field(jEnv, java_obj, fieldID);              \
    if ((*jEnv)->ExceptionOccurred(jEnv)) {                                  \
        jsj_UnexpectedJavaError(cx, jEnv, "Error reading Java field");           \
        return JS_FALSE;                                                     \
    }                                                                        \
    JS_END_MACRO

    signature = field_spec->signature;
    field_type = signature->type;
    switch(field_type) {
    case JAVA_SIGNATURE_BYTE:
        GET_JAVA_FIELD(Byte,b);
        break;

    case JAVA_SIGNATURE_CHAR:
        GET_JAVA_FIELD(Char,c);
        break;

    case JAVA_SIGNATURE_SHORT:
        GET_JAVA_FIELD(Short,s);
        break;

    case JAVA_SIGNATURE_INT:
        GET_JAVA_FIELD(Int,i);
        break;

    case JAVA_SIGNATURE_BOOLEAN:
        GET_JAVA_FIELD(Boolean,z);
        break;

    case JAVA_SIGNATURE_LONG:
        GET_JAVA_FIELD(Long,j);
        break;
  
    case JAVA_SIGNATURE_FLOAT:
        GET_JAVA_FIELD(Float,f);
        break;

    case JAVA_SIGNATURE_DOUBLE:
        GET_JAVA_FIELD(Double,d);
        break;
     
    case JAVA_SIGNATURE_UNKNOWN:
    case JAVA_SIGNATURE_VOID:
        JS_ASSERT(0);        /* Unknown java type signature */
        return JS_FALSE;
        
    /* Non-primitive (reference) type */
    default:
        JS_ASSERT(IS_REFERENCE_TYPE(field_type));
        GET_JAVA_FIELD(Object,l);
        success = jsj_ConvertJavaObjectToJSValue(cx, jEnv, java_value.l, vp);
        (*jEnv)->DeleteLocalRef(jEnv, java_value.l);
        return success;
    }
    
#undef GET_JAVA_FIELD

    return jsj_ConvertJavaValueToJSValue(cx, jEnv, signature, &java_value, vp);
}

JSBool
jsj_SetJavaFieldValue(JSContext *cx, JNIEnv *jEnv, JavaFieldSpec *field_spec,
                      jclass java_obj, jsval js_val)
{
    JSBool is_static_field, is_local_ref;
    int dummy_cost;
    jvalue java_value;
    JavaSignature *signature;
    JavaSignatureChar field_type;
    jfieldID fieldID = field_spec->fieldID;

    is_static_field = field_spec->modifiers & ACC_STATIC;

#define SET_JAVA_FIELD(Type,member)                                          \
    JS_BEGIN_MACRO                                                           \
    if (is_static_field) {                                                   \
        (*jEnv)->SetStatic##Type##Field(jEnv, java_obj, fieldID,             \
                                        java_value.member);                  \
    } else {                                                                 \
        (*jEnv)->Set##Type##Field(jEnv, java_obj, fieldID,java_value.member);\
    }                                                                        \
    if ((*jEnv)->ExceptionOccurred(jEnv)) {                                  \
        jsj_UnexpectedJavaError(cx, jEnv, "Error assigning to Java field");      \
        return JS_FALSE;                                                     \
    }                                                                        \
    JS_END_MACRO

    signature = field_spec->signature;
    if (!jsj_ConvertJSValueToJavaValue(cx, jEnv, js_val, signature, &dummy_cost,
                                       &java_value, &is_local_ref))
        return JS_FALSE;

    field_type = signature->type;
    switch(field_type) {
    case JAVA_SIGNATURE_BYTE:
        SET_JAVA_FIELD(Byte,b);
        break;

    case JAVA_SIGNATURE_CHAR:
        SET_JAVA_FIELD(Char,c);
        break;

    case JAVA_SIGNATURE_SHORT:
        SET_JAVA_FIELD(Short,s);
        break;

    case JAVA_SIGNATURE_INT:
        SET_JAVA_FIELD(Int,i);
        break;

    case JAVA_SIGNATURE_BOOLEAN:
        SET_JAVA_FIELD(Boolean,z);
        break;

    case JAVA_SIGNATURE_LONG:
        SET_JAVA_FIELD(Long,j);
        break;
  
    case JAVA_SIGNATURE_FLOAT:
        SET_JAVA_FIELD(Float,f);
        break;

    case JAVA_SIGNATURE_DOUBLE:
        SET_JAVA_FIELD(Double,d);
        break;
        
    /* Non-primitive (reference) type */
    default:
        JS_ASSERT(IS_REFERENCE_TYPE(field_type));
        SET_JAVA_FIELD(Object,l);
        if (is_local_ref)
            (*jEnv)->DeleteLocalRef(jEnv, java_value.l);
        break;

    case JAVA_SIGNATURE_UNKNOWN:
    case JAVA_SIGNATURE_VOID:
        JS_ASSERT(0);        /* Unknown java type signature */
        return JS_FALSE;
    }

#undef SET_JAVA_FIELD
    
    return JS_TRUE;
}
