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

/**
 * A {@link MethodVisitor} that generates methods in bytecode form. Each visit
 * method of this class appends the bytecode corresponding to the visited
 * instruction to a byte vector, in the order these methods are called.
 *
 * @author Eric Bruneton
 * @author Eugene Kuleshov
 */
class MethodWriter implements MethodVisitor {

  /**
   * Pseudo access flag used to denote constructors.
   */
  final static int ACC_CONSTRUCTOR = 262144;

  /**
   * Frame has exactly the same locals as the previous stack map frame and
   * number of stack items is zero.
   */
  final static int SAME_FRAME = 0; // to 63 (0-3f)

  /**
   * Frame has exactly the same locals as the previous stack map frame and
   * number of stack items is 1
   */
  final static int SAME_LOCALS_1_STACK_ITEM_FRAME = 64; // to 127 (40-7f)

  /**
   * Reserved for future use
   */
  final static int RESERVED = 128;

  /**
   * Frame has exactly the same locals as the previous stack map frame and
   * number of stack items is 1. Offset is bigger then 63;
   */
  final static int SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED = 247; // f7

  /**
   * Frame where current locals are the same as the locals in the previous
   * frame, except that the k last locals are absent. The value of k is given
   * by the formula 251-frame_type.
   */
  final static int CHOP_FRAME = 248; // to 250 (f8-fA)

  /**
   * Frame has exactly the same locals as the previous stack map frame and
   * number of stack items is zero. Offset is bigger then 63;
   */
  final static int SAME_FRAME_EXTENDED = 251; // fb

  /**
   * Frame where current locals are the same as the locals in the previous
   * frame, except that k additional locals are defined. The value of k is
   * given by the formula frame_type-251.
   */
  final static int APPEND_FRAME = 252; // to 254 // fc-fe

  /**
   * Full frame
   */
  final static int FULL_FRAME = 255; // ff

  /**
   * Indicates that the stack map frames must be recomputed from scratch. In
   * this case the maximum stack size and number of local variables is also
   * recomputed from scratch.
   *
   * @see #compute
   */
  private final static int FRAMES = 0;

  /**
   * Indicates that the maximum stack size and number of local variables must
   * be automatically computed.
   *
   * @see #compute
   */
  private final static int MAXS = 1;

  /**
   * Indicates that nothing must be automatically computed.
   *
   * @see #compute
   */
  private final static int NOTHING = 2;

  /**
   * Next method writer (see {@link ClassWriter#firstMethod firstMethod}).
   */
  MethodWriter next;

  /**
   * The class writer to which this method must be added.
   */
  ClassWriter cw;

  /**
   * Access flags of this method.
   */
  private int access;

  /**
   * The index of the constant pool item that contains the name of this
   * method.
   */
  private int name;

  /**
   * The index of the constant pool item that contains the descriptor of this
   * method.
   */
  private int desc;

  /**
   * The descriptor of this method.
   */
  private String descriptor;

  /**
   * The signature of this method.
   */
  String signature;

  /**
   * If not zero, indicates that the code of this method must be copied from
   * the ClassReader associated to this writer in <code>cw.cr</code>. More
   * precisely, this field gives the index of the first byte to copied from
   * <code>cw.cr.b</code>.
   */
  int classReaderOffset;

  /**
   * If not zero, indicates that the code of this method must be copied from
   * the ClassReader associated to this writer in <code>cw.cr</code>. More
   * precisely, this field gives the number of bytes to copied from
   * <code>cw.cr.b</code>.
   */
  int classReaderLength;

  /**
   * Number of exceptions that can be thrown by this method.
   */
  int exceptionCount;

  /**
   * The exceptions that can be thrown by this method. More precisely, this
   * array contains the indexes of the constant pool items that contain the
   * internal names of these exception classes.
   */
  int[] exceptions;

  /**
   * The annotation default attribute of this method. May be <tt>null</tt>.
   */
  private ByteVector annd;

  /**
   * The runtime visible annotations of this method. May be <tt>null</tt>.
   */
  private AnnotationWriter anns;

  /**
   * The runtime invisible annotations of this method. May be <tt>null</tt>.
   */
  private AnnotationWriter ianns;

  /**
   * The runtime visible parameter annotations of this method. May be
   * <tt>null</tt>.
   */
  private AnnotationWriter[] panns;

  /**
   * The runtime invisible parameter annotations of this method. May be
   * <tt>null</tt>.
   */
  private AnnotationWriter[] ipanns;

  /**
   * The non standard attributes of the method.
   */
  private Attribute attrs;

  /**
   * The bytecode of this method.
   */
  private ByteVector code = new ByteVector();

  /**
   * Maximum stack size of this method.
   */
  private int maxStack;

  /**
   * Maximum number of local variables for this method.
   */
  private int maxLocals;

  /**
   * Number of stack map frames in the StackMapTable attribute.
   */
  private int frameCount;

  /**
   * The StackMapTable attribute.
   */
  private ByteVector stackMap;

  /**
   * The offset of the last frame that was written in the StackMapTable
   * attribute.
   */
  private int previousFrameOffset;

  /**
   * The last frame that was written in the StackMapTable attribute.
   *
   * @see #frame
   */
  private int[] previousFrame;

  /**
   * Index of the next element to be added in {@link #frame}.
   */
  private int frameIndex;

  /**
   * The current stack map frame. The first element contains the offset of the
   * instruction to which the frame corresponds, the second element is the
   * number of locals and the third one is the number of stack elements. The
   * local variables start at index 3 and are followed by the operand stack
   * values. In summary frame[0] = offset, frame[1] = nLocal, frame[2] =
   * nStack, frame[3] = nLocal. All types are encoded as integers, with the
   * same format as the one used in {@link Label}, but limited to BASE types.
   */
  private int[] frame;

  /**
   * Number of elements in the exception handler list.
   */
  private int handlerCount;

  /**
   * The first element in the exception handler list.
   */
  private Handler firstHandler;

  /**
   * The last element in the exception handler list.
   */
  private Handler lastHandler;

  /**
   * Number of entries in the LocalVariableTable attribute.
   */
  private int localVarCount;

  /**
   * The LocalVariableTable attribute.
   */
  private ByteVector localVar;

  /**
   * Number of entries in the LocalVariableTypeTable attribute.
   */
  private int localVarTypeCount;

  /**
   * The LocalVariableTypeTable attribute.
   */
  private ByteVector localVarType;

  /**
   * Number of entries in the LineNumberTable attribute.
   */
  private int lineNumberCount;

  /**
   * The LineNumberTable attribute.
   */
  private ByteVector lineNumber;

  /**
   * The non standard attributes of the method's code.
   */
  private Attribute cattrs;

  /**
   * Indicates if some jump instructions are too small and need to be resized.
   */
  private boolean resize;

  /**
   * Indicates if the instructions contain at least one JSR instruction.
   */
  private boolean jsr;

  // ------------------------------------------------------------------------

  /*
  * Fields for the control flow graph analysis algorithm (used to compute the
  * maximum stack size). A control flow graph contains one node per "basic
  * ast", and one edge per "jump" from one basic ast to another. Each
  * node (i.e., each basic ast) is represented by the Label object that
  * corresponds to the first instruction of this basic ast. Each node also
  * stores the list of its successors in the graph, as a linked list of Edge
  * objects.
  */

  /**
   * Indicates what must be automatically computed.
   */
  private int compute;

  /**
   * A list of labels. This list is the list of basic blocks in the method,
   * i.e. a list of Label objects linked to each other by their
   * {@link Label#successor} field, in the order they are visited by
   * and starting with the first basic ast.
   */
  private Label labels;

  /**
   * The previous basic ast.
   */
  private Label previousBlock;

  /**
   * The current basic ast.
   */
  private Label currentBlock;

  /**
   * The (relative) stack size after the last visited instruction. This size
   * is relative to the beginning of the current basic ast, i.e., the true
   * stack size after the last visited instruction is equal to the
   * {@link Label#inputStackTop beginStackSize} of the current basic ast
   * plus <tt>stackSize</tt>.
   */
  private int stackSize;

  /**
   * The (relative) maximum stack size after the last visited instruction.
   * This size is relative to the beginning of the current basic ast, i.e.,
   * the true maximum stack size after the last visited instruction is equal
   * to the {@link Label#inputStackTop beginStackSize} of the current basic
   * ast plus <tt>stackSize</tt>.
   */
  private int maxStackSize;

  // ------------------------------------------------------------------------
  // Constructor
  // ------------------------------------------------------------------------

  /**
   * Constructs a new {@link MethodWriter}.
   *
   * @param cw            the class writer in which the method must be added.
   * @param access        the method's access flags (see {@link Opcodes}).
   * @param name          the method's name.
   * @param desc          the method's descriptor (see {@link Type}).
   * @param signature     the method's signature. May be <tt>null</tt>.
   * @param exceptions    the internal names of the method's exceptions. May be
   *                      <tt>null</tt>.
   * @param computeMaxs   <tt>true</tt> if the maximum stack size and number
   *                      of local variables must be automatically computed.
   * @param computeFrames <tt>true</tt> if the stack map tables must be
   *                      recomputed from scratch.
   */
  MethodWriter(
      final ClassWriter cw,
      final int access,
      final String name,
      final String desc,
      final String signature,
      final String[] exceptions,
      final boolean computeMaxs,
      final boolean computeFrames) {
    if (cw.firstMethod == null) {
      cw.firstMethod = this;
    }
    else {
      cw.lastMethod.next = this;
    }
    cw.lastMethod = this;
    this.cw = cw;
    this.access = access;
    this.name = cw.newUTF8(name);
    this.desc = cw.newUTF8(desc);
    this.descriptor = desc;
    this.signature = signature;
    if (exceptions != null && exceptions.length > 0) {
      exceptionCount = exceptions.length;
      this.exceptions = new int[exceptionCount];
      for (int i = 0; i < exceptionCount; ++i) {
        this.exceptions[i] = cw.newClass(exceptions[i]);
      }
    }
    this.compute = computeFrames ? FRAMES : (computeMaxs ? MAXS : NOTHING);
    if (computeMaxs || computeFrames) {
      if (computeFrames && name.equals("<init>")) {
        this.access |= ACC_CONSTRUCTOR;
      }
      // updates maxLocals
      int size = getArgumentsAndReturnSizes(descriptor) >> 2;
      if ((access & Opcodes.ACC_STATIC) != 0) {
        --size;
      }
      maxLocals = size;
      // creates and visits the label for the first basic ast
      labels = new Label();
      labels.status |= Label.PUSHED;
      visitLabel(labels);
    }
  }

  // ------------------------------------------------------------------------
  // Implementation of the MethodVisitor interface
  // ------------------------------------------------------------------------

  public AnnotationVisitor visitAnnotationDefault() {
    annd = new ByteVector();
    return new AnnotationWriter(cw, false, annd, null, 0);
  }

  public AnnotationVisitor visitAnnotation(
      final String desc,
      final boolean visible) {
    ByteVector bv = new ByteVector();
    // write type, and reserve space for values count
    bv.putShort(cw.newUTF8(desc)).putShort(0);
    AnnotationWriter aw = new AnnotationWriter(cw, true, bv, bv, 2);
    if (visible) {
      aw.next = anns;
      anns = aw;
    }
    else {
      aw.next = ianns;
      ianns = aw;
    }
    return aw;
  }

  public AnnotationVisitor visitParameterAnnotation(
      final int parameter,
      final String desc,
      final boolean visible) {
    ByteVector bv = new ByteVector();
    // write type, and reserve space for values count
    bv.putShort(cw.newUTF8(desc)).putShort(0);
    AnnotationWriter aw = new AnnotationWriter(cw, true, bv, bv, 2);
    if (visible) {
      if (panns == null) {
        panns = new AnnotationWriter[Type.getArgumentTypes(descriptor).length];
      }
      aw.next = panns[parameter];
      panns[parameter] = aw;
    }
    else {
      if (ipanns == null) {
        ipanns = new AnnotationWriter[Type.getArgumentTypes(descriptor).length];
      }
      aw.next = ipanns[parameter];
      ipanns[parameter] = aw;
    }
    return aw;
  }

  public void visitAttribute(final Attribute attr) {
    if (attr.isCodeAttribute()) {
      attr.next = cattrs;
      cattrs = attr;
    }
    else {
      attr.next = attrs;
      attrs = attr;
    }
  }

  public void visitCode() {
  }

  public void visitFrame(
      final int type,
      final int nLocal,
      final Object[] local,
      final int nStack,
      final Object[] stack) {
    if (compute == FRAMES) {
      return;
    }

    if (type == Opcodes.F_NEW) {
      startFrame(code.length, nLocal, nStack);
      for (int i = 0; i < nLocal; ++i) {
        if (local[i] instanceof String) {
          frame[frameIndex++] = Frame.OBJECT
              | cw.addType((String) local[i]);
        }
        else if (local[i] instanceof Integer) {
          frame[frameIndex++] = ((Integer) local[i]).intValue();
        }
        else {
          frame[frameIndex++] = Frame.UNINITIALIZED
              | cw.addUninitializedType("",
              ((Label) local[i]).position);
        }
      }
      for (int i = 0; i < nStack; ++i) {
        if (stack[i] instanceof String) {
          frame[frameIndex++] = Frame.OBJECT
              | cw.addType((String) stack[i]);
        }
        else if (stack[i] instanceof Integer) {
          frame[frameIndex++] = ((Integer) stack[i]).intValue();
        }
        else {
          frame[frameIndex++] = Frame.UNINITIALIZED
              | cw.addUninitializedType("",
              ((Label) stack[i]).position);
        }
      }
      endFrame();
    }
    else {
      int delta;
      if (stackMap == null) {
        stackMap = new ByteVector();
        delta = code.length;
      }
      else {
        delta = code.length - previousFrameOffset - 1;
      }

      switch (type) {
        case Opcodes.F_FULL:
          stackMap.putByte(FULL_FRAME)
              .putShort(delta)
              .putShort(nLocal);
          for (int i = 0; i < nLocal; ++i) {
            writeFrameType(local[i]);
          }
          stackMap.putShort(nStack);
          for (int i = 0; i < nStack; ++i) {
            writeFrameType(stack[i]);
          }
          break;
        case Opcodes.F_APPEND:
          stackMap.putByte(SAME_FRAME_EXTENDED + nLocal)
              .putShort(delta);
          for (int i = 0; i < nLocal; ++i) {
            writeFrameType(local[i]);
          }
          break;
        case Opcodes.F_CHOP:
          stackMap.putByte(SAME_FRAME_EXTENDED - nLocal)
              .putShort(delta);
          break;
        case Opcodes.F_SAME:
          if (delta < 64) {
            stackMap.putByte(delta);
          }
          else {
            stackMap.putByte(SAME_FRAME_EXTENDED).putShort(delta);
          }
          break;
        case Opcodes.F_SAME1:
          if (delta < 64) {
            stackMap.putByte(SAME_LOCALS_1_STACK_ITEM_FRAME + delta);
          }
          else {
            stackMap.putByte(SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED)
                .putShort(delta);
          }
          writeFrameType(stack[0]);
          break;
      }

      previousFrameOffset = code.length;
      ++frameCount;
    }
  }

  public void visitInsn(final int opcode) {
    // adds the instruction to the bytecode of the method
    code.putByte(opcode);
    // update currentBlock
    // Label currentBlock = this.currentBlock;
    if (currentBlock != null) {
      if (compute == FRAMES) {
        currentBlock.frame.execute(opcode, 0, null, null);
      }
      else {
        // updates current and max stack sizes
        int size = stackSize + Frame.SIZE[opcode];
        if (size > maxStackSize) {
          maxStackSize = size;
        }
        stackSize = size;
      }
      // if opcode == ATHROW or xRETURN, ends current ast (no successor)
      if ((opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN)
          || opcode == Opcodes.ATHROW) {
        noSuccessor();
      }
    }
  }

  public void visitIntInsn(final int opcode, final int operand) {
    // Label currentBlock = this.currentBlock;
    if (currentBlock != null) {
      if (compute == FRAMES) {
        currentBlock.frame.execute(opcode, operand, null, null);
      }
      else if (opcode != Opcodes.NEWARRAY) {
        // updates current and max stack sizes only for NEWARRAY
        // (stack size variation = 0 for BIPUSH or SIPUSH)
        int size = stackSize + 1;
        if (size > maxStackSize) {
          maxStackSize = size;
        }
        stackSize = size;
      }
    }
    // adds the instruction to the bytecode of the method
    if (opcode == Opcodes.SIPUSH) {
      code.put12(opcode, operand);
    }
    else { // BIPUSH or NEWARRAY
      code.put11(opcode, operand);
    }
  }

  public void visitVarInsn(final int opcode, final int var) {
    // Label currentBlock = this.currentBlock;
    if (currentBlock != null) {
      if (compute == FRAMES) {
        currentBlock.frame.execute(opcode, var, null, null);
      }
      else {
        // updates current and max stack sizes
        if (opcode == Opcodes.RET) {
          // no stack change, but end of current ast (no successor)
          currentBlock.status |= Label.RET;
          // save 'stackSize' here for future use
          // (see {@link #findSubroutineSuccessors})
          currentBlock.inputStackTop = stackSize;
          noSuccessor();
        }
        else { // xLOAD or xSTORE
          int size = stackSize + Frame.SIZE[opcode];
          if (size > maxStackSize) {
            maxStackSize = size;
          }
          stackSize = size;
        }
      }
    }
    if (compute != NOTHING) {
      // updates max locals
      int n;
      if (opcode == Opcodes.LLOAD || opcode == Opcodes.DLOAD
          || opcode == Opcodes.LSTORE || opcode == Opcodes.DSTORE) {
        n = var + 2;
      }
      else {
        n = var + 1;
      }
      if (n > maxLocals) {
        maxLocals = n;
      }
    }
    // adds the instruction to the bytecode of the method
    if (var < 4 && opcode != Opcodes.RET) {
      int opt;
      if (opcode < Opcodes.ISTORE) {
        /* ILOAD_0 */
        opt = 26 + ((opcode - Opcodes.ILOAD) << 2) + var;
      }
      else {
        /* ISTORE_0 */
        opt = 59 + ((opcode - Opcodes.ISTORE) << 2) + var;
      }
      code.putByte(opt);
    }
    else if (var >= 256) {
      code.putByte(196 /* WIDE */).put12(opcode, var);
    }
    else {
      code.put11(opcode, var);
    }
    if (opcode >= Opcodes.ISTORE && compute == FRAMES && handlerCount > 0) {
      visitLabel(new Label());
    }
  }

  public void visitTypeInsn(final int opcode, final String desc) {
    Item i = cw.newClassItem(desc);
    // Label currentBlock = this.currentBlock;
    if (currentBlock != null) {
      if (compute == FRAMES) {
        currentBlock.frame.execute(opcode, code.length, cw, i);
      }
      else if (opcode == Opcodes.NEW) {
        // updates current and max stack sizes only if opcode == NEW
        // (no stack change for ANEWARRAY, CHECKCAST, INSTANCEOF)
        int size = stackSize + 1;
        if (size > maxStackSize) {
          maxStackSize = size;
        }
        stackSize = size;
      }
    }
    // adds the instruction to the bytecode of the method
    code.put12(opcode, i.index);
  }

  public void visitFieldInsn(
      final int opcode,
      final String owner,
      final String name,
      final String desc) {
    Item i = cw.newFieldItem(owner, name, desc);
    // Label currentBlock = this.currentBlock;
    if (currentBlock != null) {
      if (compute == FRAMES) {
        currentBlock.frame.execute(opcode, 0, cw, i);
      }
      else {
        int size;
        // computes the stack size variation
        char c = desc.charAt(0);
        switch (opcode) {
          case Opcodes.GETSTATIC:
            size = stackSize + (c == 'D' || c == 'J' ? 2 : 1);
            break;
          case Opcodes.PUTSTATIC:
            size = stackSize + (c == 'D' || c == 'J' ? -2 : -1);
            break;
          case Opcodes.GETFIELD:
            size = stackSize + (c == 'D' || c == 'J' ? 1 : 0);
            break;
          // case Constants.PUTFIELD:
          default:
            size = stackSize + (c == 'D' || c == 'J' ? -3 : -2);
            break;
        }
        // updates current and max stack sizes
        if (size > maxStackSize) {
          maxStackSize = size;
        }
        stackSize = size;
      }
    }
    // adds the instruction to the bytecode of the method
    code.put12(opcode, i.index);
  }

  public void visitMethodInsn(
      final int opcode,
      final String owner,
      final String name,
      final String desc) {
    boolean itf = opcode == Opcodes.INVOKEINTERFACE;
    Item i = cw.newMethodItem(owner, name, desc, itf);
    int argSize = i.intVal;
    // Label currentBlock = this.currentBlock;
    if (currentBlock != null) {
      if (compute == FRAMES) {
        currentBlock.frame.execute(opcode, 0, cw, i);
      }
      else {
        /*
        * computes the stack size variation. In order not to recompute
        * several times this variation for the same Item, we use the
        * intVal field of this item to store this variation, once it
        * has been computed. More precisely this intVal field stores
        * the sizes of the arguments and of the return value
        * corresponding to desc.
        */
        if (argSize == 0) {
          // the above sizes have not been computed yet,
          // so we compute them...
          argSize = getArgumentsAndReturnSizes(desc);
          // ... and we save them in order
          // not to recompute them in the future
          i.intVal = argSize;
        }
        int size;
        if (opcode == Opcodes.INVOKESTATIC) {
          size = stackSize - (argSize >> 2) + (argSize & 0x03) + 1;
        }
        else {
          size = stackSize - (argSize >> 2) + (argSize & 0x03);
        }
        // updates current and max stack sizes
        if (size > maxStackSize) {
          maxStackSize = size;
        }
        stackSize = size;
      }
    }
    // adds the instruction to the bytecode of the method
    if (itf) {
      if (argSize == 0) {
        argSize = getArgumentsAndReturnSizes(desc);
        i.intVal = argSize;
      }
      code.put12(Opcodes.INVOKEINTERFACE, i.index).put11(argSize >> 2, 0);
    }
    else {
      code.put12(opcode, i.index);
    }
  }

  public void visitJumpInsn(final int opcode, final Label label) {
    Label nextInsn = null;
    // Label currentBlock = this.currentBlock;
    if (currentBlock != null) {
      if (compute == FRAMES) {
        currentBlock.frame.execute(opcode, 0, null, null);
        // 'label' is the target of a jump instruction
        label.getFirst().status |= Label.TARGET;
        // adds 'label' as a successor of this basic ast
        addSuccessor(Edge.NORMAL, label);
        if (opcode != Opcodes.GOTO) {
          // creates a Label for the next basic ast
          nextInsn = new Label();
        }
      }
      else {
        if (opcode == Opcodes.JSR) {
          jsr = true;
          currentBlock.status |= Label.JSR;
          addSuccessor(stackSize + 1, label);
          // creates a Label for the next basic ast
          nextInsn = new Label();
          /*
          * note that, by construction in this method, a JSR ast
          * has at least two successors in the control flow graph:
          * the first one leads the next instruction after the JSR,
          * while the second one leads to the JSR target.
          */
        }
        else {
          // updates current stack size (max stack size unchanged
          // because stack size variation always negative in this
          // case)
          stackSize += Frame.SIZE[opcode];
          addSuccessor(stackSize, label);
        }
      }
    }
    // adds the instruction to the bytecode of the method
    if ((label.status & Label.RESOLVED) != 0
        && label.position - code.length < Short.MIN_VALUE) {
      /*
      * case of a backward jump with an offset < -32768. In this case we
      * automatically replace GOTO with GOTO_W, JSR with JSR_W and IFxxx
      * <l> with IFNOTxxx <l'> GOTO_W <l>, where IFNOTxxx is the
      * "opposite" opcode of IFxxx (i.e., IFNE for IFEQ) and where <l'>
      * designates the instruction just after the GOTO_W.
      */
      if (opcode == Opcodes.GOTO) {
        code.putByte(200); // GOTO_W
      }
      else if (opcode == Opcodes.JSR) {
        code.putByte(201); // JSR_W
      }
      else {
        // if the IF instruction is transformed into IFNOT GOTO_W the
        // next instruction becomes the target of the IFNOT instruction
        if (nextInsn != null) {
          nextInsn.status |= Label.TARGET;
        }
        code.putByte(opcode <= 166
            ? ((opcode + 1) ^ 1) - 1
            : opcode ^ 1);
        code.putShort(8); // jump offset
        code.putByte(200); // GOTO_W
      }
      label.put(this, code, code.length - 1, true);
    }
    else {
      /*
      * case of a backward jump with an offset >= -32768, or of a forward
      * jump with, of course, an unknown offset. In these cases we store
      * the offset in 2 bytes (which will be increased in
      * resizeInstructions, if needed).
      */
      code.putByte(opcode);
      label.put(this, code, code.length - 1, false);
    }
    if (currentBlock != null) {
      if (nextInsn != null) {
        // if the jump instruction is not a GOTO, the next instruction
        // is also a successor of this instruction. Calling visitLabel
        // adds the label of this next instruction as a successor of the
        // current ast, and starts a new basic ast
        visitLabel(nextInsn);
      }
      if (opcode == Opcodes.GOTO) {
        noSuccessor();
      }
    }
  }

  public void visitLabel(final Label label) {
    // resolves previous forward references to label, if any
    resize |= label.resolve(this, code.length, code.data);
    // updates currentBlock
    if ((label.status & Label.DEBUG) != 0) {
      return;
    }
    if (compute == FRAMES) {
      if (currentBlock != null) {
        if (label.position == currentBlock.position) {
          // successive labels, do not start a new basic ast
          currentBlock.status |= (label.status & Label.TARGET);
          label.frame = currentBlock.frame;
          return;
        }
        // ends current ast (with one new successor)
        addSuccessor(Edge.NORMAL, label);
      }
      // begins a new current ast
      currentBlock = label;
      if (label.frame == null) {
        label.frame = new Frame();
        label.frame.owner = label;
      }
      // updates the basic ast list
      if (previousBlock != null) {
        if (label.position == previousBlock.position) {
          previousBlock.status |= (label.status & Label.TARGET);
          label.frame = previousBlock.frame;
          currentBlock = previousBlock;
          return;
        }
        previousBlock.successor = label;
      }
      previousBlock = label;
    }
    else if (compute == MAXS) {
      if (currentBlock != null) {
        // ends current ast (with one new successor)
        currentBlock.outputStackMax = maxStackSize;
        addSuccessor(stackSize, label);
      }
      // begins a new current ast
      currentBlock = label;
      // resets the relative current and max stack sizes
      stackSize = 0;
      maxStackSize = 0;
      // updates the basic ast list
      if (previousBlock != null) {
        previousBlock.successor = label;
      }
      previousBlock = label;
    }
  }

  public void visitLdcInsn(final Object cst) {
    Item i = cw.newConstItem(cst);
    // Label currentBlock = this.currentBlock;
    if (currentBlock != null) {
      if (compute == FRAMES) {
        currentBlock.frame.execute(Opcodes.LDC, 0, cw, i);
      }
      else {
        int size;
        // computes the stack size variation
        if (i.type == ClassWriter.LONG || i.type == ClassWriter.DOUBLE) {
          size = stackSize + 2;
        }
        else {
          size = stackSize + 1;
        }
        // updates current and max stack sizes
        if (size > maxStackSize) {
          maxStackSize = size;
        }
        stackSize = size;
      }
    }
    // adds the instruction to the bytecode of the method
    int index = i.index;
    if (i.type == ClassWriter.LONG || i.type == ClassWriter.DOUBLE) {
      code.put12(20 /* LDC2_W */, index);
    }
    else if (index >= 256) {
      code.put12(19 /* LDC_W */, index);
    }
    else {
      code.put11(Opcodes.LDC, index);
    }
  }

  public void visitIincInsn(final int var, final int increment) {
    if (currentBlock != null) {
      if (compute == FRAMES) {
        currentBlock.frame.execute(Opcodes.IINC, var, null, null);
      }
    }
    if (compute != NOTHING) {
      // updates max locals
      int n = var + 1;
      if (n > maxLocals) {
        maxLocals = n;
      }
    }
    // adds the instruction to the bytecode of the method
    if ((var > 255) || (increment > 127) || (increment < -128)) {
      code.putByte(196 /* WIDE */)
          .put12(Opcodes.IINC, var)
          .putShort(increment);
    }
    else {
      code.putByte(Opcodes.IINC).put11(var, increment);
    }
  }

  public void visitTableSwitchInsn(
      final int min,
      final int max,
      final Label dflt,
      final Label labels[]) {
    // adds the instruction to the bytecode of the method
    int source = code.length;
    code.putByte(Opcodes.TABLESWITCH);
    code.length += (4 - code.length % 4) % 4;
    dflt.put(this, code, source, true);
    code.putInt(min).putInt(max);
    for (int i = 0; i < labels.length; ++i) {
      labels[i].put(this, code, source, true);
    }
    // updates currentBlock
    visitSwitchInsn(dflt, labels);
  }

  public void visitLookupSwitchInsn(
      final Label dflt,
      final int keys[],
      final Label labels[]) {
    // adds the instruction to the bytecode of the method
    int source = code.length;
    code.putByte(Opcodes.LOOKUPSWITCH);
    code.length += (4 - code.length % 4) % 4;
    dflt.put(this, code, source, true);
    code.putInt(labels.length);
    for (int i = 0; i < labels.length; ++i) {
      code.putInt(keys[i]);
      labels[i].put(this, code, source, true);
    }
    // updates currentBlock
    visitSwitchInsn(dflt, labels);
  }

  private void visitSwitchInsn(final Label dflt, final Label[] labels) {
    // Label currentBlock = this.currentBlock;
    if (currentBlock != null) {
      if (compute == FRAMES) {
        currentBlock.frame.execute(Opcodes.LOOKUPSWITCH, 0, null, null);
        // adds current ast successors
        addSuccessor(Edge.NORMAL, dflt);
        dflt.getFirst().status |= Label.TARGET;
        for (int i = 0; i < labels.length; ++i) {
          addSuccessor(Edge.NORMAL, labels[i]);
          labels[i].getFirst().status |= Label.TARGET;
        }
      }
      else {
        // updates current stack size (max stack size unchanged)
        --stackSize;
        // adds current ast successors
        addSuccessor(stackSize, dflt);
        for (int i = 0; i < labels.length; ++i) {
          addSuccessor(stackSize, labels[i]);
        }
      }
      // ends current ast
      noSuccessor();
    }
  }

  public void visitMultiANewArrayInsn(final String desc, final int dims) {
    Item i = cw.newClassItem(desc);
    // Label currentBlock = this.currentBlock;
    if (currentBlock != null) {
      if (compute == FRAMES) {
        currentBlock.frame.execute(Opcodes.MULTIANEWARRAY, dims, cw, i);
      }
      else {
        // updates current stack size (max stack size unchanged because
        // stack size variation always negative or null)
        stackSize += 1 - dims;
      }
    }
    // adds the instruction to the bytecode of the method
    code.put12(Opcodes.MULTIANEWARRAY, i.index).putByte(dims);
  }

  public void visitTryCatchBlock(
      final Label start,
      final Label end,
      final Label handler,
      final String type) {
    ++handlerCount;
    Handler h = new Handler();
    h.start = start;
    h.end = end;
    h.handler = handler;
    h.desc = type;
    h.type = type != null ? cw.newClass(type) : 0;
    if (lastHandler == null) {
      firstHandler = h;
    }
    else {
      lastHandler.next = h;
    }
    lastHandler = h;
  }

  public void visitLocalVariable(
      final String name,
      final String desc,
      final String signature,
      final Label start,
      final Label end,
      final int index) {
    if (signature != null) {
      if (localVarType == null) {
        localVarType = new ByteVector();
      }
      ++localVarTypeCount;
      localVarType.putShort(start.position)
          .putShort(end.position - start.position)
          .putShort(cw.newUTF8(name))
          .putShort(cw.newUTF8(signature))
          .putShort(index);
    }
    if (localVar == null) {
      localVar = new ByteVector();
    }
    ++localVarCount;
    localVar.putShort(start.position)
        .putShort(end.position - start.position)
        .putShort(cw.newUTF8(name))
        .putShort(cw.newUTF8(desc))
        .putShort(index);
    if (compute != NOTHING) {
      // updates max locals
      char c = desc.charAt(0);
      int n = index + (c == 'J' || c == 'D' ? 2 : 1);
      if (n > maxLocals) {
        maxLocals = n;
      }
    }
  }

  public void visitLineNumber(final int line, final Label start) {
    if (lineNumber == null) {
      lineNumber = new ByteVector();
    }
    ++lineNumberCount;
    lineNumber.putShort(start.position);
    lineNumber.putShort(line);
  }

  public void visitMaxs(final int maxStack, final int maxLocals) {
    if (compute == FRAMES) {
      // completes the control flow graph with exception handler blocks
      Handler handler = firstHandler;
      while (handler != null) {
        Label l = handler.start.getFirst();
        Label h = handler.handler.getFirst();
        Label e = handler.end.getFirst();
        // computes the kind of the edges to 'h'
        String t = handler.desc == null
            ? "java/lang/Throwable"
            : handler.desc;
        int kind = Frame.OBJECT | cw.addType(t);
        // h is an exception handler
        h.status |= Label.TARGET;
        // adds 'h' as a successor of labels between 'start' and 'end'
        while (l != e) {
          // creates an edge to 'h'
          Edge b = new Edge();
          b.info = kind;
          b.successor = h;
          // adds it to the successors of 'l'
          b.next = l.successors;
          l.successors = b;
          // goes to the next label
          l = l.successor;
        }
        handler = handler.next;
      }

      // creates and visits the first (implicit) frame
      Frame f = labels.frame;
      Type[] args = Type.getArgumentTypes(descriptor);
      f.initInputFrame(cw, access, args, this.maxLocals);
      visitFrame(f);

      /*
      * fix point algorithm: mark the first basic ast as 'changed'
      * (i.e. put it in the 'changed' list) and, while there are changed
      * basic blocks, choose one, mark it as unchanged, and update its
      * successors (which can be changed in the process).
      */
      int max = 0;
      Label changed = labels;
      while (changed != null) {
        // removes a basic ast from the list of changed basic blocks
        Label l = changed;
        changed = changed.next;
        l.next = null;
        f = l.frame;
        // a reacheable jump target must be stored in the stack map
        if ((l.status & Label.TARGET) != 0) {
          l.status |= Label.STORE;
        }
        // all visited labels are reacheable, by definition
        l.status |= Label.REACHABLE;
        // updates the (absolute) maximum stack size
        int blockMax = f.inputStack.length + l.outputStackMax;
        if (blockMax > max) {
          max = blockMax;
        }
        // updates the successors of the current basic ast
        Edge e = l.successors;
        while (e != null) {
          Label n = e.successor.getFirst();
          boolean change = f.merge(cw, n.frame, e.info);
          if (change && n.next == null) {
            // if n has changed and is not already in the 'changed'
            // list, adds it to this list
            n.next = changed;
            changed = n;
          }
          e = e.next;
        }
      }
      this.maxStack = max;

      // visits all the frames that must be stored in the stack map
      Label l = labels;
      while (l != null) {
        f = l.frame;
        if ((l.status & Label.STORE) != 0) {
          visitFrame(f);
        }
        if ((l.status & Label.REACHABLE) == 0) {
          // finds start and end of dead basic ast
          Label k = l.successor;
          int start = l.position;
          int end = (k == null ? code.length : k.position) - 1;
          // if non empty basic ast
          if (end >= start) {
            // replaces instructions with NOP ... NOP ATHROW
            for (int i = start; i < end; ++i) {
              code.data[i] = Opcodes.NOP;
            }
            code.data[end] = (byte) Opcodes.ATHROW;
            // emits a frame for this unreachable ast
            startFrame(start, 0, 1);
            frame[frameIndex++] = Frame.OBJECT
                | cw.addType("java/lang/Throwable");
            endFrame();
          }
        }
        l = l.successor;
      }
    }
    else if (compute == MAXS) {
      // completes the control flow graph with exception handler blocks
      Handler handler = firstHandler;
      while (handler != null) {
        Label l = handler.start;
        Label h = handler.handler;
        Label e = handler.end;
        // adds 'h' as a successor of labels between 'start' and 'end'
        while (l != e) {
          // creates an edge to 'h'
          Edge b = new Edge();
          b.info = Edge.EXCEPTION;
          b.successor = h;
          // adds it to the successors of 'l'
          if ((l.status & Label.JSR) != 0) {
            // if l is a JSR ast, adds b after the first two edges
            // to preserve the hypothesis about JSR ast successors
            // order (see {@link #visitJumpInsn})
            b.next = l.successors.next.next;
            l.successors.next.next = b;
          }
          else {
            b.next = l.successors;
            l.successors = b;
          }
          // goes to the next label
          l = l.successor;
        }
        handler = handler.next;
      }

      if (jsr) {
        // completes the control flow graph with the RET successors
        /*
        * first step: finds the subroutines. This step determines, for
        * each basic ast, to which subroutine(s) it belongs, and
        * stores this set as a bit set in the {@link Label#status}
        * field. Subroutines are numbered with powers of two, from
        * 0x1000 to 0x80000000 (so there must be at most 20 subroutines
        * in a method).
        */
        // finds the basic blocks that belong to the "core" subroutine
        int id = 0x1000;
        findSubroutine(labels, id);
        // finds the basic blocks that belong to the real subroutines
        Label l = labels;
        while (l != null) {
          if ((l.status & Label.JSR) != 0) {
            // the subroutine is defined by l's TARGET, not by l
            Label subroutine = l.successors.next.successor;
            // if this subroutine does not have an id yet...
            if ((subroutine.status & ~0xFFF) == 0) {
              // ...assigns it a new id and finds its basic blocks
              id = id << 1;
              findSubroutine(subroutine, id);
            }
          }
          l = l.successor;
        }
        // second step: finds the successors of RET blocks
        findSubroutineSuccessors(0x1000, new Label[10], 0);
      }

      /*
      * control flow analysis algorithm: while the ast stack is not
      * empty, pop a ast from this stack, update the max stack size,
      * compute the true (non relative) begin stack size of the
      * successors of this ast, and push these successors onto the
      * stack (unless they have already been pushed onto the stack).
      * Note: by hypothesis, the {@link Label#inputStackTop} of the
      * blocks in the ast stack are the true (non relative) beginning
      * stack sizes of these blocks.
      */
      int max = 0;
      Label stack = labels;
      while (stack != null) {
        // pops a ast from the stack
        Label l = stack;
        stack = stack.next;
        // computes the true (non relative) max stack size of this ast
        int start = l.inputStackTop;
        int blockMax = start + l.outputStackMax;
        // updates the global max stack size
        if (blockMax > max) {
          max = blockMax;
        }
        // analyses the successors of the ast
        Edge b = l.successors;
        if ((l.status & Label.JSR) != 0) {
          // ignores the first edge of JSR blocks (virtual successor)
          b = b.next;
        }
        while (b != null) {
          l = b.successor;
          // if this successor has not already been pushed...
          if ((l.status & Label.PUSHED) == 0) {
            // computes its true beginning stack size...
            l.inputStackTop = b.info == Edge.EXCEPTION ? 1 : start
                + b.info;
            // ...and pushes it onto the stack
            l.status |= Label.PUSHED;
            l.next = stack;
            stack = l;
          }
          b = b.next;
        }
      }
      this.maxStack = max;
    }
    else {
      this.maxStack = maxStack;
      this.maxLocals = maxLocals;
    }
  }

  public void visitEnd() {
  }

  // ------------------------------------------------------------------------
  // Utility methods: control flow analysis algorithm
  // ------------------------------------------------------------------------

  /**
   * Computes the size of the arguments and of the return value of a method.
   *
   * @param desc the descriptor of a method.
   * @return the size of the arguments of the method (plus one for the
   *         implicit this argument), argSize, and the size of its return
   *         value, retSize, packed into a single int i =
   *         <tt>(argSize << 2) | retSize</tt> (argSize is therefore equal
   *         to <tt>i >> 2</tt>, and retSize to <tt>i & 0x03</tt>).
   */
  static int getArgumentsAndReturnSizes(final String desc) {
    int n = 1;
    int c = 1;
    while (true) {
      char car = desc.charAt(c++);
      if (car == ')') {
        car = desc.charAt(c);
        return n << 2
            | (car == 'V' ? 0 : (car == 'D' || car == 'J' ? 2 : 1));
      }
      else if (car == 'L') {
        while (desc.charAt(c++) != ';') {
        }
        n += 1;
      }
      else if (car == '[') {
        while ((car = desc.charAt(c)) == '[') {
          ++c;
        }
        if (car == 'D' || car == 'J') {
          n -= 1;
        }
      }
      else if (car == 'D' || car == 'J') {
        n += 2;
      }
      else {
        n += 1;
      }
    }
  }

  /**
   * Adds a successor to the {@link #currentBlock currentBlock} ast.
   *
   * @param info      information about the control flow edge to be added.
   * @param successor the successor ast to be added to the current ast.
   */
  private void addSuccessor(final int info, final Label successor) {
    // creates and initializes an Edge object...
    Edge b = new Edge();
    b.info = info;
    b.successor = successor;
    // ...and adds it to the successor list of the currentBlock ast
    b.next = currentBlock.successors;
    currentBlock.successors = b;
  }

  /**
   * Ends the current basic ast. This method must be used in the case where
   * the current basic ast does not have any successor.
   */
  private void noSuccessor() {
    if (compute == FRAMES) {
      Label l = new Label();
      l.frame = new Frame();
      l.frame.owner = l;
      l.resolve(this, code.length, code.data);
      previousBlock.successor = l;
      previousBlock = l;
    }
    else {
      currentBlock.outputStackMax = maxStackSize;
    }
    currentBlock = null;
  }

  /**
   * Finds the basic blocks that belong to a given subroutine, and marks these
   * blocks as belonging to this subroutine (by using {@link Label#status} as
   * a bit set (see {@link #visitMaxs}). This recursive method follows the
   * control flow graph to find all the blocks that are reachable from the
   * given ast WITHOUT following any JSR target.
   *
   * @param block a ast that belongs to the subroutine
   * @param id    the id of this subroutine
   */
  private void findSubroutine(final Label block, final int id) {
    // if 'ast' is already marked as belonging to subroutine 'id', returns
    if ((block.status & id) != 0) {
      return;
    }
    // marks 'ast' as belonging to subroutine 'id'
    block.status |= id;
    // calls this method recursively on each successor, except JSR targets
    Edge e = block.successors;
    while (e != null) {
      // if 'ast' is a JSR ast, then 'ast.successors.next' leads
      // to the JSR target (see {@link #visitJumpInsn}) and must therefore
      // not be followed
      if ((block.status & Label.JSR) == 0 || e != block.successors.next) {
        findSubroutine(e.successor, id);
      }
      e = e.next;
    }
  }

  /**
   * Finds the successors of the RET blocks of the specified subroutine, and
   * of any nested subroutine it calls.
   *
   * @param id    id of the subroutine whose RET ast successors must be found.
   * @param JSRs  the JSR blocks that were followed to reach this subroutine.
   * @param nJSRs number of JSR blocks in the JSRs array.
   */
  private void findSubroutineSuccessors(
      final int id,
      final Label[] JSRs,
      final int nJSRs) {
    // iterates over all the basic blocks...
    Label l = labels;
    while (l != null) {
      // for those that belong to subroutine 'id'...
      if ((l.status & id) != 0) {
        if ((l.status & Label.JSR) != 0) {
          // finds the subroutine to which 'l' leads by following the
          // second edge of l.successors (see {@link #visitJumpInsn})
          int nId = l.successors.next.successor.status & ~0xFFF;
          if (nId != id) {
            // calls this method recursively with l pushed onto the
            // JSRs stack to find the successors of the RET blocks
            // of this nested subroutine 'nId'
            JSRs[nJSRs] = l;
            findSubroutineSuccessors(nId, JSRs, nJSRs + 1);
          }
        }
        else if ((l.status & Label.RET) != 0) {
          /*
          * finds the JSR ast in the JSRs stack that corresponds to
          * this RET ast, and updates the successors of this RET
          * ast accordingly. This corresponding JSR is the one that
          * leads to the subroutine to which the RET ast belongs.
          * But the RET ast can belong to several subroutines (if a
          * nested subroutine returns to its parent subroutine
          * implicitely, without a RET). So, in fact, the JSR that
          * corresponds to this RET is the first ast in the JSRs
          * stack, starting from the bottom of the stack, that leads
          * to a subroutine to which the RET ast belongs.
          */
          for (int i = 0; i < nJSRs; ++i) {
            int JSRstatus = JSRs[i].successors.next.successor.status;
            if (((JSRstatus & ~0xFFF) & (l.status & ~0xFFF)) != 0) {
              Edge e = new Edge();
              e.info = l.inputStackTop;
              e.successor = JSRs[i].successors.successor;
              e.next = l.successors;
              l.successors = e;
              break;
            }
          }
        }
      }
      l = l.successor;
    }
  }

  // ------------------------------------------------------------------------
  // Utility methods: stack map frames
  // ------------------------------------------------------------------------

  /**
   * Visits a frame that has been computed from scratch.
   *
   * @param f the frame that must be visited.
   */
  private void visitFrame(final Frame f) {
    int i, t;
    int nTop = 0;
    int nLocal = 0;
    int nStack = 0;
    int[] locals = f.inputLocals;
    int[] stacks = f.inputStack;
    // computes the number of locals (ignores TOP types that are just after
    // a LONG or a DOUBLE, and all trailing TOP types)
    for (i = 0; i < locals.length; ++i) {
      t = locals[i];
      if (t == Frame.TOP) {
        ++nTop;
      }
      else {
        nLocal += nTop + 1;
        nTop = 0;
      }
      if (t == Frame.LONG || t == Frame.DOUBLE) {
        ++i;
      }
    }
    // computes the stack size (ignores TOP types that are just after
    // a LONG or a DOUBLE)
    for (i = 0; i < stacks.length; ++i) {
      t = stacks[i];
      ++nStack;
      if (t == Frame.LONG || t == Frame.DOUBLE) {
        ++i;
      }
    }
    // visits the frame and its content
    startFrame(f.owner.position, nLocal, nStack);
    for (i = 0; nLocal > 0; ++i, --nLocal) {
      t = locals[i];
      frame[frameIndex++] = t;
      if (t == Frame.LONG || t == Frame.DOUBLE) {
        ++i;
      }
    }
    for (i = 0; i < stacks.length; ++i) {
      t = stacks[i];
      frame[frameIndex++] = t;
      if (t == Frame.LONG || t == Frame.DOUBLE) {
        ++i;
      }
    }
    endFrame();
  }

  /**
   * Starts the visit of a stack map frame.
   *
   * @param offset the offset of the instruction to which the frame
   *               corresponds.
   * @param nLocal the number of local variables in the frame.
   * @param nStack the number of stack elements in the frame.
   */
  private void startFrame(final int offset, final int nLocal, final int nStack) {
    int n = 3 + nLocal + nStack;
    if (frame == null || frame.length < n) {
      frame = new int[n];
    }
    frame[0] = offset;
    frame[1] = nLocal;
    frame[2] = nStack;
    frameIndex = 3;
  }

  /**
   * Checks if the visit of the current frame {@link #frame} is finished, and
   * if yes, write it in the StackMapTable attribute.
   */
  private void endFrame() {
    if (previousFrame != null) { // do not write the first frame
      if (stackMap == null) {
        stackMap = new ByteVector();
      }
      writeFrame();
      ++frameCount;
    }
    previousFrame = frame;
    frame = null;
  }

  /**
   * Compress and writes the current frame {@link #frame} in the StackMapTable
   * attribute.
   */
  private void writeFrame() {
    int clocalsSize = frame[1];
    int cstackSize = frame[2];
    if ((cw.version & 0xFFFF) < Opcodes.V1_6) {
      stackMap.putShort(frame[0]).putShort(clocalsSize);
      writeFrameTypes(3, 3 + clocalsSize);
      stackMap.putShort(cstackSize);
      writeFrameTypes(3 + clocalsSize, 3 + clocalsSize + cstackSize);
      return;
    }
    int localsSize = previousFrame[1];
    int type = FULL_FRAME;
    int k = 0;
    int delta;
    if (frameCount == 0) {
      delta = frame[0];
    }
    else {
      delta = frame[0] - previousFrame[0] - 1;
    }
    if (cstackSize == 0) {
      k = clocalsSize - localsSize;
      switch (k) {
        case -3:
        case -2:
        case -1:
          type = CHOP_FRAME;
          localsSize = clocalsSize;
          break;
        case 0:
          type = delta < 64 ? SAME_FRAME : SAME_FRAME_EXTENDED;
          break;
        case 1:
        case 2:
        case 3:
          type = APPEND_FRAME;
          break;
      }
    }
    else if (clocalsSize == localsSize && cstackSize == 1) {
      type = delta < 63
          ? SAME_LOCALS_1_STACK_ITEM_FRAME
          : SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED;
    }
    if (type != FULL_FRAME) {
      // verify if locals are the same
      int l = 3;
      for (int j = 0; j < localsSize; j++) {
        if (frame[l] != previousFrame[l]) {
          type = FULL_FRAME;
          break;
        }
        l++;
      }
    }
    switch (type) {
      case SAME_FRAME:
        stackMap.putByte(delta);
        break;
      case SAME_LOCALS_1_STACK_ITEM_FRAME:
        stackMap.putByte(SAME_LOCALS_1_STACK_ITEM_FRAME + delta);
        writeFrameTypes(3 + clocalsSize, 4 + clocalsSize);
        break;
      case SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED:
        stackMap.putByte(SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED)
            .putShort(delta);
        writeFrameTypes(3 + clocalsSize, 4 + clocalsSize);
        break;
      case SAME_FRAME_EXTENDED:
        stackMap.putByte(SAME_FRAME_EXTENDED).putShort(delta);
        break;
      case CHOP_FRAME:
        stackMap.putByte(SAME_FRAME_EXTENDED + k).putShort(delta);
        break;
      case APPEND_FRAME:
        stackMap.putByte(SAME_FRAME_EXTENDED + k).putShort(delta);
        writeFrameTypes(3 + localsSize, 3 + clocalsSize);
        break;
      // case FULL_FRAME:
      default:
        stackMap.putByte(FULL_FRAME)
            .putShort(delta)
            .putShort(clocalsSize);
        writeFrameTypes(3, 3 + clocalsSize);
        stackMap.putShort(cstackSize);
        writeFrameTypes(3 + clocalsSize, 3 + clocalsSize + cstackSize);
    }
  }

  /**
   * Writes some types of the current frame {@link #frame} into the
   * StackMapTableAttribute. This method converts types from the format used
   * in {@link Label} to the format used in StackMapTable attributes. In
   * particular, it converts type table indexes to constant pool indexes.
   *
   * @param start index of the first type in {@link #frame} to write.
   * @param end   index of last type in {@link #frame} to write (exclusive).
   */
  private void writeFrameTypes(final int start, final int end) {
    for (int i = start; i < end; ++i) {
      int t = frame[i];
      int d = t & Frame.DIM;
      if (d == 0) {
        int v = t & Frame.BASE_VALUE;
        switch (t & Frame.BASE_KIND) {
          case Frame.OBJECT:
            stackMap.putByte(7)
                .putShort(cw.newClass(cw.typeTable[v].strVal1));
            break;
          case Frame.UNINITIALIZED:
            stackMap.putByte(8).putShort(cw.typeTable[v].intVal);
            break;
          default:
            stackMap.putByte(v);
        }
      }
      else {
        StringBuffer buf = new StringBuffer();
        d >>= 28;
        while (d-- > 0) {
          buf.append('[');
        }
        if ((t & Frame.BASE_KIND) == Frame.OBJECT) {
          buf.append('L');
          buf.append(cw.typeTable[t & Frame.BASE_VALUE].strVal1);
          buf.append(';');
        }
        else {
          switch (t & 0xF) {
            case 1:
              buf.append('I');
              break;
            case 2:
              buf.append('F');
              break;
            case 3:
              buf.append('D');
              break;
            case 9:
              buf.append('Z');
              break;
            case 10:
              buf.append('B');
              break;
            case 11:
              buf.append('C');
              break;
            case 12:
              buf.append('S');
              break;
            default:
              buf.append('J');
          }
        }
        stackMap.putByte(7).putShort(cw.newClass(buf.toString()));
      }
    }
  }

  private void writeFrameType(final Object type) {
    if (type instanceof String) {
      stackMap.putByte(7).putShort(cw.newClass((String) type));
    }
    else if (type instanceof Integer) {
      stackMap.putByte(((Integer) type).intValue());
    }
    else {
      stackMap.putByte(8).putShort(((Label) type).position);
    }
  }

  // ------------------------------------------------------------------------
  // Utility methods: dump bytecode array
  // ------------------------------------------------------------------------

  /**
   * Returns the size of the bytecode of this method.
   *
   * @return the size of the bytecode of this method.
   */
  final int getSize() {
    if (classReaderOffset != 0) {
      return 6 + classReaderLength;
    }
    if (resize) {
      // replaces the temporary jump opcodes introduced by Label.resolve.
      resizeInstructions();
    }
    int size = 8;
    if (code.length > 0) {
      cw.newUTF8("Code");
      size += 18 + code.length + 8 * handlerCount;
      if (localVar != null) {
        cw.newUTF8("LocalVariableTable");
        size += 8 + localVar.length;
      }
      if (localVarType != null) {
        cw.newUTF8("LocalVariableTypeTable");
        size += 8 + localVarType.length;
      }
      if (lineNumber != null) {
        cw.newUTF8("LineNumberTable");
        size += 8 + lineNumber.length;
      }
      if (stackMap != null) {
        boolean zip = (cw.version & 0xFFFF) >= Opcodes.V1_6;
        cw.newUTF8(zip ? "StackMapTable" : "StackMap");
        size += 8 + stackMap.length;
      }
      if (cattrs != null) {
        size += cattrs.getSize(cw,
            code.data,
            code.length,
            maxStack,
            maxLocals);
      }
    }
    if (exceptionCount > 0) {
      cw.newUTF8("Exceptions");
      size += 8 + 2 * exceptionCount;
    }
    if ((access & Opcodes.ACC_SYNTHETIC) != 0
        && (cw.version & 0xffff) < Opcodes.V1_5) {
      cw.newUTF8("Synthetic");
      size += 6;
    }
    if ((access & Opcodes.ACC_DEPRECATED) != 0) {
      cw.newUTF8("Deprecated");
      size += 6;
    }
    if (signature != null) {
      cw.newUTF8("Signature");
      cw.newUTF8(signature);
      size += 8;
    }
    if (annd != null) {
      cw.newUTF8("AnnotationDefault");
      size += 6 + annd.length;
    }
    if (anns != null) {
      cw.newUTF8("RuntimeVisibleAnnotations");
      size += 8 + anns.getSize();
    }
    if (ianns != null) {
      cw.newUTF8("RuntimeInvisibleAnnotations");
      size += 8 + ianns.getSize();
    }
    if (panns != null) {
      cw.newUTF8("RuntimeVisibleParameterAnnotations");
      size += 7 + 2 * panns.length;
      for (int i = panns.length - 1; i >= 0; --i) {
        size += panns[i] == null ? 0 : panns[i].getSize();
      }
    }
    if (ipanns != null) {
      cw.newUTF8("RuntimeInvisibleParameterAnnotations");
      size += 7 + 2 * ipanns.length;
      for (int i = ipanns.length - 1; i >= 0; --i) {
        size += ipanns[i] == null ? 0 : ipanns[i].getSize();
      }
    }
    if (attrs != null) {
      size += attrs.getSize(cw, null, 0, -1, -1);
    }
    return size;
  }

  /**
   * Puts the bytecode of this method in the given byte vector.
   *
   * @param out the byte vector into which the bytecode of this method must be
   *            copied.
   */
  final void put(final ByteVector out) {
    out.putShort(access).putShort(name).putShort(desc);
    if (classReaderOffset != 0) {
      out.putByteArray(cw.cr.b, classReaderOffset, classReaderLength);
      return;
    }
    int attributeCount = 0;
    if (code.length > 0) {
      ++attributeCount;
    }
    if (exceptionCount > 0) {
      ++attributeCount;
    }
    if ((access & Opcodes.ACC_SYNTHETIC) != 0
        && (cw.version & 0xffff) < Opcodes.V1_5) {
      ++attributeCount;
    }
    if ((access & Opcodes.ACC_DEPRECATED) != 0) {
      ++attributeCount;
    }
    if (signature != null) {
      ++attributeCount;
    }
    if (annd != null) {
      ++attributeCount;
    }
    if (anns != null) {
      ++attributeCount;
    }
    if (ianns != null) {
      ++attributeCount;
    }
    if (panns != null) {
      ++attributeCount;
    }
    if (ipanns != null) {
      ++attributeCount;
    }
    if (attrs != null) {
      attributeCount += attrs.getCount();
    }
    out.putShort(attributeCount);
    if (code.length > 0) {
      int size = 12 + code.length + 8 * handlerCount;
      if (localVar != null) {
        size += 8 + localVar.length;
      }
      if (localVarType != null) {
        size += 8 + localVarType.length;
      }
      if (lineNumber != null) {
        size += 8 + lineNumber.length;
      }
      if (stackMap != null) {
        size += 8 + stackMap.length;
      }
      if (cattrs != null) {
        size += cattrs.getSize(cw,
            code.data,
            code.length,
            maxStack,
            maxLocals);
      }
      out.putShort(cw.newUTF8("Code")).putInt(size);
      out.putShort(maxStack).putShort(maxLocals);
      out.putInt(code.length).putByteArray(code.data, 0, code.length);
      out.putShort(handlerCount);
      if (handlerCount > 0) {
        Handler h = firstHandler;
        while (h != null) {
          out.putShort(h.start.position)
              .putShort(h.end.position)
              .putShort(h.handler.position)
              .putShort(h.type);
          h = h.next;
        }
      }
      attributeCount = 0;
      if (localVar != null) {
        ++attributeCount;
      }
      if (localVarType != null) {
        ++attributeCount;
      }
      if (lineNumber != null) {
        ++attributeCount;
      }
      if (stackMap != null) {
        ++attributeCount;
      }
      if (cattrs != null) {
        attributeCount += cattrs.getCount();
      }
      out.putShort(attributeCount);
      if (localVar != null) {
        out.putShort(cw.newUTF8("LocalVariableTable"));
        out.putInt(localVar.length + 2).putShort(localVarCount);
        out.putByteArray(localVar.data, 0, localVar.length);
      }
      if (localVarType != null) {
        out.putShort(cw.newUTF8("LocalVariableTypeTable"));
        out.putInt(localVarType.length + 2).putShort(localVarTypeCount);
        out.putByteArray(localVarType.data, 0, localVarType.length);
      }
      if (lineNumber != null) {
        out.putShort(cw.newUTF8("LineNumberTable"));
        out.putInt(lineNumber.length + 2).putShort(lineNumberCount);
        out.putByteArray(lineNumber.data, 0, lineNumber.length);
      }
      if (stackMap != null) {
        boolean zip = (cw.version & 0xFFFF) >= Opcodes.V1_6;
        out.putShort(cw.newUTF8(zip ? "StackMapTable" : "StackMap"));
        out.putInt(stackMap.length + 2).putShort(frameCount);
        out.putByteArray(stackMap.data, 0, stackMap.length);
      }
      if (cattrs != null) {
        cattrs.put(cw, code.data, code.length, maxLocals, maxStack, out);
      }
    }
    if (exceptionCount > 0) {
      out.putShort(cw.newUTF8("Exceptions"))
          .putInt(2 * exceptionCount + 2);
      out.putShort(exceptionCount);
      for (int i = 0; i < exceptionCount; ++i) {
        out.putShort(exceptions[i]);
      }
    }
    if ((access & Opcodes.ACC_SYNTHETIC) != 0
        && (cw.version & 0xffff) < Opcodes.V1_5) {
      out.putShort(cw.newUTF8("Synthetic")).putInt(0);
    }
    if ((access & Opcodes.ACC_DEPRECATED) != 0) {
      out.putShort(cw.newUTF8("Deprecated")).putInt(0);
    }
    if (signature != null) {
      out.putShort(cw.newUTF8("Signature"))
          .putInt(2)
          .putShort(cw.newUTF8(signature));
    }
    if (annd != null) {
      out.putShort(cw.newUTF8("AnnotationDefault"));
      out.putInt(annd.length);
      out.putByteArray(annd.data, 0, annd.length);
    }
    if (anns != null) {
      out.putShort(cw.newUTF8("RuntimeVisibleAnnotations"));
      anns.put(out);
    }
    if (ianns != null) {
      out.putShort(cw.newUTF8("RuntimeInvisibleAnnotations"));
      ianns.put(out);
    }
    if (panns != null) {
      out.putShort(cw.newUTF8("RuntimeVisibleParameterAnnotations"));
      AnnotationWriter.put(panns, out);
    }
    if (ipanns != null) {
      out.putShort(cw.newUTF8("RuntimeInvisibleParameterAnnotations"));
      AnnotationWriter.put(ipanns, out);
    }
    if (attrs != null) {
      attrs.put(cw, null, 0, -1, -1, out);
    }
  }

  // ------------------------------------------------------------------------
  // Utility methods: instruction resizing (used to handle GOTO_W and JSR_W)
  // ------------------------------------------------------------------------

  /**
   * Resizes and replaces the temporary instructions inserted by
   * {@link Label#resolve} for wide forward jumps, while keeping jump offsets
   * and instruction addresses consistent. This may require to resize other
   * existing instructions, or even to introduce new instructions: for
   * example, increasing the size of an instruction by 2 at the middle of a
   * method can increases the offset of an IFEQ instruction from 32766 to
   * 32768, in which case IFEQ 32766 must be replaced with IFNEQ 8 GOTO_W
   * 32765. This, in turn, may require to increase the size of another jump
   * instruction, and so on... All these operations are handled automatically
   * by this method. <p> <i>This method must be called after all the method
   * that is being built has been visited</i>. In particular, the
   * {@link Label Label} objects used to construct the method are no longer
   * valid after this method has been called.
   */
  private void resizeInstructions() {
    byte[] b = code.data; // bytecode of the method
    int u, v, label; // indexes in b
    int i, j; // loop indexes
    /*
    * 1st step: As explained above, resizing an instruction may require to
    * resize another one, which may require to resize yet another one, and
    * so on. The first step of the algorithm consists in finding all the
    * instructions that need to be resized, without modifying the code.
    * This is done by the following "fix point" algorithm:
    *
    * Parse the code to find the jump instructions whose offset will need
    * more than 2 bytes to be stored (the future offset is computed from
    * the current offset and from the number of bytes that will be inserted
    * or removed between the source and target instructions). For each such
    * instruction, adds an entry in (a copy of) the indexes and sizes
    * arrays (if this has not already been done in a previous iteration!).
    *
    * If at least one entry has been added during the previous step, go
    * back to the beginning, otherwise stop.
    *
    * In fact the real algorithm is complicated by the fact that the size
    * of TABLESWITCH and LOOKUPSWITCH instructions depends on their
    * position in the bytecode (because of padding). In order to ensure the
    * convergence of the algorithm, the number of bytes to be added or
    * removed from these instructions is over estimated during the previous
    * loop, and computed exactly only after the loop is finished (this
    * requires another pass to parse the bytecode of the method).
    */
    int[] allIndexes = new int[0]; // copy of indexes
    int[] allSizes = new int[0]; // copy of sizes
    boolean[] resize; // instructions to be resized
    int newOffset; // future offset of a jump instruction

    resize = new boolean[code.length];

    // 3 = loop again, 2 = loop ended, 1 = last pass, 0 = done
    int state = 3;
    do {
      if (state == 3) {
        state = 2;
      }
      u = 0;
      while (u < b.length) {
        int opcode = b[u] & 0xFF; // opcode of current instruction
        int insert = 0; // bytes to be added after this instruction

        switch (ClassWriter.TYPE[opcode]) {
          case ClassWriter.NOARG_INSN:
          case ClassWriter.IMPLVAR_INSN:
            u += 1;
            break;
          case ClassWriter.LABEL_INSN:
            if (opcode > 201) {
              // converts temporary opcodes 202 to 217, 218 and
              // 219 to IFEQ ... JSR (inclusive), IFNULL and
              // IFNONNULL
              opcode = opcode < 218 ? opcode - 49 : opcode - 20;
              label = u + readUnsignedShort(b, u + 1);
            }
            else {
              label = u + readShort(b, u + 1);
            }
            newOffset = getNewOffset(allIndexes, allSizes, u, label);
            if (newOffset < Short.MIN_VALUE
                || newOffset > Short.MAX_VALUE) {
              if (!resize[u]) {
                if (opcode == Opcodes.GOTO
                    || opcode == Opcodes.JSR) {
                  // two additional bytes will be required to
                  // replace this GOTO or JSR instruction with
                  // a GOTO_W or a JSR_W
                  insert = 2;
                }
                else {
                  // five additional bytes will be required to
                  // replace this IFxxx <l> instruction with
                  // IFNOTxxx <l'> GOTO_W <l>, where IFNOTxxx
                  // is the "opposite" opcode of IFxxx (i.e.,
                  // IFNE for IFEQ) and where <l'> designates
                  // the instruction just after the GOTO_W.
                  insert = 5;
                }
                resize[u] = true;
              }
            }
            u += 3;
            break;
          case ClassWriter.LABELW_INSN:
            u += 5;
            break;
          case ClassWriter.TABL_INSN:
            if (state == 1) {
              // true number of bytes to be added (or removed)
              // from this instruction = (future number of padding
              // bytes - current number of padding byte) -
              // previously over estimated variation =
              // = ((3 - newOffset%4) - (3 - u%4)) - u%4
              // = (-newOffset%4 + u%4) - u%4
              // = -(newOffset & 3)
              newOffset = getNewOffset(allIndexes, allSizes, 0, u);
              insert = -(newOffset & 3);
            }
            else if (!resize[u]) {
              // over estimation of the number of bytes to be
              // added to this instruction = 3 - current number
              // of padding bytes = 3 - (3 - u%4) = u%4 = u & 3
              insert = u & 3;
              resize[u] = true;
            }
            // skips instruction
            u = u + 4 - (u & 3);
            u += 4 * (readInt(b, u + 8) - readInt(b, u + 4) + 1) + 12;
            break;
          case ClassWriter.LOOK_INSN:
            if (state == 1) {
              // like TABL_INSN
              newOffset = getNewOffset(allIndexes, allSizes, 0, u);
              insert = -(newOffset & 3);
            }
            else if (!resize[u]) {
              // like TABL_INSN
              insert = u & 3;
              resize[u] = true;
            }
            // skips instruction
            u = u + 4 - (u & 3);
            u += 8 * readInt(b, u + 4) + 8;
            break;
          case ClassWriter.WIDE_INSN:
            opcode = b[u + 1] & 0xFF;
            if (opcode == Opcodes.IINC) {
              u += 6;
            }
            else {
              u += 4;
            }
            break;
          case ClassWriter.VAR_INSN:
          case ClassWriter.SBYTE_INSN:
          case ClassWriter.LDC_INSN:
            u += 2;
            break;
          case ClassWriter.SHORT_INSN:
          case ClassWriter.LDCW_INSN:
          case ClassWriter.FIELDORMETH_INSN:
          case ClassWriter.TYPE_INSN:
          case ClassWriter.IINC_INSN:
            u += 3;
            break;
          case ClassWriter.ITFMETH_INSN:
            u += 5;
            break;
          // case ClassWriter.MANA_INSN:
          default:
            u += 4;
            break;
        }
        if (insert != 0) {
          // adds a new (u, insert) entry in the allIndexes and
          // allSizes arrays
          int[] newIndexes = new int[allIndexes.length + 1];
          int[] newSizes = new int[allSizes.length + 1];
          System.arraycopy(allIndexes,
              0,
              newIndexes,
              0,
              allIndexes.length);
          System.arraycopy(allSizes, 0, newSizes, 0, allSizes.length);
          newIndexes[allIndexes.length] = u;
          newSizes[allSizes.length] = insert;
          allIndexes = newIndexes;
          allSizes = newSizes;
          if (insert > 0) {
            state = 3;
          }
        }
      }
      if (state < 3) {
        --state;
      }
    }
    while (state != 0);

    // 2nd step:
    // copies the bytecode of the method into a new bytevector, updates the
    // offsets, and inserts (or removes) bytes as requested.

    ByteVector newCode = new ByteVector(code.length);

    u = 0;
    while (u < code.length) {
      int opcode = b[u] & 0xFF;
      switch (ClassWriter.TYPE[opcode]) {
        case ClassWriter.NOARG_INSN:
        case ClassWriter.IMPLVAR_INSN:
          newCode.putByte(opcode);
          u += 1;
          break;
        case ClassWriter.LABEL_INSN:
          if (opcode > 201) {
            // changes temporary opcodes 202 to 217 (inclusive), 218
            // and 219 to IFEQ ... JSR (inclusive), IFNULL and
            // IFNONNULL
            opcode = opcode < 218 ? opcode - 49 : opcode - 20;
            label = u + readUnsignedShort(b, u + 1);
          }
          else {
            label = u + readShort(b, u + 1);
          }
          newOffset = getNewOffset(allIndexes, allSizes, u, label);
          if (resize[u]) {
            // replaces GOTO with GOTO_W, JSR with JSR_W and IFxxx
            // <l> with IFNOTxxx <l'> GOTO_W <l>, where IFNOTxxx is
            // the "opposite" opcode of IFxxx (i.e., IFNE for IFEQ)
            // and where <l'> designates the instruction just after
            // the GOTO_W.
            if (opcode == Opcodes.GOTO) {
              newCode.putByte(200); // GOTO_W
            }
            else if (opcode == Opcodes.JSR) {
              newCode.putByte(201); // JSR_W
            }
            else {
              newCode.putByte(opcode <= 166
                  ? ((opcode + 1) ^ 1) - 1
                  : opcode ^ 1);
              newCode.putShort(8); // jump offset
              newCode.putByte(200); // GOTO_W
              // newOffset now computed from start of GOTO_W
              newOffset -= 3;
            }
            newCode.putInt(newOffset);
          }
          else {
            newCode.putByte(opcode);
            newCode.putShort(newOffset);
          }
          u += 3;
          break;
        case ClassWriter.LABELW_INSN:
          label = u + readInt(b, u + 1);
          newOffset = getNewOffset(allIndexes, allSizes, u, label);
          newCode.putByte(opcode);
          newCode.putInt(newOffset);
          u += 5;
          break;
        case ClassWriter.TABL_INSN:
          // skips 0 to 3 padding bytes
          v = u;
          u = u + 4 - (v & 3);
          // reads and copies instruction
          newCode.putByte(Opcodes.TABLESWITCH);
          newCode.length += (4 - newCode.length % 4) % 4;
          label = v + readInt(b, u);
          u += 4;
          newOffset = getNewOffset(allIndexes, allSizes, v, label);
          newCode.putInt(newOffset);
          j = readInt(b, u);
          u += 4;
          newCode.putInt(j);
          j = readInt(b, u) - j + 1;
          u += 4;
          newCode.putInt(readInt(b, u - 4));
          for (; j > 0; --j) {
            label = v + readInt(b, u);
            u += 4;
            newOffset = getNewOffset(allIndexes, allSizes, v, label);
            newCode.putInt(newOffset);
          }
          break;
        case ClassWriter.LOOK_INSN:
          // skips 0 to 3 padding bytes
          v = u;
          u = u + 4 - (v & 3);
          // reads and copies instruction
          newCode.putByte(Opcodes.LOOKUPSWITCH);
          newCode.length += (4 - newCode.length % 4) % 4;
          label = v + readInt(b, u);
          u += 4;
          newOffset = getNewOffset(allIndexes, allSizes, v, label);
          newCode.putInt(newOffset);
          j = readInt(b, u);
          u += 4;
          newCode.putInt(j);
          for (; j > 0; --j) {
            newCode.putInt(readInt(b, u));
            u += 4;
            label = v + readInt(b, u);
            u += 4;
            newOffset = getNewOffset(allIndexes, allSizes, v, label);
            newCode.putInt(newOffset);
          }
          break;
        case ClassWriter.WIDE_INSN:
          opcode = b[u + 1] & 0xFF;
          if (opcode == Opcodes.IINC) {
            newCode.putByteArray(b, u, 6);
            u += 6;
          }
          else {
            newCode.putByteArray(b, u, 4);
            u += 4;
          }
          break;
        case ClassWriter.VAR_INSN:
        case ClassWriter.SBYTE_INSN:
        case ClassWriter.LDC_INSN:
          newCode.putByteArray(b, u, 2);
          u += 2;
          break;
        case ClassWriter.SHORT_INSN:
        case ClassWriter.LDCW_INSN:
        case ClassWriter.FIELDORMETH_INSN:
        case ClassWriter.TYPE_INSN:
        case ClassWriter.IINC_INSN:
          newCode.putByteArray(b, u, 3);
          u += 3;
          break;
        case ClassWriter.ITFMETH_INSN:
          newCode.putByteArray(b, u, 5);
          u += 5;
          break;
        // case MANA_INSN:
        default:
          newCode.putByteArray(b, u, 4);
          u += 4;
          break;
      }
    }

    // recomputes the stack map frames
    if (frameCount > 0) {
      if (compute == FRAMES) {
        frameCount = 0;
        stackMap = null;
        previousFrame = null;
        frame = null;
        Frame f = new Frame();
        f.owner = labels;
        Type[] args = Type.getArgumentTypes(descriptor);
        f.initInputFrame(cw, access, args, maxLocals);
        visitFrame(f);
        Label l = labels;
        while (l != null) {
          /*
          * here we need the original label position. getNewOffset
          * must therefore never have been called for this label.
          */
          u = l.position - 3;
          if ((l.status & Label.STORE) != 0 || (u >= 0 && resize[u])) {
            getNewOffset(allIndexes, allSizes, l);
            visitFrame(l.frame);
          }
          l = l.successor;
        }
      }
      else {
        /*
        * Resizing an existing stack map frame table is really hard.
        * Not only the table must be parsed to update the offets, but
        * new frames may be needed for jump instructions that were
        * inserted by this method. And updating the offsets or
        * inserting frames can change the format of the following
        * frames, in case of packed frames. In practice the whole table
        * must be recomputed. For this the frames are marked as
        * potentially invalid. This will cause the whole class to be
        * reread and rewritten with the COMPUTE_FRAMES option (see the
        * ClassWriter.toByteArray method). This is not very efficient
        * but is much easier and requires much less code than any other
        * method I can think of.
        */
        cw.invalidFrames = true;
      }
    }
    // updates the exception handler ast labels
    Handler h = firstHandler;
    while (h != null) {
      getNewOffset(allIndexes, allSizes, h.start);
      getNewOffset(allIndexes, allSizes, h.end);
      getNewOffset(allIndexes, allSizes, h.handler);
      h = h.next;
    }
    // updates the instructions addresses in the
    // local var and line number tables
    for (i = 0; i < 2; ++i) {
      ByteVector bv = i == 0 ? localVar : localVarType;
      if (bv != null) {
        b = bv.data;
        u = 0;
        while (u < bv.length) {
          label = readUnsignedShort(b, u);
          newOffset = getNewOffset(allIndexes, allSizes, 0, label);
          writeShort(b, u, newOffset);
          label += readUnsignedShort(b, u + 2);
          newOffset = getNewOffset(allIndexes, allSizes, 0, label)
              - newOffset;
          writeShort(b, u + 2, newOffset);
          u += 10;
        }
      }
    }
    if (lineNumber != null) {
      b = lineNumber.data;
      u = 0;
      while (u < lineNumber.length) {
        writeShort(b, u, getNewOffset(allIndexes,
            allSizes,
            0,
            readUnsignedShort(b, u)));
        u += 4;
      }
    }
    // updates the labels of the other attributes
    Attribute attr = cattrs;
    while (attr != null) {
      Label[] labels = attr.getLabels();
      if (labels != null) {
        for (i = labels.length - 1; i >= 0; --i) {
          getNewOffset(allIndexes, allSizes, labels[i]);
        }
      }
      attr = attr.next;
    }

    // replaces old bytecodes with new ones
    code = newCode;
  }

  /**
   * Reads an unsigned short value in the given byte array.
   *
   * @param b     a byte array.
   * @param index the start index of the value to be read.
   * @return the read value.
   */
  static int readUnsignedShort(final byte[] b, final int index) {
    return ((b[index] & 0xFF) << 8) | (b[index + 1] & 0xFF);
  }

  /**
   * Reads a signed short value in the given byte array.
   *
   * @param b     a byte array.
   * @param index the start index of the value to be read.
   * @return the read value.
   */
  static short readShort(final byte[] b, final int index) {
    return (short) (((b[index] & 0xFF) << 8) | (b[index + 1] & 0xFF));
  }

  /**
   * Reads a signed int value in the given byte array.
   *
   * @param b     a byte array.
   * @param index the start index of the value to be read.
   * @return the read value.
   */
  static int readInt(final byte[] b, final int index) {
    return ((b[index] & 0xFF) << 24) | ((b[index + 1] & 0xFF) << 16)
        | ((b[index + 2] & 0xFF) << 8) | (b[index + 3] & 0xFF);
  }

  /**
   * Writes a short value in the given byte array.
   *
   * @param b     a byte array.
   * @param index where the first byte of the short value must be written.
   * @param s     the value to be written in the given byte array.
   */
  static void writeShort(final byte[] b, final int index, final int s) {
    b[index] = (byte) (s >>> 8);
    b[index + 1] = (byte) s;
  }

  /**
   * Computes the future value of a bytecode offset. <p> Note: it is possible
   * to have several entries for the same instruction in the <tt>indexes</tt>
   * and <tt>sizes</tt>: two entries (index=a,size=b) and (index=a,size=b')
   * are equivalent to a single entry (index=a,size=b+b').
   *
   * @param indexes current positions of the instructions to be resized. Each
   *                instruction must be designated by the index of its <i>last</i>
   *                byte, plus one (or, in other words, by the index of the <i>first</i>
   *                byte of the <i>next</i> instruction).
   * @param sizes   the number of bytes to be <i>added</i> to the above
   *                instructions. More precisely, for each i < <tt>len</tt>,
   *                <tt>sizes</tt>[i] bytes will be added at the end of the
   *                instruction designated by <tt>indexes</tt>[i] or, if
   *                <tt>sizes</tt>[i] is negative, the <i>last</i> |<tt>sizes[i]</tt>|
   *                bytes of the instruction will be removed (the instruction size
   *                <i>must not</i> become negative or null).
   * @param begin   index of the first byte of the source instruction.
   * @param end     index of the first byte of the target instruction.
   * @return the future value of the given bytecode offset.
   */
  static int getNewOffset(
      final int[] indexes,
      final int[] sizes,
      final int begin,
      final int end) {
    int offset = end - begin;
    for (int i = 0; i < indexes.length; ++i) {
      if (begin < indexes[i] && indexes[i] <= end) {
        // forward jump
        offset += sizes[i];
      }
      else if (end < indexes[i] && indexes[i] <= begin) {
        // backward jump
        offset -= sizes[i];
      }
    }
    return offset;
  }

  /**
   * Updates the offset of the given label.
   *
   * @param indexes current positions of the instructions to be resized. Each
   *                instruction must be designated by the index of its <i>last</i>
   *                byte, plus one (or, in other words, by the index of the <i>first</i>
   *                byte of the <i>next</i> instruction).
   * @param sizes   the number of bytes to be <i>added</i> to the above
   *                instructions. More precisely, for each i < <tt>len</tt>,
   *                <tt>sizes</tt>[i] bytes will be added at the end of the
   *                instruction designated by <tt>indexes</tt>[i] or, if
   *                <tt>sizes</tt>[i] is negative, the <i>last</i> |<tt>sizes[i]</tt>|
   *                bytes of the instruction will be removed (the instruction size
   *                <i>must not</i> become negative or null).
   * @param label   the label whose offset must be updated.
   */
  static void getNewOffset(
      final int[] indexes,
      final int[] sizes,
      final Label label) {
    if ((label.status & Label.RESIZED) == 0) {
      label.position = getNewOffset(indexes, sizes, 0, label.position);
      label.status |= Label.RESIZED;
    }
  }
}
