/*
 * Copyright (c) 1998, 2016, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code 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 General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

#include "util.h"
#include "ReferenceTypeImpl.h"
#include "inStream.h"
#include "outStream.h"


static jboolean
signature(PacketInputStream *in, PacketOutputStream *out)
{
    char *signature = NULL;
    jclass clazz;
    jvmtiError error;

    clazz = inStream_readClassRef(getEnv(), in);
    if (inStream_error(in)) {
        return JNI_TRUE;
    }

    error = classSignature(clazz, &signature, NULL);
    if (error != JVMTI_ERROR_NONE) {
        outStream_setError(out, map2jdwpError(error));
        return JNI_TRUE;
    }

    (void)outStream_writeString(out, signature);
    jvmtiDeallocate(signature);

    return JNI_TRUE;
}

static jboolean
signatureWithGeneric(PacketInputStream *in, PacketOutputStream *out)
{
  /* Returns both the signature and the generic signature */
    char *signature = NULL;
    char *genericSignature = NULL;
    jclass clazz;
    jvmtiError error;

    clazz = inStream_readClassRef(getEnv(), in);
    if (inStream_error(in)) {
        return JNI_TRUE;
    }
    error = classSignature(clazz, &signature, &genericSignature);
    if (error != JVMTI_ERROR_NONE) {
        outStream_setError(out, map2jdwpError(error));
        return JNI_TRUE;
    }

    (void)outStream_writeString(out, signature);
    writeGenericSignature(out, genericSignature);
    jvmtiDeallocate(signature);
    if (genericSignature != NULL) {
      jvmtiDeallocate(genericSignature);
    }


    return JNI_TRUE;
}

static jboolean
getClassLoader(PacketInputStream *in, PacketOutputStream *out)
{
    jclass clazz;
    jobject loader;
    jvmtiError error;
    JNIEnv *env;

    env = getEnv();

    clazz = inStream_readClassRef(env, in);
    if (inStream_error(in)) {
        return JNI_TRUE;
    }

    error = classLoader(clazz, &loader);
    if (error != JVMTI_ERROR_NONE) {
        outStream_setError(out, map2jdwpError(error));
        return JNI_TRUE;
    }

    (void)outStream_writeObjectRef(env, out, loader);
    return JNI_TRUE;
}

static jboolean
getModule(PacketInputStream *in, PacketOutputStream *out)
{
    jobject clazz;
    jobject module;
    JNIEnv *env;

    env = getEnv();

    clazz = inStream_readClassRef(env, in);
    if (inStream_error(in)) {
        return JNI_TRUE;
    }

    module = JNI_FUNC_PTR(env, GetModule)(env, clazz);

    (void)outStream_writeModuleRef(env, out, module);
    return JNI_TRUE;
}

static jboolean
modifiers(PacketInputStream *in, PacketOutputStream *out)
{
    jint modifiers;
    jclass clazz;
    jvmtiError error;

    clazz = inStream_readClassRef(getEnv(), in);
    if (inStream_error(in)) {
        return JNI_TRUE;
    }

    error = JVMTI_FUNC_PTR(gdata->jvmti,GetClassModifiers)
                (gdata->jvmti, clazz, &modifiers);
    if (error != JVMTI_ERROR_NONE) {
        outStream_setError(out, map2jdwpError(error));
        return JNI_TRUE;
    }

    (void)outStream_writeInt(out, modifiers);

    return JNI_TRUE;
}

static void
writeMethodInfo(PacketOutputStream *out, jclass clazz, jmethodID method,
                int outputGenerics)
{
    char *name = NULL;
    char *signature = NULL;
    char *genericSignature = NULL;
    jint modifiers;
    jvmtiError error;
    jboolean isSynthetic;

    error = isMethodSynthetic(method, &isSynthetic);
    if (error != JVMTI_ERROR_NONE) {
        outStream_setError(out, map2jdwpError(error));
        return;
    }

    error = methodModifiers(method, &modifiers);
    if (error != JVMTI_ERROR_NONE) {
        outStream_setError(out, map2jdwpError(error));
        return;
    }

    error = methodSignature(method, &name, &signature, &genericSignature);
    if (error != JVMTI_ERROR_NONE) {
        outStream_setError(out, map2jdwpError(error));
        return;
    }

    if (isSynthetic) {
        modifiers |= MOD_SYNTHETIC;
    }
    (void)outStream_writeMethodID(out, method);
    (void)outStream_writeString(out, name);
    (void)outStream_writeString(out, signature);
    if (outputGenerics == 1) {
        writeGenericSignature(out, genericSignature);
    }
    (void)outStream_writeInt(out, modifiers);
    jvmtiDeallocate(name);
    jvmtiDeallocate(signature);
    if (genericSignature != NULL) {
      jvmtiDeallocate(genericSignature);
    }
}

static jboolean
methods1(PacketInputStream *in, PacketOutputStream *out,
         int outputGenerics)
{
    int i;
    jclass clazz;
    jint methodCount = 0;
    jmethodID *methods = NULL;
    jvmtiError error;

    clazz = inStream_readClassRef(getEnv(), in);
    if (inStream_error(in)) {
        return JNI_TRUE;
    }

    error = JVMTI_FUNC_PTR(gdata->jvmti,GetClassMethods)
                (gdata->jvmti, clazz, &methodCount, &methods);
    if (error != JVMTI_ERROR_NONE) {
        outStream_setError(out, map2jdwpError(error));
        return JNI_TRUE;
    }

    (void)outStream_writeInt(out, methodCount);
    for (i = 0; (i < methodCount) && !outStream_error(out); i++) {
        writeMethodInfo(out, clazz, methods[i], outputGenerics);
    }

    /* Free methods array */
    if ( methods != NULL ) {
        jvmtiDeallocate(methods);
    }
    return JNI_TRUE;
}

static jboolean
methods(PacketInputStream *in, PacketOutputStream *out,
         int outputGenerics)
{
    return methods1(in, out, 0);
}

static jboolean
methodsWithGeneric(PacketInputStream *in, PacketOutputStream *out)
{
    return methods1(in, out, 1);
}



static jboolean
instances(PacketInputStream *in, PacketOutputStream *out)
{
    jint maxInstances;
    jclass clazz;
    JNIEnv *env;

    if (gdata->vmDead) {
        outStream_setError(out, JDWP_ERROR(VM_DEAD));
        return JNI_TRUE;
    }

    env = getEnv();
    clazz = inStream_readClassRef(env, in);
    maxInstances = inStream_readInt(in);
    if (inStream_error(in)) {
        return JNI_TRUE;
    }

    WITH_LOCAL_REFS(env, 1) {
        jvmtiError   error;
        ObjectBatch  batch;

        error = classInstances(clazz, &batch, maxInstances);
        if (error != JVMTI_ERROR_NONE) {
            outStream_setError(out, map2jdwpError(error));
        } else {
            int kk;
            jbyte typeKey;

            (void)outStream_writeInt(out, batch.count);
            if (batch.count > 0) {
                /*
                 * They are all instances of this class and will all have
                 * the same typeKey, so just compute it once.
                 */
                typeKey = specificTypeKey(env, batch.objects[0]);

                for (kk = 0; kk < batch.count; kk++) {
                  jobject inst;

                  inst = batch.objects[kk];
                  (void)outStream_writeByte(out, typeKey);
                  (void)outStream_writeObjectRef(env, out, inst);
                }
            }
            jvmtiDeallocate(batch.objects);
        }
    } END_WITH_LOCAL_REFS(env);

    return JNI_TRUE;
}

static jboolean
getClassVersion(PacketInputStream *in, PacketOutputStream *out)
{
    jclass clazz;
    jvmtiError error;
    jint majorVersion;
    jint minorVersion;

    clazz = inStream_readClassRef(getEnv(), in);
    if (inStream_error(in)) {
        return JNI_TRUE;
    }

    error = JVMTI_FUNC_PTR(gdata->jvmti, GetClassVersionNumbers)
                (gdata->jvmti, clazz, &minorVersion, &majorVersion);
    if (error != JVMTI_ERROR_NONE) {
        outStream_setError(out, map2jdwpError(error));
        return JNI_TRUE;
    }

    (void)outStream_writeInt(out, majorVersion);
    (void)outStream_writeInt(out, minorVersion);

    return JNI_TRUE;
}

static jboolean
getConstantPool(PacketInputStream *in, PacketOutputStream *out)
{

    jclass clazz;
    jvmtiError error;
    jint cpCount;
    jint cpByteCount;
    unsigned char* cpBytesPtr;


    clazz = inStream_readClassRef(getEnv(), in);
    if (inStream_error(in)) {
        return JNI_TRUE;
    }

    /* Initialize assuming no bytecodes and no error */
    error         = JVMTI_ERROR_NONE;
    cpCount       = 0;
    cpByteCount   = 0;
    cpBytesPtr    = NULL;


    error = JVMTI_FUNC_PTR(gdata->jvmti,GetConstantPool)
                (gdata->jvmti, clazz, &cpCount, &cpByteCount, &cpBytesPtr);
    if (error != JVMTI_ERROR_NONE) {
        outStream_setError(out, map2jdwpError(error));
    } else {
        (void)outStream_writeInt(out, cpCount);
        (void)outStream_writeByteArray(out, cpByteCount, (jbyte *)cpBytesPtr);
        jvmtiDeallocate(cpBytesPtr);
    }

    return JNI_TRUE;
}

static void
writeFieldInfo(PacketOutputStream *out, jclass clazz, jfieldID fieldID,
               int outputGenerics)
{
    char *name;
    char *signature = NULL;
    char *genericSignature = NULL;
    jint modifiers;
    jboolean isSynthetic;
    jvmtiError error;

    error = isFieldSynthetic(clazz, fieldID, &isSynthetic);
    if (error != JVMTI_ERROR_NONE) {
        outStream_setError(out, map2jdwpError(error));
        return;
    }

    error = fieldModifiers(clazz, fieldID, &modifiers);
    if (error != JVMTI_ERROR_NONE) {
        outStream_setError(out, map2jdwpError(error));
        return;
    }

    error = fieldSignature(clazz, fieldID, &name, &signature, &genericSignature);
    if (error != JVMTI_ERROR_NONE) {
        outStream_setError(out, map2jdwpError(error));
        return;
    }
    if (isSynthetic) {
        modifiers |= MOD_SYNTHETIC;
    }
    (void)outStream_writeFieldID(out, fieldID);
    (void)outStream_writeString(out, name);
    (void)outStream_writeString(out, signature);
    if (outputGenerics == 1) {
        writeGenericSignature(out, genericSignature);
    }
    (void)outStream_writeInt(out, modifiers);
    jvmtiDeallocate(name);
    jvmtiDeallocate(signature);
    if (genericSignature != NULL) {
      jvmtiDeallocate(genericSignature);
    }
}

static jboolean
fields1(PacketInputStream *in, PacketOutputStream *out, int outputGenerics)
{
    int i;
    jclass clazz;
    jint fieldCount = 0;
    jfieldID *fields = NULL;
    jvmtiError error;

    clazz = inStream_readClassRef(getEnv(), in);
    if (inStream_error(in)) {
        return JNI_TRUE;
    }

    error = JVMTI_FUNC_PTR(gdata->jvmti,GetClassFields)
                (gdata->jvmti, clazz, &fieldCount, &fields);
    if (error != JVMTI_ERROR_NONE) {
        outStream_setError(out, map2jdwpError(error));
        return JNI_TRUE;
    }

    (void)outStream_writeInt(out, fieldCount);
    for (i = 0; (i < fieldCount) && !outStream_error(out); i++) {
        writeFieldInfo(out, clazz, fields[i], outputGenerics);
    }

    /* Free fields array */
    if ( fields != NULL ) {
        jvmtiDeallocate(fields);
    }
    return JNI_TRUE;
}


static jboolean
fields(PacketInputStream *in, PacketOutputStream *out)
{
    return fields1(in, out, 0);
}

static jboolean
fieldsWithGeneric(PacketInputStream *in, PacketOutputStream *out)
{
    return fields1(in, out, 1);

}

static jboolean
getValues(PacketInputStream *in, PacketOutputStream *out)
{
    sharedGetFieldValues(in, out, JNI_TRUE);
    return JNI_TRUE;
}

static jboolean
sourceFile(PacketInputStream *in, PacketOutputStream *out)
{
    char *fileName;
    jvmtiError error;
    jclass clazz;

    clazz = inStream_readClassRef(getEnv(), in);
    if (inStream_error(in)) {
        return JNI_TRUE;
    }

    error = JVMTI_FUNC_PTR(gdata->jvmti,GetSourceFileName)
                (gdata->jvmti, clazz, &fileName);
    if (error != JVMTI_ERROR_NONE) {
        outStream_setError(out, map2jdwpError(error));
        return JNI_TRUE;
    }

    (void)outStream_writeString(out, fileName);
    jvmtiDeallocate(fileName);
    return JNI_TRUE;
}

static jboolean
sourceDebugExtension(PacketInputStream *in, PacketOutputStream *out)
{
    char *extension;
    jvmtiError error;
    jclass clazz;

    clazz = inStream_readClassRef(getEnv(), in);
    if (inStream_error(in)) {
        return JNI_TRUE;
    }

    error = getSourceDebugExtension(clazz, &extension);
    if (error != JVMTI_ERROR_NONE) {
        outStream_setError(out, map2jdwpError(error));
        return JNI_TRUE;
    }

    (void)outStream_writeString(out, extension);
    jvmtiDeallocate(extension);
    return JNI_TRUE;
}

static jboolean
nestedTypes(PacketInputStream *in, PacketOutputStream *out)
{
    JNIEnv *env;
    jclass clazz;

    env = getEnv();

    clazz = inStream_readClassRef(env, in);
    if (inStream_error(in)) {
        return JNI_TRUE;
    }

    WITH_LOCAL_REFS(env, 1) {

        jvmtiError error;
        jint count;
        jclass *nested;

        error = allNestedClasses(clazz, &nested, &count);
        if (error != JVMTI_ERROR_NONE) {
            outStream_setError(out, map2jdwpError(error));
        } else {
            int i;
            (void)outStream_writeInt(out, count);
            for (i = 0; i < count; i++) {
                (void)outStream_writeByte(out, referenceTypeTag(nested[i]));
                (void)outStream_writeObjectRef(env, out, nested[i]);
            }
            if ( nested != NULL ) {
                jvmtiDeallocate(nested);
            }
        }

    } END_WITH_LOCAL_REFS(env);

    return JNI_TRUE;
}

static jboolean
getClassStatus(PacketInputStream *in, PacketOutputStream *out)
{
    jint status;
    jclass clazz;

    clazz = inStream_readClassRef(getEnv(), in);
    if (inStream_error(in)) {
        return JNI_TRUE;
    }

    status = classStatus(clazz);
    (void)outStream_writeInt(out, map2jdwpClassStatus(status));
    return JNI_TRUE;
}

static jboolean
interfaces(PacketInputStream *in, PacketOutputStream *out)
{
    JNIEnv *env;
    jclass clazz;

    env = getEnv();

    clazz = inStream_readClassRef(env, in);
    if (inStream_error(in)) {
        return JNI_TRUE;
    }

    WITH_LOCAL_REFS(env, 1) {

        jvmtiError error;
        jint interfaceCount;
        jclass *interfaces;

        error = allInterfaces(clazz, &interfaces, &interfaceCount);
        if (error != JVMTI_ERROR_NONE) {
            outStream_setError(out, map2jdwpError(error));
        } else {
            int i;

            (void)outStream_writeInt(out, interfaceCount);
            for (i = 0; i < interfaceCount; i++) {
                (void)outStream_writeObjectRef(env, out, interfaces[i]);
            }
            if ( interfaces != NULL ) {
                jvmtiDeallocate(interfaces);
            }
        }

    } END_WITH_LOCAL_REFS(env);

    return JNI_TRUE;
}

static jboolean
classObject(PacketInputStream *in, PacketOutputStream *out)
{
    jclass clazz;
    JNIEnv *env;

    env = getEnv();
    clazz = inStream_readClassRef(env, in);
    if (inStream_error(in)) {
        return JNI_TRUE;
    }

    /*
     * In our implementation, the reference type id is the same as the
     * class object id, so we bounce it right back.
     *
     */

    (void)outStream_writeObjectRef(env, out, clazz);

    return JNI_TRUE;
}

void *ReferenceType_Cmds[] = { (void *)19
    ,(void *)signature
    ,(void *)getClassLoader
    ,(void *)modifiers
    ,(void *)fields
    ,(void *)methods
    ,(void *)getValues
    ,(void *)sourceFile
    ,(void *)nestedTypes
    ,(void *)getClassStatus
    ,(void *)interfaces
    ,(void *)classObject
    ,(void *)sourceDebugExtension
    ,(void *)signatureWithGeneric
    ,(void *)fieldsWithGeneric
    ,(void *)methodsWithGeneric
    ,(void *)instances
    ,(void *)getClassVersion
    ,(void *)getConstantPool
    ,(void *)getModule
};
