/***
 * 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.mvel2.asm;

import java.io.IOException;
import java.io.InputStream;

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

  /**
   * 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 final static 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 final static 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 final static 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 final static 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 performances (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;

  // ------------------------------------------------------------------------
  // 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 n = items.length;
    strings = new String[n];
    int max = 0;
    int index = off + 10;
    for (int i = 1; i < n; ++i) {
      items[i] = index + 1;
      int size;
      switch (b[index]) {
        case ClassWriter.FIELD:
        case ClassWriter.METH:
        case ClassWriter.IMETH:
        case ClassWriter.INT:
        case ClassWriter.FLOAT:
        case ClassWriter.NAME_TYPE:
          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.CLASS:
        // case ClassWriter.STR:
        default:
          size = 3;
          break;
      }
      index += size;
    }
    maxStringLength = max;
    // the class header information starts just after the constant pool
    header = index;
  }

  /**
   * Returns the class's access flags (see {@link Opcodes}). This value may
   * not reflect Deprecated and Synthetic flags when bytecode is before 1.5
   * and those flags are represented by attributes.
   *
   * @return the class access flags
   * @see ClassVisitor#visit(int, int, String, String, String, String[])
   */
  public int getAccess() {
    return readUnsignedShort(header);
  }

  /**
   * Returns the internal name of the class (see
   * {@link Type#getInternalName() getInternalName}).
   *
   * @return the internal class name
   * @see ClassVisitor#visit(int, int, String, String, String, String[])
   */
  public String getClassName() {
    return readClass(header + 2, new char[maxStringLength]);
  }

  /**
   * Returns the internal of name of the super class (see
   * {@link Type#getInternalName() getInternalName}). For interfaces, the
   * super class is {@link Object}.
   *
   * @return the internal name of super class, or <tt>null</tt> for
   *         {@link Object} class.
   * @see ClassVisitor#visit(int, int, String, String, String, String[])
   */
  public String getSuperName() {
    int n = items[readUnsignedShort(header + 4)];
    return n == 0 ? null : readUTF8(n, new char[maxStringLength]);
  }

  /**
   * Returns the internal names of the class's interfaces (see
   * {@link Type#getInternalName() getInternalName}).
   *
   * @return the array of internal names for all implemented interfaces or
   *         <tt>null</tt>.
   * @see ClassVisitor#visit(int, int, String, String, String, String[])
   */
  public String[] getInterfaces() {
    int index = header + 6;
    int n = readUnsignedShort(index);
    String[] interfaces = new String[n];
    if (n > 0) {
      char[] buf = new char[maxStringLength];
      for (int i = 0; i < n; ++i) {
        index += 2;
        interfaces[i] = readClass(index, buf);
      }
    }
    return interfaces;
  }

  /**
   * Copies the constant pool data into the given {@link ClassWriter}. Should
   * be called before the {@link #accept(ClassVisitor, int)} 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.STR:
        // case ClassWriter.CLASS:
        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;
  }

  /**
   * 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) {
        byte[] c = new byte[b.length + 1000];
        System.arraycopy(b, 0, c, 0, len);
        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 flags        option flags that can be used to modify the default behavior
   *                     of this class. See {@link #SKIP_DEBUG}, {@link #EXPAND_FRAMES}.
   */
  public void accept(final ClassVisitor classVisitor, final int flags) {
    accept(classVisitor, new Attribute[0], flags);
  }

  /**
   * 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 not be parsed: its byte array
   *                     value will be passed unchanged to the ClassWriter. <i>This may
   *                     corrupt it if this value contains references to the constant pool,
   *                     or has syntactic or semantic links with a class element that has
   *                     been transformed by a class adapter between the reader and the
   *                     writer</i>.
   * @param flags        option flags that can be used to modify the default behavior
   *                     of this class. See {@link #SKIP_DEBUG}, {@link #EXPAND_FRAMES}.
   */
  public void accept(
      final ClassVisitor classVisitor,
      final Attribute[] attrs,
      final int flags) {
    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;
    Attribute cattrs = null;

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

    boolean skipCode = (flags & SKIP_CODE) != 0;
    boolean skipDebug = (flags & SKIP_DEBUG) != 0;
    boolean unzip = (flags & EXPAND_FRAMES) != 0;

    // skips fields and methods
    v = u;
    i = readUnsignedShort(v);
    v += 2;
    for (; i > 0; --i) {
      j = readUnsignedShort(v + 6);
      v += 8;
      for (; j > 0; --j) {
        v += 6 + readInt(v + 2);
      }
    }
    i = readUnsignedShort(v);
    v += 2;
    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);
    v += 2;
    for (; i > 0; --i) {
      attrName = readUTF8(v, c);
      // tests are sorted in decreasing frequency order
      // (based on frequencies observed on typical classes)
      if (attrName.equals("SourceFile")) {
        sourceFile = readUTF8(v + 6, c);
      }
      else if (attrName.equals("InnerClasses")) {
        w = v + 6;
      }
      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("Signature")) {
        signature = readUTF8(v + 6, c);
      }
      else if (attrName.equals("RuntimeVisibleAnnotations")) {
        anns = v + 6;
      }
      else if (attrName.equals("Deprecated")) {
        access |= Opcodes.ACC_DEPRECATED;
      }
      else if (attrName.equals("Synthetic")) {
        access |= Opcodes.ACC_SYNTHETIC;
      }
      else if (attrName.equals("SourceDebugExtension")) {
        int len = readInt(v + 2);
        sourceDebug = readUTF(v + 6, len, new char[len]);
      }
      else if (attrName.equals("RuntimeInvisibleAnnotations")) {
        ianns = v + 6;
      }
      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 (!skipDebug && (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) {
          v = readAnnotationValues(v + 2,
              c,
              true,
              classVisitor.visitAnnotation(readUTF8(v, c), i != 0));
        }
      }
    }

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

    // calls 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
    i = readUnsignedShort(u);
    u += 2;
    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;
      cattrs = null;

      j = readUnsignedShort(u + 6);
      u += 8;
      for (; j > 0; --j) {
        attrName = readUTF8(u, c);
        // tests are sorted in decreasing frequency order
        // (based on frequencies observed on typical classes)
        if (attrName.equals("ConstantValue")) {
          fieldValueItem = readUnsignedShort(u + 6);
        }
        else if (attrName.equals("Signature")) {
          signature = readUTF8(u + 6, c);
        }
        else if (attrName.equals("Deprecated")) {
          access |= Opcodes.ACC_DEPRECATED;
        }
        else if (attrName.equals("Synthetic")) {
          access |= Opcodes.ACC_SYNTHETIC;
        }
        else if (attrName.equals("RuntimeVisibleAnnotations")) {
          anns = u + 6;
        }
        else if (attrName.equals("RuntimeInvisibleAnnotations")) {
          ianns = 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);
      }
      // visits the field
      FieldVisitor fv = classVisitor.visitField(access,
          name,
          desc,
          signature,
          fieldValueItem == 0 ? null : readConst(fieldValueItem, c));
      // 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) {
              v = readAnnotationValues(v + 2,
                  c,
                  true,
                  fv.visitAnnotation(readUTF8(v, c), j != 0));
            }
          }
        }
        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;
      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);
        int attrSize = readInt(u + 2);
        u += 6;
        // tests are sorted in decreasing frequency order
        // (based on frequencies observed on typical classes)
        if (attrName.equals("Code")) {
          if (!skipCode) {
            v = u;
          }
        }
        else if (attrName.equals("Exceptions")) {
          w = u;
        }
        else if (attrName.equals("Signature")) {
          signature = readUTF8(u, c);
        }
        else if (attrName.equals("Deprecated")) {
          access |= Opcodes.ACC_DEPRECATED;
        }
        else if (attrName.equals("RuntimeVisibleAnnotations")) {
          anns = u;
        }
        else if (attrName.equals("AnnotationDefault")) {
          dann = u;
        }
        else if (attrName.equals("Synthetic")) {
          access |= Opcodes.ACC_SYNTHETIC;
        }
        else if (attrName.equals("RuntimeInvisibleAnnotations")) {
          ianns = 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);
          if (dv != null) {
            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) {
              w = readAnnotationValues(w + 2,
                  c,
                  true,
                  mv.visitAnnotation(readUTF8(w, c), 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);
              if (labels[label] == null) {
                labels[label] = new Label();
              }
              j = readInt(v + 8) - readInt(v + 4) + 1;
              v += 12;
              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);
              if (labels[label] == null) {
                labels[label] = new Label();
              }
              j = readInt(v + 4);
              v += 8;
              for (; j > 0; --j) {
                label = w + readInt(v + 4);
                v += 8;
                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:
              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;
        int stackMap = 0;
        int frameCount = 0;
        int frameMode = 0;
        int frameOffset = 0;
        int frameLocalCount = 0;
        int frameLocalDiff = 0;
        int frameStackCount = 0;
        Object[] frameLocal = null;
        Object[] frameStack = null;
        boolean zip = true;
        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(true);
                }
                label += readUnsignedShort(w + 2);
                if (labels[label] == null) {
                  labels[label] = new Label(true);
                }
                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(true);
                }
                labels[label].line = readUnsignedShort(w + 2);
                w += 4;
              }
            }
          }
          else if (attrName.equals("StackMapTable")) {
            if ((flags & SKIP_FRAMES) == 0) {
              stackMap = v + 8;
              frameCount = readUnsignedShort(v + 6);
            }
            /*
            * here we do not extract the labels corresponding to
            * the attribute content. This would require a full
            * parsing of the attribute, which would need to be
            * repeated in the second phase (see below). Instead the
            * content of the attribute is read one frame at a time
            * (i.e. after a frame has been visited, the next frame
            * is read), and the labels it contains are also
            * extracted one frame at a time. Thanks to the ordering
            * of frames, having only a "one frame lookahead" is not
            * a problem, i.e. it is not possible to see an offset
            * smaller than the offset of the current insn and for
            * which no Label exist.
            */
            //
            // but for UNINITIALIZED type offsets?
          }
          else if (attrName.equals("StackMap")) {
            if ((flags & SKIP_FRAMES) == 0) {
              stackMap = v + 8;
              frameCount = readUnsignedShort(v + 6);
              zip = false;
            }
            /*
            * IMPORTANT! here we assume that the frames are
            * ordered, as in the StackMapTable attribute, although
            * this is not guaranteed by the attribute format.
            */
          }
          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
        if (stackMap != 0) {
          // creates the very first (implicit) frame from the method
          // descriptor
          frameLocal = new Object[maxLocals];
          frameStack = new Object[maxStack];
          if (unzip) {
            int local = 0;
            if ((access & Opcodes.ACC_STATIC) == 0) {
              if (name.equals("<init>")) {
                frameLocal[local++] = Opcodes.UNINITIALIZED_THIS;
              }
              else {
                frameLocal[local++] = readClass(header + 2, c);
              }
            }
            j = 1;
            loop:
            while (true) {
              k = j;
              switch (desc.charAt(j++)) {
                case 'Z':
                case 'C':
                case 'B':
                case 'S':
                case 'I':
                  frameLocal[local++] = Opcodes.INTEGER;
                  break;
                case 'F':
                  frameLocal[local++] = Opcodes.FLOAT;
                  break;
                case 'J':
                  frameLocal[local++] = Opcodes.LONG;
                  break;
                case 'D':
                  frameLocal[local++] = Opcodes.DOUBLE;
                  break;
                case '[':
                  while (desc.charAt(j) == '[') {
                    ++j;
                  }
                  if (desc.charAt(j) == 'L') {
                    ++j;
                    while (desc.charAt(j) != ';') {
                      ++j;
                    }
                  }
                  frameLocal[local++] = desc.substring(k, ++j);
                  break;
                case 'L':
                  while (desc.charAt(j) != ';') {
                    ++j;
                  }
                  frameLocal[local++] = desc.substring(k + 1,
                      j++);
                  break;
                default:
                  break loop;
              }
            }
            frameLocalCount = local;
          }
          /*
          * for the first explicit frame the offset is not
          * offset_delta + 1 but only offset_delta; setting the
          * implicit frame offset to -1 allow the use of the
          * "offset_delta + 1" rule in all cases
          */
          frameOffset = -1;
        }
        v = codeStart;
        Label l;
        while (v < codeEnd) {
          w = v - codeStart;

          l = labels[w];
          if (l != null) {
            mv.visitLabel(l);
            if (!skipDebug && l.line > 0) {
              mv.visitLineNumber(l.line, l);
            }
          }

          while (frameLocal != null
              && (frameOffset == w || frameOffset == -1)) {
            // if there is a frame for this offset,
            // makes the visitor visit it,
            // and reads the next frame if there is one.
            if (!zip || unzip) {
              mv.visitFrame(Opcodes.F_NEW,
                  frameLocalCount,
                  frameLocal,
                  frameStackCount,
                  frameStack);
            }
            else if (frameOffset != -1) {
              mv.visitFrame(frameMode,
                  frameLocalDiff,
                  frameLocal,
                  frameStackCount,
                  frameStack);
            }

            if (frameCount > 0) {
              int tag, delta, n;
              if (zip) {
                tag = b[stackMap++] & 0xFF;
              }
              else {
                tag = MethodWriter.FULL_FRAME;
                frameOffset = -1;
              }
              frameLocalDiff = 0;
              if (tag < MethodWriter.SAME_LOCALS_1_STACK_ITEM_FRAME) {
                delta = tag;
                frameMode = Opcodes.F_SAME;
                frameStackCount = 0;
              }
              else if (tag < MethodWriter.RESERVED) {
                delta = tag
                    - MethodWriter.SAME_LOCALS_1_STACK_ITEM_FRAME;
                stackMap = readFrameType(frameStack,
                    0,
                    stackMap,
                    c,
                    labels);
                frameMode = Opcodes.F_SAME1;
                frameStackCount = 1;
              }
              else {
                delta = readUnsignedShort(stackMap);
                stackMap += 2;
                if (tag == MethodWriter.SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED) {
                  stackMap = readFrameType(frameStack,
                      0,
                      stackMap,
                      c,
                      labels);
                  frameMode = Opcodes.F_SAME1;
                  frameStackCount = 1;
                }
                else if (tag >= MethodWriter.CHOP_FRAME
                    && tag < MethodWriter.SAME_FRAME_EXTENDED) {
                  frameMode = Opcodes.F_CHOP;
                  frameLocalDiff = MethodWriter.SAME_FRAME_EXTENDED
                      - tag;
                  frameLocalCount -= frameLocalDiff;
                  frameStackCount = 0;
                }
                else if (tag == MethodWriter.SAME_FRAME_EXTENDED) {
                  frameMode = Opcodes.F_SAME;
                  frameStackCount = 0;
                }
                else if (tag < MethodWriter.FULL_FRAME) {
                  j = unzip ? frameLocalCount : 0;
                  for (k = tag
                      - MethodWriter.SAME_FRAME_EXTENDED; k > 0; k--) {
                    stackMap = readFrameType(frameLocal,
                        j++,
                        stackMap,
                        c,
                        labels);
                  }
                  frameMode = Opcodes.F_APPEND;
                  frameLocalDiff = tag
                      - MethodWriter.SAME_FRAME_EXTENDED;
                  frameLocalCount += frameLocalDiff;
                  frameStackCount = 0;
                }
                else { // if (tag == FULL_FRAME) {
                  frameMode = Opcodes.F_FULL;
                  n = frameLocalDiff = frameLocalCount = readUnsignedShort(stackMap);
                  stackMap += 2;
                  for (j = 0; n > 0; n--) {
                    stackMap = readFrameType(frameLocal,
                        j++,
                        stackMap,
                        c,
                        labels);
                  }
                  n = frameStackCount = readUnsignedShort(stackMap);
                  stackMap += 2;
                  for (j = 0; n > 0; n--) {
                    stackMap = readFrameType(frameStack,
                        j++,
                        stackMap,
                        c,
                        labels);
                  }
                }
              }
              frameOffset += delta + 1;
              if (labels[frameOffset] == null) {
                labels[frameOffset] = new Label();
              }

              --frameCount;
            }
            else {
              frameLocal = null;
            }
          }

          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);
              int min = readInt(v + 4);
              int max = readInt(v + 8);
              v += 12;
              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);
              j = readInt(v + 4);
              v += 8;
              int[] keys = new int[j];
              Label[] values = new Label[j];
              for (j = 0; j < keys.length; ++j) {
                keys[j] = readInt(v);
                values[j] = labels[w + readInt(v + 4)];
                v += 8;
              }
              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 MANA_INSN:
            default:
              mv.visitMultiANewArrayInsn(readClass(v + 1, c),
                  b[v + 3] & 0xFF);
              v += 4;
              break;
          }
        }
        l = labels[codeEnd - codeStart];
        if (l != null) {
          mv.visitLabel(l);
        }
        // visits the local variable tables
        if (!skipDebug && varTable != 0) {
          int[] typeTable = null;
          if (varTypeTable != 0) {
            k = readUnsignedShort(varTypeTable) * 3;
            w = varTypeTable + 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;
            }
          }
          k = readUnsignedShort(varTable);
          w = varTable + 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) {
        v = readAnnotationValues(v + 2,
            buf,
            true,
            mv.visitParameterAnnotation(i,
                readUTF8(v, buf),
                visible));
      }
    }
  }

  /**
   * 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 named if the annotation values are named or not.
   * @param av    the visitor that must visit the values.
   * @return the end offset of the annotation values.
   */
  private int readAnnotationValues(
      int v,
      final char[] buf,
      final boolean named,
      final AnnotationVisitor av) {
    int i = readUnsignedShort(v);
    v += 2;
    if (named) {
      for (; i > 0; --i) {
        v = readAnnotationValue(v + 2, buf, readUTF8(v, buf), av);
      }
    }
    else {
      for (; i > 0; --i) {
        v = readAnnotationValue(v, buf, null, av);
      }
    }
    if (av != null) {
      av.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;
    if (av == null) {
      switch (b[v] & 0xFF) {
        case 'e': // enum_const_value
          return v + 5;
        case '@': // annotation_value
          return readAnnotationValues(v + 3, buf, true, null);
        case '[': // array_value
          return readAnnotationValues(v + 1, buf, false, null);
        default:
          return v + 3;
      }
    }
    switch (b[v++] & 0xFF) {
      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
        av.visit(name, readInt(items[readUnsignedShort(v)]) == 0
            ? 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
        v = readAnnotationValues(v + 2,
            buf,
            true,
            av.visitAnnotation(name, readUTF8(v, buf)));
        break;
      case '[': // array_value
        int size = readUnsignedShort(v);
        v += 2;
        if (size == 0) {
          return readAnnotationValues(v - 2,
              buf,
              false,
              av.visitArray(name));
        }
        switch (this.b[v++] & 0xFF) {
          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 = readAnnotationValues(v - 3,
                buf,
                false,
                av.visitArray(name));
        }
    }
    return v;
  }

  private int readFrameType(
      final Object[] frame,
      final int index,
      int v,
      final char[] buf,
      final Label[] labels) {
    int type = b[v++] & 0xFF;
    switch (type) {
      case 0:
        frame[index] = Opcodes.TOP;
        break;
      case 1:
        frame[index] = Opcodes.INTEGER;
        break;
      case 2:
        frame[index] = Opcodes.FLOAT;
        break;
      case 3:
        frame[index] = Opcodes.DOUBLE;
        break;
      case 4:
        frame[index] = Opcodes.LONG;
        break;
      case 5:
        frame[index] = Opcodes.NULL;
        break;
      case 6:
        frame[index] = Opcodes.UNINITIALIZED_THIS;
        break;
      case 7: // Object
        frame[index] = readClass(v, buf);
        v += 2;
        break;
      default: // Uninitialized
        int offset = readUnsignedShort(v);
        if (labels[offset] == null) {
          labels[offset] = new Label();
        }
        frame[index] = labels[offset];
        v += 2;
    }
    return v;
  }

  /**
   * 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, final int utfLen, final 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 s.charAt(0) == '['
            ? Type.getType(s)
            : Type.getObjectType(s);
      // case ClassWriter.STR:
      default:
        return readUTF8(index, buf);
    }
  }
}
