// ASM: a very small and fast Java bytecode manipulation framework
// Copyright (c) 2000-2011 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.apache.tapestry5.internal.plastic.asm.tree;

import java.util.ArrayList;
import java.util.List;

import org.apache.tapestry5.internal.plastic.asm.AnnotationVisitor;
import org.apache.tapestry5.internal.plastic.asm.Attribute;
import org.apache.tapestry5.internal.plastic.asm.ClassVisitor;
import org.apache.tapestry5.internal.plastic.asm.ConstantDynamic;
import org.apache.tapestry5.internal.plastic.asm.Handle;
import org.apache.tapestry5.internal.plastic.asm.Label;
import org.apache.tapestry5.internal.plastic.asm.MethodVisitor;
import org.apache.tapestry5.internal.plastic.asm.Opcodes;
import org.apache.tapestry5.internal.plastic.asm.Type;
import org.apache.tapestry5.internal.plastic.asm.TypePath;

/**
 * A node that represents a method.
 *
 * @author Eric Bruneton
 */
public class MethodNode extends MethodVisitor {

  /**
   * The method's access flags (see {@link Opcodes}). This field also indicates if the method is
   * synthetic and/or deprecated.
   */
  public int access;

  /** The method's name. */
  public String name;

  /** The method's descriptor (see {@link Type}). */
  public String desc;

  /** The method's signature. May be {@literal null}. */
  public String signature;

  /** The internal names of the method's exception classes (see {@link Type#getInternalName()}). */
  public List<String> exceptions;

  /** The method parameter info (access flags and name). */
  public List<ParameterNode> parameters;

  /** The runtime visible annotations of this method. May be {@literal null}. */
  public List<AnnotationNode> visibleAnnotations;

  /** The runtime invisible annotations of this method. May be {@literal null}. */
  public List<AnnotationNode> invisibleAnnotations;

  /** The runtime visible type annotations of this method. May be {@literal null}. */
  public List<TypeAnnotationNode> visibleTypeAnnotations;

  /** The runtime invisible type annotations of this method. May be {@literal null}. */
  public List<TypeAnnotationNode> invisibleTypeAnnotations;

  /** The non standard attributes of this method. May be {@literal null}. */
  public List<Attribute> attrs;

  /**
   * The default value of this annotation interface method. This field must be a {@link Byte},
   * {@link Boolean}, {@link Character}, {@link Short}, {@link Integer}, {@link Long}, {@link
   * Float}, {@link Double}, {@link String} or {@link Type}, or an two elements String array (for
   * enumeration values), a {@link AnnotationNode}, or a {@link List} of values of one of the
   * preceding types. May be {@literal null}.
   */
  public Object annotationDefault;

  /**
   * The number of method parameters than can have runtime visible annotations. This number must be
   * less or equal than the number of parameter types in the method descriptor (the default value 0
   * indicates that all the parameters described in the method descriptor can have annotations). It
   * can be strictly less when a method has synthetic parameters and when these parameters are
   * ignored when computing parameter indices for the purpose of parameter annotations (see
   * https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.18).
   */
  public int visibleAnnotableParameterCount;

  /**
   * The runtime visible parameter annotations of this method. These lists are lists of {@link
   * AnnotationNode} objects. May be {@literal null}.
   */
  public List<AnnotationNode>[] visibleParameterAnnotations;

  /**
   * The number of method parameters than can have runtime invisible annotations. This number must
   * be less or equal than the number of parameter types in the method descriptor (the default value
   * 0 indicates that all the parameters described in the method descriptor can have annotations).
   * It can be strictly less when a method has synthetic parameters and when these parameters are
   * ignored when computing parameter indices for the purpose of parameter annotations (see
   * https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.18).
   */
  public int invisibleAnnotableParameterCount;

  /**
   * The runtime invisible parameter annotations of this method. These lists are lists of {@link
   * AnnotationNode} objects. May be {@literal null}.
   */
  public List<AnnotationNode>[] invisibleParameterAnnotations;

  /** The instructions of this method. */
  public InsnList instructions;

  /** The try catch blocks of this method. */
  public List<TryCatchBlockNode> tryCatchBlocks;

  /** The maximum stack size of this method. */
  public int maxStack;

  /** The maximum number of local variables of this method. */
  public int maxLocals;

  /** The local variables of this method. May be {@literal null} */
  public List<LocalVariableNode> localVariables;

  /** The visible local variable annotations of this method. May be {@literal null} */
  public List<LocalVariableAnnotationNode> visibleLocalVariableAnnotations;

  /** The invisible local variable annotations of this method. May be {@literal null} */
  public List<LocalVariableAnnotationNode> invisibleLocalVariableAnnotations;

  /** Whether the accept method has been called on this object. */
  private boolean visited;

  /**
   * Constructs an uninitialized {@link MethodNode}. <i>Subclasses must not use this
   * constructor</i>. Instead, they must use the {@link #MethodNode(int)} version.
   *
   * @throws IllegalStateException If a subclass calls this constructor.
   */
  public MethodNode() {
    this(/* latest api = */ Opcodes.ASM9);
    if (getClass() != MethodNode.class) {
      throw new IllegalStateException();
    }
  }

  /**
   * Constructs an uninitialized {@link MethodNode}.
   *
   * @param api the ASM API version implemented by this visitor. Must be one of {@link
   *     Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6}, {@link Opcodes#ASM7}, {@link
   *     Opcodes#ASM8} or {@link Opcodes#ASM9}.
   */
  public MethodNode(final int api) {
    super(api);
    this.instructions = new InsnList();
  }

  /**
   * Constructs a new {@link MethodNode}. <i>Subclasses must not use this constructor</i>. Instead,
   * they must use the {@link #MethodNode(int, int, String, String, String, String[])} version.
   *
   * @param access the method's access flags (see {@link Opcodes}). This parameter also indicates if
   *     the method is synthetic and/or deprecated.
   * @param name the method's name.
   * @param descriptor the method's descriptor (see {@link Type}).
   * @param signature the method's signature. May be {@literal null}.
   * @param exceptions the internal names of the method's exception classes (see {@link
   *     Type#getInternalName()}). May be {@literal null}.
   * @throws IllegalStateException If a subclass calls this constructor.
   */
  public MethodNode(
      final int access,
      final String name,
      final String descriptor,
      final String signature,
      final String[] exceptions) {
    this(/* latest api = */ Opcodes.ASM9, access, name, descriptor, signature, exceptions);
    if (getClass() != MethodNode.class) {
      throw new IllegalStateException();
    }
  }

  /**
   * Constructs a new {@link MethodNode}.
   *
   * @param api the ASM API version implemented by this visitor. Must be one of {@link
   *     Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6}, {@link Opcodes#ASM7}, {@link
   *     Opcodes#ASM8} or {@link Opcodes#ASM9}.
   * @param access the method's access flags (see {@link Opcodes}). This parameter also indicates if
   *     the method is synthetic and/or deprecated.
   * @param name the method's name.
   * @param descriptor the method's descriptor (see {@link Type}).
   * @param signature the method's signature. May be {@literal null}.
   * @param exceptions the internal names of the method's exception classes (see {@link
   *     Type#getInternalName()}). May be {@literal null}.
   */
  public MethodNode(
      final int api,
      final int access,
      final String name,
      final String descriptor,
      final String signature,
      final String[] exceptions) {
    super(api);
    this.access = access;
    this.name = name;
    this.desc = descriptor;
    this.signature = signature;
    this.exceptions = Util.asArrayList(exceptions);
    if ((access & Opcodes.ACC_ABSTRACT) == 0) {
      this.localVariables = new ArrayList<>(5);
    }
    this.tryCatchBlocks = new ArrayList<>();
    this.instructions = new InsnList();
  }

  // -----------------------------------------------------------------------------------------------
  // Implementation of the MethodVisitor abstract class
  // -----------------------------------------------------------------------------------------------

  @Override
  public void visitParameter(final String name, final int access) {
    if (parameters == null) {
      parameters = new ArrayList<>(5);
    }
    parameters.add(new ParameterNode(name, access));
  }

  @Override
  @SuppressWarnings("serial")
  public AnnotationVisitor visitAnnotationDefault() {
    return new AnnotationNode(
        new ArrayList<Object>(0) {
          @Override
          public boolean add(final Object o) {
            annotationDefault = o;
            return super.add(o);
          }
        });
  }

  @Override
  public AnnotationVisitor visitAnnotation(final String descriptor, final boolean visible) {
    AnnotationNode annotation = new AnnotationNode(descriptor);
    if (visible) {
      visibleAnnotations = Util.add(visibleAnnotations, annotation);
    } else {
      invisibleAnnotations = Util.add(invisibleAnnotations, annotation);
    }
    return annotation;
  }

  @Override
  public AnnotationVisitor visitTypeAnnotation(
      final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) {
    TypeAnnotationNode typeAnnotation = new TypeAnnotationNode(typeRef, typePath, descriptor);
    if (visible) {
      visibleTypeAnnotations = Util.add(visibleTypeAnnotations, typeAnnotation);
    } else {
      invisibleTypeAnnotations = Util.add(invisibleTypeAnnotations, typeAnnotation);
    }
    return typeAnnotation;
  }

  @Override
  public void visitAnnotableParameterCount(final int parameterCount, final boolean visible) {
    if (visible) {
      visibleAnnotableParameterCount = parameterCount;
    } else {
      invisibleAnnotableParameterCount = parameterCount;
    }
  }

  @Override
  @SuppressWarnings("unchecked")
  public AnnotationVisitor visitParameterAnnotation(
      final int parameter, final String descriptor, final boolean visible) {
    AnnotationNode annotation = new AnnotationNode(descriptor);
    if (visible) {
      if (visibleParameterAnnotations == null) {
        int params = Type.getArgumentTypes(desc).length;
        visibleParameterAnnotations = (List<AnnotationNode>[]) new List<?>[params];
      }
      visibleParameterAnnotations[parameter] =
          Util.add(visibleParameterAnnotations[parameter], annotation);
    } else {
      if (invisibleParameterAnnotations == null) {
        int params = Type.getArgumentTypes(desc).length;
        invisibleParameterAnnotations = (List<AnnotationNode>[]) new List<?>[params];
      }
      invisibleParameterAnnotations[parameter] =
          Util.add(invisibleParameterAnnotations[parameter], annotation);
    }
    return annotation;
  }

  @Override
  public void visitAttribute(final Attribute attribute) {
    attrs = Util.add(attrs, attribute);
  }

  @Override
  public void visitCode() {
    // Nothing to do.
  }

  @Override
  public void visitFrame(
      final int type,
      final int numLocal,
      final Object[] local,
      final int numStack,
      final Object[] stack) {
    instructions.add(
        new FrameNode(
            type,
            numLocal,
            local == null ? null : getLabelNodes(local),
            numStack,
            stack == null ? null : getLabelNodes(stack)));
  }

  @Override
  public void visitInsn(final int opcode) {
    instructions.add(new InsnNode(opcode));
  }

  @Override
  public void visitIntInsn(final int opcode, final int operand) {
    instructions.add(new IntInsnNode(opcode, operand));
  }

  @Override
  public void visitVarInsn(final int opcode, final int var) {
    instructions.add(new VarInsnNode(opcode, var));
  }

  @Override
  public void visitTypeInsn(final int opcode, final String type) {
    instructions.add(new TypeInsnNode(opcode, type));
  }

  @Override
  public void visitFieldInsn(
      final int opcode, final String owner, final String name, final String descriptor) {
    instructions.add(new FieldInsnNode(opcode, owner, name, descriptor));
  }

  @Override
  public void visitMethodInsn(
      final int opcodeAndSource,
      final String owner,
      final String name,
      final String descriptor,
      final boolean isInterface) {
    if (api < Opcodes.ASM5 && (opcodeAndSource & Opcodes.SOURCE_DEPRECATED) == 0) {
      // Redirect the call to the deprecated version of this method.
      super.visitMethodInsn(opcodeAndSource, owner, name, descriptor, isInterface);
      return;
    }
    int opcode = opcodeAndSource & ~Opcodes.SOURCE_MASK;

    instructions.add(new MethodInsnNode(opcode, owner, name, descriptor, isInterface));
  }

  @Override
  public void visitInvokeDynamicInsn(
      final String name,
      final String descriptor,
      final Handle bootstrapMethodHandle,
      final Object... bootstrapMethodArguments) {
    instructions.add(
        new InvokeDynamicInsnNode(
            name, descriptor, bootstrapMethodHandle, bootstrapMethodArguments));
  }

  @Override
  public void visitJumpInsn(final int opcode, final Label label) {
    instructions.add(new JumpInsnNode(opcode, getLabelNode(label)));
  }

  @Override
  public void visitLabel(final Label label) {
    instructions.add(getLabelNode(label));
  }

  @Override
  public void visitLdcInsn(final Object value) {
    instructions.add(new LdcInsnNode(value));
  }

  @Override
  public void visitIincInsn(final int var, final int increment) {
    instructions.add(new IincInsnNode(var, increment));
  }

  @Override
  public void visitTableSwitchInsn(
      final int min, final int max, final Label dflt, final Label... labels) {
    instructions.add(new TableSwitchInsnNode(min, max, getLabelNode(dflt), getLabelNodes(labels)));
  }

  @Override
  public void visitLookupSwitchInsn(final Label dflt, final int[] keys, final Label[] labels) {
    instructions.add(new LookupSwitchInsnNode(getLabelNode(dflt), keys, getLabelNodes(labels)));
  }

  @Override
  public void visitMultiANewArrayInsn(final String descriptor, final int numDimensions) {
    instructions.add(new MultiANewArrayInsnNode(descriptor, numDimensions));
  }

  @Override
  public AnnotationVisitor visitInsnAnnotation(
      final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) {
    // Find the last real instruction, i.e. the instruction targeted by this annotation.
    AbstractInsnNode currentInsn = instructions.getLast();
    while (currentInsn.getOpcode() == -1) {
      currentInsn = currentInsn.getPrevious();
    }
    // Add the annotation to this instruction.
    TypeAnnotationNode typeAnnotation = new TypeAnnotationNode(typeRef, typePath, descriptor);
    if (visible) {
      currentInsn.visibleTypeAnnotations =
          Util.add(currentInsn.visibleTypeAnnotations, typeAnnotation);
    } else {
      currentInsn.invisibleTypeAnnotations =
          Util.add(currentInsn.invisibleTypeAnnotations, typeAnnotation);
    }
    return typeAnnotation;
  }

  @Override
  public void visitTryCatchBlock(
      final Label start, final Label end, final Label handler, final String type) {
    TryCatchBlockNode tryCatchBlock =
        new TryCatchBlockNode(getLabelNode(start), getLabelNode(end), getLabelNode(handler), type);
    tryCatchBlocks = Util.add(tryCatchBlocks, tryCatchBlock);
  }

  @Override
  public AnnotationVisitor visitTryCatchAnnotation(
      final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) {
    TryCatchBlockNode tryCatchBlock = tryCatchBlocks.get((typeRef & 0x00FFFF00) >> 8);
    TypeAnnotationNode typeAnnotation = new TypeAnnotationNode(typeRef, typePath, descriptor);
    if (visible) {
      tryCatchBlock.visibleTypeAnnotations =
          Util.add(tryCatchBlock.visibleTypeAnnotations, typeAnnotation);
    } else {
      tryCatchBlock.invisibleTypeAnnotations =
          Util.add(tryCatchBlock.invisibleTypeAnnotations, typeAnnotation);
    }
    return typeAnnotation;
  }

  @Override
  public void visitLocalVariable(
      final String name,
      final String descriptor,
      final String signature,
      final Label start,
      final Label end,
      final int index) {
    LocalVariableNode localVariable =
        new LocalVariableNode(
            name, descriptor, signature, getLabelNode(start), getLabelNode(end), index);
    localVariables = Util.add(localVariables, localVariable);
  }

  @Override
  public AnnotationVisitor visitLocalVariableAnnotation(
      final int typeRef,
      final TypePath typePath,
      final Label[] start,
      final Label[] end,
      final int[] index,
      final String descriptor,
      final boolean visible) {
    LocalVariableAnnotationNode localVariableAnnotation =
        new LocalVariableAnnotationNode(
            typeRef, typePath, getLabelNodes(start), getLabelNodes(end), index, descriptor);
    if (visible) {
      visibleLocalVariableAnnotations =
          Util.add(visibleLocalVariableAnnotations, localVariableAnnotation);
    } else {
      invisibleLocalVariableAnnotations =
          Util.add(invisibleLocalVariableAnnotations, localVariableAnnotation);
    }
    return localVariableAnnotation;
  }

  @Override
  public void visitLineNumber(final int line, final Label start) {
    instructions.add(new LineNumberNode(line, getLabelNode(start)));
  }

  @Override
  public void visitMaxs(final int maxStack, final int maxLocals) {
    this.maxStack = maxStack;
    this.maxLocals = maxLocals;
  }

  @Override
  public void visitEnd() {
    // Nothing to do.
  }

  /**
   * Returns the LabelNode corresponding to the given Label. Creates a new LabelNode if necessary.
   * The default implementation of this method uses the {@link Label#info} field to store
   * associations between labels and label nodes.
   *
   * @param label a Label.
   * @return the LabelNode corresponding to label.
   */
  protected LabelNode getLabelNode(final Label label) {
    if (!(label.info instanceof LabelNode)) {
      label.info = new LabelNode();
    }
    return (LabelNode) label.info;
  }

  private LabelNode[] getLabelNodes(final Label[] labels) {
    LabelNode[] labelNodes = new LabelNode[labels.length];
    for (int i = 0, n = labels.length; i < n; ++i) {
      labelNodes[i] = getLabelNode(labels[i]);
    }
    return labelNodes;
  }

  private Object[] getLabelNodes(final Object[] objects) {
    Object[] labelNodes = new Object[objects.length];
    for (int i = 0, n = objects.length; i < n; ++i) {
      Object o = objects[i];
      if (o instanceof Label) {
        o = getLabelNode((Label) o);
      }
      labelNodes[i] = o;
    }
    return labelNodes;
  }

  // -----------------------------------------------------------------------------------------------
  // Accept method
  // -----------------------------------------------------------------------------------------------

  /**
   * Checks that this method node is compatible with the given ASM API version. This method checks
   * that this node, and all its children recursively, do not contain elements that were introduced
   * in more recent versions of the ASM API than the given version.
   *
   * @param api an ASM API version. Must be one of {@link Opcodes#ASM4}, {@link Opcodes#ASM5},
   *     {@link Opcodes#ASM6}, {@link Opcodes#ASM7}, {@link Opcodes#ASM8} or {@link Opcodes#ASM9}.
   */
  public void check(final int api) {
    if (api == Opcodes.ASM4) {
      if (parameters != null && !parameters.isEmpty()) {
        throw new UnsupportedClassVersionException();
      }
      if (visibleTypeAnnotations != null && !visibleTypeAnnotations.isEmpty()) {
        throw new UnsupportedClassVersionException();
      }
      if (invisibleTypeAnnotations != null && !invisibleTypeAnnotations.isEmpty()) {
        throw new UnsupportedClassVersionException();
      }
      if (tryCatchBlocks != null) {
        for (int i = tryCatchBlocks.size() - 1; i >= 0; --i) {
          TryCatchBlockNode tryCatchBlock = tryCatchBlocks.get(i);
          if (tryCatchBlock.visibleTypeAnnotations != null
              && !tryCatchBlock.visibleTypeAnnotations.isEmpty()) {
            throw new UnsupportedClassVersionException();
          }
          if (tryCatchBlock.invisibleTypeAnnotations != null
              && !tryCatchBlock.invisibleTypeAnnotations.isEmpty()) {
            throw new UnsupportedClassVersionException();
          }
        }
      }
      for (int i = instructions.size() - 1; i >= 0; --i) {
        AbstractInsnNode insn = instructions.get(i);
        if (insn.visibleTypeAnnotations != null && !insn.visibleTypeAnnotations.isEmpty()) {
          throw new UnsupportedClassVersionException();
        }
        if (insn.invisibleTypeAnnotations != null && !insn.invisibleTypeAnnotations.isEmpty()) {
          throw new UnsupportedClassVersionException();
        }
        if (insn instanceof MethodInsnNode) {
          boolean isInterface = ((MethodInsnNode) insn).itf;
          if (isInterface != (insn.opcode == Opcodes.INVOKEINTERFACE)) {
            throw new UnsupportedClassVersionException();
          }
        } else if (insn instanceof LdcInsnNode) {
          Object value = ((LdcInsnNode) insn).cst;
          if (value instanceof Handle
              || (value instanceof Type && ((Type) value).getSort() == Type.METHOD)) {
            throw new UnsupportedClassVersionException();
          }
        }
      }
      if (visibleLocalVariableAnnotations != null && !visibleLocalVariableAnnotations.isEmpty()) {
        throw new UnsupportedClassVersionException();
      }
      if (invisibleLocalVariableAnnotations != null
          && !invisibleLocalVariableAnnotations.isEmpty()) {
        throw new UnsupportedClassVersionException();
      }
    }
    if (api < Opcodes.ASM7) {
      for (int i = instructions.size() - 1; i >= 0; --i) {
        AbstractInsnNode insn = instructions.get(i);
        if (insn instanceof LdcInsnNode) {
          Object value = ((LdcInsnNode) insn).cst;
          if (value instanceof ConstantDynamic) {
            throw new UnsupportedClassVersionException();
          }
        }
      }
    }
  }

  /**
   * Makes the given class visitor visit this method.
   *
   * @param classVisitor a class visitor.
   */
  public void accept(final ClassVisitor classVisitor) {
    String[] exceptionsArray = exceptions == null ? null : exceptions.toArray(new String[0]);
    MethodVisitor methodVisitor =
        classVisitor.visitMethod(access, name, desc, signature, exceptionsArray);
    if (methodVisitor != null) {
      accept(methodVisitor);
    }
  }

  /**
   * Makes the given method visitor visit this method.
   *
   * @param methodVisitor a method visitor.
   */
  public void accept(final MethodVisitor methodVisitor) {
    // Visit the parameters.
    if (parameters != null) {
      for (int i = 0, n = parameters.size(); i < n; i++) {
        parameters.get(i).accept(methodVisitor);
      }
    }
    // Visit the annotations.
    if (annotationDefault != null) {
      AnnotationVisitor annotationVisitor = methodVisitor.visitAnnotationDefault();
      AnnotationNode.accept(annotationVisitor, null, annotationDefault);
      if (annotationVisitor != null) {
        annotationVisitor.visitEnd();
      }
    }
    if (visibleAnnotations != null) {
      for (int i = 0, n = visibleAnnotations.size(); i < n; ++i) {
        AnnotationNode annotation = visibleAnnotations.get(i);
        annotation.accept(methodVisitor.visitAnnotation(annotation.desc, true));
      }
    }
    if (invisibleAnnotations != null) {
      for (int i = 0, n = invisibleAnnotations.size(); i < n; ++i) {
        AnnotationNode annotation = invisibleAnnotations.get(i);
        annotation.accept(methodVisitor.visitAnnotation(annotation.desc, false));
      }
    }
    if (visibleTypeAnnotations != null) {
      for (int i = 0, n = visibleTypeAnnotations.size(); i < n; ++i) {
        TypeAnnotationNode typeAnnotation = visibleTypeAnnotations.get(i);
        typeAnnotation.accept(
            methodVisitor.visitTypeAnnotation(
                typeAnnotation.typeRef, typeAnnotation.typePath, typeAnnotation.desc, true));
      }
    }
    if (invisibleTypeAnnotations != null) {
      for (int i = 0, n = invisibleTypeAnnotations.size(); i < n; ++i) {
        TypeAnnotationNode typeAnnotation = invisibleTypeAnnotations.get(i);
        typeAnnotation.accept(
            methodVisitor.visitTypeAnnotation(
                typeAnnotation.typeRef, typeAnnotation.typePath, typeAnnotation.desc, false));
      }
    }
    if (visibleAnnotableParameterCount > 0) {
      methodVisitor.visitAnnotableParameterCount(visibleAnnotableParameterCount, true);
    }
    if (visibleParameterAnnotations != null) {
      for (int i = 0, n = visibleParameterAnnotations.length; i < n; ++i) {
        List<AnnotationNode> parameterAnnotations = visibleParameterAnnotations[i];
        if (parameterAnnotations == null) {
          continue;
        }
        for (int j = 0, m = parameterAnnotations.size(); j < m; ++j) {
          AnnotationNode annotation = parameterAnnotations.get(j);
          annotation.accept(methodVisitor.visitParameterAnnotation(i, annotation.desc, true));
        }
      }
    }
    if (invisibleAnnotableParameterCount > 0) {
      methodVisitor.visitAnnotableParameterCount(invisibleAnnotableParameterCount, false);
    }
    if (invisibleParameterAnnotations != null) {
      for (int i = 0, n = invisibleParameterAnnotations.length; i < n; ++i) {
        List<AnnotationNode> parameterAnnotations = invisibleParameterAnnotations[i];
        if (parameterAnnotations == null) {
          continue;
        }
        for (int j = 0, m = parameterAnnotations.size(); j < m; ++j) {
          AnnotationNode annotation = parameterAnnotations.get(j);
          annotation.accept(methodVisitor.visitParameterAnnotation(i, annotation.desc, false));
        }
      }
    }
    // Visit the non standard attributes.
    if (visited) {
      instructions.resetLabels();
    }
    if (attrs != null) {
      for (int i = 0, n = attrs.size(); i < n; ++i) {
        methodVisitor.visitAttribute(attrs.get(i));
      }
    }
    // Visit the code.
    if (instructions.size() > 0) {
      methodVisitor.visitCode();
      // Visits the try catch blocks.
      if (tryCatchBlocks != null) {
        for (int i = 0, n = tryCatchBlocks.size(); i < n; ++i) {
          tryCatchBlocks.get(i).updateIndex(i);
          tryCatchBlocks.get(i).accept(methodVisitor);
        }
      }
      // Visit the instructions.
      instructions.accept(methodVisitor);
      // Visits the local variables.
      if (localVariables != null) {
        for (int i = 0, n = localVariables.size(); i < n; ++i) {
          localVariables.get(i).accept(methodVisitor);
        }
      }
      // Visits the local variable annotations.
      if (visibleLocalVariableAnnotations != null) {
        for (int i = 0, n = visibleLocalVariableAnnotations.size(); i < n; ++i) {
          visibleLocalVariableAnnotations.get(i).accept(methodVisitor, true);
        }
      }
      if (invisibleLocalVariableAnnotations != null) {
        for (int i = 0, n = invisibleLocalVariableAnnotations.size(); i < n; ++i) {
          invisibleLocalVariableAnnotations.get(i).accept(methodVisitor, false);
        }
      }
      methodVisitor.visitMaxs(maxStack, maxLocals);
      visited = true;
    }
    methodVisitor.visitEnd();
  }
}
