You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tapestry.apache.org by th...@apache.org on 2018/11/29 00:33:52 UTC

[05/46] tapestry-5 git commit: TAP5-2588: upgrading from ASM 6 to 7 for Java 9+ support

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/1c71aec7/plastic/src/external/java/org/apache/tapestry5/internal/plastic/asm/util/Textifier.java
----------------------------------------------------------------------
diff --git a/plastic/src/external/java/org/apache/tapestry5/internal/plastic/asm/util/Textifier.java b/plastic/src/external/java/org/apache/tapestry5/internal/plastic/asm/util/Textifier.java
old mode 100644
new mode 100755
index 448728a..70a974d
--- a/plastic/src/external/java/org/apache/tapestry5/internal/plastic/asm/util/Textifier.java
+++ b/plastic/src/external/java/org/apache/tapestry5/internal/plastic/asm/util/Textifier.java
@@ -1,41 +1,36 @@
-/***
- * 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.
- */
+// 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.util;
 
-import java.io.FileInputStream;
-import java.io.PrintWriter;
+import java.io.IOException;
 import java.util.HashMap;
 import java.util.Map;
-
 import org.apache.tapestry5.internal.plastic.asm.Attribute;
-import org.apache.tapestry5.internal.plastic.asm.ClassReader;
 import org.apache.tapestry5.internal.plastic.asm.Handle;
 import org.apache.tapestry5.internal.plastic.asm.Label;
 import org.apache.tapestry5.internal.plastic.asm.Opcodes;
@@ -51,1546 +46,1568 @@ import org.apache.tapestry5.internal.plastic.asm.signature.SignatureReader;
  */
 public class Textifier extends Printer {
 
-    /**
-     * Constant used in {@link #appendDescriptor appendDescriptor} for internal
-     * type names in bytecode notation.
-     */
-    public static final int INTERNAL_NAME = 0;
-
-    /**
-     * Constant used in {@link #appendDescriptor appendDescriptor} for field
-     * descriptors, formatted in bytecode notation
-     */
-    public static final int FIELD_DESCRIPTOR = 1;
-
-    /**
-     * Constant used in {@link #appendDescriptor appendDescriptor} for field
-     * signatures, formatted in bytecode notation
-     */
-    public static final int FIELD_SIGNATURE = 2;
-
-    /**
-     * Constant used in {@link #appendDescriptor appendDescriptor} for method
-     * descriptors, formatted in bytecode notation
-     */
-    public static final int METHOD_DESCRIPTOR = 3;
-
-    /**
-     * Constant used in {@link #appendDescriptor appendDescriptor} for method
-     * signatures, formatted in bytecode notation
-     */
-    public static final int METHOD_SIGNATURE = 4;
-
-    /**
-     * Constant used in {@link #appendDescriptor appendDescriptor} for class
-     * signatures, formatted in bytecode notation
-     */
-    public static final int CLASS_SIGNATURE = 5;
-
-    /**
-     * Constant used in {@link #appendDescriptor appendDescriptor} for field or
-     * method return value signatures, formatted in default Java notation
-     * (non-bytecode)
-     */
-    public static final int TYPE_DECLARATION = 6;
-
-    /**
-     * Constant used in {@link #appendDescriptor appendDescriptor} for class
-     * signatures, formatted in default Java notation (non-bytecode)
-     */
-    public static final int CLASS_DECLARATION = 7;
-
-    /**
-     * Constant used in {@link #appendDescriptor appendDescriptor} for method
-     * parameter signatures, formatted in default Java notation (non-bytecode)
-     */
-    public static final int PARAMETERS_DECLARATION = 8;
-
-    /**
-     * Constant used in {@link #appendDescriptor appendDescriptor} for handle
-     * descriptors, formatted in bytecode notation
-     */
-    public static final int HANDLE_DESCRIPTOR = 9;
-
-    /**
-     * Tab for class members.
-     */
-    protected String tab = "  ";
-
-    /**
-     * Tab for bytecode instructions.
-     */
-    protected String tab2 = "    ";
-
-    /**
-     * Tab for table and lookup switch instructions.
-     */
-    protected String tab3 = "      ";
-
-    /**
-     * Tab for labels.
-     */
-    protected String ltab = "   ";
-
-    /**
-     * The label names. This map associate String values to Label keys.
-     */
-    protected Map<Label, String> labelNames;
-
-    /**
-     * Class access flags
-     */
-    private int access;
-
-    private int valueNumber = 0;
-
-    /**
-     * Constructs a new {@link Textifier}. <i>Subclasses must not use this
-     * constructor</i>. Instead, they must use the {@link #Textifier(int)}
-     * version.
-     *
-     * @throws IllegalStateException
-     *             If a subclass calls this constructor.
-     */
-    public Textifier() {
-        this(Opcodes.ASM6);
-        if (getClass() != Textifier.class) {
-            throw new IllegalStateException();
-        }
-    }
-
-    /**
-     * Constructs a new {@link Textifier}.
-     *
-     * @param api
-     *            the ASM API version implemented by this visitor. Must be one
-     *            of {@link Opcodes#ASM4}, {@link Opcodes#ASM5} or {@link Opcodes#ASM6}.
-     */
-    protected Textifier(final int api) {
-        super(api);
-    }
-
-    /**
-     * Prints a disassembled view of the given class to the standard output.
-     * <p>
-     * Usage: Textifier [-debug] &lt;binary class name or class file name &gt;
-     *
-     * @param args
-     *            the command line arguments.
-     *
-     * @throws Exception
-     *             if the class cannot be found, or if an IO exception occurs.
-     */
-    public static void main(final String[] args) throws Exception {
-        int i = 0;
-        int flags = ClassReader.SKIP_DEBUG;
-
-        boolean ok = true;
-        if (args.length < 1 || args.length > 2) {
-            ok = false;
-        }
-        if (ok && "-debug".equals(args[0])) {
-            i = 1;
-            flags = 0;
-            if (args.length != 2) {
-                ok = false;
-            }
-        }
-        if (!ok) {
-            System.err
-                    .println("Prints a disassembled view of the given class.");
-            System.err.println("Usage: Textifier [-debug] "
-                    + "<fully qualified class name or class file name>");
-            return;
-        }
-        ClassReader cr;
-        if (args[i].endsWith(".class") || args[i].indexOf('\\') > -1
-                || args[i].indexOf('/') > -1) {
-            cr = new ClassReader(new FileInputStream(args[i]));
-        } else {
-            cr = new ClassReader(args[i]);
-        }
-        cr.accept(new TraceClassVisitor(new PrintWriter(System.out)), flags);
-    }
-
-    // ------------------------------------------------------------------------
-    // Classes
-    // ------------------------------------------------------------------------
-
-    @Override
-    public void visit(final int version, final int access, final String name,
-            final String signature, final String superName,
-            final String[] interfaces) {
-        if ((access & Opcodes.ACC_MODULE) != 0) {
-            // visitModule will print the module
-            return;
-        }
-        this.access = access;
-        int major = version & 0xFFFF;
-        int minor = version >>> 16;
-        buf.setLength(0);
-        buf.append("// class version ").append(major).append('.').append(minor)
-                .append(" (").append(version).append(")\n");
-        if ((access & Opcodes.ACC_DEPRECATED) != 0) {
-            buf.append("// DEPRECATED\n");
-        }
-        buf.append("// access flags 0x")
-                .append(Integer.toHexString(access).toUpperCase()).append('\n');
-
-        appendDescriptor(CLASS_SIGNATURE, signature);
-        if (signature != null) {
-            TraceSignatureVisitor sv = new TraceSignatureVisitor(access);
-            SignatureReader r = new SignatureReader(signature);
-            r.accept(sv);
-            buf.append("// declaration: ").append(name)
-                    .append(sv.getDeclaration()).append('\n');
-        }
-
-        appendAccess(access & ~(Opcodes.ACC_SUPER | Opcodes.ACC_MODULE));
-        if ((access & Opcodes.ACC_ANNOTATION) != 0) {
-            buf.append("@interface ");
-        } else if ((access & Opcodes.ACC_INTERFACE) != 0) {
-            buf.append("interface ");
-        } else if ((access & Opcodes.ACC_ENUM) == 0) {
-            buf.append("class ");
-        }
-        appendDescriptor(INTERNAL_NAME, name);
-
-        if (superName != null && !"java/lang/Object".equals(superName)) {
-            buf.append(" extends ");
-            appendDescriptor(INTERNAL_NAME, superName);
-            buf.append(' ');
-        }
-        if (interfaces != null && interfaces.length > 0) {
-            buf.append(" implements ");
-            for (int i = 0; i < interfaces.length; ++i) {
-                appendDescriptor(INTERNAL_NAME, interfaces[i]);
-                buf.append(' ');
-            }
-        }
-        buf.append(" {\n\n");
-
-        text.add(buf.toString());
-    }
-
-    @Override
-    public void visitSource(final String file, final String debug) {
-        buf.setLength(0);
-        if (file != null) {
-            buf.append(tab).append("// compiled from: ").append(file)
-                    .append('\n');
-        }
-        if (debug != null) {
-            buf.append(tab).append("// debug info: ").append(debug)
-                    .append('\n');
-        }
-        if (buf.length() > 0) {
-            text.add(buf.toString());
-        }
-    }
-    
-    @Override
-    public Printer visitModule(final String name, final int access,
-            final String version) {
-        buf.setLength(0);
-        if ((access & Opcodes.ACC_OPEN) != 0) {
-            buf.append("open ");
-        }
-        buf.append("module ")
-           .append(name)
-           .append(" { ")
-           .append(version == null ? "" : "// " + version)
-           .append("\n\n");
-        text.add(buf.toString());
-        Textifier t = createTextifier();
-        text.add(t.getText());
-        return t;
-    }
-
-    @Override
-    public void visitOuterClass(final String owner, final String name,
-            final String desc) {
-        buf.setLength(0);
-        buf.append(tab).append("OUTERCLASS ");
-        appendDescriptor(INTERNAL_NAME, owner);
-        buf.append(' ');
-        if (name != null) {
-            buf.append(name).append(' ');
-        }
-        appendDescriptor(METHOD_DESCRIPTOR, desc);
-        buf.append('\n');
-        text.add(buf.toString());
-    }
-
-    @Override
-    public Textifier visitClassAnnotation(final String desc,
-            final boolean visible) {
-        text.add("\n");
-        return visitAnnotation(desc, visible);
-    }
-
-    @Override
-    public Printer visitClassTypeAnnotation(int typeRef, TypePath typePath,
-            String desc, boolean visible) {
-        text.add("\n");
-        return visitTypeAnnotation(typeRef, typePath, desc, visible);
-    }
-
-    @Override
-    public void visitClassAttribute(final Attribute attr) {
-        text.add("\n");
-        visitAttribute(attr);
-    }
-
-    @Override
-    public void visitInnerClass(final String name, final String outerName,
-            final String innerName, final int access) {
-        buf.setLength(0);
-        buf.append(tab).append("// access flags 0x");
-        buf.append(
-                Integer.toHexString(access & ~Opcodes.ACC_SUPER).toUpperCase())
-                .append('\n');
-        buf.append(tab);
-        appendAccess(access);
-        buf.append("INNERCLASS ");
-        appendDescriptor(INTERNAL_NAME, name);
-        buf.append(' ');
-        appendDescriptor(INTERNAL_NAME, outerName);
-        buf.append(' ');
-        appendDescriptor(INTERNAL_NAME, innerName);
-        buf.append('\n');
-        text.add(buf.toString());
-    }
-
-    @Override
-    public Textifier visitField(final int access, final String name,
-            final String desc, final String signature, final Object value) {
-        buf.setLength(0);
-        buf.append('\n');
-        if ((access & Opcodes.ACC_DEPRECATED) != 0) {
-            buf.append(tab).append("// DEPRECATED\n");
-        }
-        buf.append(tab).append("// access flags 0x")
-                .append(Integer.toHexString(access).toUpperCase()).append('\n');
-        if (signature != null) {
-            buf.append(tab);
-            appendDescriptor(FIELD_SIGNATURE, signature);
-
-            TraceSignatureVisitor sv = new TraceSignatureVisitor(0);
-            SignatureReader r = new SignatureReader(signature);
-            r.acceptType(sv);
-            buf.append(tab).append("// declaration: ")
-                    .append(sv.getDeclaration()).append('\n');
-        }
-
-        buf.append(tab);
-        appendAccess(access);
-
-        appendDescriptor(FIELD_DESCRIPTOR, desc);
-        buf.append(' ').append(name);
-        if (value != null) {
-            buf.append(" = ");
-            if (value instanceof String) {
-                buf.append('\"').append(value).append('\"');
-            } else {
-                buf.append(value);
-            }
-        }
-
-        buf.append('\n');
-        text.add(buf.toString());
-
-        Textifier t = createTextifier();
-        text.add(t.getText());
-        return t;
-    }
-
-    @Override
-    public Textifier visitMethod(final int access, final String name,
-            final String desc, final String signature, final String[] exceptions) {
-        buf.setLength(0);
-        buf.append('\n');
-        if ((access & Opcodes.ACC_DEPRECATED) != 0) {
-            buf.append(tab).append("// DEPRECATED\n");
-        }
-        buf.append(tab).append("// access flags 0x")
-                .append(Integer.toHexString(access).toUpperCase()).append('\n');
-
-        if (signature != null) {
-            buf.append(tab);
-            appendDescriptor(METHOD_SIGNATURE, signature);
-
-            TraceSignatureVisitor v = new TraceSignatureVisitor(0);
-            SignatureReader r = new SignatureReader(signature);
-            r.accept(v);
-            String genericDecl = v.getDeclaration();
-            String genericReturn = v.getReturnType();
-            String genericExceptions = v.getExceptions();
-
-            buf.append(tab).append("// declaration: ").append(genericReturn)
-                    .append(' ').append(name).append(genericDecl);
-            if (genericExceptions != null) {
-                buf.append(" throws ").append(genericExceptions);
-            }
-            buf.append('\n');
-        }
-
-        buf.append(tab);
-        appendAccess(access & ~(Opcodes.ACC_VOLATILE | Opcodes.ACC_TRANSIENT));
-        if ((access & Opcodes.ACC_NATIVE) != 0) {
-            buf.append("native ");
-        }
-        if ((access & Opcodes.ACC_VARARGS) != 0) {
-            buf.append("varargs ");
-        }
-        if ((access & Opcodes.ACC_BRIDGE) != 0) {
-            buf.append("bridge ");
-        }
-        if ((this.access & Opcodes.ACC_INTERFACE) != 0
-                && (access & Opcodes.ACC_ABSTRACT) == 0
-                && (access & Opcodes.ACC_STATIC) == 0) {
-            buf.append("default ");
-        }
-
-        buf.append(name);
-        appendDescriptor(METHOD_DESCRIPTOR, desc);
-        if (exceptions != null && exceptions.length > 0) {
-            buf.append(" throws ");
-            for (int i = 0; i < exceptions.length; ++i) {
-                appendDescriptor(INTERNAL_NAME, exceptions[i]);
-                buf.append(' ');
-            }
-        }
-
-        buf.append('\n');
-        text.add(buf.toString());
-
-        Textifier t = createTextifier();
-        text.add(t.getText());
-        return t;
-    }
-
-    @Override
-    public void visitClassEnd() {
-        text.add("}\n");
-    }
-
-    // ------------------------------------------------------------------------
-    // Module
-    // ------------------------------------------------------------------------
-    
-    @Override
-    public void visitMainClass(String mainClass) {
-        buf.setLength(0);
-        buf.append("  // main class ").append(mainClass).append('\n');
-        text.add(buf.toString());
-    }
-    
-    @Override
-    public void visitPackage(String packaze) {
-        buf.setLength(0);
-        buf.append("  // package ").append(packaze).append('\n');
-        text.add(buf.toString());
-    }
-    
-    @Override
-    public void visitRequire(String require, int access, String version) {
-        buf.setLength(0);
-        buf.append(tab).append("requires ");
-        if ((access & Opcodes.ACC_TRANSITIVE) != 0) {
-            buf.append("transitive ");
-        }
-        if ((access & Opcodes.ACC_STATIC_PHASE) != 0) {
-            buf.append("static ");
-        }
-        buf.append(require)
-           .append(";  // access flags 0x")
-           .append(Integer.toHexString(access).toUpperCase())
-           .append('\n');
-        if (version != null) {
-            buf.append("  // version ")
-               .append(version)
-               .append('\n');
-        }
-        text.add(buf.toString());
-    }
-    
-    @Override
-    public void visitExport(String export, int access, String... modules) {
-        buf.setLength(0);
-        buf.append(tab).append("exports ");
-        buf.append(export);
-        if (modules != null && modules.length > 0) {
-            buf.append(" to");
-        } else {
-            buf.append(';');
-        }
-        buf.append("  // access flags 0x")
-           .append(Integer.toHexString(access).toUpperCase())
-           .append('\n');
-        if (modules != null && modules.length > 0) {
-            for (int i = 0; i < modules.length; ++i) {
-                buf.append(tab2).append(modules[i]);
-                buf.append(i != modules.length - 1 ? ",\n": ";\n");
-            }
-        }
-        text.add(buf.toString());
-    }
-    
-    @Override
-    public void visitOpen(String export, int access, String... modules) {
-        buf.setLength(0);
-        buf.append(tab).append("opens ");
-        buf.append(export);
-        if (modules != null && modules.length > 0) {
-            buf.append(" to");
-        } else {
-            buf.append(';');
-        }
-        buf.append("  // access flags 0x")
-           .append(Integer.toHexString(access).toUpperCase())
-           .append('\n');
-        if (modules != null && modules.length > 0) {
-            for (int i = 0; i < modules.length; ++i) {
-                buf.append(tab2).append(modules[i]);
-                buf.append(i != modules.length - 1 ? ",\n": ";\n");
-            }
-        }
-        text.add(buf.toString());
-    }
-    
-    @Override
-    public void visitUse(String use) {
-        buf.setLength(0);
-        buf.append(tab).append("uses ");
-        appendDescriptor(INTERNAL_NAME, use);
-        buf.append(";\n");
-        text.add(buf.toString());
-    }
-    
-    @Override
-    public void visitProvide(String provide, String... providers) {
-        buf.setLength(0);
-        buf.append(tab).append("provides ");
-        appendDescriptor(INTERNAL_NAME, provide);
-        buf.append(" with\n");
-        for (int i = 0; i < providers.length; ++i) {
-            buf.append(tab2);
-            appendDescriptor(INTERNAL_NAME, providers[i]);
-            buf.append(i != providers.length - 1 ? ",\n": ";\n");
-        }
-        text.add(buf.toString());
-    }
-    
-    @Override
-    public void visitModuleEnd() {
-        // empty
-    }
-    
-    // ------------------------------------------------------------------------
-    // Annotations
-    // ------------------------------------------------------------------------
-
-    @Override
-    public void visit(final String name, final Object value) {
-        buf.setLength(0);
-        appendComa(valueNumber++);
-
-        if (name != null) {
-            buf.append(name).append('=');
-        }
-
+  /** The type of internal names. See {@link #appendDescriptor}. */
+  public static final int INTERNAL_NAME = 0;
+
+  /** The type of field descriptors. See {@link #appendDescriptor}. */
+  public static final int FIELD_DESCRIPTOR = 1;
+
+  /** The type of field signatures. See {@link #appendDescriptor}. */
+  public static final int FIELD_SIGNATURE = 2;
+
+  /** The type of method descriptors. See {@link #appendDescriptor}. */
+  public static final int METHOD_DESCRIPTOR = 3;
+
+  /** The type of method signatures. See {@link #appendDescriptor}. */
+  public static final int METHOD_SIGNATURE = 4;
+
+  /** The type of class signatures. See {@link #appendDescriptor}. */
+  public static final int CLASS_SIGNATURE = 5;
+
+  /**
+   * Deprecated.
+   *
+   * @deprecated this constant has never been used.
+   */
+  @Deprecated public static final int TYPE_DECLARATION = 6;
+
+  /**
+   * Deprecated.
+   *
+   * @deprecated this constant has never been used.
+   */
+  @Deprecated public static final int CLASS_DECLARATION = 7;
+
+  /**
+   * Deprecated.
+   *
+   * @deprecated this constant has never been used.
+   */
+  @Deprecated public static final int PARAMETERS_DECLARATION = 8;
+
+  /** The type of method handle descriptors. See {@link #appendDescriptor}. */
+  public static final int HANDLE_DESCRIPTOR = 9;
+
+  private static final String CLASS_SUFFIX = ".class";
+  private static final String DEPRECATED = "// DEPRECATED\n";
+  private static final String INVISIBLE = " // invisible\n";
+
+  /** The indentation of class members at depth level 1 (e.g. fields, methods). */
+  protected String tab = "  ";
+
+  /** The indentation of class elements at depth level 2 (e.g. bytecode instructions in methods). */
+  protected String tab2 = "    ";
+
+  /** The indentation of class elements at depth level 3 (e.g. switch cases in methods). */
+  protected String tab3 = "      ";
+
+  /** The indentation of labels. */
+  protected String ltab = "   ";
+
+  /** The names of the labels. */
+  protected Map<Label, String> labelNames;
+
+  /** The access flags of the visited class. */
+  private int access;
+
+  /** The number of annotation values visited so far. */
+  private int numAnnotationValues;
+
+  /**
+   * Constructs a new {@link Textifier}. <i>Subclasses must not use this constructor</i>. Instead,
+   * they must use the {@link #Textifier(int)} version.
+   *
+   * @throws IllegalStateException If a subclass calls this constructor.
+   */
+  public Textifier() {
+    this(Opcodes.ASM7);
+    if (getClass() != Textifier.class) {
+      throw new IllegalStateException();
+    }
+  }
+
+  /**
+   * Constructs a new {@link Textifier}.
+   *
+   * @param api the ASM API version implemented by this visitor. Must be one of {@link
+   *     Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link Opcodes#ASM7}.
+   */
+  protected Textifier(final int api) {
+    super(api);
+  }
+
+  /**
+   * Prints a disassembled view of the given class to the standard output.
+   *
+   * <p>Usage: Textifier [-debug] &lt;binary class name or class file name &gt;
+   *
+   * @param args the command line arguments.
+   * @throws IOException if the class cannot be found, or if an IOException occurs.
+   */
+  public static void main(final String[] args) throws IOException {
+    String usage =
+        "Prints a disassembled view of the given class.\n"
+            + "Usage: Textifier [-debug] <fully qualified class name or class file name>";
+    main(usage, new Textifier(), args);
+  }
+
+  // -----------------------------------------------------------------------------------------------
+  // Classes
+  // -----------------------------------------------------------------------------------------------
+
+  @Override
+  public void visit(
+      final int version,
+      final int access,
+      final String name,
+      final String signature,
+      final String superName,
+      final String[] interfaces) {
+    if ((access & Opcodes.ACC_MODULE) != 0) {
+      // Modules are printed in visitModule.
+      return;
+    }
+    this.access = access;
+    int majorVersion = version & 0xFFFF;
+    int minorVersion = version >>> 16;
+    stringBuilder.setLength(0);
+    stringBuilder
+        .append("// class version ")
+        .append(majorVersion)
+        .append('.')
+        .append(minorVersion)
+        .append(" (")
+        .append(version)
+        .append(")\n");
+    if ((access & Opcodes.ACC_DEPRECATED) != 0) {
+      stringBuilder.append(DEPRECATED);
+    }
+    appendRawAccess(access);
+
+    appendDescriptor(CLASS_SIGNATURE, signature);
+    if (signature != null) {
+      appendJavaDeclaration(name, signature);
+    }
+
+    appendAccess(access & ~(Opcodes.ACC_SUPER | Opcodes.ACC_MODULE));
+    if ((access & Opcodes.ACC_ANNOTATION) != 0) {
+      stringBuilder.append("@interface ");
+    } else if ((access & Opcodes.ACC_INTERFACE) != 0) {
+      stringBuilder.append("interface ");
+    } else if ((access & Opcodes.ACC_ENUM) == 0) {
+      stringBuilder.append("class ");
+    }
+    appendDescriptor(INTERNAL_NAME, name);
+
+    if (superName != null && !"java/lang/Object".equals(superName)) {
+      stringBuilder.append(" extends ");
+      appendDescriptor(INTERNAL_NAME, superName);
+    }
+    if (interfaces != null && interfaces.length > 0) {
+      stringBuilder.append(" implements ");
+      for (int i = 0; i < interfaces.length; ++i) {
+        appendDescriptor(INTERNAL_NAME, interfaces[i]);
+        if (i != interfaces.length - 1) {
+          stringBuilder.append(' ');
+        }
+      }
+    }
+    stringBuilder.append(" {\n\n");
+
+    text.add(stringBuilder.toString());
+  }
+
+  @Override
+  public void visitSource(final String file, final String debug) {
+    stringBuilder.setLength(0);
+    if (file != null) {
+      stringBuilder.append(tab).append("// compiled from: ").append(file).append('\n');
+    }
+    if (debug != null) {
+      stringBuilder.append(tab).append("// debug info: ").append(debug).append('\n');
+    }
+    if (stringBuilder.length() > 0) {
+      text.add(stringBuilder.toString());
+    }
+  }
+
+  @Override
+  public Printer visitModule(final String name, final int access, final String version) {
+    stringBuilder.setLength(0);
+    if ((access & Opcodes.ACC_OPEN) != 0) {
+      stringBuilder.append("open ");
+    }
+    stringBuilder
+        .append("module ")
+        .append(name)
+        .append(" { ")
+        .append(version == null ? "" : "// " + version)
+        .append("\n\n");
+    text.add(stringBuilder.toString());
+    return addNewTextifier(null);
+  }
+
+  @Override
+  public void visitNestHost(final String nestHost) {
+    stringBuilder.setLength(0);
+    stringBuilder.append(tab).append("NESTHOST ");
+    appendDescriptor(INTERNAL_NAME, nestHost);
+    stringBuilder.append('\n');
+    text.add(stringBuilder.toString());
+  }
+
+  @Override
+  public void visitOuterClass(final String owner, final String name, final String descriptor) {
+    stringBuilder.setLength(0);
+    stringBuilder.append(tab).append("OUTERCLASS ");
+    appendDescriptor(INTERNAL_NAME, owner);
+    stringBuilder.append(' ');
+    if (name != null) {
+      stringBuilder.append(name).append(' ');
+    }
+    appendDescriptor(METHOD_DESCRIPTOR, descriptor);
+    stringBuilder.append('\n');
+    text.add(stringBuilder.toString());
+  }
+
+  @Override
+  public Textifier visitClassAnnotation(final String descriptor, final boolean visible) {
+    text.add("\n");
+    return visitAnnotation(descriptor, visible);
+  }
+
+  @Override
+  public Printer visitClassTypeAnnotation(
+      final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) {
+    text.add("\n");
+    return visitTypeAnnotation(typeRef, typePath, descriptor, visible);
+  }
+
+  @Override
+  public void visitClassAttribute(final Attribute attribute) {
+    text.add("\n");
+    visitAttribute(attribute);
+  }
+
+  @Override
+  public void visitNestMember(final String nestMember) {
+    stringBuilder.setLength(0);
+    stringBuilder.append(tab).append("NESTMEMBER ");
+    appendDescriptor(INTERNAL_NAME, nestMember);
+    stringBuilder.append('\n');
+    text.add(stringBuilder.toString());
+  }
+
+  @Override
+  public void visitInnerClass(
+      final String name, final String outerName, final String innerName, final int access) {
+    stringBuilder.setLength(0);
+    stringBuilder.append(tab);
+    appendRawAccess(access & ~Opcodes.ACC_SUPER);
+    stringBuilder.append(tab);
+    appendAccess(access);
+    stringBuilder.append("INNERCLASS ");
+    appendDescriptor(INTERNAL_NAME, name);
+    stringBuilder.append(' ');
+    appendDescriptor(INTERNAL_NAME, outerName);
+    stringBuilder.append(' ');
+    appendDescriptor(INTERNAL_NAME, innerName);
+    stringBuilder.append('\n');
+    text.add(stringBuilder.toString());
+  }
+
+  @Override
+  public Textifier visitField(
+      final int access,
+      final String name,
+      final String descriptor,
+      final String signature,
+      final Object value) {
+    stringBuilder.setLength(0);
+    stringBuilder.append('\n');
+    if ((access & Opcodes.ACC_DEPRECATED) != 0) {
+      stringBuilder.append(tab).append(DEPRECATED);
+    }
+    stringBuilder.append(tab);
+    appendRawAccess(access);
+    if (signature != null) {
+      stringBuilder.append(tab);
+      appendDescriptor(FIELD_SIGNATURE, signature);
+      stringBuilder.append(tab);
+      appendJavaDeclaration(name, signature);
+    }
+
+    stringBuilder.append(tab);
+    appendAccess(access);
+
+    appendDescriptor(FIELD_DESCRIPTOR, descriptor);
+    stringBuilder.append(' ').append(name);
+    if (value != null) {
+      stringBuilder.append(" = ");
+      if (value instanceof String) {
+        stringBuilder.append('\"').append(value).append('\"');
+      } else {
+        stringBuilder.append(value);
+      }
+    }
+
+    stringBuilder.append('\n');
+    text.add(stringBuilder.toString());
+    return addNewTextifier(null);
+  }
+
+  @Override
+  public Textifier visitMethod(
+      final int access,
+      final String name,
+      final String descriptor,
+      final String signature,
+      final String[] exceptions) {
+    stringBuilder.setLength(0);
+    stringBuilder.append('\n');
+    if ((access & Opcodes.ACC_DEPRECATED) != 0) {
+      stringBuilder.append(tab).append(DEPRECATED);
+    }
+    stringBuilder.append(tab);
+    appendRawAccess(access);
+
+    if (signature != null) {
+      stringBuilder.append(tab);
+      appendDescriptor(METHOD_SIGNATURE, signature);
+      stringBuilder.append(tab);
+      appendJavaDeclaration(name, signature);
+    }
+
+    stringBuilder.append(tab);
+    appendAccess(access & ~(Opcodes.ACC_VOLATILE | Opcodes.ACC_TRANSIENT));
+    if ((access & Opcodes.ACC_NATIVE) != 0) {
+      stringBuilder.append("native ");
+    }
+    if ((access & Opcodes.ACC_VARARGS) != 0) {
+      stringBuilder.append("varargs ");
+    }
+    if ((access & Opcodes.ACC_BRIDGE) != 0) {
+      stringBuilder.append("bridge ");
+    }
+    if ((this.access & Opcodes.ACC_INTERFACE) != 0
+        && (access & (Opcodes.ACC_ABSTRACT | Opcodes.ACC_STATIC)) == 0) {
+      stringBuilder.append("default ");
+    }
+
+    stringBuilder.append(name);
+    appendDescriptor(METHOD_DESCRIPTOR, descriptor);
+    if (exceptions != null && exceptions.length > 0) {
+      stringBuilder.append(" throws ");
+      for (String exception : exceptions) {
+        appendDescriptor(INTERNAL_NAME, exception);
+        stringBuilder.append(' ');
+      }
+    }
+
+    stringBuilder.append('\n');
+    text.add(stringBuilder.toString());
+    return addNewTextifier(null);
+  }
+
+  @Override
+  public void visitClassEnd() {
+    text.add("}\n");
+  }
+
+  // -----------------------------------------------------------------------------------------------
+  // Modules
+  // -----------------------------------------------------------------------------------------------
+
+  @Override
+  public void visitMainClass(final String mainClass) {
+    stringBuilder.setLength(0);
+    stringBuilder.append("  // main class ").append(mainClass).append('\n');
+    text.add(stringBuilder.toString());
+  }
+
+  @Override
+  public void visitPackage(final String packaze) {
+    stringBuilder.setLength(0);
+    stringBuilder.append("  // package ").append(packaze).append('\n');
+    text.add(stringBuilder.toString());
+  }
+
+  @Override
+  public void visitRequire(final String require, final int access, final String version) {
+    stringBuilder.setLength(0);
+    stringBuilder.append(tab).append("requires ");
+    if ((access & Opcodes.ACC_TRANSITIVE) != 0) {
+      stringBuilder.append("transitive ");
+    }
+    if ((access & Opcodes.ACC_STATIC_PHASE) != 0) {
+      stringBuilder.append("static ");
+    }
+    stringBuilder.append(require).append(';');
+    appendRawAccess(access);
+    if (version != null) {
+      stringBuilder.append("  // version ").append(version).append('\n');
+    }
+    text.add(stringBuilder.toString());
+  }
+
+  @Override
+  public void visitExport(final String export, final int access, final String... modules) {
+    stringBuilder.setLength(0);
+    stringBuilder.append(tab).append("exports ");
+    stringBuilder.append(export);
+    if (modules != null && modules.length > 0) {
+      stringBuilder.append(" to");
+    } else {
+      stringBuilder.append(';');
+    }
+    appendRawAccess(access);
+    if (modules != null && modules.length > 0) {
+      for (int i = 0; i < modules.length; ++i) {
+        stringBuilder.append(tab2).append(modules[i]);
+        stringBuilder.append(i != modules.length - 1 ? ",\n" : ";\n");
+      }
+    }
+    text.add(stringBuilder.toString());
+  }
+
+  @Override
+  public void visitOpen(final String export, final int access, final String... modules) {
+    stringBuilder.setLength(0);
+    stringBuilder.append(tab).append("opens ");
+    stringBuilder.append(export);
+    if (modules != null && modules.length > 0) {
+      stringBuilder.append(" to");
+    } else {
+      stringBuilder.append(';');
+    }
+    appendRawAccess(access);
+    if (modules != null && modules.length > 0) {
+      for (int i = 0; i < modules.length; ++i) {
+        stringBuilder.append(tab2).append(modules[i]);
+        stringBuilder.append(i != modules.length - 1 ? ",\n" : ";\n");
+      }
+    }
+    text.add(stringBuilder.toString());
+  }
+
+  @Override
+  public void visitUse(final String use) {
+    stringBuilder.setLength(0);
+    stringBuilder.append(tab).append("uses ");
+    appendDescriptor(INTERNAL_NAME, use);
+    stringBuilder.append(";\n");
+    text.add(stringBuilder.toString());
+  }
+
+  @Override
+  public void visitProvide(final String provide, final String... providers) {
+    stringBuilder.setLength(0);
+    stringBuilder.append(tab).append("provides ");
+    appendDescriptor(INTERNAL_NAME, provide);
+    stringBuilder.append(" with\n");
+    for (int i = 0; i < providers.length; ++i) {
+      stringBuilder.append(tab2);
+      appendDescriptor(INTERNAL_NAME, providers[i]);
+      stringBuilder.append(i != providers.length - 1 ? ",\n" : ";\n");
+    }
+    text.add(stringBuilder.toString());
+  }
+
+  @Override
+  public void visitModuleEnd() {
+    // Nothing to do.
+  }
+
+  // -----------------------------------------------------------------------------------------------
+  // Annotations
+  // -----------------------------------------------------------------------------------------------
+
+  // DontCheck(OverloadMethodsDeclarationOrder): overloads are semantically different.
+  @Override
+  public void visit(final String name, final Object value) {
+    visitAnnotationValue(name);
+    if (value instanceof String) {
+      visitString((String) value);
+    } else if (value instanceof Type) {
+      visitType((Type) value);
+    } else if (value instanceof Byte) {
+      visitByte(((Byte) value).byteValue());
+    } else if (value instanceof Boolean) {
+      visitBoolean(((Boolean) value).booleanValue());
+    } else if (value instanceof Short) {
+      visitShort(((Short) value).shortValue());
+    } else if (value instanceof Character) {
+      visitChar(((Character) value).charValue());
+    } else if (value instanceof Integer) {
+      visitInt(((Integer) value).intValue());
+    } else if (value instanceof Float) {
+      visitFloat(((Float) value).floatValue());
+    } else if (value instanceof Long) {
+      visitLong(((Long) value).longValue());
+    } else if (value instanceof Double) {
+      visitDouble(((Double) value).doubleValue());
+    } else if (value.getClass().isArray()) {
+      stringBuilder.append('{');
+      if (value instanceof byte[]) {
+        byte[] byteArray = (byte[]) value;
+        for (int i = 0; i < byteArray.length; i++) {
+          maybeAppendComma(i);
+          visitByte(byteArray[i]);
+        }
+      } else if (value instanceof boolean[]) {
+        boolean[] booleanArray = (boolean[]) value;
+        for (int i = 0; i < booleanArray.length; i++) {
+          maybeAppendComma(i);
+          visitBoolean(booleanArray[i]);
+        }
+      } else if (value instanceof short[]) {
+        short[] shortArray = (short[]) value;
+        for (int i = 0; i < shortArray.length; i++) {
+          maybeAppendComma(i);
+          visitShort(shortArray[i]);
+        }
+      } else if (value instanceof char[]) {
+        char[] charArray = (char[]) value;
+        for (int i = 0; i < charArray.length; i++) {
+          maybeAppendComma(i);
+          visitChar(charArray[i]);
+        }
+      } else if (value instanceof int[]) {
+        int[] intArray = (int[]) value;
+        for (int i = 0; i < intArray.length; i++) {
+          maybeAppendComma(i);
+          visitInt(intArray[i]);
+        }
+      } else if (value instanceof long[]) {
+        long[] longArray = (long[]) value;
+        for (int i = 0; i < longArray.length; i++) {
+          maybeAppendComma(i);
+          visitLong(longArray[i]);
+        }
+      } else if (value instanceof float[]) {
+        float[] floatArray = (float[]) value;
+        for (int i = 0; i < floatArray.length; i++) {
+          maybeAppendComma(i);
+          visitFloat(floatArray[i]);
+        }
+      } else if (value instanceof double[]) {
+        double[] doubleArray = (double[]) value;
+        for (int i = 0; i < doubleArray.length; i++) {
+          maybeAppendComma(i);
+          visitDouble(doubleArray[i]);
+        }
+      }
+      stringBuilder.append('}');
+    }
+    text.add(stringBuilder.toString());
+  }
+
+  private void visitInt(final int value) {
+    stringBuilder.append(value);
+  }
+
+  private void visitLong(final long value) {
+    stringBuilder.append(value).append('L');
+  }
+
+  private void visitFloat(final float value) {
+    stringBuilder.append(value).append('F');
+  }
+
+  private void visitDouble(final double value) {
+    stringBuilder.append(value).append('D');
+  }
+
+  private void visitChar(final char value) {
+    stringBuilder.append("(char)").append((int) value);
+  }
+
+  private void visitShort(final short value) {
+    stringBuilder.append("(short)").append(value);
+  }
+
+  private void visitByte(final byte value) {
+    stringBuilder.append("(byte)").append(value);
+  }
+
+  private void visitBoolean(final boolean value) {
+    stringBuilder.append(value);
+  }
+
+  private void visitString(final String value) {
+    appendString(stringBuilder, value);
+  }
+
+  private void visitType(final Type value) {
+    stringBuilder.append(value.getClassName()).append(CLASS_SUFFIX);
+  }
+
+  @Override
+  public void visitEnum(final String name, final String descriptor, final String value) {
+    visitAnnotationValue(name);
+    appendDescriptor(FIELD_DESCRIPTOR, descriptor);
+    stringBuilder.append('.').append(value);
+    text.add(stringBuilder.toString());
+  }
+
+  @Override
+  public Textifier visitAnnotation(final String name, final String descriptor) {
+    visitAnnotationValue(name);
+    stringBuilder.append('@');
+    appendDescriptor(FIELD_DESCRIPTOR, descriptor);
+    stringBuilder.append('(');
+    text.add(stringBuilder.toString());
+    return addNewTextifier(")");
+  }
+
+  @Override
+  public Textifier visitArray(final String name) {
+    visitAnnotationValue(name);
+    stringBuilder.append('{');
+    text.add(stringBuilder.toString());
+    return addNewTextifier("}");
+  }
+
+  @Override
+  public void visitAnnotationEnd() {
+    // Nothing to do.
+  }
+
+  private void visitAnnotationValue(final String name) {
+    stringBuilder.setLength(0);
+    maybeAppendComma(numAnnotationValues++);
+    if (name != null) {
+      stringBuilder.append(name).append('=');
+    }
+  }
+
+  // -----------------------------------------------------------------------------------------------
+  // Fields
+  // -----------------------------------------------------------------------------------------------
+
+  @Override
+  public Textifier visitFieldAnnotation(final String descriptor, final boolean visible) {
+    return visitAnnotation(descriptor, visible);
+  }
+
+  @Override
+  public Printer visitFieldTypeAnnotation(
+      final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) {
+    return visitTypeAnnotation(typeRef, typePath, descriptor, visible);
+  }
+
+  @Override
+  public void visitFieldAttribute(final Attribute attribute) {
+    visitAttribute(attribute);
+  }
+
+  @Override
+  public void visitFieldEnd() {
+    // Nothing to do.
+  }
+
+  // -----------------------------------------------------------------------------------------------
+  // Methods
+  // -----------------------------------------------------------------------------------------------
+
+  @Override
+  public void visitParameter(final String name, final int access) {
+    stringBuilder.setLength(0);
+    stringBuilder.append(tab2).append("// parameter ");
+    appendAccess(access);
+    stringBuilder.append(' ').append((name == null) ? "<no name>" : name).append('\n');
+    text.add(stringBuilder.toString());
+  }
+
+  @Override
+  public Textifier visitAnnotationDefault() {
+    text.add(tab2 + "default=");
+    return addNewTextifier("\n");
+  }
+
+  @Override
+  public Textifier visitMethodAnnotation(final String descriptor, final boolean visible) {
+    return visitAnnotation(descriptor, visible);
+  }
+
+  @Override
+  public Printer visitMethodTypeAnnotation(
+      final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) {
+    return visitTypeAnnotation(typeRef, typePath, descriptor, visible);
+  }
+
+  @Override
+  public Textifier visitAnnotableParameterCount(final int parameterCount, final boolean visible) {
+    stringBuilder.setLength(0);
+    stringBuilder.append(tab2).append("// annotable parameter count: ");
+    stringBuilder.append(parameterCount);
+    stringBuilder.append(visible ? " (visible)\n" : " (invisible)\n");
+    text.add(stringBuilder.toString());
+    return this;
+  }
+
+  @Override
+  public Textifier visitParameterAnnotation(
+      final int parameter, final String descriptor, final boolean visible) {
+    stringBuilder.setLength(0);
+    stringBuilder.append(tab2).append('@');
+    appendDescriptor(FIELD_DESCRIPTOR, descriptor);
+    stringBuilder.append('(');
+    text.add(stringBuilder.toString());
+
+    stringBuilder.setLength(0);
+    stringBuilder
+        .append(visible ? ") // parameter " : ") // invisible, parameter ")
+        .append(parameter)
+        .append('\n');
+    return addNewTextifier(stringBuilder.toString());
+  }
+
+  @Override
+  public void visitMethodAttribute(final Attribute attribute) {
+    stringBuilder.setLength(0);
+    stringBuilder.append(tab).append("ATTRIBUTE ");
+    appendDescriptor(-1, attribute.type);
+
+    if (attribute instanceof Textifiable) {
+      StringBuffer stringBuffer = new StringBuffer();
+      ((Textifiable) attribute).textify(stringBuffer, labelNames);
+      stringBuilder.append(stringBuffer.toString());
+    } else {
+      stringBuilder.append(" : unknown\n");
+    }
+
+    text.add(stringBuilder.toString());
+  }
+
+  @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) {
+    stringBuilder.setLength(0);
+    stringBuilder.append(ltab);
+    stringBuilder.append("FRAME ");
+    switch (type) {
+      case Opcodes.F_NEW:
+      case Opcodes.F_FULL:
+        stringBuilder.append("FULL [");
+        appendFrameTypes(numLocal, local);
+        stringBuilder.append("] [");
+        appendFrameTypes(numStack, stack);
+        stringBuilder.append(']');
+        break;
+      case Opcodes.F_APPEND:
+        stringBuilder.append("APPEND [");
+        appendFrameTypes(numLocal, local);
+        stringBuilder.append(']');
+        break;
+      case Opcodes.F_CHOP:
+        stringBuilder.append("CHOP ").append(numLocal);
+        break;
+      case Opcodes.F_SAME:
+        stringBuilder.append("SAME");
+        break;
+      case Opcodes.F_SAME1:
+        stringBuilder.append("SAME1 ");
+        appendFrameTypes(1, stack);
+        break;
+      default:
+        throw new IllegalArgumentException();
+    }
+    stringBuilder.append('\n');
+    text.add(stringBuilder.toString());
+  }
+
+  @Override
+  public void visitInsn(final int opcode) {
+    stringBuilder.setLength(0);
+    stringBuilder.append(tab2).append(OPCODES[opcode]).append('\n');
+    text.add(stringBuilder.toString());
+  }
+
+  @Override
+  public void visitIntInsn(final int opcode, final int operand) {
+    stringBuilder.setLength(0);
+    stringBuilder
+        .append(tab2)
+        .append(OPCODES[opcode])
+        .append(' ')
+        .append(opcode == Opcodes.NEWARRAY ? TYPES[operand] : Integer.toString(operand))
+        .append('\n');
+    text.add(stringBuilder.toString());
+  }
+
+  @Override
+  public void visitVarInsn(final int opcode, final int var) {
+    stringBuilder.setLength(0);
+    stringBuilder.append(tab2).append(OPCODES[opcode]).append(' ').append(var).append('\n');
+    text.add(stringBuilder.toString());
+  }
+
+  @Override
+  public void visitTypeInsn(final int opcode, final String type) {
+    stringBuilder.setLength(0);
+    stringBuilder.append(tab2).append(OPCODES[opcode]).append(' ');
+    appendDescriptor(INTERNAL_NAME, type);
+    stringBuilder.append('\n');
+    text.add(stringBuilder.toString());
+  }
+
+  @Override
+  public void visitFieldInsn(
+      final int opcode, final String owner, final String name, final String descriptor) {
+    stringBuilder.setLength(0);
+    stringBuilder.append(tab2).append(OPCODES[opcode]).append(' ');
+    appendDescriptor(INTERNAL_NAME, owner);
+    stringBuilder.append('.').append(name).append(" : ");
+    appendDescriptor(FIELD_DESCRIPTOR, descriptor);
+    stringBuilder.append('\n');
+    text.add(stringBuilder.toString());
+  }
+
+  /**
+   * Deprecated.
+   *
+   * @deprecated use {@link #visitMethodInsn(int, String, String, String, boolean)} instead.
+   */
+  @Deprecated
+  @Override
+  public void visitMethodInsn(
+      final int opcode, final String owner, final String name, final String descriptor) {
+    if (api >= Opcodes.ASM5) {
+      super.visitMethodInsn(opcode, owner, name, descriptor);
+      return;
+    }
+    doVisitMethodInsn(opcode, owner, name, descriptor, opcode == Opcodes.INVOKEINTERFACE);
+  }
+
+  @Override
+  public void visitMethodInsn(
+      final int opcode,
+      final String owner,
+      final String name,
+      final String descriptor,
+      final boolean isInterface) {
+    if (api < Opcodes.ASM5) {
+      super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
+      return;
+    }
+    doVisitMethodInsn(opcode, owner, name, descriptor, isInterface);
+  }
+
+  private void doVisitMethodInsn(
+      final int opcode,
+      final String owner,
+      final String name,
+      final String descriptor,
+      final boolean isInterface) {
+    stringBuilder.setLength(0);
+    stringBuilder.append(tab2).append(OPCODES[opcode]).append(' ');
+    appendDescriptor(INTERNAL_NAME, owner);
+    stringBuilder.append('.').append(name).append(' ');
+    appendDescriptor(METHOD_DESCRIPTOR, descriptor);
+    if (isInterface) {
+      stringBuilder.append(" (itf)");
+    }
+    stringBuilder.append('\n');
+    text.add(stringBuilder.toString());
+  }
+
+  @Override
+  public void visitInvokeDynamicInsn(
+      final String name,
+      final String descriptor,
+      final Handle bootstrapMethodHandle,
+      final Object... bootstrapMethodArguments) {
+    stringBuilder.setLength(0);
+    stringBuilder.append(tab2).append("INVOKEDYNAMIC").append(' ');
+    stringBuilder.append(name);
+    appendDescriptor(METHOD_DESCRIPTOR, descriptor);
+    stringBuilder.append(" [");
+    stringBuilder.append('\n');
+    stringBuilder.append(tab3);
+    appendHandle(bootstrapMethodHandle);
+    stringBuilder.append('\n');
+    stringBuilder.append(tab3).append("// arguments:");
+    if (bootstrapMethodArguments.length == 0) {
+      stringBuilder.append(" none");
+    } else {
+      stringBuilder.append('\n');
+      for (Object value : bootstrapMethodArguments) {
+        stringBuilder.append(tab3);
         if (value instanceof String) {
-            visitString((String) value);
+          Printer.appendString(stringBuilder, (String) value);
         } else if (value instanceof Type) {
-            visitType((Type) value);
-        } else if (value instanceof Byte) {
-            visitByte(((Byte) value).byteValue());
-        } else if (value instanceof Boolean) {
-            visitBoolean(((Boolean) value).booleanValue());
-        } else if (value instanceof Short) {
-            visitShort(((Short) value).shortValue());
-        } else if (value instanceof Character) {
-            visitChar(((Character) value).charValue());
-        } else if (value instanceof Integer) {
-            visitInt(((Integer) value).intValue());
-        } else if (value instanceof Float) {
-            visitFloat(((Float) value).floatValue());
-        } else if (value instanceof Long) {
-            visitLong(((Long) value).longValue());
-        } else if (value instanceof Double) {
-            visitDouble(((Double) value).doubleValue());
-        } else if (value.getClass().isArray()) {
-            buf.append('{');
-            if (value instanceof byte[]) {
-                byte[] v = (byte[]) value;
-                for (int i = 0; i < v.length; i++) {
-                    appendComa(i);
-                    visitByte(v[i]);
-                }
-            } else if (value instanceof boolean[]) {
-                boolean[] v = (boolean[]) value;
-                for (int i = 0; i < v.length; i++) {
-                    appendComa(i);
-                    visitBoolean(v[i]);
-                }
-            } else if (value instanceof short[]) {
-                short[] v = (short[]) value;
-                for (int i = 0; i < v.length; i++) {
-                    appendComa(i);
-                    visitShort(v[i]);
-                }
-            } else if (value instanceof char[]) {
-                char[] v = (char[]) value;
-                for (int i = 0; i < v.length; i++) {
-                    appendComa(i);
-                    visitChar(v[i]);
-                }
-            } else if (value instanceof int[]) {
-                int[] v = (int[]) value;
-                for (int i = 0; i < v.length; i++) {
-                    appendComa(i);
-                    visitInt(v[i]);
-                }
-            } else if (value instanceof long[]) {
-                long[] v = (long[]) value;
-                for (int i = 0; i < v.length; i++) {
-                    appendComa(i);
-                    visitLong(v[i]);
-                }
-            } else if (value instanceof float[]) {
-                float[] v = (float[]) value;
-                for (int i = 0; i < v.length; i++) {
-                    appendComa(i);
-                    visitFloat(v[i]);
-                }
-            } else if (value instanceof double[]) {
-                double[] v = (double[]) value;
-                for (int i = 0; i < v.length; i++) {
-                    appendComa(i);
-                    visitDouble(v[i]);
-                }
-            }
-            buf.append('}');
-        }
-
-        text.add(buf.toString());
-    }
-
-    private void visitInt(final int value) {
-        buf.append(value);
-    }
-
-    private void visitLong(final long value) {
-        buf.append(value).append('L');
-    }
-
-    private void visitFloat(final float value) {
-        buf.append(value).append('F');
-    }
-
-    private void visitDouble(final double value) {
-        buf.append(value).append('D');
-    }
-
-    private void visitChar(final char value) {
-        buf.append("(char)").append((int) value);
-    }
-
-    private void visitShort(final short value) {
-        buf.append("(short)").append(value);
-    }
-
-    private void visitByte(final byte value) {
-        buf.append("(byte)").append(value);
-    }
-
-    private void visitBoolean(final boolean value) {
-        buf.append(value);
-    }
-
-    private void visitString(final String value) {
-        appendString(buf, value);
-    }
-
-    private void visitType(final Type value) {
-        buf.append(value.getClassName()).append(".class");
-    }
-
-    @Override
-    public void visitEnum(final String name, final String desc,
-            final String value) {
-        buf.setLength(0);
-        appendComa(valueNumber++);
-        if (name != null) {
-            buf.append(name).append('=');
-        }
-        appendDescriptor(FIELD_DESCRIPTOR, desc);
-        buf.append('.').append(value);
-        text.add(buf.toString());
-    }
-
-    @Override
-    public Textifier visitAnnotation(final String name, final String desc) {
-        buf.setLength(0);
-        appendComa(valueNumber++);
-        if (name != null) {
-            buf.append(name).append('=');
-        }
-        buf.append('@');
-        appendDescriptor(FIELD_DESCRIPTOR, desc);
-        buf.append('(');
-        text.add(buf.toString());
-        Textifier t = createTextifier();
-        text.add(t.getText());
-        text.add(")");
-        return t;
-    }
-
-    @Override
-    public Textifier visitArray(final String name) {
-        buf.setLength(0);
-        appendComa(valueNumber++);
-        if (name != null) {
-            buf.append(name).append('=');
-        }
-        buf.append('{');
-        text.add(buf.toString());
-        Textifier t = createTextifier();
-        text.add(t.getText());
-        text.add("}");
-        return t;
-    }
-
-    @Override
-    public void visitAnnotationEnd() {
-    }
-
-    // ------------------------------------------------------------------------
-    // Fields
-    // ------------------------------------------------------------------------
-
-    @Override
-    public Textifier visitFieldAnnotation(final String desc,
-            final boolean visible) {
-        return visitAnnotation(desc, visible);
-    }
-
-    @Override
-    public Printer visitFieldTypeAnnotation(int typeRef, TypePath typePath,
-            String desc, boolean visible) {
-        return visitTypeAnnotation(typeRef, typePath, desc, visible);
-    }
-
-    @Override
-    public void visitFieldAttribute(final Attribute attr) {
-        visitAttribute(attr);
-    }
-
-    @Override
-    public void visitFieldEnd() {
-    }
-
-    // ------------------------------------------------------------------------
-    // Methods
-    // ------------------------------------------------------------------------
-
-    @Override
-    public void visitParameter(final String name, final int access) {
-        buf.setLength(0);
-        buf.append(tab2).append("// parameter ");
-        appendAccess(access);
-        buf.append(' ').append((name == null) ? "<no name>" : name)
-                .append('\n');
-        text.add(buf.toString());
-    }
-
-    @Override
-    public Textifier visitAnnotationDefault() {
-        text.add(tab2 + "default=");
-        Textifier t = createTextifier();
-        text.add(t.getText());
-        text.add("\n");
-        return t;
-    }
-
-    @Override
-    public Textifier visitMethodAnnotation(final String desc,
-            final boolean visible) {
-        return visitAnnotation(desc, visible);
-    }
-
-    @Override
-    public Printer visitMethodTypeAnnotation(int typeRef, TypePath typePath,
-            String desc, boolean visible) {
-        return visitTypeAnnotation(typeRef, typePath, desc, visible);
-    }
-
-    @Override
-    public Textifier visitParameterAnnotation(final int parameter,
-            final String desc, final boolean visible) {
-        buf.setLength(0);
-        buf.append(tab2).append('@');
-        appendDescriptor(FIELD_DESCRIPTOR, desc);
-        buf.append('(');
-        text.add(buf.toString());
-        Textifier t = createTextifier();
-        text.add(t.getText());
-        text.add(visible ? ") // parameter " : ") // invisible, parameter ");
-        text.add(parameter);
-        text.add("\n");
-        return t;
-    }
-
-    @Override
-    public void visitMethodAttribute(final Attribute attr) {
-        buf.setLength(0);
-        buf.append(tab).append("ATTRIBUTE ");
-        appendDescriptor(-1, attr.type);
-
-        if (attr instanceof Textifiable) {
-            ((Textifiable) attr).textify(buf, labelNames);
+          Type type = (Type) value;
+          if (type.getSort() == Type.METHOD) {
+            appendDescriptor(METHOD_DESCRIPTOR, type.getDescriptor());
+          } else {
+            visitType(type);
+          }
+        } else if (value instanceof Handle) {
+          appendHandle((Handle) value);
         } else {
-            buf.append(" : unknown\n");
-        }
-
-        text.add(buf.toString());
-    }
-
-    @Override
-    public void visitCode() {
-    }
-
-    @Override
-    public void visitFrame(final int type, final int nLocal,
-            final Object[] local, final int nStack, final Object[] stack) {
-        buf.setLength(0);
-        buf.append(ltab);
-        buf.append("FRAME ");
-        switch (type) {
-        case Opcodes.F_NEW:
-        case Opcodes.F_FULL:
-            buf.append("FULL [");
-            appendFrameTypes(nLocal, local);
-            buf.append("] [");
-            appendFrameTypes(nStack, stack);
-            buf.append(']');
-            break;
-        case Opcodes.F_APPEND:
-            buf.append("APPEND [");
-            appendFrameTypes(nLocal, local);
-            buf.append(']');
-            break;
-        case Opcodes.F_CHOP:
-            buf.append("CHOP ").append(nLocal);
-            break;
-        case Opcodes.F_SAME:
-            buf.append("SAME");
-            break;
-        case Opcodes.F_SAME1:
-            buf.append("SAME1 ");
-            appendFrameTypes(1, stack);
-            break;
-        }
-        buf.append('\n');
-        text.add(buf.toString());
-    }
-
-    @Override
-    public void visitInsn(final int opcode) {
-        buf.setLength(0);
-        buf.append(tab2).append(OPCODES[opcode]).append('\n');
-        text.add(buf.toString());
-    }
-
-    @Override
-    public void visitIntInsn(final int opcode, final int operand) {
-        buf.setLength(0);
-        buf.append(tab2)
-                .append(OPCODES[opcode])
-                .append(' ')
-                .append(opcode == Opcodes.NEWARRAY ? TYPES[operand] : Integer
-                        .toString(operand)).append('\n');
-        text.add(buf.toString());
-    }
-
-    @Override
-    public void visitVarInsn(final int opcode, final int var) {
-        buf.setLength(0);
-        buf.append(tab2).append(OPCODES[opcode]).append(' ').append(var)
-                .append('\n');
-        text.add(buf.toString());
-    }
-
-    @Override
-    public void visitTypeInsn(final int opcode, final String type) {
-        buf.setLength(0);
-        buf.append(tab2).append(OPCODES[opcode]).append(' ');
-        appendDescriptor(INTERNAL_NAME, type);
-        buf.append('\n');
-        text.add(buf.toString());
-    }
-
-    @Override
-    public void visitFieldInsn(final int opcode, final String owner,
-            final String name, final String desc) {
-        buf.setLength(0);
-        buf.append(tab2).append(OPCODES[opcode]).append(' ');
-        appendDescriptor(INTERNAL_NAME, owner);
-        buf.append('.').append(name).append(" : ");
-        appendDescriptor(FIELD_DESCRIPTOR, desc);
-        buf.append('\n');
-        text.add(buf.toString());
-    }
-
-    @Deprecated
-    @Override
-    public void visitMethodInsn(final int opcode, final String owner,
-            final String name, final String desc) {
-        if (api >= Opcodes.ASM5) {
-            super.visitMethodInsn(opcode, owner, name, desc);
-            return;
-        }
-        doVisitMethodInsn(opcode, owner, name, desc,
-                opcode == Opcodes.INVOKEINTERFACE);
-    }
-
-    @Override
-    public void visitMethodInsn(final int opcode, final String owner,
-            final String name, final String desc, final boolean itf) {
-        if (api < Opcodes.ASM5) {
-            super.visitMethodInsn(opcode, owner, name, desc, itf);
-            return;
-        }
-        doVisitMethodInsn(opcode, owner, name, desc, itf);
-    }
-
-    private void doVisitMethodInsn(final int opcode, final String owner,
-            final String name, final String desc, final boolean itf) {
-        buf.setLength(0);
-        buf.append(tab2).append(OPCODES[opcode]).append(' ');
-        appendDescriptor(INTERNAL_NAME, owner);
-        buf.append('.').append(name).append(' ');
-        appendDescriptor(METHOD_DESCRIPTOR, desc);
-        buf.append('\n');
-        text.add(buf.toString());
-    }
-
-    @Override
-    public void visitInvokeDynamicInsn(String name, String desc, Handle bsm,
-            Object... bsmArgs) {
-        buf.setLength(0);
-        buf.append(tab2).append("INVOKEDYNAMIC").append(' ');
-        buf.append(name);
-        appendDescriptor(METHOD_DESCRIPTOR, desc);
-        buf.append(" [");
-        buf.append('\n');
-        buf.append(tab3);
-        appendHandle(bsm);
-        buf.append('\n');
-        buf.append(tab3).append("// arguments:");
-        if (bsmArgs.length == 0) {
-            buf.append(" none");
-        } else {
-            buf.append('\n');
-            for (int i = 0; i < bsmArgs.length; i++) {
-                buf.append(tab3);
-                Object cst = bsmArgs[i];
-                if (cst instanceof String) {
-                    Printer.appendString(buf, (String) cst);
-                } else if (cst instanceof Type) {
-                    Type type = (Type) cst;
-                    if(type.getSort() == Type.METHOD){
-                        appendDescriptor(METHOD_DESCRIPTOR, type.getDescriptor());
-                    } else {
-                        buf.append(type.getDescriptor()).append(".class");
-                    }
-                } else if (cst instanceof Handle) {
-                    appendHandle((Handle) cst);
-                } else {
-                    buf.append(cst);
-                }
-                buf.append(", \n");
-            }
-            buf.setLength(buf.length() - 3);
-        }
-        buf.append('\n');
-        buf.append(tab2).append("]\n");
-        text.add(buf.toString());
-    }
-
-    @Override
-    public void visitJumpInsn(final int opcode, final Label label) {
-        buf.setLength(0);
-        buf.append(tab2).append(OPCODES[opcode]).append(' ');
-        appendLabel(label);
-        buf.append('\n');
-        text.add(buf.toString());
-    }
-
-    @Override
-    public void visitLabel(final Label label) {
-        buf.setLength(0);
-        buf.append(ltab);
-        appendLabel(label);
-        buf.append('\n');
-        text.add(buf.toString());
-    }
-
-    @Override
-    public void visitLdcInsn(final Object cst) {
-        buf.setLength(0);
-        buf.append(tab2).append("LDC ");
-        if (cst instanceof String) {
-            Printer.appendString(buf, (String) cst);
-        } else if (cst instanceof Type) {
-            buf.append(((Type) cst).getDescriptor()).append(".class");
+          stringBuilder.append(value);
+        }
+        stringBuilder.append(", \n");
+      }
+      stringBuilder.setLength(stringBuilder.length() - 3);
+    }
+    stringBuilder.append('\n');
+    stringBuilder.append(tab2).append("]\n");
+    text.add(stringBuilder.toString());
+  }
+
+  @Override
+  public void visitJumpInsn(final int opcode, final Label label) {
+    stringBuilder.setLength(0);
+    stringBuilder.append(tab2).append(OPCODES[opcode]).append(' ');
+    appendLabel(label);
+    stringBuilder.append('\n');
+    text.add(stringBuilder.toString());
+  }
+
+  @Override
+  public void visitLabel(final Label label) {
+    stringBuilder.setLength(0);
+    stringBuilder.append(ltab);
+    appendLabel(label);
+    stringBuilder.append('\n');
+    text.add(stringBuilder.toString());
+  }
+
+  @Override
+  public void visitLdcInsn(final Object value) {
+    stringBuilder.setLength(0);
+    stringBuilder.append(tab2).append("LDC ");
+    if (value instanceof String) {
+      Printer.appendString(stringBuilder, (String) value);
+    } else if (value instanceof Type) {
+      stringBuilder.append(((Type) value).getDescriptor()).append(CLASS_SUFFIX);
+    } else {
+      stringBuilder.append(value);
+    }
+    stringBuilder.append('\n');
+    text.add(stringBuilder.toString());
+  }
+
+  @Override
+  public void visitIincInsn(final int var, final int increment) {
+    stringBuilder.setLength(0);
+    stringBuilder
+        .append(tab2)
+        .append("IINC ")
+        .append(var)
+        .append(' ')
+        .append(increment)
+        .append('\n');
+    text.add(stringBuilder.toString());
+  }
+
+  @Override
+  public void visitTableSwitchInsn(
+      final int min, final int max, final Label dflt, final Label... labels) {
+    stringBuilder.setLength(0);
+    stringBuilder.append(tab2).append("TABLESWITCH\n");
+    for (int i = 0; i < labels.length; ++i) {
+      stringBuilder.append(tab3).append(min + i).append(": ");
+      appendLabel(labels[i]);
+      stringBuilder.append('\n');
+    }
+    stringBuilder.append(tab3).append("default: ");
+    appendLabel(dflt);
+    stringBuilder.append('\n');
+    text.add(stringBuilder.toString());
+  }
+
+  @Override
+  public void visitLookupSwitchInsn(final Label dflt, final int[] keys, final Label[] labels) {
+    stringBuilder.setLength(0);
+    stringBuilder.append(tab2).append("LOOKUPSWITCH\n");
+    for (int i = 0; i < labels.length; ++i) {
+      stringBuilder.append(tab3).append(keys[i]).append(": ");
+      appendLabel(labels[i]);
+      stringBuilder.append('\n');
+    }
+    stringBuilder.append(tab3).append("default: ");
+    appendLabel(dflt);
+    stringBuilder.append('\n');
+    text.add(stringBuilder.toString());
+  }
+
+  @Override
+  public void visitMultiANewArrayInsn(final String descriptor, final int numDimensions) {
+    stringBuilder.setLength(0);
+    stringBuilder.append(tab2).append("MULTIANEWARRAY ");
+    appendDescriptor(FIELD_DESCRIPTOR, descriptor);
+    stringBuilder.append(' ').append(numDimensions).append('\n');
+    text.add(stringBuilder.toString());
+  }
+
+  @Override
+  public Printer visitInsnAnnotation(
+      final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) {
+    return visitTypeAnnotation(typeRef, typePath, descriptor, visible);
+  }
+
+  @Override
+  public void visitTryCatchBlock(
+      final Label start, final Label end, final Label handler, final String type) {
+    stringBuilder.setLength(0);
+    stringBuilder.append(tab2).append("TRYCATCHBLOCK ");
+    appendLabel(start);
+    stringBuilder.append(' ');
+    appendLabel(end);
+    stringBuilder.append(' ');
+    appendLabel(handler);
+    stringBuilder.append(' ');
+    appendDescriptor(INTERNAL_NAME, type);
+    stringBuilder.append('\n');
+    text.add(stringBuilder.toString());
+  }
+
+  @Override
+  public Printer visitTryCatchAnnotation(
+      final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) {
+    stringBuilder.setLength(0);
+    stringBuilder.append(tab2).append("TRYCATCHBLOCK @");
+    appendDescriptor(FIELD_DESCRIPTOR, descriptor);
+    stringBuilder.append('(');
+    text.add(stringBuilder.toString());
+
+    stringBuilder.setLength(0);
+    stringBuilder.append(") : ");
+    appendTypeReference(typeRef);
+    stringBuilder.append(", ").append(typePath);
+    stringBuilder.append(visible ? "\n" : INVISIBLE);
+    return addNewTextifier(stringBuilder.toString());
+  }
+
+  @Override
+  public void visitLocalVariable(
+      final String name,
+      final String descriptor,
+      final String signature,
+      final Label start,
+      final Label end,
+      final int index) {
+    stringBuilder.setLength(0);
+    stringBuilder.append(tab2).append("LOCALVARIABLE ").append(name).append(' ');
+    appendDescriptor(FIELD_DESCRIPTOR, descriptor);
+    stringBuilder.append(' ');
+    appendLabel(start);
+    stringBuilder.append(' ');
+    appendLabel(end);
+    stringBuilder.append(' ').append(index).append('\n');
+
+    if (signature != null) {
+      stringBuilder.append(tab2);
+      appendDescriptor(FIELD_SIGNATURE, signature);
+      stringBuilder.append(tab2);
+      appendJavaDeclaration(name, signature);
+    }
+    text.add(stringBuilder.toString());
+  }
+
+  @Override
+  public Printer visitLocalVariableAnnotation(
+      final int typeRef,
+      final TypePath typePath,
+      final Label[] start,
+      final Label[] end,
+      final int[] index,
+      final String descriptor,
+      final boolean visible) {
+    stringBuilder.setLength(0);
+    stringBuilder.append(tab2).append("LOCALVARIABLE @");
+    appendDescriptor(FIELD_DESCRIPTOR, descriptor);
+    stringBuilder.append('(');
+    text.add(stringBuilder.toString());
+
+    stringBuilder.setLength(0);
+    stringBuilder.append(") : ");
+    appendTypeReference(typeRef);
+    stringBuilder.append(", ").append(typePath);
+    for (int i = 0; i < start.length; ++i) {
+      stringBuilder.append(" [ ");
+      appendLabel(start[i]);
+      stringBuilder.append(" - ");
+      appendLabel(end[i]);
+      stringBuilder.append(" - ").append(index[i]).append(" ]");
+    }
+    stringBuilder.append(visible ? "\n" : INVISIBLE);
+    return addNewTextifier(stringBuilder.toString());
+  }
+
+  @Override
+  public void visitLineNumber(final int line, final Label start) {
+    stringBuilder.setLength(0);
+    stringBuilder.append(tab2).append("LINENUMBER ").append(line).append(' ');
+    appendLabel(start);
+    stringBuilder.append('\n');
+    text.add(stringBuilder.toString());
+  }
+
+  @Override
+  public void visitMaxs(final int maxStack, final int maxLocals) {
+    stringBuilder.setLength(0);
+    stringBuilder.append(tab2).append("MAXSTACK = ").append(maxStack).append('\n');
+    text.add(stringBuilder.toString());
+
+    stringBuilder.setLength(0);
+    stringBuilder.append(tab2).append("MAXLOCALS = ").append(maxLocals).append('\n');
+    text.add(stringBuilder.toString());
+  }
+
+  @Override
+  public void visitMethodEnd() {
+    // Nothing to do.
+  }
+
+  // -----------------------------------------------------------------------------------------------
+  // Common methods
+  // -----------------------------------------------------------------------------------------------
+
+  /**
+   * Prints a disassembled view of the given annotation.
+   *
+   * @param descriptor the class descriptor of the annotation class.
+   * @param visible {@literal true} if the annotation is visible at runtime.
+   * @return a visitor to visit the annotation values.
+   */
+  // DontCheck(OverloadMethodsDeclarationOrder): overloads are semantically different.
+  public Textifier visitAnnotation(final String descriptor, final boolean visible) {
+    stringBuilder.setLength(0);
+    stringBuilder.append(tab).append('@');
+    appendDescriptor(FIELD_DESCRIPTOR, descriptor);
+    stringBuilder.append('(');
+    text.add(stringBuilder.toString());
+    return addNewTextifier(visible ? ")\n" : ") // invisible\n");
+  }
+
+  /**
+   * Prints a disassembled view of the given type annotation.
+   *
+   * @param typeRef a reference to the annotated type. See {@link TypeReference}.
+   * @param typePath the path to the annotated type argument, wildcard bound, array element type, or
+   *     static inner type within 'typeRef'. May be {@literal null} if the annotation targets
+   *     'typeRef' as a whole.
+   * @param descriptor the class descriptor of the annotation class.
+   * @param visible {@literal true} if the annotation is visible at runtime.
+   * @return a visitor to visit the annotation values.
+   */
+  public Textifier visitTypeAnnotation(
+      final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) {
+    stringBuilder.setLength(0);
+    stringBuilder.append(tab).append('@');
+    appendDescriptor(FIELD_DESCRIPTOR, descriptor);
+    stringBuilder.append('(');
+    text.add(stringBuilder.toString());
+
+    stringBuilder.setLength(0);
+    stringBuilder.append(") : ");
+    appendTypeReference(typeRef);
+    stringBuilder.append(", ").append(typePath);
+    stringBuilder.append(visible ? "\n" : INVISIBLE);
+    return addNewTextifier(stringBuilder.toString());
+  }
+
+  /**
+   * Prints a disassembled view of the given attribute.
+   *
+   * @param attribute an attribute.
+   */
+  public void visitAttribute(final Attribute attribute) {
+    stringBuilder.setLength(0);
+    stringBuilder.append(tab).append("ATTRIBUTE ");
+    appendDescriptor(-1, attribute.type);
+
+    if (attribute instanceof Textifiable) {
+      StringBuffer stringBuffer = new StringBuffer();
+      ((Textifiable) attribute).textify(stringBuffer, null);
+      stringBuilder.append(stringBuffer.toString());
+    } else {
+      stringBuilder.append(" : unknown\n");
+    }
+
+    text.add(stringBuilder.toString());
+  }
+
+  // -----------------------------------------------------------------------------------------------
+  // Utility methods
+  // -----------------------------------------------------------------------------------------------
+
+  /**
+   * Appends a string representation of the given access flags to {@link #stringBuilder}.
+   *
+   * @param accessFlags some access flags.
+   */
+  private void appendAccess(final int accessFlags) {
+    if ((accessFlags & Opcodes.ACC_PUBLIC) != 0) {
+      stringBuilder.append("public ");
+    }
+    if ((accessFlags & Opcodes.ACC_PRIVATE) != 0) {
+      stringBuilder.append("private ");
+    }
+    if ((accessFlags & Opcodes.ACC_PROTECTED) != 0) {
+      stringBuilder.append("protected ");
+    }
+    if ((accessFlags & Opcodes.ACC_FINAL) != 0) {
+      stringBuilder.append("final ");
+    }
+    if ((accessFlags & Opcodes.ACC_STATIC) != 0) {
+      stringBuilder.append("static ");
+    }
+    if ((accessFlags & Opcodes.ACC_SYNCHRONIZED) != 0) {
+      stringBuilder.append("synchronized ");
+    }
+    if ((accessFlags & Opcodes.ACC_VOLATILE) != 0) {
+      stringBuilder.append("volatile ");
+    }
+    if ((accessFlags & Opcodes.ACC_TRANSIENT) != 0) {
+      stringBuilder.append("transient ");
+    }
+    if ((accessFlags & Opcodes.ACC_ABSTRACT) != 0) {
+      stringBuilder.append("abstract ");
+    }
+    if ((accessFlags & Opcodes.ACC_STRICT) != 0) {
+      stringBuilder.append("strictfp ");
+    }
+    if ((accessFlags & Opcodes.ACC_SYNTHETIC) != 0) {
+      stringBuilder.append("synthetic ");
+    }
+    if ((accessFlags & Opcodes.ACC_MANDATED) != 0) {
+      stringBuilder.append("mandated ");
+    }
+    if ((accessFlags & Opcodes.ACC_ENUM) != 0) {
+      stringBuilder.append("enum ");
+    }
+  }
+
+  /**
+   * Appends the hexadecimal value of the given access flags to {@link #stringBuilder}.
+   *
+   * @param accessFlags some access flags.
+   */
+  private void appendRawAccess(final int accessFlags) {
+    stringBuilder
+        .append("// access flags 0x")
+        .append(Integer.toHexString(accessFlags).toUpperCase())
+        .append('\n');
+  }
+
+  /**
+   * Appends an internal name, a type descriptor or a type signature to {@link #stringBuilder}.
+   *
+   * @param type the type of 'value'. Must be one of {@link #INTERNAL_NAME}, {@link
+   *     #FIELD_DESCRIPTOR}, {@link #FIELD_SIGNATURE}, {@link #METHOD_DESCRIPTOR}, {@link
+   *     #METHOD_SIGNATURE}, {@link #CLASS_SIGNATURE}, {@link #TYPE_DECLARATION}, {@link
+   *     #CLASS_DECLARATION}, {@link #PARAMETERS_DECLARATION} of {@link #HANDLE_DESCRIPTOR}.
+   * @param value an internal name, type descriptor or a type signature. May be {@literal null}.
+   */
+  protected void appendDescriptor(final int type, final String value) {
+    if (type == CLASS_SIGNATURE || type == FIELD_SIGNATURE || type == METHOD_SIGNATURE) {
+      if (value != null) {
+        stringBuilder.append("// signature ").append(value).append('\n');
+      }
+    } else {
+      stringBuilder.append(value);
+    }
+  }
+
+  /**
+   * Appends the Java generic type declaration corresponding to the given signature.
+   *
+   * @param name a class, field or method name.
+   * @param signature a class, field or method signature.
+   */
+  private void appendJavaDeclaration(final String name, final String signature) {
+    TraceSignatureVisitor traceSignatureVisitor = new TraceSignatureVisitor(access);
+    new SignatureReader(signature).accept(traceSignatureVisitor);
+    stringBuilder.append("// declaration: ");
+    if (traceSignatureVisitor.getReturnType() != null) {
+      stringBuilder.append(traceSignatureVisitor.getReturnType());
+      stringBuilder.append(' ');
+    }
+    stringBuilder.append(name);
+    stringBuilder.append(traceSignatureVisitor.getDeclaration());
+    if (traceSignatureVisitor.getExceptions() != null) {
+      stringBuilder.append(" throws ").append(traceSignatureVisitor.getExceptions());
+    }
+    stringBuilder.append('\n');
+  }
+
+  /**
+   * Appends the name of the given label to {@link #stringBuilder}. Constructs a new label name if
+   * the given label does not yet have one.
+   *
+   * @param label a label.
+   */
+  protected void appendLabel(final Label label) {
+    if (labelNames == null) {
+      labelNames = new HashMap<Label, String>();
+    }
+    String name = labelNames.get(label);
+    if (name == null) {
+      name = "L" + labelNames.size();
+      labelNames.put(label, name);
+    }
+    stringBuilder.append(name);
+  }
+
+  /**
+   * Appends a string representation of the given handle to {@link #stringBuilder}.
+   *
+   * @param handle a handle.
+   */
+  protected void appendHandle(final Handle handle) {
+    int tag = handle.getTag();
+    stringBuilder.append("// handle kind 0x").append(Integer.toHexString(tag)).append(" : ");
+    boolean isMethodHandle = false;
+    switch (tag) {
+      case Opcodes.H_GETFIELD:
+        stringBuilder.append("GETFIELD");
+        break;
+      case Opcodes.H_GETSTATIC:
+        stringBuilder.append("GETSTATIC");
+        break;
+      case Opcodes.H_PUTFIELD:
+        stringBuilder.append("PUTFIELD");
+        break;
+      case Opcodes.H_PUTSTATIC:
+        stringBuilder.append("PUTSTATIC");
+        break;
+      case Opcodes.H_INVOKEINTERFACE:
+        stringBuilder.append("INVOKEINTERFACE");
+        isMethodHandle = true;
+        break;
+      case Opcodes.H_INVOKESPECIAL:
+        stringBuilder.append("INVOKESPECIAL");
+        isMethodHandle = true;
+        break;
+      case Opcodes.H_INVOKESTATIC:
+        stringBuilder.append("INVOKESTATIC");
+        isMethodHandle = true;
+        break;
+      case Opcodes.H_INVOKEVIRTUAL:
+        stringBuilder.append("INVOKEVIRTUAL");
+        isMethodHandle = true;
+        break;
+      case Opcodes.H_NEWINVOKESPECIAL:
+        stringBuilder.append("NEWINVOKESPECIAL");
+        isMethodHandle = true;
+        break;
+      default:
+        throw new IllegalArgumentException();
+    }
+    stringBuilder.append('\n');
+    stringBuilder.append(tab3);
+    appendDescriptor(INTERNAL_NAME, handle.getOwner());
+    stringBuilder.append('.');
+    stringBuilder.append(handle.getName());
+    if (!isMethodHandle) {
+      stringBuilder.append('(');
+    }
+    appendDescriptor(HANDLE_DESCRIPTOR, handle.getDesc());
+    if (!isMethodHandle) {
+      stringBuilder.append(')');
+    }
+    if (handle.isInterface()) {
+      stringBuilder.append(" itf");
+    }
+  }
+
+  /**
+   * Appends a comma to {@link #stringBuilder} if the given number is strictly positive.
+   *
+   * @param numValues a number of 'values visited so far', for instance the number of annotation
+   *     values visited so far in an annotation visitor.
+   */
+  private void maybeAppendComma(final int numValues) {
+    if (numValues > 0) {
+      stringBuilder.append(", ");
+    }
+  }
+
+  /**
+   * Appends a string representation of the given type reference to {@link #stringBuilder}.
+   *
+   * @param typeRef a type reference. See {@link TypeReference}.
+   */
+  private void appendTypeReference(final int typeRef) {
+    TypeReference typeReference = new TypeReference(typeRef);
+    switch (typeReference.getSort()) {
+      case TypeReference.CLASS_TYPE_PARAMETER:
+        stringBuilder.append("CLASS_TYPE_PARAMETER ").append(typeReference.getTypeParameterIndex());
+        break;
+      case TypeReference.METHOD_TYPE_PARAMETER:
+        stringBuilder
+            .append("METHOD_TYPE_PARAMETER ")
+            .append(typeReference.getTypeParameterIndex());
+        break;
+      case TypeReference.CLASS_EXTENDS:
+        stringBuilder.append("CLASS_EXTENDS ").append(typeReference.getSuperTypeIndex());
+        break;
+      case TypeReference.CLASS_TYPE_PARAMETER_BOUND:
+        stringBuilder
+            .append("CLASS_TYPE_PARAMETER_BOUND ")
+            .append(typeReference.getTypeParameterIndex())
+            .append(", ")
+            .append(typeReference.getTypeParameterBoundIndex());
+        break;
+      case TypeReference.METHOD_TYPE_PARAMETER_BOUND:
+        stringBuilder
+            .append("METHOD_TYPE_PARAMETER_BOUND ")
+            .append(typeReference.getTypeParameterIndex())
+            .append(", ")
+            .append(typeReference.getTypeParameterBoundIndex());
+        break;
+      case TypeReference.FIELD:
+        stringBuilder.append("FIELD");
+        break;
+      case TypeReference.METHOD_RETURN:
+        stringBuilder.append("METHOD_RETURN");
+        break;
+      case TypeReference.METHOD_RECEIVER:
+        stringBuilder.append("METHOD_RECEIVER");
+        break;
+      case TypeReference.METHOD_FORMAL_PARAMETER:
+        stringBuilder
+            .append("METHOD_FORMAL_PARAMETER ")
+            .append(typeReference.getFormalParameterIndex());
+        break;
+      case TypeReference.THROWS:
+        stringBuilder.append("THROWS ").append(typeReference.getExceptionIndex());
+        break;
+      case TypeReference.LOCAL_VARIABLE:
+        stringBuilder.append("LOCAL_VARIABLE");
+        break;
+      case TypeReference.RESOURCE_VARIABLE:
+        stringBuilder.append("RESOURCE_VARIABLE");
+        break;
+      case TypeReference.EXCEPTION_PARAMETER:
+        stringBuilder.append("EXCEPTION_PARAMETER ").append(typeReference.getTryCatchBlockIndex());
+        break;
+      case TypeReference.INSTANCEOF:
+        stringBuilder.append("INSTANCEOF");
+        break;
+      case TypeReference.NEW:
+        stringBuilder.append("NEW");
+        break;
+      case TypeReference.CONSTRUCTOR_REFERENCE:
+        stringBuilder.append("CONSTRUCTOR_REFERENCE");
+        break;
+      case TypeReference.METHOD_REFERENCE:
+        stringBuilder.append("METHOD_REFERENCE");
+        break;
+      case TypeReference.CAST:
+        stringBuilder.append("CAST ").append(typeReference.getTypeArgumentIndex());
+        break;
+      case TypeReference.CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT:
+        stringBuilder
+            .append("CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT ")
+            .append(typeReference.getTypeArgumentIndex());
+        break;
+      case TypeReference.METHOD_INVOCATION_TYPE_ARGUMENT:
+        stringBuilder
+            .append("METHOD_INVOCATION_TYPE_ARGUMENT ")
+            .append(typeReference.getTypeArgumentIndex());
+        break;
+      case TypeReference.CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT:
+        stringBuilder
+            .append("CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT ")
+            .append(typeReference.getTypeArgumentIndex());
+        break;
+      case TypeReference.METHOD_REFERENCE_TYPE_ARGUMENT:
+        stringBuilder
+            .append("METHOD_REFERENCE_TYPE_ARGUMENT ")
+            .append(typeReference.getTypeArgumentIndex());
+        break;
+      default:
+        throw new IllegalArgumentException();
+    }
+  }
+
+  /**
+   * Appends the given stack map frame types to {@link #stringBuilder}.
+   *
+   * @param numTypes the number of stack map frame types in 'frameTypes'.
+   * @param frameTypes an array of stack map frame types, in the format described in {@link
+   *     org.apache.tapestry5.internal.plastic.asm.MethodVisitor#visitFrame}.
+   */
+  private void appendFrameTypes(final int numTypes, final Object[] frameTypes) {
+    for (int i = 0; i < numTypes; ++i) {
+      if (i > 0) {
+        stringBuilder.append(' ');
+      }
+      if (frameTypes[i] instanceof String) {
+        String descriptor = (String) frameTypes[i];
+        if (descriptor.charAt(0) == '[') {
+          appendDescriptor(FIELD_DESCRIPTOR, descriptor);
         } else {
-            buf.append(cst);
-        }
-        buf.append('\n');
-        text.add(buf.toString());
-    }
-
-    @Override
-    public void visitIincInsn(final int var, final int increment) {
-        buf.setLength(0);
-        buf.append(tab2).append("IINC ").append(var).append(' ')
-                .append(increment).append('\n');
-        text.add(buf.toStrin

<TRUNCATED>