/*
 * Copyright 2007-2008 Sun Microsystems, Inc.  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.  Sun designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
 * CA 95054 USA or visit www.sun.com if you need additional information or
 * have any questions.
 */

package com.sun.tools.javap;

import com.sun.tools.classfile.AccessFlags;
import com.sun.tools.classfile.Code_attribute;
import com.sun.tools.classfile.ConstantPool;
import com.sun.tools.classfile.ConstantPoolException;
import com.sun.tools.classfile.DescriptorException;
import com.sun.tools.classfile.Method;

import static com.sun.tools.classfile.OpCodes.*;

/*
 *  Write the contents of a Code attribute.
 *
 *  <p><b>This is NOT part of any API supported by Sun Microsystems.  If
 *  you write code that depends on this, you do so at your own risk.
 *  This code and its internal interfaces are subject to change or
 *  deletion without notice.</b>
 */
class CodeWriter extends BasicWriter {
    static CodeWriter instance(Context context) {
        CodeWriter instance = context.get(CodeWriter.class);
        if (instance == null)
            instance = new CodeWriter(context);
        return instance;
    }

    protected CodeWriter(Context context) {
        super(context);
        context.put(CodeWriter.class, this);
        attrWriter = AttributeWriter.instance(context);
        classWriter = ClassWriter.instance(context);
        constantWriter = ConstantWriter.instance(context);
    }

    void write(Code_attribute attr, ConstantPool constant_pool) {
        println("  Code:");
        writeVerboseHeader(attr, constant_pool);
        writeInstrs(attr);
        writeExceptionTable(attr);
        attrWriter.write(attr, attr.attributes, constant_pool);
    }

    public void writeVerboseHeader(Code_attribute attr, ConstantPool constant_pool) {
        Method method = classWriter.getMethod();
        String argCount;
        try {
            int n = method.descriptor.getParameterCount(constant_pool);
            if (!method.access_flags.is(AccessFlags.ACC_STATIC))
                ++n;  // for 'this'
            argCount = Integer.toString(n);
        } catch (ConstantPoolException e) {
            argCount = report(e);
        } catch (DescriptorException e) {
            argCount = report(e);
        }

        println("   Stack=" + attr.max_stack +
                ", Locals=" + attr.max_locals +
                ", Args_size=" + argCount);

    }

    public void writeInstrs(Code_attribute attr) {
        try {
            for (int pc = 0; pc < attr.code_length;) {
                print("   " + pc + ":\t");
                pc += writeInstr(attr, pc);
                println();
            }
        } catch (Code_attribute.InvalidIndex e) {
            println(report(e));
        }
    }

    public int writeInstr(Code_attribute attr, int pc)
            throws Code_attribute.InvalidIndex {
        String lP = "";
        int opcode = attr.getUnsignedByte(pc);
        int opcode2;
        String mnem;
        switch (opcode) {
            case opc_nonpriv:
            case opc_priv: {
                opcode2 = attr.getUnsignedByte(pc + 1);
                mnem = opcName((opcode << 8) + opcode2);
                if (mnem == null) {
                    mnem = opcName(opcode) + " " + opcode2;
                }
                print(mnem);
                return 2;
            }
            case opc_wide: {
                opcode2 = attr.getUnsignedByte(pc + 1);
                mnem = opcName((opcode << 8) + opcode2);
                if (mnem == null) {
                    print("bytecode " + opcode);
                    return 1;
                }
                print(mnem + " " + attr.getUnsignedShort(pc + 2));
                if (opcode2 == opc_iinc) {
                    print(", " + attr.getShort(pc + 4));
                    return 6;
                }
                return 4;
            }
        }
        mnem = opcName(opcode);
        if (mnem == null) {
            print("bytecode " + opcode);
            return 1;
        }
        if (opcode > opc_jsr_w) {
            print("bytecode " + opcode);
            return 1;
        }
        print(opcName(opcode));
        switch (opcode) {
            case opc_aload:
            case opc_astore:
            case opc_fload:
            case opc_fstore:
            case opc_iload:
            case opc_istore:
            case opc_lload:
            case opc_lstore:
            case opc_dload:
            case opc_dstore:
            case opc_ret:
                print("\t" + attr.getUnsignedByte(pc + 1));
                return 2;
            case opc_iinc:
                print("\t" + attr.getUnsignedByte(pc + 1) + ", " + attr.getByte(pc + 2));
                return 3;
            case opc_tableswitch:
                {
                    int tb = align(pc + 1);
                    int default_skip = attr.getInt(tb);
                    int low = attr.getInt(tb + 4);
                    int high = attr.getInt(tb + 8);
                    int count = high - low;
                    print("{ //" + low + " to " + high);
                    for (int i = 0; i <= count; i++) {
                        print("\n\t\t" + (i + low) + ": " + lP + (pc + attr.getInt(tb + 12 + 4 * i)) + ";");
                    }
                    print("\n\t\tdefault: " + lP + (default_skip + pc) + " }");
                    return tb - pc + 16 + count * 4;
                }
            case opc_lookupswitch:
                {
                    int tb = align(pc + 1);
                    int default_skip = attr.getInt(tb);
                    int npairs = attr.getInt(tb + 4);
                    print("{ //" + npairs);
                    for (int i = 1; i <= npairs; i++) {
                        print("\n\t\t" + attr.getInt(tb + i * 8) + ": " + lP + (pc + attr.getInt(tb + 4 + i * 8)) + ";");
                    }
                    print("\n\t\tdefault: " + lP + (default_skip + pc) + " }");
                    return tb - pc + (npairs + 1) * 8;
                }
            case opc_newarray:
                int type = attr.getUnsignedByte(pc + 1);
                switch (type) {
                    case T_BOOLEAN:
                        print(" boolean");
                        break;
                    case T_BYTE:
                        print(" byte");
                        break;
                    case T_CHAR:
                        print(" char");
                        break;
                    case T_SHORT:
                        print(" short");
                        break;
                    case T_INT:
                        print(" int");
                        break;
                    case T_LONG:
                        print(" long");
                        break;
                    case T_FLOAT:
                        print(" float");
                        break;
                    case T_DOUBLE:
                        print(" double");
                        break;
                    case T_CLASS:
                        print(" class");
                        break;
                    default:
                        print(" BOGUS TYPE:" + type);
                }
                return 2;
            case opc_anewarray:
                {
                    int index = attr.getUnsignedShort(pc + 1);
                    print("\t#" + index + "; //");
                    printConstant(index);
                    return 3;
                }
            case opc_sipush:
                print("\t" + attr.getShort(pc + 1));
                return 3;
            case opc_bipush:
                print("\t" + attr.getByte(pc + 1));
                return 2;
            case opc_ldc:
                {
                    int index = attr.getUnsignedByte(pc + 1);
                    print("\t#" + index + "; //");
                    printConstant(index);
                    return 2;
                }
            case opc_ldc_w:
            case opc_ldc2_w:
            case opc_instanceof:
            case opc_checkcast:
            case opc_new:
            case opc_putstatic:
            case opc_getstatic:
            case opc_putfield:
            case opc_getfield:
            case opc_invokevirtual:
            case opc_invokespecial:
            case opc_invokestatic:
                {
                    int index = attr.getUnsignedShort(pc + 1);
                    print("\t#" + index + "; //");
                    printConstant(index);
                    return 3;
                }
            case opc_invokeinterface:
                {
                    int index = attr.getUnsignedShort(pc + 1);
                    int nargs = attr.getUnsignedByte(pc + 3);
                    print("\t#" + index + ",  " + nargs + "; //");
                    printConstant(index);
                    return 5;
                }
            case opc_multianewarray:
                {
                    int index = attr.getUnsignedShort(pc + 1);
                    int dimensions = attr.getUnsignedByte(pc + 3);
                    print("\t#" + index + ",  " + dimensions + "; //");
                    printConstant(index);
                    return 4;
                }
            case opc_jsr:
            case opc_goto:
            case opc_ifeq:
            case opc_ifge:
            case opc_ifgt:
            case opc_ifle:
            case opc_iflt:
            case opc_ifne:
            case opc_if_icmpeq:
            case opc_if_icmpne:
            case opc_if_icmpge:
            case opc_if_icmpgt:
            case opc_if_icmple:
            case opc_if_icmplt:
            case opc_if_acmpeq:
            case opc_if_acmpne:
            case opc_ifnull:
            case opc_ifnonnull:
                print("\t" + lP + (pc + attr.getShort(pc + 1)));
                return 3;
            case opc_jsr_w:
            case opc_goto_w:
                print("\t" + lP + (pc + attr.getInt(pc + 1)));
                return 5;
            default:
                return 1;
        }
    }

    public void writeExceptionTable(Code_attribute attr) {
        if (attr.exception_table_langth > 0) {
            println("  Exception table:");
            println("   from   to  target type");
            for (int i = 0; i < attr.exception_table.length; i++) {
                Code_attribute.Exception_data handler = attr.exception_table[i];
                printFixedWidthInt(handler.start_pc, 6);
                printFixedWidthInt(handler.end_pc, 6);
                printFixedWidthInt(handler.handler_pc, 6);
                print("   ");
                int catch_type = handler.catch_type;
                if (catch_type == 0) {
                    println("any");
                } else {
                    print("Class ");
                    println(constantWriter.stringValue(catch_type));
                    println("");
                }
            }
        }

    }

    private void printConstant(int index) {
        constantWriter.write(index);
    }

    private void printFixedWidthInt(int n, int width) {
        String s = String.valueOf(n);
        for (int i = s.length(); i < width; i++)
            print(" ");
        print(s);
    }

    private static int align(int n) {
        return (n + 3) & ~3;
    }

    private AttributeWriter attrWriter;
    private ClassWriter classWriter;
    private ConstantWriter constantWriter;
}
