Java程序  |  2147行  |  82.25 KB

/***
 * 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;

import com.sun.tools.javac.code.TargetType;
import com.sun.tools.javac.code.TypeAnnotationPosition.TypePathEntry;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

/**
 * A Java class parser to make a {@link ClassVisitor} visit an existing class.
 * This class parses a byte array conforming to the Java class file format and
 * calls the appropriate visit methods of a given class visitor for each field,
 * method and bytecode instruction encountered.
 *
 * @author Eric Bruneton
 * @author Eugene Kuleshov
 */
public class ClassReader {

    /**
     * True to enable signatures support.
     */
    static final boolean SIGNATURES = true;

    /**
     * True to enable annotations support.
     */
    static final boolean ANNOTATIONS = true;

    /**
     * True to enable stack map frames support.
     */
    static final boolean FRAMES = true;

    /**
     * True to enable bytecode writing support.
     */
    static final boolean WRITER = true;

    /**
     * True to enable JSR_W and GOTO_W support.
     */
    static final boolean RESIZE = true;

    /**
     * Flag to skip method code. If this class is set <code>CODE</code>
     * attribute won't be visited. This can be used, for example, to retrieve
     * annotations for methods and method parameters.
     */
    public static final int SKIP_CODE = 1;

    /**
     * Flag to skip the debug information in the class. If this flag is set the
     * debug information of the class is not visited, i.e. the
     * {@link MethodVisitor#visitLocalVariable visitLocalVariable} and
     * {@link MethodVisitor#visitLineNumber visitLineNumber} methods will not be
     * called.
     */
    public static final int SKIP_DEBUG = 2;

    /**
     * Flag to skip the stack map frames in the class. If this flag is set the
     * stack map frames of the class is not visited, i.e. the
     * {@link MethodVisitor#visitFrame visitFrame} method will not be called.
     * This flag is useful when the {@link ClassWriter#COMPUTE_FRAMES} option is
     * used: it avoids visiting frames that will be ignored and recomputed from
     * scratch in the class writer.
     */
    public static final int SKIP_FRAMES = 4;

    /**
     * Flag to expand the stack map frames. By default stack map frames are
     * visited in their original format (i.e. "expanded" for classes whose
     * version is less than V1_6, and "compressed" for the other classes). If
     * this flag is set, stack map frames are always visited in expanded format
     * (this option adds a decompression/recompression step in ClassReader and
     * ClassWriter which degrades performances quite a lot).
     */
    public static final int EXPAND_FRAMES = 8;

    /**
     * The class to be parsed. <i>The content of this array must not be
     * modified. This field is intended for {@link Attribute} sub classes, and
     * is normally not needed by class generators or adapters.</i>
     */
    public final byte[] b;

    /**
     * The start index of each constant pool item in {@link #b b}, plus one.
     * The one byte offset skips the constant pool item tag that indicates its
     * type.
     */
    private final int[] items;

    /**
     * The String objects corresponding to the CONSTANT_Utf8 items. This cache
     * avoids multiple parsing of a given CONSTANT_Utf8 constant pool item,
     * which GREATLY improves performance (by a factor 2 to 3). This caching
     * strategy could be extended to all constant pool items, but its benefit
     * would not be so great for these items (because they are much less
     * expensive to parse than CONSTANT_Utf8 items).
     */
    private final String[] strings;

    /**
     * Maximum length of the strings contained in the constant pool of the
     * class.
     */
    private final int maxStringLength;

    /**
     * Start index of the class header information (access, name...) in
     * {@link #b b}.
     */
    public final int header;

    /**
     * The start index of each bootstrap method.
     */
    int[] bootstrapMethods;

    // ------------------------------------------------------------------------
    // Constructors
    // ------------------------------------------------------------------------

    /**
     * Constructs a new {@link ClassReader} object.
     *
     * @param b the bytecode of the class to be read.
     */
    public ClassReader(final byte[] b) {
        this(b, 0, b.length);
    }

    /**
     * Constructs a new {@link ClassReader} object.
     *
     * @param b the bytecode of the class to be read.
     * @param off the start offset of the class data.
     * @param len the length of the class data.
     */
    public ClassReader(final byte[] b, final int off, final int len) {
        this.b = b;
        // parses the constant pool
        items = new int[readUnsignedShort(off + 8)];
        int ll = items.length;
        strings = new String[ll];
        int max = 0;
        int index = off + 10;
        for (int i = 1; i < ll; ++i) {
            items[i] = index + 1;
            int tag = b[index];
            int size;
            switch (tag) {
                case ClassWriter.FIELD:
                case ClassWriter.METH:
                case ClassWriter.IMETH:
                case ClassWriter.INT:
                case ClassWriter.FLOAT:
                case ClassWriter.NAME_TYPE:
                case ClassWriter.INDY:
                    size = 5;
                    break;
                case ClassWriter.LONG:
                case ClassWriter.DOUBLE:
                    size = 9;
                    ++i;
                    break;
                case ClassWriter.UTF8:
                    size = 3 + readUnsignedShort(index + 1);
                    if (size > max) {
                        max = size;
                    }
                    break;
                case ClassWriter.HANDLE:
                    size = 4;
                    break;
                // case ClassWriter.CLASS:
                // case ClassWriter.STR:
                // case ClassWriter.MTYPE
                default:
                    size = 3;
                    break;
            }
            index += size;
        }
        maxStringLength = max;
        // the class header information starts just after the constant pool
        header = index;
    }

    /**
     * Copies the constant pool data into the given {@link ClassWriter}. Should
     * be called before the {@link #accept(ClassVisitor,boolean)} method.
     *
     * @param classWriter the {@link ClassWriter} to copy constant pool into.
     */
    void copyPool(final ClassWriter classWriter) {
        char[] buf = new char[maxStringLength];
        int ll = items.length;
        Item[] items2 = new Item[ll];
        for (int i = 1; i < ll; i++) {
            int index = items[i];
            int tag = b[index - 1];
            Item item = new Item(i);
            int nameType;
            switch (tag) {
                case ClassWriter.FIELD:
                case ClassWriter.METH:
                case ClassWriter.IMETH:
                    nameType = items[readUnsignedShort(index + 2)];
                    item.set(tag,
                            readClass(index, buf),
                            readUTF8(nameType, buf),
                            readUTF8(nameType + 2, buf));
                    break;

                case ClassWriter.INT:
                    item.set(readInt(index));
                    break;

                case ClassWriter.FLOAT:
                    item.set(Float.intBitsToFloat(readInt(index)));
                    break;

                case ClassWriter.NAME_TYPE:
                    item.set(tag,
                            readUTF8(index, buf),
                            readUTF8(index + 2, buf),
                            null);
                    break;

                case ClassWriter.LONG:
                    item.set(readLong(index));
                    ++i;
                    break;

                case ClassWriter.DOUBLE:
                    item.set(Double.longBitsToDouble(readLong(index)));
                    ++i;
                    break;

                case ClassWriter.UTF8: {
                    String s = strings[i];
                    if (s == null) {
                        index = items[i];
                        s = strings[i] = readUTF(index + 2,
                                readUnsignedShort(index),
                                buf);
                    }
                    item.set(tag, s, null, null);
                    break;
                }
                case ClassWriter.HANDLE: {
                    int fieldOrMethodRef = items[readUnsignedShort(index + 1)];
                    nameType = items[readUnsignedShort(fieldOrMethodRef + 2)];
                    item.set(ClassWriter.HANDLE_BASE + readByte(index),
                            readClass(fieldOrMethodRef, buf),
                            readUTF8(nameType, buf),
                            readUTF8(nameType + 2, buf));
                    break;
                }
                case ClassWriter.INDY:
                    if (classWriter.bootstrapMethods == null) {
                        copyBootstrapMethods(classWriter, items2, buf);
                    }
                    nameType = items[readUnsignedShort(index + 2)];
                    item.set(readUTF8(nameType, buf),
                            readUTF8(nameType + 2, buf),
                            readUnsignedShort(index));
                    break;
                // case ClassWriter.STR:
                // case ClassWriter.CLASS:
                // case ClassWriter.MTYPE
                default:
                    item.set(tag, readUTF8(index, buf), null, null);
                    break;
            }

            int index2 = item.hashCode % items2.length;
            item.next = items2[index2];
            items2[index2] = item;
        }

        int off = items[1] - 1;
        classWriter.pool.putByteArray(b, off, header - off);
        classWriter.items = items2;
        classWriter.threshold = (int) (0.75d * ll);
        classWriter.index = ll;
    }

    /**
     * Copies the bootstrap method data into the given {@link ClassWriter}.
     * Should be called before the {@link #accept(ClassVisitor,int)} method.
     * 
     * @param classWriter
     *            the {@link ClassWriter} to copy bootstrap methods into.
     */
    private void copyBootstrapMethods(final ClassWriter classWriter,
            final Item[] items, final char[] c) {
        // finds the "BootstrapMethods" attribute
        int u = getAttributes();
        boolean found = false;
        for (int i = readUnsignedShort(u); i > 0; --i) {
            String attrName = readUTF8(u + 2, c);
            if ("BootstrapMethods".equals(attrName)) {
                found = true;
                break;
            }
            u += 6 + readInt(u + 4);
        }
        if (!found) {
            return;
        }
        // copies the bootstrap methods in the class writer
        int boostrapMethodCount = readUnsignedShort(u + 8);
        for (int j = 0, v = u + 10; j < boostrapMethodCount; j++) {
            int position = v - u - 10;
            int hashCode = readConst(readUnsignedShort(v), c).hashCode();
            for (int k = readUnsignedShort(v + 2); k > 0; --k) {
                hashCode ^= readConst(readUnsignedShort(v + 4), c).hashCode();
                v += 2;
            }
            v += 4;
            Item item = new Item(j);
            item.set(position, hashCode & 0x7FFFFFFF);
            int index = item.hashCode % items.length;
            item.next = items[index];
            items[index] = item;
        }
        int attrSize = readInt(u + 4);
        ByteVector bootstrapMethods = new ByteVector(attrSize + 62);
        bootstrapMethods.putByteArray(b, u + 10, attrSize - 2);
        classWriter.bootstrapMethodsCount = boostrapMethodCount;
        classWriter.bootstrapMethods = bootstrapMethods;
    }

    /**
     * Constructs a new {@link ClassReader} object.
     *
     * @param is an input stream from which to read the class.
     * @throws IOException if a problem occurs during reading.
     */
    public ClassReader(final InputStream is) throws IOException {
        this(readClass(is));
    }

    /**
     * Constructs a new {@link ClassReader} object.
     *
     * @param name the fully qualified name of the class to be read.
     * @throws IOException if an exception occurs during reading.
     */
    public ClassReader(final String name) throws IOException {
        this(ClassLoader.getSystemResourceAsStream(name.replace('.', '/')
                + ".class"));
    }

    /**
     * Reads the bytecode of a class.
     *
     * @param is an input stream from which to read the class.
     * @return the bytecode read from the given input stream.
     * @throws IOException if a problem occurs during reading.
     */
    private static byte[] readClass(final InputStream is) throws IOException {
        if (is == null) {
            throw new IOException("Class not found");
        }
        byte[] b = new byte[is.available()];
        int len = 0;
        while (true) {
            int n = is.read(b, len, b.length - len);
            if (n == -1) {
                if (len < b.length) {
                    byte[] c = new byte[len];
                    System.arraycopy(b, 0, c, 0, len);
                    b = c;
                }
                return b;
            }
            len += n;
            if (len == b.length) {
                    int last = is.read();
                    if (last < 0) {
                        return b;
                    }
                byte[] c = new byte[b.length + 1000];
                System.arraycopy(b, 0, c, 0, len);
                c[len++] = (byte) last;
                b = c;
            }
        }
    }

    // ------------------------------------------------------------------------
    // Public methods
    // ------------------------------------------------------------------------

    /**
     * Makes the given visitor visit the Java class of this {@link ClassReader}.
     * This class is the one specified in the constructor (see
     * {@link #ClassReader(byte[]) ClassReader}).
     *
     * @param classVisitor the visitor that must visit this class.
     * @param skipDebug <tt>true</tt> if the debug information of the class
     *        must not be visited. In this case the
     *        {@link MethodVisitor#visitLocalVariable visitLocalVariable} and
     *        {@link MethodVisitor#visitLineNumber visitLineNumber} methods will
     *        not be called.
     */
    public void accept(final ClassVisitor classVisitor, final boolean skipDebug)
    {
        accept(classVisitor, new Attribute[0], skipDebug);
    }

    /**
     * Makes the given visitor visit the Java class of this {@link ClassReader}.
     * This class is the one specified in the constructor (see
     * {@link #ClassReader(byte[]) ClassReader}).
     *
     * @param classVisitor the visitor that must visit this class.
     * @param attrs prototypes of the attributes that must be parsed during the
     *        visit of the class. Any attribute whose type is not equal to the
     *        type of one the prototypes will be ignored.
     * @param skipDebug <tt>true</tt> if the debug information of the class
     *        must not be visited. In this case the
     *        {@link MethodVisitor#visitLocalVariable visitLocalVariable} and
     *        {@link MethodVisitor#visitLineNumber visitLineNumber} methods will
     *        not be called.
     */
    public void accept(
        final ClassVisitor classVisitor,
        final Attribute[] attrs,
        final boolean skipDebug)
    {
        byte[] b = this.b; // the bytecode array
        char[] c = new char[maxStringLength]; // buffer used to read strings
        int i, j, k; // loop variables
        int u, v, w; // indexes in b
        Attribute attr;

        int access;
        String name;
        String desc;
        String attrName;
        String signature;
        int anns = 0;
        int ianns = 0;
        int xanns = 0;
        int ixanns = 0;
        Attribute cattrs = null;

        // visits the header
        u = header; // u = u2 access_flags
        access = readUnsignedShort(u);
        name = readClass(u + 2, c); // name = u2 this_class
        v = items[readUnsignedShort(u + 4)]; // u + 4 = u2 super_class
        String superClassName = v == 0 ? null : readUTF8(v, c);
        String[] implementedItfs = new String[readUnsignedShort(u + 6)];
                  // u + 6 = u2 interfaces_count;
        w = 0;
        u += 8; // u + 8 = interfaces[interfaces_count]
        for (i = 0; i < implementedItfs.length; ++i) {
            implementedItfs[i] = readClass(u, c);
            u += 2;
        }

        // u = u2 fields_count

        // skips fields and methods
        v = u;
        i = readUnsignedShort(v); // i = u2 fields_count
        v += 2; // v = field_info fields[fields_count]
        for (; i > 0; --i) {
            j = readUnsignedShort(v + 6);
            v += 8;
            for (; j > 0; --j) {
                v += 6 + readInt(v + 2);
            }
        }
        i = readUnsignedShort(v); // i = u2 methods_count
        v += 2; // v = method_info methods[methods_count]
        for (; i > 0; --i) {
            j = readUnsignedShort(v + 6);
            v += 8;
            for (; j > 0; --j) {
                v += 6 + readInt(v + 2);
            }
        }
        // reads the class's attributes
        signature = null;
        String sourceFile = null;
        String sourceDebug = null;
        String enclosingOwner = null;
        String enclosingName = null;
        String enclosingDesc = null;

        i = readUnsignedShort(v); // i = u2 attributes_count
        v += 2; // v = attribute_info attributes[attributes_count]
        for (; i > 0; --i) {
            attrName = readUTF8(v, c);
            if (attrName.equals("SourceFile")) {
                sourceFile = readUTF8(v + 6, c);
            } else if (attrName.equals("Deprecated")) {
                access |= Opcodes.ACC_DEPRECATED;
            } else if (attrName.equals("Synthetic")) {
                access |= Opcodes.ACC_SYNTHETIC;
            } else if (attrName.equals("Annotation")) {
                access |= Opcodes.ACC_ANNOTATION;
            } else if (attrName.equals("Enum")) {
                access |= Opcodes.ACC_ENUM;
            } else if (attrName.equals("InnerClasses")) {
                w = v + 6;
            } else if (attrName.equals("Signature")) {
                signature = readUTF8(v + 6, c);
            } else if (attrName.equals("SourceDebugExtension")) {
                int len = readInt(v + 2);
                sourceDebug = readUTF(v + 6, len, new char[len]);
            } else if (attrName.equals("EnclosingMethod")) {
                enclosingOwner = readClass(v + 6, c);
                int item = readUnsignedShort(v + 8);
                if (item != 0) {
                    enclosingName = readUTF8(items[item], c);
                    enclosingDesc = readUTF8(items[item] + 2, c);
                }
            } else if (attrName.equals("RuntimeVisibleAnnotations")) {
                anns = v + 6;
            } else if (attrName.equals("RuntimeInvisibleAnnotations")) {
                ianns = v + 6;
            } else if (attrName.equals("RuntimeVisibleTypeAnnotations")) {
                xanns = v + 6;
            } else if (attrName.equals("RuntimeInvisibleTypeAnnotations")) {
                ixanns = v + 6;
            } else if (attrName.equals("BootstrapMethods")) {
                bootstrapMethods = new int[readUnsignedShort(v + 6)];
                for (j = 0, u = v + 8; j < bootstrapMethods.length; j++) {
                    bootstrapMethods[j] = u;
                    u += 2 + readUnsignedShort(u + 2) << 1;
                }
            } else {
                attr = readAttribute(attrs,
                        attrName,
                        v + 6,
                        readInt(v + 2),
                        c,
                        -1,
                        null);
                if (attr != null) {
                    attr.next = cattrs;
                    cattrs = attr;
                }
            }
            v += 6 + readInt(v + 2);
        }
        // calls the visit method
        classVisitor.visit(readInt(4),
                access,
                name,
                signature,
                superClassName,
                implementedItfs);

        // calls the visitSource method
        if (sourceFile != null || sourceDebug != null) {
            classVisitor.visitSource(sourceFile, sourceDebug);
        }

        // calls the visitOuterClass method
        if (enclosingOwner != null) {
            classVisitor.visitOuterClass(enclosingOwner,
                    enclosingName,
                    enclosingDesc);
        }

        // visits the class annotations
        for (i = 1; i >= 0; --i) {
            v = i == 0 ? ianns : anns;
            if (v != 0) {
                j = readUnsignedShort(v);
                v += 2;
                for (; j > 0; --j) {
                    desc = readUTF8(v, c);
                    v += 2;
                    v = readAnnotationValues(v,
                            c,
                            classVisitor.visitAnnotation(desc, i != 0));
                }
            }
        }

        // TODO
        // visits the class extended annotations
        for (i = 1; i >= 0; --i) {
            v = i == 0 ? ixanns : xanns;
            if (v != 0) {
                j = readUnsignedShort(v);
                v += 2;
                for (; j > 0; --j) {
                    v = readTypeAnnotationValues(v,
                            c, classVisitor, i != 0);
                }
            }
        }

        // visits the class attributes
        while (cattrs != null) {
            attr = cattrs.next;
            cattrs.next = null;
            classVisitor.visitAttribute(cattrs);
            cattrs = attr;
        }

        // class the visitInnerClass method
        if (w != 0) {
            i = readUnsignedShort(w);
            w += 2;
            for (; i > 0; --i) {
                classVisitor.visitInnerClass(readUnsignedShort(w) == 0
                        ? null
                        : readClass(w, c), readUnsignedShort(w + 2) == 0
                        ? null
                        : readClass(w + 2, c), readUnsignedShort(w + 4) == 0
                        ? null
                        : readUTF8(w + 4, c), readUnsignedShort(w + 6));
                w += 8;
            }
        }

        // visits the fields
        u = header + 8 + 2 * implementedItfs.length;
        i = readUnsignedShort(u); // i = u2 fields_count
        u += 2; // u = field_info[fields_count]
        for (; i > 0; --i) {
            access = readUnsignedShort(u);
            name = readUTF8(u + 2, c);
            desc = readUTF8(u + 4, c);
            // visits the field's attributes and looks for a ConstantValue
            // attribute
            int fieldValueItem = 0;
            signature = null;
            anns = 0;
            ianns = 0;
            xanns = 0;
            ixanns = 0;
            cattrs = null;

            j = readUnsignedShort(u + 6); // j = u2 attributes_count
            u += 8; // u = attributes[attributes_count]
            for (; j > 0; --j) {
                attrName = readUTF8(u, c);
                if (attrName.equals("ConstantValue")) {
                    fieldValueItem = readUnsignedShort(u + 6);
                } else if (attrName.equals("Synthetic")) {
                    access |= Opcodes.ACC_SYNTHETIC;
                } else if (attrName.equals("Deprecated")) {
                    access |= Opcodes.ACC_DEPRECATED;
                } else if (attrName.equals("Enum")) {
                    access |= Opcodes.ACC_ENUM;
                } else if (attrName.equals("Signature")) {
                    signature = readUTF8(u + 6, c);
                } else if (attrName.equals("RuntimeVisibleAnnotations")) {
                    anns = u + 6;
                } else if (attrName.equals("RuntimeInvisibleAnnotations")) {
                    ianns = u + 6;
                } else if (attrName.equals("RuntimeVisibleTypeAnnotations")) {
                    xanns = u + 6;
                } else if (attrName.equals("RuntimeInvisibleTypeAnnotations")) {
                    ixanns = u + 6;
                } else {

                    attr = readAttribute(attrs,
                            attrName,
                            u + 6,
                            readInt(u + 2),
                            c,
                            -1,
                            null);
                    if (attr != null) {
                        attr.next = cattrs;
                        cattrs = attr;
                    }
                }
                u += 6 + readInt(u + 2);
            }
            // reads the field's value, if any
            Object value = (fieldValueItem == 0
                    ? null
                    : readConst(fieldValueItem, c));
            // visits the field
            FieldVisitor fv = classVisitor.visitField(access,
                    name,
                    desc,
                    signature,
                    value);
            // visits the field annotations and attributes
            if (fv != null) {
                for (j = 1; j >= 0; --j) {
                    v = j == 0 ? ianns : anns;
                    if (v != 0) {
                        k = readUnsignedShort(v);
                        v += 2;
                        for (; k > 0; --k) {
                            desc = readUTF8(v, c);
                            v += 2;
                            v = readAnnotationValues(v,
                                    c,
                                    fv.visitAnnotation(desc, j != 0));
                        }
                    }
                }
                //TODO
                // now visit the extended annotations
                if(xanns != 0) {
                    v = xanns;
                    k = readUnsignedShort(v);
                    v += 2;
                    for(; k > 0; --k) {
                        v = readTypeAnnotationValues(v,
                            c, fv, true);
                    }
                }

                if(ixanns != 0) {
                    v = ixanns;
                    k = readUnsignedShort(v);
                    v += 2;
                    for(; k > 0; --k) {
                        v = readTypeAnnotationValues(v,
                            c, fv, false);
                    }
                }

                while (cattrs != null) {
                    attr = cattrs.next;
                    cattrs.next = null;
                    fv.visitAttribute(cattrs);
                    cattrs = attr;
                }
                fv.visitEnd();
            }
        }

        // visits the methods
        i = readUnsignedShort(u);
        u += 2;
        for (; i > 0; --i) {
            int u0 = u + 6;
            access = readUnsignedShort(u);
            name = readUTF8(u + 2, c);
            desc = readUTF8(u + 4, c);
            signature = null;
            anns = 0;
            ianns = 0;
            //jaime
            xanns = 0;
            ixanns = 0;
            // end jaime
            int dann = 0;
            int mpanns = 0;
            int impanns = 0;
            cattrs = null;
            v = 0;
            w = 0;

            // looks for Code and Exceptions attributes
            j = readUnsignedShort(u + 6);
            u += 8;
            for (; j > 0; --j) {
                attrName = readUTF8(u, c);
                u += 2;
                int attrSize = readInt(u);
                u += 4;
                if (attrName.equals("Code")) {
                    v = u;
                } else if (attrName.equals("Exceptions")) {
                    w = u;
                } else if (attrName.equals("Synthetic")) {
                    access |= Opcodes.ACC_SYNTHETIC;
                } else if (attrName.equals("Varargs")) {
                    access |= Opcodes.ACC_VARARGS;
                } else if (attrName.equals("Bridge")) {
                    access |= Opcodes.ACC_BRIDGE;
                } else if (attrName.equals("Deprecated")) {
                    access |= Opcodes.ACC_DEPRECATED;
                } else if (attrName.equals("Signature")) {
                    signature = readUTF8(u, c);
                } else if (attrName.equals("AnnotationDefault")) {
                    dann = u;
                } else if (attrName.equals("RuntimeVisibleAnnotations")) {
                    anns = u;
                } else if (attrName.equals("RuntimeInvisibleAnnotations")) {
                    ianns = u;
                } else if (attrName.equals("RuntimeVisibleTypeAnnotations")) {
                    xanns = u;
                } else if (attrName.equals("RuntimeInvisibleTypeAnnotations")) {
                    ixanns = u;
                } else if (attrName.equals("RuntimeVisibleParameterAnnotations")) {
                    mpanns = u;
                } else if (attrName.equals("RuntimeInvisibleParameterAnnotations")) {
                    impanns = u;
                } else {
                    attr = readAttribute(attrs,
                            attrName,
                            u,
                            attrSize,
                            c,
                            -1,
                            null);
                    if (attr != null) {
                        attr.next = cattrs;
                        cattrs = attr;
                    }
                }
                u += attrSize;
            }
            // reads declared exceptions
            String[] exceptions;
            if (w == 0) {
                exceptions = null;
            } else {
                exceptions = new String[readUnsignedShort(w)];
                w += 2;
                for (j = 0; j < exceptions.length; ++j) {
                    exceptions[j] = readClass(w, c);
                    w += 2;
                }
            }

            // visits the method's code, if any
            MethodVisitor mv = classVisitor.visitMethod(access,
                    name,
                    desc,
                    signature,
                    exceptions);

            if (mv != null) {
                /*
                 * if the returned MethodVisitor is in fact a MethodWriter, it
                 * means there is no method adapter between the reader and the
                 * writer. If, in addition, the writer's constant pool was
                 * copied from this reader (mw.cw.cr == this), and the signature
                 * and exceptions of the method have not been changed, then it
                 * is possible to skip all visit events and just copy the
                 * original code of the method to the writer (the access, name
                 * and descriptor can have been changed, this is not important
                 * since they are not copied as is from the reader).
                 */
                if (mv instanceof MethodWriter) {
                    MethodWriter mw = (MethodWriter) mv;
                    if (mw.cw.cr == this) {
                        if (signature == mw.signature) {
                            boolean sameExceptions = false;
                            if (exceptions == null) {
                                sameExceptions = mw.exceptionCount == 0;
                            } else {
                                if (exceptions.length == mw.exceptionCount) {
                                    sameExceptions = true;
                                    for (j = exceptions.length - 1; j >= 0; --j)
                                    {
                                        w -= 2;
                                        if (mw.exceptions[j] != readUnsignedShort(w))
                                        {
                                            sameExceptions = false;
                                            break;
                                        }
                                    }
                                }
                            }
                            if (sameExceptions) {
                                /*
                                 * we do not copy directly the code into
                                 * MethodWriter to save a byte array copy
                                 * operation. The real copy will be done in
                                 * ClassWriter.toByteArray().
                                 */
                                mw.classReaderOffset = u0;
                                mw.classReaderLength = u - u0;
                                continue;
                            }
                        }
                    }
                }
                if (dann != 0) {
                    AnnotationVisitor dv = mv.visitAnnotationDefault();
                    readAnnotationValue(dann, c, null, dv);
                    dv.visitEnd();
                }
                for (j = 1; j >= 0; --j) {
                    w = j == 0 ? ianns : anns;
                    if (w != 0) {
                        k = readUnsignedShort(w);
                        w += 2;
                        for (; k > 0; --k) {
                            desc = readUTF8(w, c);
                            w += 2;
                            w = readAnnotationValues(w,
                                    c,
                                    mv.visitAnnotation(desc, j != 0));
                        }
                    }
                }

                // now visit the method extended annotations
                for (j = 1; j >= 0; --j) {
                    w = j == 0 ? ixanns : xanns;
                    if (w != 0) {
                        k = readUnsignedShort(w);
                        w += 2;
                        for (; k > 0; --k) {
                            w = readTypeAnnotationValues(w,
                                  c, mv, j != 0);
                        }
                    }
                }

                if (mpanns != 0) {
                    readParameterAnnotations(mpanns, c, true, mv);
                }
                if (impanns != 0) {
                    readParameterAnnotations(impanns, c, false, mv);
                }

                while (cattrs != null) {
                    attr = cattrs.next;
                    cattrs.next = null;
                    mv.visitAttribute(cattrs);
                    cattrs = attr;
                }
            }

            if (mv != null && v != 0) {
                int maxStack = readUnsignedShort(v);
                int maxLocals = readUnsignedShort(v + 2);
                int codeLength = readInt(v + 4);
                v += 8;

                int codeStart = v;
                int codeEnd = v + codeLength;

                mv.visitCode();

                // 1st phase: finds the labels
                int label;
                Label[] labels = new Label[codeLength + 1];
                while (v < codeEnd) {
                    int opcode = b[v] & 0xFF;
                    switch (ClassWriter.TYPE[opcode]) {
                        case ClassWriter.NOARG_INSN:
                        case ClassWriter.IMPLVAR_INSN:
                            v += 1;
                            break;
                        case ClassWriter.LABEL_INSN:
                            label = v - codeStart + readShort(v + 1);
                            if (labels[label] == null) {
                                labels[label] = new Label();
                            }
                            v += 3;
                            break;
                        case ClassWriter.LABELW_INSN:
                            label = v - codeStart + readInt(v + 1);
                            if (labels[label] == null) {
                                labels[label] = new Label();
                            }
                            v += 5;
                            break;
                        case ClassWriter.WIDE_INSN:
                            opcode = b[v + 1] & 0xFF;
                            if (opcode == Opcodes.IINC) {
                                v += 6;
                            } else {
                                v += 4;
                            }
                            break;
                        case ClassWriter.TABL_INSN:
                            // skips 0 to 3 padding bytes
                            w = v - codeStart;
                            v = v + 4 - (w & 3);
                            // reads instruction
                            label = w + readInt(v);
                            v += 4;
                            if (labels[label] == null) {
                                labels[label] = new Label();
                            }
                            j = readInt(v);
                            v += 4;
                            j = readInt(v) - j + 1;
                            v += 4;
                            for (; j > 0; --j) {
                                label = w + readInt(v);
                                v += 4;
                                if (labels[label] == null) {
                                    labels[label] = new Label();
                                }
                            }
                            break;
                        case ClassWriter.LOOK_INSN:
                            // skips 0 to 3 padding bytes
                            w = v - codeStart;
                            v = v + 4 - (w & 3);
                            // reads instruction
                            label = w + readInt(v);
                            v += 4;
                            if (labels[label] == null) {
                                labels[label] = new Label();
                            }
                            j = readInt(v);
                            v += 4;
                            for (; j > 0; --j) {
                                v += 4; // skips key
                                label = w + readInt(v);
                                v += 4;
                                if (labels[label] == null) {
                                    labels[label] = new Label();
                                }
                            }
                            break;
                        case ClassWriter.VAR_INSN:
                        case ClassWriter.SBYTE_INSN:
                        case ClassWriter.LDC_INSN:
                            v += 2;
                            break;
                        case ClassWriter.SHORT_INSN:
                        case ClassWriter.LDCW_INSN:
                        case ClassWriter.FIELDORMETH_INSN:
                        case ClassWriter.TYPE_INSN:
                        case ClassWriter.IINC_INSN:
                            v += 3;
                            break;
                        case ClassWriter.ITFMETH_INSN:
                        case ClassWriter.INDY:
                            v += 5;
                            break;
                        // case MANA_INSN:
                        default:
                            v += 4;
                            break;
                    }
                }
                // parses the try catch entries
                j = readUnsignedShort(v);
                v += 2;
                for (; j > 0; --j) {
                    label = readUnsignedShort(v);
                    Label start = labels[label];
                    if (start == null) {
                        labels[label] = start = new Label();
                    }
                    label = readUnsignedShort(v + 2);
                    Label end = labels[label];
                    if (end == null) {
                        labels[label] = end = new Label();
                    }
                    label = readUnsignedShort(v + 4);
                    Label handler = labels[label];
                    if (handler == null) {
                        labels[label] = handler = new Label();
                    }

                    int type = readUnsignedShort(v + 6);
                    if (type == 0) {
                        mv.visitTryCatchBlock(start, end, handler, null);
                    } else {
                        mv.visitTryCatchBlock(start,
                                end,
                                handler,
                                readUTF8(items[type], c));
                    }
                    v += 8;
                }
                // parses the local variable, line number tables, and code
                // attributes
                int varTable = 0;
                int varTypeTable = 0;
                cattrs = null;
                j = readUnsignedShort(v);
                v += 2;
                for (; j > 0; --j) {
                    attrName = readUTF8(v, c);
                    if (attrName.equals("LocalVariableTable")) {
                        if (!skipDebug) {
                            varTable = v + 6;
                            k = readUnsignedShort(v + 6);
                            w = v + 8;
                            for (; k > 0; --k) {
                                label = readUnsignedShort(w);
                                if (labels[label] == null) {
                                    labels[label] = new Label();
                                }
                                label += readUnsignedShort(w + 2);
                                if (labels[label] == null) {
                                    labels[label] = new Label();
                                }
                                w += 10;
                            }
                        }
                    } else if (attrName.equals("LocalVariableTypeTable")) {
                        varTypeTable = v + 6;
                    } else if (attrName.equals("LineNumberTable")) {
                        if (!skipDebug) {
                            k = readUnsignedShort(v + 6);
                            w = v + 8;
                            for (; k > 0; --k) {
                                label = readUnsignedShort(w);
                                if (labels[label] == null) {
                                    labels[label] = new Label();
                                }
                                labels[label].line = readUnsignedShort(w + 2);
                                w += 4;
                            }
                        }
                    } else if (attrName.equals("RuntimeInvisibleTypeAnnotations")) {
                        k = readUnsignedShort(v + 6);
                        w = v + 8;
                        for (; k > 0; --k) {
                            w = readTypeAnnotationValues(w,
                                    c, mv, false);
                        }
                    } else if (attrName.equals("RuntimeVisibleTypeAnnotations")) {
                        k = readUnsignedShort(v + 6);
                        w = v + 8;
                        for (; k > 0; --k) {
                            w = readTypeAnnotationValues(w,
                                    c, mv, true);
                        }
                    } else {
                        for (k = 0; k < attrs.length; ++k) {
                            if (attrs[k].type.equals(attrName)) {
                                attr = attrs[k].read(this,
                                        v + 6,
                                        readInt(v + 2),
                                        c,
                                        codeStart - 8,
                                        labels);
                                if (attr != null) {
                                    attr.next = cattrs;
                                    cattrs = attr;
                                }
                            }
                        }
                    }
                    v += 6 + readInt(v + 2);
                }

                // 2nd phase: visits each instruction
                v = codeStart;
                PrecompiledMethodVisitor pmv =
                    (mv instanceof PrecompiledMethodVisitor)
                    ? (PrecompiledMethodVisitor) mv : null;
                Label l;
                while (v < codeEnd) {
                    w = v - codeStart;
                    if (pmv != null)
                        pmv.visitCurrentPosition(w);
                    l = labels[w];
                    if (l != null) {
                        mv.visitLabel(l);
                        if (!skipDebug && l.line > 0) {
                            mv.visitLineNumber(l.line, l);
                        }
                    }
                    int opcode = b[v] & 0xFF;
                    switch (ClassWriter.TYPE[opcode]) {
                        case ClassWriter.NOARG_INSN:
                            mv.visitInsn(opcode);
                            v += 1;
                            break;
                        case ClassWriter.IMPLVAR_INSN:
                            if (opcode > Opcodes.ISTORE) {
                                opcode -= 59; // ISTORE_0
                                mv.visitVarInsn(Opcodes.ISTORE + (opcode >> 2),
                                        opcode & 0x3);
                            } else {
                                opcode -= 26; // ILOAD_0
                                mv.visitVarInsn(Opcodes.ILOAD + (opcode >> 2),
                                        opcode & 0x3);
                            }
                            v += 1;
                            break;
                        case ClassWriter.LABEL_INSN:
                            mv.visitJumpInsn(opcode, labels[w
                                    + readShort(v + 1)]);
                            v += 3;
                            break;
                        case ClassWriter.LABELW_INSN:
                            mv.visitJumpInsn(opcode - 33, labels[w
                                    + readInt(v + 1)]);
                            v += 5;
                            break;
                        case ClassWriter.WIDE_INSN:
                            opcode = b[v + 1] & 0xFF;
                            if (opcode == Opcodes.IINC) {
                                mv.visitIincInsn(readUnsignedShort(v + 2),
                                        readShort(v + 4));
                                v += 6;
                            } else {
                                mv.visitVarInsn(opcode,
                                        readUnsignedShort(v + 2));
                                v += 4;
                            }
                            break;
                        case ClassWriter.TABL_INSN:
                            // skips 0 to 3 padding bytes
                            v = v + 4 - (w & 3);
                            // reads instruction
                            label = w + readInt(v);
                            v += 4;
                            int min = readInt(v);
                            v += 4;
                            int max = readInt(v);
                            v += 4;
                            Label[] table = new Label[max - min + 1];
                            for (j = 0; j < table.length; ++j) {
                                table[j] = labels[w + readInt(v)];
                                v += 4;
                            }
                            mv.visitTableSwitchInsn(min,
                                    max,
                                    labels[label],
                                    table);
                            break;
                        case ClassWriter.LOOK_INSN:
                            // skips 0 to 3 padding bytes
                            v = v + 4 - (w & 3);
                            // reads instruction
                            label = w + readInt(v);
                            v += 4;
                            j = readInt(v);
                            v += 4;
                            int[] keys = new int[j];
                            Label[] values = new Label[j];
                            for (j = 0; j < keys.length; ++j) {
                                keys[j] = readInt(v);
                                v += 4;
                                values[j] = labels[w + readInt(v)];
                                v += 4;
                            }
                            mv.visitLookupSwitchInsn(labels[label],
                                    keys,
                                    values);
                            break;
                        case ClassWriter.VAR_INSN:
                            mv.visitVarInsn(opcode, b[v + 1] & 0xFF);
                            v += 2;
                            break;
                        case ClassWriter.SBYTE_INSN:
                            mv.visitIntInsn(opcode, b[v + 1]);
                            v += 2;
                            break;
                        case ClassWriter.SHORT_INSN:
                            mv.visitIntInsn(opcode, readShort(v + 1));
                            v += 3;
                            break;
                        case ClassWriter.LDC_INSN:
                            mv.visitLdcInsn(readConst(b[v + 1] & 0xFF, c));
                            v += 2;
                            break;
                        case ClassWriter.LDCW_INSN:
                            mv.visitLdcInsn(readConst(readUnsignedShort(v + 1),
                                    c));
                            v += 3;
                            break;
                        case ClassWriter.FIELDORMETH_INSN:
                        case ClassWriter.ITFMETH_INSN:
                            int cpIndex = items[readUnsignedShort(v + 1)];
                            String iowner = readClass(cpIndex, c);
                            cpIndex = items[readUnsignedShort(cpIndex + 2)];
                            String iname = readUTF8(cpIndex, c);
                            String idesc = readUTF8(cpIndex + 2, c);
                            if (opcode < Opcodes.INVOKEVIRTUAL) {
                                mv.visitFieldInsn(opcode, iowner, iname, idesc);
                            } else {
                                mv.visitMethodInsn(opcode, iowner, iname, idesc);
                            }
                            if (opcode == Opcodes.INVOKEINTERFACE) {
                                v += 5;
                            } else {
                                v += 3;
                            }
                            break;
                        case ClassWriter.TYPE_INSN:
                            mv.visitTypeInsn(opcode, readClass(v + 1, c));
                            v += 3;
                            break;
                        case ClassWriter.IINC_INSN:
                            mv.visitIincInsn(b[v + 1] & 0xFF, b[v + 2]);
                            v += 3;
                            break;
                        case ClassWriter.INDY:
                            cpIndex = items[readUnsignedShort(v + 1)];
                            int bsmIndex = bootstrapMethods[readUnsignedShort(cpIndex)];
                            Handle bsm = (Handle) readConst(readUnsignedShort(bsmIndex), c);
                            int bsmArgCount = readUnsignedShort(bsmIndex + 2);
                            Object[] bsmArgs = new Object[bsmArgCount];
                            bsmIndex += 4;
                            for (j = 0; j < bsmArgCount; j++) {
                                bsmArgs[j] = readConst(readUnsignedShort(bsmIndex), c);
                                bsmIndex += 2;
                            }
                            cpIndex = items[readUnsignedShort(cpIndex + 2)];
                            iname = readUTF8(cpIndex, c);
                            idesc = readUTF8(cpIndex + 2, c);
                            mv.visitInvokeDynamicInsn(iname, idesc, bsm, bsmArgs);
                            v += 5;
                            break;
                        // case MANA_INSN:
                        default:
                            mv.visitMultiANewArrayInsn(readClass(v + 1, c),
                                    b[v + 3] & 0xFF);
                            v += 4;
                            break;
                    }
                }
                l = labels[codeEnd - codeStart];
                if (l != null) {
                    if (pmv != null)
                        pmv.visitCurrentPosition(codeEnd - codeStart);
                    mv.visitLabel(l);
                }

                // visits the local variable tables
                if (!skipDebug && varTable != 0) {
                    int[] typeTable = null;
                    if (varTypeTable != 0) {
                        w = varTypeTable;
                        k = readUnsignedShort(w) * 3;
                        w += 2;
                        typeTable = new int[k];
                        while (k > 0) {
                            typeTable[--k] = w + 6; // signature
                            typeTable[--k] = readUnsignedShort(w + 8); // index
                            typeTable[--k] = readUnsignedShort(w); // start
                            w += 10;
                        }
                    }
                    w = varTable;
                    k = readUnsignedShort(w);
                    w += 2;
                    for (; k > 0; --k) {
                        int start = readUnsignedShort(w);
                        int length = readUnsignedShort(w + 2);
                        int index = readUnsignedShort(w + 8);
                        String vsignature = null;
                        if (typeTable != null) {
                            for (int a = 0; a < typeTable.length; a += 3) {
                                if (typeTable[a] == start
                                        && typeTable[a + 1] == index)
                                {
                                    vsignature = readUTF8(typeTable[a + 2], c);
                                    break;
                                }
                            }
                        }
                        mv.visitLocalVariable(readUTF8(w + 4, c),
                                readUTF8(w + 6, c),
                                vsignature,
                                labels[start],
                                labels[start + length],
                                index);
                        w += 10;
                    }
                }
                // visits the other attributes
                while (cattrs != null) {
                    attr = cattrs.next;
                    cattrs.next = null;
                    mv.visitAttribute(cattrs);
                    cattrs = attr;
                }
                // visits the max stack and max locals values
                mv.visitMaxs(maxStack, maxLocals);
            }

            if (mv != null) {
                mv.visitEnd();
            }
        }

        // visits the end of the class
        classVisitor.visitEnd();
    }

    /**
     * Reads parameter annotations and makes the given visitor visit them.
     *
     * @param v start offset in {@link #b b} of the annotations to be read.
     * @param buf buffer to be used to call {@link #readUTF8 readUTF8},
     *        {@link #readClass(int,char[]) readClass} or
     *        {@link #readConst readConst}.
     * @param visible <tt>true</tt> if the annotations to be read are visible
     *        at runtime.
     * @param mv the visitor that must visit the annotations.
     */
    private void readParameterAnnotations(
        int v,
        final char[] buf,
        final boolean visible,
        final MethodVisitor mv)
    {
        int n = b[v++] & 0xFF;
        for (int i = 0; i < n; ++i) {
            int j = readUnsignedShort(v);
            v += 2;
            for (; j > 0; --j) {
                String desc = readUTF8(v, buf);
                v += 2;
                AnnotationVisitor av = mv.visitParameterAnnotation(i,
                        desc,
                        visible);
                v = readAnnotationValues(v, buf, av);
            }
        }
    }

    /**
     * Reads the values of an annotation and makes the given visitor visit them.
     *
     * @param v the start offset in {@link #b b} of the values to be read
     *        (including the unsigned short that gives the number of values).
     * @param buf buffer to be used to call {@link #readUTF8 readUTF8},
     *        {@link #readClass(int,char[]) readClass} or
     *        {@link #readConst readConst}.
     * @param av the visitor that must visit the values.
     * @return the end offset of the annotations values.
     */
    private int readAnnotationValues(
        int v,
        final char[] buf,
        final AnnotationVisitor av)
    {
        int i = readUnsignedShort(v);
        v += 2;
        for (; i > 0; --i) {
            String name = readUTF8(v, buf);
            v += 2;
            v = readAnnotationValue(v, buf, name, av);
        }
        av.visitEnd();
        return v;
    }
   /**
    * Reads the values and reference info of an extended annotation
    * and makes the given visitor visit them.
    *
    * @param v the start offset in {@link #b b} of the values to be read
    *        (including the unsigned short that gives the number of values).
    * @param buf buffer to be used to call {@link #readUTF8 readUTF8},
    *        {@link #readClass(int,char[]) readClass} or
    *        {@link #readConst readConst}.
    * @param mv the visitor to generate the visitor that must visit the values.
    * @param visible {@code true} if the annotation is visible at runtime.
    * @return the end offset of the annotations values.
    * @author jaimeq
    */
    private int readTypeAnnotationValues(
        int v,
        final char[] buf,
        final MemberVisitor mv,
        final boolean visible)
    {
        // first handle
        //
        // u1 target_type
        // { ...
        // } reference_info
        //

        int target_type_value = readByte(v);
        v += 1;

        Integer offset = null;
        Integer location_length = null;
        List<TypePathEntry> locations = new ArrayList<TypePathEntry>();
        Integer start_pc = null;
        Integer length = null;
        Integer index = null;
        Integer param_index = null;
        Integer bound_index = null;
        Integer type_index = null;
        Integer exception_index = null;
        Integer table_length = null;

        TargetType target_type = TargetType.fromTargetTypeValue(target_type_value);

        switch(target_type) {
        // type test (instanceof)
        // object creation
        // constructor/method reference receiver
        // {
        //   u2 offset;
        // } reference_info;
        case INSTANCEOF:
        case NEW:
        case CONSTRUCTOR_REFERENCE:
        case METHOD_REFERENCE:
          offset = readUnsignedShort(v);
          v += 2;
          break;

        // method receiver
        // {
        // } reference_info;
        case METHOD_RECEIVER:
          break;

        // local variable
        // u2 table_length;
        // {
        //   u2 start_pc;
        //   u2 length;
        //   u2 index;
        // } reference_info;
        case LOCAL_VARIABLE:
        // resource variable
        case RESOURCE_VARIABLE:
          table_length = readUnsignedShort(v);
          v += 2;
          assert table_length == 1; // FIXME
          start_pc = readUnsignedShort(v);
          v += 2;
          length = readUnsignedShort(v);
          v += 2;
          index = readUnsignedShort(v);
          v += 2;
          break;

        // method return type
        // {
        // } reference_info;
        case METHOD_RETURN:
          break;

        // method parameter
        // {
        //   u1 param;
        // } reference_info;
        case METHOD_FORMAL_PARAMETER:
          param_index = readByte(v);
          v++;
          break;

        // field
        // {
        // } reference_info;
        case FIELD:
          break;

        // class type parameter bound
        // method type parameter bound
        // {
        //   u1 param_index;
        //   u1 bound_index;
        // } reference_info;
        case CLASS_TYPE_PARAMETER_BOUND:
        case METHOD_TYPE_PARAMETER_BOUND:
          param_index = readByte(v);
          v++;
          bound_index = readByte(v);
          v++;
          break;

        // class extends/implements
        // exception type in throws
        // {
        //    u1 type_index;
        // } reference_info;
        case CLASS_EXTENDS:
          type_index = readUnsignedShort(v);
          if (type_index == 0xFFFF) type_index = -1;
          v += 2;
          break;
        case THROWS:
          type_index = readUnsignedShort(v);
          v += 2;
          break;
        case EXCEPTION_PARAMETER:
          exception_index = readUnsignedShort(v);
          v += 2;
          break;

        // typecast
        // type argument in constructor call
        // type argument in method call
        // type argument in constructor reference
        // type argument in method reference
        // {
        //   u2 offset;
        //   u1 type_index;
        // } reference_info;
        case CAST:
        case CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT:
        case METHOD_INVOCATION_TYPE_ARGUMENT:
        case CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT:
        case METHOD_REFERENCE_TYPE_ARGUMENT:
          offset = readUnsignedShort(v);
          v += 2;

          type_index = readByte(v);
          v++;
          break;

        // method type parameter
        // {
        //    u1 param_index;
        // } reference_info;
        case CLASS_TYPE_PARAMETER:
        case METHOD_TYPE_PARAMETER:
          param_index = readByte(v);
          v++;
          break;

        default: throw new RuntimeException(
              "Unrecognized target type: " + target_type);
        }

        // now read in the location information
        {
            location_length = readByte(v);
            v += 1;
            for (int m = 0; m < location_length; m++) {
              int loctag = readByte(v);
              int locarg = readByte(v + 1);
              v += TypePathEntry.bytesPerEntry;
              locations.add(TypePathEntry.fromBinary(loctag, locarg));
            }
        }

        String desc = readUTF8(v, buf);
        v += 2;
        TypeAnnotationVisitor xav = mv.visitTypeAnnotation(desc, visible, false);

        xav.visitXTargetType(target_type_value);
        if (start_pc != null) {
            xav.visitXStartPc(start_pc);
        }
        if (length != null) {
            xav.visitXLength(length);
        }
        if (index != null) {
            xav.visitXIndex(index);
        }
        if (offset != null) {
            xav.visitXOffset(offset);
        }
        if (type_index != null) {
            xav.visitXTypeIndex(type_index);
        }
        if (param_index != null) {
            xav.visitXParamIndex(param_index);
        }
        if (bound_index != null) {
            xav.visitXBoundIndex(bound_index);
        }
        if (exception_index != null) {
            xav.visitXExceptionIndex(exception_index);
        }
        if (location_length != null) {
            xav.visitXLocationLength(location_length);
        }
        for (TypePathEntry location : locations) {
            xav.visitXLocation(location);
        }
        // Visit the annotation name and save space for the values count.
        xav.visitXNameAndArgsSize();

        // then read annotation values
        int i = readUnsignedShort(v);
        v += 2;
        for (; i > 0; --i) {
            String name = readUTF8(v, buf);
            v += 2;
            // can use the same method as for declaration annotations because
            // the first part of an extended annotation matches the normal
            // annotations
            v = readAnnotationValue(v, buf, name, xav);
        }

        xav.visitEnd();
        return v;
    }

    /**
     * Reads a value of an annotation and makes the given visitor visit it.
     *
     * @param v the start offset in {@link #b b} of the value to be read (<i>not
     *        including the value name constant pool index</i>).
     * @param buf buffer to be used to call {@link #readUTF8 readUTF8},
     *        {@link #readClass(int,char[]) readClass} or
     *        {@link #readConst readConst}.
     * @param name the name of the value to be read.
     * @param av the visitor that must visit the value.
     * @return the end offset of the annotation value.
     */
    private int readAnnotationValue(
        int v,
        final char[] buf,
        final String name,
        final AnnotationVisitor av)
    {
        int i;
        switch (readByte(v++)) {
            case 'I': // pointer to CONSTANT_Integer
            case 'J': // pointer to CONSTANT_Long
            case 'F': // pointer to CONSTANT_Float
            case 'D': // pointer to CONSTANT_Double
                av.visit(name, readConst(readUnsignedShort(v), buf));
                v += 2;
                break;
            case 'B': // pointer to CONSTANT_Byte
                av.visit(name,
                        new Byte((byte) readInt(items[readUnsignedShort(v)])));
                v += 2;
                break;
            case 'Z': // pointer to CONSTANT_Boolean
                boolean b = readInt(items[readUnsignedShort(v)]) == 0;
                av.visit(name, b ? Boolean.FALSE : Boolean.TRUE);
                v += 2;
                break;
            case 'S': // pointer to CONSTANT_Short
                av.visit(name,
                        new Short((short) readInt(items[readUnsignedShort(v)])));
                v += 2;
                break;
            case 'C': // pointer to CONSTANT_Char
                av.visit(name,
                        new Character((char) readInt(items[readUnsignedShort(v)])));
                v += 2;
                break;
            case 's': // pointer to CONSTANT_Utf8
                av.visit(name, readUTF8(v, buf));
                v += 2;
                break;
            case 'e': // enum_const_value
                av.visitEnum(name, readUTF8(v, buf), readUTF8(v + 2, buf));
                v += 4;
                break;
            case 'c': // class_info
                av.visit(name, Type.getType(readUTF8(v, buf)));
                v += 2;
                break;
            case '@': // annotation_value
                String desc = readUTF8(v, buf);
                v += 2;
                v = readAnnotationValues(v, buf, av.visitAnnotation(name, desc));
                break;
            case '[': // array_value
                int size = readUnsignedShort(v);
                v += 2;
                if (size == 0) {
                    av.visitArray(name).visitEnd();
                    return v;
                }
                switch (readByte(v++)) {
                    case 'B':
                        byte[] bv = new byte[size];
                        for (i = 0; i < size; i++) {
                            bv[i] = (byte) readInt(items[readUnsignedShort(v)]);
                            v += 3;
                        }
                        av.visit(name, bv);
                        --v;
                        break;
                    case 'Z':
                        boolean[] zv = new boolean[size];
                        for (i = 0; i < size; i++) {
                            zv[i] = readInt(items[readUnsignedShort(v)]) != 0;
                            v += 3;
                        }
                        av.visit(name, zv);
                        --v;
                        break;
                    case 'S':
                        short[] sv = new short[size];
                        for (i = 0; i < size; i++) {
                            sv[i] = (short) readInt(items[readUnsignedShort(v)]);
                            v += 3;
                        }
                        av.visit(name, sv);
                        --v;
                        break;
                    case 'C':
                        char[] cv = new char[size];
                        for (i = 0; i < size; i++) {
                            cv[i] = (char) readInt(items[readUnsignedShort(v)]);
                            v += 3;
                        }
                        av.visit(name, cv);
                        --v;
                        break;
                    case 'I':
                        int[] iv = new int[size];
                        for (i = 0; i < size; i++) {
                            iv[i] = readInt(items[readUnsignedShort(v)]);
                            v += 3;
                        }
                        av.visit(name, iv);
                        --v;
                        break;
                    case 'J':
                        long[] lv = new long[size];
                        for (i = 0; i < size; i++) {
                            lv[i] = readLong(items[readUnsignedShort(v)]);
                            v += 3;
                        }
                        av.visit(name, lv);
                        --v;
                        break;
                    case 'F':
                        float[] fv = new float[size];
                        for (i = 0; i < size; i++) {
                            fv[i] = Float.intBitsToFloat(readInt(items[readUnsignedShort(v)]));
                            v += 3;
                        }
                        av.visit(name, fv);
                        --v;
                        break;
                    case 'D':
                        double[] dv = new double[size];
                        for (i = 0; i < size; i++) {
                            dv[i] = Double.longBitsToDouble(readLong(items[readUnsignedShort(v)]));
                            v += 3;
                        }
                        av.visit(name, dv);
                        --v;
                        break;
                    default:
                        v--;
                        AnnotationVisitor aav = av.visitArray(name);
                        for (i = size; i > 0; --i) {
                            v = readAnnotationValue(v, buf, null, aav);
                        }
                        aav.visitEnd();
                }
        }
        return v;
    }

    /**
     * Returns the start index of the attribute_info structure of this class.
     * 
     * @return the start index of the attribute_info structure of this class.
     */
    private int getAttributes() {
        // skips the header
        int u = header + 8 + readUnsignedShort(header + 6) * 2;
        // skips fields and methods
        for (int i = readUnsignedShort(u); i > 0; --i) {
            for (int j = readUnsignedShort(u + 8); j > 0; --j) {
                u += 6 + readInt(u + 12);
            }
            u += 8;
        }
        u += 2;
        for (int i = readUnsignedShort(u); i > 0; --i) {
            for (int j = readUnsignedShort(u + 8); j > 0; --j) {
                u += 6 + readInt(u + 12);
            }
            u += 8;
        }
        // the attribute_info structure starts just after the methods
        return u + 2;
    }

    /**
     * Reads an attribute in {@link #b b}.
     *
     * @param attrs prototypes of the attributes that must be parsed during the
     *        visit of the class. Any attribute whose type is not equal to the
     *        type of one the prototypes is ignored (i.e. an empty
     *        {@link Attribute} instance is returned).
     * @param type the type of the attribute.
     * @param off index of the first byte of the attribute's content in
     *        {@link #b b}. The 6 attribute header bytes, containing the type
     *        and the length of the attribute, are not taken into account here
     *        (they have already been read).
     * @param len the length of the attribute's content.
     * @param buf buffer to be used to call {@link #readUTF8 readUTF8},
     *        {@link #readClass(int,char[]) readClass} or
     *        {@link #readConst readConst}.
     * @param codeOff index of the first byte of code's attribute content in
     *        {@link #b b}, or -1 if the attribute to be read is not a code
     *        attribute. The 6 attribute header bytes, containing the type and
     *        the length of the attribute, are not taken into account here.
     * @param labels the labels of the method's code, or <tt>null</tt> if the
     *        attribute to be read is not a code attribute.
     * @return the attribute that has been read, or <tt>null</tt> to skip this
     *         attribute.
     */
    private Attribute readAttribute(
        final Attribute[] attrs,
        final String type,
        final int off,
        final int len,
        final char[] buf,
        final int codeOff,
        final Label[] labels)
    {
        for (int i = 0; i < attrs.length; ++i) {
            if (attrs[i].type.equals(type)) {
                return attrs[i].read(this, off, len, buf, codeOff, labels);
            }
        }
        return new Attribute(type).read(this, off, len, null, -1, null);
    }

    // ------------------------------------------------------------------------
    // Utility methods: low level parsing
    // ------------------------------------------------------------------------

    /**
     * Returns the start index of the constant pool item in {@link #b b}, plus
     * one. <i>This method is intended for {@link Attribute} sub classes, and is
     * normally not needed by class generators or adapters.</i>
     *
     * @param item the index a constant pool item.
     * @return the start index of the constant pool item in {@link #b b}, plus
     *         one.
     */
    public int getItem(final int item) {
        return items[item];
    }

    /**
     * Reads a byte value in {@link #b b}. <i>This method is intended for
     * {@link Attribute} sub classes, and is normally not needed by class
     * generators or adapters.</i>
     *
     * @param index the start index of the value to be read in {@link #b b}.
     * @return the read value.
     */
    public int readByte(final int index) {
        return b[index] & 0xFF;
    }

    /**
     * Reads an unsigned short value in {@link #b b}. <i>This method is
     * intended for {@link Attribute} sub classes, and is normally not needed by
     * class generators or adapters.</i>
     *
     * @param index the start index of the value to be read in {@link #b b}.
     * @return the read value.
     */
    public int readUnsignedShort(final int index) {
        byte[] b = this.b;
        return ((b[index] & 0xFF) << 8) | (b[index + 1] & 0xFF);
    }

    /**
     * Reads a signed short value in {@link #b b}. <i>This method is intended
     * for {@link Attribute} sub classes, and is normally not needed by class
     * generators or adapters.</i>
     *
     * @param index the start index of the value to be read in {@link #b b}.
     * @return the read value.
     */
    public short readShort(final int index) {
        byte[] b = this.b;
        return (short) (((b[index] & 0xFF) << 8) | (b[index + 1] & 0xFF));
    }

    /**
     * Reads a signed int value in {@link #b b}. <i>This method is intended for
     * {@link Attribute} sub classes, and is normally not needed by class
     * generators or adapters.</i>
     *
     * @param index the start index of the value to be read in {@link #b b}.
     * @return the read value.
     */
    public int readInt(final int index) {
        byte[] b = this.b;
        return ((b[index] & 0xFF) << 24) | ((b[index + 1] & 0xFF) << 16)
                | ((b[index + 2] & 0xFF) << 8) | (b[index + 3] & 0xFF);
    }

    /**
     * Reads a signed long value in {@link #b b}. <i>This method is intended
     * for {@link Attribute} sub classes, and is normally not needed by class
     * generators or adapters.</i>
     *
     * @param index the start index of the value to be read in {@link #b b}.
     * @return the read value.
     */
    public long readLong(final int index) {
        long l1 = readInt(index);
        long l0 = readInt(index + 4) & 0xFFFFFFFFL;
        return (l1 << 32) | l0;
    }

    /**
     * Reads an UTF8 string constant pool item in {@link #b b}. <i>This method
     * is intended for {@link Attribute} sub classes, and is normally not needed
     * by class generators or adapters.</i>
     *
     * @param index the start index of an unsigned short value in {@link #b b},
     *        whose value is the index of an UTF8 constant pool item.
     * @param buf buffer to be used to read the item. This buffer must be
     *        sufficiently large. It is not automatically resized.
     * @return the String corresponding to the specified UTF8 item.
     */
    public String readUTF8(int index, final char[] buf) {
        int item = readUnsignedShort(index);
        String s = strings[item];
        if (s != null) {
            return s;
        }
        index = items[item];
        return strings[item] = readUTF(index + 2, readUnsignedShort(index), buf);
    }

    /**
     * Reads UTF8 string in {@link #b b}.
     *
     * @param index start offset of the UTF8 string to be read.
     * @param utfLen length of the UTF8 string to be read.
     * @param buf buffer to be used to read the string. This buffer must be
     *        sufficiently large. It is not automatically resized.
     * @return the String corresponding to the specified UTF8 string.
     */
    private String readUTF(int index, int utfLen, char[] buf) {
        int endIndex = index + utfLen;
        byte[] b = this.b;
        int strLen = 0;
        int c, d, e;
        while (index < endIndex) {
            c = b[index++] & 0xFF;
            switch (c >> 4) {
                case 0:
                case 1:
                case 2:
                case 3:
                case 4:
                case 5:
                case 6:
                case 7:
                    // 0xxxxxxx
                    buf[strLen++] = (char) c;
                    break;
                case 12:
                case 13:
                    // 110x xxxx 10xx xxxx
                    d = b[index++];
                    buf[strLen++] = (char) (((c & 0x1F) << 6) | (d & 0x3F));
                    break;
                default:
                    // 1110 xxxx 10xx xxxx 10xx xxxx
                    d = b[index++];
                    e = b[index++];
                    buf[strLen++] = (char) (((c & 0x0F) << 12)
                            | ((d & 0x3F) << 6) | (e & 0x3F));
                    break;
            }
        }
        return new String(buf, 0, strLen);
    }

    /**
     * Reads a class constant pool item in {@link #b b}. <i>This method is
     * intended for {@link Attribute} sub classes, and is normally not needed by
     * class generators or adapters.</i>
     *
     * @param index the start index of an unsigned short value in {@link #b b},
     *        whose value is the index of a class constant pool item.
     * @param buf buffer to be used to read the item. This buffer must be
     *        sufficiently large. It is not automatically resized.
     * @return the String corresponding to the specified class item.
     */
    public String readClass(final int index, final char[] buf) {
        // computes the start index of the CONSTANT_Class item in b
        // and reads the CONSTANT_Utf8 item designated by
        // the first two bytes of this CONSTANT_Class item
        return readUTF8(items[readUnsignedShort(index)], buf);
    }

    /**
     * Reads a numeric or string constant pool item in {@link #b b}. <i>This
     * method is intended for {@link Attribute} sub classes, and is normally not
     * needed by class generators or adapters.</i>
     *
     * @param item the index of a constant pool item.
     * @param buf buffer to be used to read the item. This buffer must be
     *        sufficiently large. It is not automatically resized.
     * @return the {@link Integer}, {@link Float}, {@link Long},
     *         {@link Double}, {@link String} or {@link Type} corresponding to
     *         the given constant pool item.
     */
    public Object readConst(final int item, final char[] buf) {
        int index = items[item];
        switch (b[index - 1]) {
            case ClassWriter.INT:
                return new Integer(readInt(index));
            case ClassWriter.FLOAT:
                return new Float(Float.intBitsToFloat(readInt(index)));
            case ClassWriter.LONG:
                return new Long(readLong(index));
            case ClassWriter.DOUBLE:
                return new Double(Double.longBitsToDouble(readLong(index)));
            case ClassWriter.CLASS:
                String s = readUTF8(index, buf);
                return Type.getType(s.charAt(0) == '[' ? s : "L" + s + ";");
            case ClassWriter.STR:
                return readUTF8(index, buf);
            case ClassWriter.MTYPE:
                return Type.getMethodType(readUTF8(index, buf));
            default: // case ClassWriter.HANDLE_BASE + [1..9]:
                int tag = readByte(index);
                int[] items = this.items;
                int cpIndex = items[readUnsignedShort(index + 1)];
                String owner = readClass(cpIndex, buf);
                cpIndex = items[readUnsignedShort(cpIndex + 2)];
                String name = readUTF8(cpIndex, buf);
                String desc = readUTF8(cpIndex + 2, buf);
                return new Handle(tag, owner, name, desc);
        }
    }
}