/*
 * Javassist, a Java-bytecode translator toolkit.
 * Copyright (C) 1999- Shigeru Chiba. All Rights Reserved.
 *
 * 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.  Alternatively, the contents of this file may be used under
 * the terms of the GNU Lesser General Public License Version 2.1 or later,
 * or the Apache License Version 2.0.
 *
 * 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.
 */

package javassist.bytecode;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * <code>Code_attribute</code>.
 *
 * <p>To browse the <code>code</code> field of
 * a <code>Code_attribute</code> structure,
 * use <code>CodeIterator</code>.
 *
 * @see CodeIterator
 * @see #iterator()
 */
public class CodeAttribute extends AttributeInfo implements Opcode {
    /**
     * The name of this attribute <code>"Code"</code>.
     */
    public static final String tag = "Code";

    // code[] is stored in AttributeInfo.info.

    private int maxStack;
    private int maxLocals;
    private ExceptionTable exceptions;
    private List<AttributeInfo> attributes;

    /**
     * Constructs a <code>Code_attribute</code>.
     *
     * @param cp        constant pool table
     * @param stack     <code>max_stack</code>
     * @param locals    <code>max_locals</code>
     * @param code      <code>code[]</code>
     * @param etable    <code>exception_table[]</code>
     */
    public CodeAttribute(ConstPool cp, int stack, int locals, byte[] code,
                         ExceptionTable etable)
    {
        super(cp, tag);
        maxStack = stack;
        maxLocals = locals;
        info = code;
        exceptions = etable;
        attributes = new ArrayList<AttributeInfo>();
    }

    /**
     * Constructs a copy of <code>Code_attribute</code>.
     * Specified class names are replaced during the copy.
     *
     * @param cp                constant pool table.
     * @param src               source Code attribute.
     * @param classnames        pairs of replaced and substituted
     *                          class names.
     */
    private CodeAttribute(ConstPool cp, CodeAttribute src, Map<String,String> classnames)
        throws BadBytecode
    {
        super(cp, tag);

        maxStack = src.getMaxStack();
        maxLocals = src.getMaxLocals();
        exceptions = src.getExceptionTable().copy(cp, classnames);
        attributes = new ArrayList<AttributeInfo>();
        List<AttributeInfo> src_attr = src.getAttributes();
        int num = src_attr.size();
        for (int i = 0; i < num; ++i) {
            AttributeInfo ai = src_attr.get(i);
            attributes.add(ai.copy(cp, classnames));
        }

        info = src.copyCode(cp, classnames, exceptions, this);
    }

    CodeAttribute(ConstPool cp, int name_id, DataInputStream in)
        throws IOException
    {
        super(cp, name_id, (byte[])null);
        @SuppressWarnings("unused")
        int attr_len = in.readInt();

        maxStack = in.readUnsignedShort();
        maxLocals = in.readUnsignedShort();

        int code_len = in.readInt();
        info = new byte[code_len];
        in.readFully(info);

        exceptions = new ExceptionTable(cp, in);

        attributes = new ArrayList<AttributeInfo>();
        int num = in.readUnsignedShort();
        for (int i = 0; i < num; ++i)
            attributes.add(AttributeInfo.read(cp, in));
    }

    /**
     * Makes a copy.  Class names are replaced according to the
     * given <code>Map</code> object.
     *
     * @param newCp     the constant pool table used by the new copy.
     * @param classnames        pairs of replaced and substituted
     *                          class names.
     * @exception RuntimeCopyException  if a <code>BadBytecode</code>
     *                          exception is thrown, it is
     *                          converted into
     *                          <code>RuntimeCopyException</code>.
     *
     * @return <code>CodeAttribute</code> object.
     */
    @Override
    public AttributeInfo copy(ConstPool newCp, Map<String,String> classnames)
        throws RuntimeCopyException
    {
        try {
            return new CodeAttribute(newCp, this, classnames);
        }
        catch (BadBytecode e) {
            throw new RuntimeCopyException("bad bytecode. fatal?");
        }
    }

    /**
     * An exception that may be thrown by <code>copy()</code>
     * in <code>CodeAttribute</code>.
     */
    public static class RuntimeCopyException extends RuntimeException {
        /** default serialVersionUID */
        private static final long serialVersionUID = 1L;

        /**
         * Constructs an exception.
         */
        public RuntimeCopyException(String s) {
            super(s);
        }
    }

    /**
     * Returns the length of this <code>attribute_info</code>
     * structure.
     * The returned value is <code>attribute_length + 6</code>.
     */
    @Override
    public int length() {
        return 18 + info.length + exceptions.size() * 8
               + AttributeInfo.getLength(attributes);
    }

    @Override
    void write(DataOutputStream out) throws IOException {
        out.writeShort(name);           // attribute_name_index
        out.writeInt(length() - 6);     // attribute_length
        out.writeShort(maxStack);       // max_stack
        out.writeShort(maxLocals);      // max_locals
        out.writeInt(info.length);      // code_length
        out.write(info);                // code
        exceptions.write(out);
        out.writeShort(attributes.size());      // attributes_count
        AttributeInfo.writeAll(attributes, out);        // attributes
    }

    /**
     * This method is not available.
     *
     * @throws java.lang.UnsupportedOperationException  always thrown.
     */
    @Override
    public byte[] get() {
        throw new UnsupportedOperationException("CodeAttribute.get()");
    }

    /**
     * This method is not available.
     *
     * @throws java.lang.UnsupportedOperationException  always thrown.
     */
    @Override
    public void set(byte[] newinfo) {
        throw new UnsupportedOperationException("CodeAttribute.set()");
    }

    @Override
    void renameClass(String oldname, String newname) {
        AttributeInfo.renameClass(attributes, oldname, newname);
    }

    @Override
    void renameClass(Map<String,String> classnames) {
        AttributeInfo.renameClass(attributes, classnames);
    }

    @Override
    void getRefClasses(Map<String,String> classnames) {
        AttributeInfo.getRefClasses(attributes, classnames);
    }

    /**
     * Returns the name of the class declaring the method including
     * this code attribute.
     */
    public String getDeclaringClass() {
        ConstPool cp = getConstPool();
        return cp.getClassName();
    }

    /**
     * Returns <code>max_stack</code>.
     */
    public int getMaxStack() {
        return maxStack;
    }

    /**
     * Sets <code>max_stack</code>.
     */
    public void setMaxStack(int value) {
        maxStack = value;
    }

    /**
     * Computes the maximum stack size and sets <code>max_stack</code>
     * to the computed size.
     *
     * @throws BadBytecode      if this method fails in computing.
     * @return the newly computed value of <code>max_stack</code>
     */
    public int computeMaxStack() throws BadBytecode {
        maxStack = new CodeAnalyzer(this).computeMaxStack();
        return maxStack;
    }

    /**
     * Returns <code>max_locals</code>.
     */
    public int getMaxLocals() {
        return maxLocals;
    }

    /**
     * Sets <code>max_locals</code>.
     */
    public void setMaxLocals(int value) {
        maxLocals = value;
    }

    /**
     * Returns <code>code_length</code>.
     */
    public int getCodeLength() {
        return info.length;
    }

    /**
     * Returns <code>code[]</code>.
     */
    public byte[] getCode() {
        return info;
    }

    /**
     * Sets <code>code[]</code>.
     */
    void setCode(byte[] newinfo) { super.set(newinfo); }

    /**
     * Makes a new iterator for reading this code attribute.
     */
    public CodeIterator iterator() {
        return new CodeIterator(this);
    }

    /**
     * Returns <code>exception_table[]</code>.
     */
    public ExceptionTable getExceptionTable() { return exceptions; }

    /**
     * Returns <code>attributes[]</code>.
     * It returns a list of <code>AttributeInfo</code>.
     * A new element can be added to the returned list
     * and an existing element can be removed from the list.
     *
     * @see AttributeInfo
     */
    public List<AttributeInfo> getAttributes() { return attributes; }

    /**
     * Returns the attribute with the specified name.
     * If it is not found, this method returns null.
     *
     * @param name      attribute name
     * @return          an <code>AttributeInfo</code> object or null.
     */
    public AttributeInfo getAttribute(String name) {
        return AttributeInfo.lookup(attributes, name);
    }

    /**
     * Adds a stack map table.  If another copy of stack map table
     * is already contained, the old one is removed.
     *
     * @param smt       the stack map table added to this code attribute.
     *                  If it is null, a new stack map is not added.
     *                  Only the old stack map is removed. 
     */
    public void setAttribute(StackMapTable smt) {
        AttributeInfo.remove(attributes, StackMapTable.tag);
        if (smt != null)
            attributes.add(smt);
    }

    /**
     * Adds a stack map table for J2ME (CLDC).  If another copy of stack map table
     * is already contained, the old one is removed.
     *
     * @param sm        the stack map table added to this code attribute.
     *                  If it is null, a new stack map is not added.
     *                  Only the old stack map is removed.
     * @since 3.12
     */
    public void setAttribute(StackMap sm) {
        AttributeInfo.remove(attributes, StackMap.tag);
        if (sm != null)
            attributes.add(sm);
    }

    /**
     * Copies code.
     */
    private byte[] copyCode(ConstPool destCp, Map<String,String> classnames,
                            ExceptionTable etable, CodeAttribute destCa)
        throws BadBytecode
    {
        int len = getCodeLength();
        byte[] newCode = new byte[len];
        destCa.info = newCode;
        LdcEntry ldc = copyCode(this.info, 0, len, this.getConstPool(),
                                newCode, destCp, classnames);
        return LdcEntry.doit(newCode, ldc, etable, destCa);
    }

    private static LdcEntry copyCode(byte[] code, int beginPos, int endPos,
                                     ConstPool srcCp, byte[] newcode,
                                     ConstPool destCp, Map<String,String> classnameMap)
        throws BadBytecode
    {
        int i2, index;
        LdcEntry ldcEntry = null;

        for (int i = beginPos; i < endPos; i = i2) {
            i2 = CodeIterator.nextOpcode(code, i);
            byte c = code[i];
            newcode[i] = c;
            switch (c & 0xff) {
            case LDC_W :
            case LDC2_W :
            case GETSTATIC :
            case PUTSTATIC :
            case GETFIELD :
            case PUTFIELD :
            case INVOKEVIRTUAL :
            case INVOKESPECIAL :
            case INVOKESTATIC :
            case NEW :
            case ANEWARRAY :
            case CHECKCAST :
            case INSTANCEOF :
                copyConstPoolInfo(i + 1, code, srcCp, newcode, destCp,
                                  classnameMap);
                break;
            case LDC :
                index = code[i + 1] & 0xff;
                index = srcCp.copy(index, destCp, classnameMap);
                if (index < 0x100)
                    newcode[i + 1] = (byte)index;
                else {
                    newcode[i] = NOP;
                    newcode[i + 1] = NOP;
                    LdcEntry ldc = new LdcEntry();
                    ldc.where = i;
                    ldc.index = index;
                    ldc.next = ldcEntry;
                    ldcEntry = ldc;
                }
                break;
            case INVOKEINTERFACE :
                copyConstPoolInfo(i + 1, code, srcCp, newcode, destCp,
                                  classnameMap);
                newcode[i + 3] = code[i + 3];
                newcode[i + 4] = code[i + 4];
                break;
            case INVOKEDYNAMIC :
                copyConstPoolInfo(i + 1, code, srcCp, newcode, destCp,
                        classnameMap);
                newcode[i + 3] = 0;
                newcode[i + 4] = 0;
                break;
            case MULTIANEWARRAY :
                copyConstPoolInfo(i + 1, code, srcCp, newcode, destCp,
                                  classnameMap);
                newcode[i + 3] = code[i + 3];
                break;
            default :
                while (++i < i2)
                    newcode[i] = code[i];

                break;
            }
        }

        return ldcEntry;
    }

    private static void copyConstPoolInfo(int i, byte[] code, ConstPool srcCp,
                                          byte[] newcode, ConstPool destCp,
                                          Map<String,String> classnameMap) {
        int index = ((code[i] & 0xff) << 8) | (code[i + 1] & 0xff);
        index = srcCp.copy(index, destCp, classnameMap);
        newcode[i] = (byte)(index >> 8);
        newcode[i + 1] = (byte)index;
    }

    static class LdcEntry {
        LdcEntry next;
        int where;
        int index;

        static byte[] doit(byte[] code, LdcEntry ldc, ExceptionTable etable,
                           CodeAttribute ca)
            throws BadBytecode
        {
            if (ldc != null)
                code = CodeIterator.changeLdcToLdcW(code, etable, ca, ldc);

            /* The original code was the following:

               while (ldc != null) {
                 int where = ldc.where;
                 code = CodeIterator.insertGapCore0(code, where, 1, false, etable, ca);
                 code[where] = (byte)Opcode.LDC_W;
                 ByteArray.write16bit(ldc.index, code, where + 1);
                 ldc = ldc.next;
               }

               But this code does not support a large method > 32KB.
            */

            return code;
        }
    }

    /**
     * Changes the index numbers of the local variables
     * to append a new parameter.
     * This method does not update <code>LocalVariableAttribute</code>,
     * <code>LocalVariableTypeAttribute</code>,
     * <code>StackMapTable</code>, or <code>StackMap</code>.
     * These attributes must be explicitly updated.
     *
     * @param where         the index of the new parameter.
     * @param size         the type size of the new parameter (1 or 2).
     *
     * @see LocalVariableAttribute#shiftIndex(int, int)
     * @see LocalVariableTypeAttribute#shiftIndex(int, int)
     * @see StackMapTable#insertLocal(int, int, int)
     * @see StackMap#insertLocal(int, int, int)
     */
    public void insertLocalVar(int where, int size) throws BadBytecode {
        CodeIterator ci = iterator();
        while (ci.hasNext())
            shiftIndex(ci, where, size);

        setMaxLocals(getMaxLocals() + size);
    }

    /**
     * @param lessThan      If the index of the local variable is
     *                      less than this value, it does not change.
     *                      Otherwise, the index is increased.
     * @param delta         the indexes of the local variables are
     *                      increased by this value.
     */
    private static void shiftIndex(CodeIterator ci, int lessThan, int delta) throws BadBytecode {
        int index = ci.next();
        int opcode = ci.byteAt(index);
        if (opcode < ILOAD)
            return;
        else if (opcode < IASTORE) {
            if (opcode < ILOAD_0) {
                // iload, lload, fload, dload, aload
                shiftIndex8(ci, index, opcode, lessThan, delta);
            }
            else if (opcode < IALOAD) {
                // iload_0, ..., aload_3
                shiftIndex0(ci, index, opcode, lessThan, delta, ILOAD_0, ILOAD);
            }
            else if (opcode < ISTORE)
                return;
            else if (opcode < ISTORE_0) {
                // istore, lstore, ...
                shiftIndex8(ci, index, opcode, lessThan, delta);
            }
            else {
                // istore_0, ..., astore_3
                shiftIndex0(ci, index, opcode, lessThan, delta, ISTORE_0, ISTORE);
            }
        }
        else if (opcode == IINC) {
            int var = ci.byteAt(index + 1);
            if (var < lessThan)
                return;

            var += delta;
            if (var < 0x100)
                ci.writeByte(var, index + 1);
            else {
                int plus = (byte)ci.byteAt(index + 2);
                int pos = ci.insertExGap(3);
                ci.writeByte(WIDE, pos - 3);
                ci.writeByte(IINC, pos - 2);
                ci.write16bit(var, pos - 1);
                ci.write16bit(plus, pos + 1);
            }
        }
        else if (opcode == RET)
            shiftIndex8(ci, index, opcode, lessThan, delta);
        else if (opcode == WIDE) {
            int var = ci.u16bitAt(index + 2);
            if (var < lessThan)
                return;

            var += delta;
            ci.write16bit(var, index + 2);
        }
    }

    private static void shiftIndex8(CodeIterator ci, int index, int opcode,
                                    int lessThan, int delta)
         throws BadBytecode
    {
        int var = ci.byteAt(index + 1);
        if (var < lessThan)
            return;

        var += delta;
        if (var < 0x100)
            ci.writeByte(var, index + 1);
        else {
            int pos = ci.insertExGap(2);
            ci.writeByte(WIDE, pos - 2);
            ci.writeByte(opcode, pos - 1);
            ci.write16bit(var, pos);
        }
    }

    private static void shiftIndex0(CodeIterator ci, int index, int opcode,
                                    int lessThan, int delta,
                                    int opcode_i_0, int opcode_i)
        throws BadBytecode
    {
        int var = (opcode - opcode_i_0) % 4;
        if (var < lessThan)
            return;

        var += delta;
        if (var < 4)
            ci.writeByte(opcode + delta, index);
        else {
            opcode = (opcode - opcode_i_0) / 4 + opcode_i;
            if (var < 0x100) {
                int pos = ci.insertExGap(1);
                ci.writeByte(opcode, pos - 1);
                ci.writeByte(var, pos);
            }
            else {
                int pos = ci.insertExGap(3);
                ci.writeByte(WIDE, pos - 1);
                ci.writeByte(opcode, pos);
                ci.write16bit(var, pos + 1);
            }
        }
    }
}
