/**
 * ASM: a very small and fast Java bytecode manipulation framework
 * Copyright (c) 2000-2005 INRIA, France Telecom
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions 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.
 * 3. Neither the name of the copyright holders nor the names of its
 *    contributors may be used to endorse or promote products derived from
 *    this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 */
package org.objectweb.asm.attrs;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import org.objectweb.asm.Attribute;
import org.objectweb.asm.ByteVector;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Label;

/**
 * StackMapAttribute is used by CDLC preverifier. Definition is given in
 * appendix "CLDC Byte Code Typechecker Specification" from CDLC 1.1
 * specification. <p> <i>Note that this implementation does not calculate
 * StackMapFrame structures from the method bytecode. If method code is changed
 * or generated from scratch, then developer is responsible to prepare a correct
 * StackMapFrame structures.</i> <p> The format of the stack map in the class
 * file is given below. In the following, <ul> <li>if the length of the
 * method's byte code1 is 65535 or less, then <tt>uoffset</tt> represents the
 * type u2; otherwise <tt>uoffset</tt> represents the type u4.</li> <li>If
 * the maximum number of local variables for the method is 65535 or less, then
 * <tt>ulocalvar</tt> represents the type u2; otherwise <tt>ulocalvar</tt>
 * represents the type u4.</li> <li>If the maximum size of the operand stack
 * is 65535 or less, then <tt>ustack</tt> represents the type u2; otherwise
 * ustack represents the type u4.</li> </ul>
 * 
 * <pre>
 * stack_map { // attribute StackMap
 *   u2 attribute_name_index;
 *   u4 attribute_length
 *   uoffset number_of_entries;
 *   stack_map_frame entries[number_of_entries];
 * }
 * </pre>
 * 
 * Each stack map frame has the following format:
 * 
 * <pre>
 * stack_map_frame {
 *   uoffset offset;
 *   ulocalvar number_of_locals;
 *   verification_type_info locals[number_of_locals];
 *   ustack number_of_stack_items;
 *   verification_type_info stack[number_of_stack_items];
 * }
 * </pre>
 * 
 * The <tt>verification_type_info</tt> structure consists of a one-byte tag
 * followed by zero or more bytes, giving more information about the tag. Each
 * <tt>verification_type_info</tt> structure specifies the verification type
 * of one or two locations.
 * 
 * <pre>
 * union verification_type_info {
 *   Top_variable_info;
 *   Integer_variable_info;
 *   Float_variable_info;
 *   Long_variable_info;
 *   Double_variable_info;
 *   Null_variable_info;
 *   UninitializedThis_variable_info;
 *   Object_variable_info;
 *   Uninitialized_variable_info;
 * }
 *      
 * Top_variable_info {
 *   u1 tag = ITEM_Top; // 0
 * }
 *      
 * Integer_variable_info {
 *   u1 tag = ITEM_Integer; // 1
 * }
 *      
 * Float_variable_info {
 *   u1 tag = ITEM_Float; // 2
 * }
 *      
 * Long_variable_info {
 *   u1 tag = ITEM_Long; // 4
 * }
 *      
 * Double_variable_info {
 *   u1 tag = ITEM_Double; // 3
 * }
 *      
 * Null_variable_info {
 *  u1 tag = ITEM_Null; // 5
 * }
 *      
 * UninitializedThis_variable_info {
 *   u1 tag = ITEM_UninitializedThis; // 6
 * }
 *      
 * Object_variable_info {
 *   u1 tag = ITEM_Object; // 7
 *   u2 cpool_index;
 * }
 *      
 * Uninitialized_variable_info {
 *   u1 tag = ITEM_Uninitialized // 8
 *   uoffset offset;
 * }
 * </pre>
 * 
 * @see <a href="http://www.jcp.org/en/jsr/detail?id=139">JSR 139 : Connected
 *      Limited Device Configuration 1.1</a>
 * 
 * @author Eugene Kuleshov
 */
public class StackMapAttribute extends Attribute {

    static final int MAX_SIZE = 65535;

    /**
     * A List of <code>StackMapFrame</code> instances.
     */
    public List frames = new ArrayList();

    public StackMapAttribute() {
        super("StackMap");
    }

    public StackMapAttribute(List frames) {
        this();
        this.frames = frames;
    }

    public List getFrames() {
        return frames;
    }

    public StackMapFrame getFrame(Label label) {
        for (int i = 0; i < frames.size(); i++) {
            StackMapFrame frame = (StackMapFrame) frames.get(i);
            if (frame.label == label) {
                return frame;
            }
        }
        return null;
    }

    public boolean isUnknown() {
        return false;
    }

    public boolean isCodeAttribute() {
        return true;
    }

    protected Attribute read(
        ClassReader cr,
        int off,
        int len,
        char[] buf,
        int codeOff,
        Label[] labels)
    {
        StackMapAttribute attr = new StackMapAttribute();
        // note that this is not the size of Code attribute
        boolean isExtCodeSize = cr.readInt(codeOff + 4) > MAX_SIZE;
        boolean isExtLocals = cr.readUnsignedShort(codeOff + 2) > MAX_SIZE;
        boolean isExtStack = cr.readUnsignedShort(codeOff) > MAX_SIZE;

        int size = 0;
        if (isExtCodeSize) {
            size = cr.readInt(off);
            off += 4;
        } else {
            size = cr.readUnsignedShort(off);
            off += 2;
        }
        for (int i = 0; i < size; i++) {
            int offset;
            if (isExtCodeSize) {
                offset = cr.readInt(off);
                off += 4;
            } else {
                offset = cr.readUnsignedShort(off);
                off += 2;
            }

            Label label = getLabel(offset, labels);
            List locals = new ArrayList();
            List stack = new ArrayList();

            off = readTypeInfo(cr,
                    off,
                    locals,
                    labels,
                    buf,
                    isExtLocals,
                    isExtCodeSize);
            off = readTypeInfo(cr,
                    off,
                    stack,
                    labels,
                    buf,
                    isExtStack,
                    isExtCodeSize);

            attr.frames.add(new StackMapFrame(label, locals, stack));
        }
        return attr;
    }

    private int readTypeInfo(
        ClassReader cr,
        int off,
        List info,
        Label[] labels,
        char[] buf,
        boolean isExt,
        boolean isExtCode)
    {
        int n = 0;
        if (isExt) {
            n = cr.readInt(off);
            off += 4;
        } else {
            n = cr.readUnsignedShort(off);
            off += 2;
        }
        for (int j = 0; j < n; j++) {
            int itemType = cr.readByte(off++);
            StackMapType typeInfo = StackMapType.getTypeInfo(itemType);
            info.add(typeInfo);
            switch (itemType) {
                case StackMapType.ITEM_Object: //
                    typeInfo.setObject(cr.readClass(off, buf));
                    off += 2;
                    break;
                case StackMapType.ITEM_Uninitialized: //
                    int offset;
                    if (isExtCode) {
                        offset = cr.readInt(off);
                        off += 4;
                    } else {
                        offset = cr.readUnsignedShort(off);
                        off += 2;
                    }
                    typeInfo.setLabel(getLabel(offset, labels));
                    break;
            }
        }
        return off;
    }

    private void writeTypeInfo(ByteVector bv, ClassWriter cw, List info, int max)
    {
        if (max > StackMapAttribute.MAX_SIZE) {
            bv.putInt(info.size());
        } else {
            bv.putShort(info.size());
        }
        for (int j = 0; j < info.size(); j++) {
            StackMapType typeInfo = (StackMapType) info.get(j);
            bv.putByte(typeInfo.getType());
            switch (typeInfo.getType()) {
                case StackMapType.ITEM_Object: //
                    bv.putShort(cw.newClass(typeInfo.getObject()));
                    break;

                case StackMapType.ITEM_Uninitialized: //
                    bv.putShort(typeInfo.getLabel().getOffset());
                    break;

            }
        }
    }

    private Label getLabel(int offset, Label[] labels) {
        Label l = labels[offset];
        if (l != null) {
            return l;
        }
        return labels[offset] = new Label();
    }

    protected ByteVector write(
        ClassWriter cw,
        byte[] code,
        int len,
        int maxStack,
        int maxLocals)
    {
        ByteVector bv = new ByteVector();
        if (code != null && code.length > MAX_SIZE) { // TODO verify value
            bv.putInt(frames.size());
        } else {
            bv.putShort(frames.size());
        }
        for (int i = 0; i < frames.size(); i++) {
            writeFrame((StackMapFrame) frames.get(i),
                    cw,
                    maxStack,
                    maxLocals,
                    bv);
        }
        return bv;
    }

    protected Label[] getLabels() {
        HashSet labels = new HashSet();
        for (int i = 0; i < frames.size(); i++) {
            getFrameLabels((StackMapFrame) frames.get(i), labels);
        }
        return (Label[]) labels.toArray(new Label[labels.size()]);
    }

    private void writeFrame(
        StackMapFrame frame,
        ClassWriter cw,
        int maxStack,
        int maxLocals,
        ByteVector bv)
    {
        bv.putShort(frame.label.getOffset());
        writeTypeInfo(bv, cw, frame.locals, maxLocals);
        writeTypeInfo(bv, cw, frame.stack, maxStack);
    }

    private void getFrameLabels(StackMapFrame frame, Set labels) {
        labels.add(frame.label);
        getTypeInfoLabels(labels, frame.locals);
        getTypeInfoLabels(labels, frame.stack);
    }

    private void getTypeInfoLabels(Set labels, List info) {
        for (Iterator it = info.iterator(); it.hasNext();) {
            StackMapType typeInfo = (StackMapType) it.next();
            if (typeInfo.getType() == StackMapType.ITEM_Uninitialized) {
                labels.add(typeInfo.getLabel());
            }
        }
    }

    public String toString() {
        StringBuffer sb = new StringBuffer("StackMap[");
        for (int i = 0; i < frames.size(); i++) {
            sb.append('\n').append('[').append(frames.get(i)).append(']');
        }
        sb.append("\n]");
        return sb.toString();
    }
}
