All Downloads are FREE. Search and download functionalities are using the official Maven repository.

scouter.org.objectweb.asm.xml.ASMContentHandler Maven / Gradle / Ivy

There is a newer version: 2.20.0
Show newest version
// 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 scouter.org.objectweb.asm.xml;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import scouter.org.objectweb.asm.AnnotationVisitor;
import scouter.org.objectweb.asm.ClassVisitor;
import scouter.org.objectweb.asm.FieldVisitor;
import scouter.org.objectweb.asm.Handle;
import scouter.org.objectweb.asm.Label;
import scouter.org.objectweb.asm.MethodVisitor;
import scouter.org.objectweb.asm.ModuleVisitor;
import scouter.org.objectweb.asm.Opcodes;
import scouter.org.objectweb.asm.Type;
import scouter.org.objectweb.asm.TypePath;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

/**
 * A {@link org.xml.sax.ContentHandler ContentHandler} that transforms XML document into Java class
 * file. This class can be feeded by any kind of SAX 2.0 event producers, e.g. XML parser, XSLT or
 * XPath engines, or custom code.
 *
 * @deprecated This class is no longer maintained, will not support new Java features, and will
 *     eventually be deleted. Use the asm or asm.tree API instead.
 * @see SAXClassAdapter
 * @see Processor
 * @author Eugene Kuleshov
 */
@Deprecated
public class ASMContentHandler extends DefaultHandler implements Opcodes {

  /** Stack of the intermediate processing contexts. */
  private final ArrayList stack = new ArrayList();

  /** Complete name of the current element. */
  String match = "";

  /** Current instance of the {@link ClassVisitor ClassVisitor} used to visit classfile bytecode. */
  protected ClassVisitor cv;

  /** Map of the active {@link Label Label} instances for current method. */
  protected Map labels;

  private static final String BASE = "class";

  private final RuleSet RULES = new RuleSet();

  {
    RULES.add(BASE, new ClassRule());
    RULES.add(BASE + "/interfaces/interface", new InterfaceRule());
    RULES.add(BASE + "/interfaces", new InterfacesRule());
    RULES.add(BASE + "/outerclass", new OuterClassRule());
    RULES.add(BASE + "/innerclass", new InnerClassRule());
    RULES.add(BASE + "/source", new SourceRule());

    ModuleRule moduleRule = new ModuleRule();
    RULES.add(BASE + "/module", moduleRule);
    RULES.add(BASE + "/module/main-class", moduleRule);
    RULES.add(BASE + "/module/packages", moduleRule);
    RULES.add(BASE + "/module/requires", moduleRule);
    RULES.add(BASE + "/module/exports", moduleRule);
    RULES.add(BASE + "/module/exports/to", moduleRule);
    RULES.add(BASE + "/module/opens", moduleRule);
    RULES.add(BASE + "/module/opens/to", moduleRule);
    RULES.add(BASE + "/module/uses", moduleRule);
    RULES.add(BASE + "/module/provides", moduleRule);
    RULES.add(BASE + "/module/provides/with", moduleRule);

    RULES.add(BASE + "/field", new FieldRule());

    RULES.add(BASE + "/method", new MethodRule());
    RULES.add(BASE + "/method/exceptions/exception", new ExceptionRule());
    RULES.add(BASE + "/method/exceptions", new ExceptionsRule());

    RULES.add(BASE + "/method/parameter", new MethodParameterRule());
    RULES.add(BASE + "/method/annotationDefault", new AnnotationDefaultRule());

    RULES.add(BASE + "/method/code/*", new OpcodesRule()); // opcodes

    RULES.add(BASE + "/method/code/frame", new FrameRule());
    RULES.add(BASE + "/method/code/frame/local", new FrameTypeRule());
    RULES.add(BASE + "/method/code/frame/stack", new FrameTypeRule());

    RULES.add(BASE + "/method/code/TABLESWITCH", new TableSwitchRule());
    RULES.add(BASE + "/method/code/TABLESWITCH/label", new TableSwitchLabelRule());
    RULES.add(BASE + "/method/code/LOOKUPSWITCH", new LookupSwitchRule());
    RULES.add(BASE + "/method/code/LOOKUPSWITCH/label", new LookupSwitchLabelRule());

    RULES.add(BASE + "/method/code/INVOKEDYNAMIC", new InvokeDynamicRule());
    RULES.add(BASE + "/method/code/INVOKEDYNAMIC/bsmArg", new InvokeDynamicBsmArgumentsRule());

    RULES.add(BASE + "/method/code/Label", new LabelRule());
    RULES.add(BASE + "/method/code/TryCatch", new TryCatchRule());
    RULES.add(BASE + "/method/code/LineNumber", new LineNumberRule());
    RULES.add(BASE + "/method/code/LocalVar", new LocalVarRule());
    RULES.add(BASE + "/method/code/Max", new MaxRule());

    RULES.add("*/annotation", new AnnotationRule());
    RULES.add("*/typeAnnotation", new TypeAnnotationRule());
    RULES.add("*/annotableParameterCount", new AnnotableParameterCountRule());
    RULES.add("*/parameterAnnotation", new AnnotationParameterRule());
    RULES.add("*/insnAnnotation", new InsnAnnotationRule());
    RULES.add("*/tryCatchAnnotation", new TryCatchAnnotationRule());
    RULES.add("*/localVariableAnnotation", new LocalVariableAnnotationRule());
    RULES.add("*/annotationValue", new AnnotationValueRule());
    RULES.add("*/annotationValueAnnotation", new AnnotationValueAnnotationRule());
    RULES.add("*/annotationValueEnum", new AnnotationValueEnumRule());
    RULES.add("*/annotationValueArray", new AnnotationValueArrayRule());
  }

  private static interface OpcodeGroup {
    public static final int INSN = 0;
    public static final int INSN_INT = 1;
    public static final int INSN_VAR = 2;
    public static final int INSN_TYPE = 3;
    public static final int INSN_FIELD = 4;
    public static final int INSN_METHOD = 5;
    public static final int INSN_JUMP = 6;
    public static final int INSN_LDC = 7;
    public static final int INSN_IINC = 8;
    public static final int INSN_MULTIANEWARRAY = 9;
  }

  /** Map of the opcode names to opcode and opcode group */
  static final HashMap OPCODES = new HashMap();

  static {
    addOpcode("NOP", NOP, OpcodeGroup.INSN);
    addOpcode("ACONST_NULL", ACONST_NULL, OpcodeGroup.INSN);
    addOpcode("ICONST_M1", ICONST_M1, OpcodeGroup.INSN);
    addOpcode("ICONST_0", ICONST_0, OpcodeGroup.INSN);
    addOpcode("ICONST_1", ICONST_1, OpcodeGroup.INSN);
    addOpcode("ICONST_2", ICONST_2, OpcodeGroup.INSN);
    addOpcode("ICONST_3", ICONST_3, OpcodeGroup.INSN);
    addOpcode("ICONST_4", ICONST_4, OpcodeGroup.INSN);
    addOpcode("ICONST_5", ICONST_5, OpcodeGroup.INSN);
    addOpcode("LCONST_0", LCONST_0, OpcodeGroup.INSN);
    addOpcode("LCONST_1", LCONST_1, OpcodeGroup.INSN);
    addOpcode("FCONST_0", FCONST_0, OpcodeGroup.INSN);
    addOpcode("FCONST_1", FCONST_1, OpcodeGroup.INSN);
    addOpcode("FCONST_2", FCONST_2, OpcodeGroup.INSN);
    addOpcode("DCONST_0", DCONST_0, OpcodeGroup.INSN);
    addOpcode("DCONST_1", DCONST_1, OpcodeGroup.INSN);
    addOpcode("BIPUSH", BIPUSH, OpcodeGroup.INSN_INT);
    addOpcode("SIPUSH", SIPUSH, OpcodeGroup.INSN_INT);
    addOpcode("LDC", LDC, OpcodeGroup.INSN_LDC);
    addOpcode("ILOAD", ILOAD, OpcodeGroup.INSN_VAR);
    addOpcode("LLOAD", LLOAD, OpcodeGroup.INSN_VAR);
    addOpcode("FLOAD", FLOAD, OpcodeGroup.INSN_VAR);
    addOpcode("DLOAD", DLOAD, OpcodeGroup.INSN_VAR);
    addOpcode("ALOAD", ALOAD, OpcodeGroup.INSN_VAR);
    addOpcode("IALOAD", IALOAD, OpcodeGroup.INSN);
    addOpcode("LALOAD", LALOAD, OpcodeGroup.INSN);
    addOpcode("FALOAD", FALOAD, OpcodeGroup.INSN);
    addOpcode("DALOAD", DALOAD, OpcodeGroup.INSN);
    addOpcode("AALOAD", AALOAD, OpcodeGroup.INSN);
    addOpcode("BALOAD", BALOAD, OpcodeGroup.INSN);
    addOpcode("CALOAD", CALOAD, OpcodeGroup.INSN);
    addOpcode("SALOAD", SALOAD, OpcodeGroup.INSN);
    addOpcode("ISTORE", ISTORE, OpcodeGroup.INSN_VAR);
    addOpcode("LSTORE", LSTORE, OpcodeGroup.INSN_VAR);
    addOpcode("FSTORE", FSTORE, OpcodeGroup.INSN_VAR);
    addOpcode("DSTORE", DSTORE, OpcodeGroup.INSN_VAR);
    addOpcode("ASTORE", ASTORE, OpcodeGroup.INSN_VAR);
    addOpcode("IASTORE", IASTORE, OpcodeGroup.INSN);
    addOpcode("LASTORE", LASTORE, OpcodeGroup.INSN);
    addOpcode("FASTORE", FASTORE, OpcodeGroup.INSN);
    addOpcode("DASTORE", DASTORE, OpcodeGroup.INSN);
    addOpcode("AASTORE", AASTORE, OpcodeGroup.INSN);
    addOpcode("BASTORE", BASTORE, OpcodeGroup.INSN);
    addOpcode("CASTORE", CASTORE, OpcodeGroup.INSN);
    addOpcode("SASTORE", SASTORE, OpcodeGroup.INSN);
    addOpcode("POP", POP, OpcodeGroup.INSN);
    addOpcode("POP2", POP2, OpcodeGroup.INSN);
    addOpcode("DUP", DUP, OpcodeGroup.INSN);
    addOpcode("DUP_X1", DUP_X1, OpcodeGroup.INSN);
    addOpcode("DUP_X2", DUP_X2, OpcodeGroup.INSN);
    addOpcode("DUP2", DUP2, OpcodeGroup.INSN);
    addOpcode("DUP2_X1", DUP2_X1, OpcodeGroup.INSN);
    addOpcode("DUP2_X2", DUP2_X2, OpcodeGroup.INSN);
    addOpcode("SWAP", SWAP, OpcodeGroup.INSN);
    addOpcode("IADD", IADD, OpcodeGroup.INSN);
    addOpcode("LADD", LADD, OpcodeGroup.INSN);
    addOpcode("FADD", FADD, OpcodeGroup.INSN);
    addOpcode("DADD", DADD, OpcodeGroup.INSN);
    addOpcode("ISUB", ISUB, OpcodeGroup.INSN);
    addOpcode("LSUB", LSUB, OpcodeGroup.INSN);
    addOpcode("FSUB", FSUB, OpcodeGroup.INSN);
    addOpcode("DSUB", DSUB, OpcodeGroup.INSN);
    addOpcode("IMUL", IMUL, OpcodeGroup.INSN);
    addOpcode("LMUL", LMUL, OpcodeGroup.INSN);
    addOpcode("FMUL", FMUL, OpcodeGroup.INSN);
    addOpcode("DMUL", DMUL, OpcodeGroup.INSN);
    addOpcode("IDIV", IDIV, OpcodeGroup.INSN);
    addOpcode("LDIV", LDIV, OpcodeGroup.INSN);
    addOpcode("FDIV", FDIV, OpcodeGroup.INSN);
    addOpcode("DDIV", DDIV, OpcodeGroup.INSN);
    addOpcode("IREM", IREM, OpcodeGroup.INSN);
    addOpcode("LREM", LREM, OpcodeGroup.INSN);
    addOpcode("FREM", FREM, OpcodeGroup.INSN);
    addOpcode("DREM", DREM, OpcodeGroup.INSN);
    addOpcode("INEG", INEG, OpcodeGroup.INSN);
    addOpcode("LNEG", LNEG, OpcodeGroup.INSN);
    addOpcode("FNEG", FNEG, OpcodeGroup.INSN);
    addOpcode("DNEG", DNEG, OpcodeGroup.INSN);
    addOpcode("ISHL", ISHL, OpcodeGroup.INSN);
    addOpcode("LSHL", LSHL, OpcodeGroup.INSN);
    addOpcode("ISHR", ISHR, OpcodeGroup.INSN);
    addOpcode("LSHR", LSHR, OpcodeGroup.INSN);
    addOpcode("IUSHR", IUSHR, OpcodeGroup.INSN);
    addOpcode("LUSHR", LUSHR, OpcodeGroup.INSN);
    addOpcode("IAND", IAND, OpcodeGroup.INSN);
    addOpcode("LAND", LAND, OpcodeGroup.INSN);
    addOpcode("IOR", IOR, OpcodeGroup.INSN);
    addOpcode("LOR", LOR, OpcodeGroup.INSN);
    addOpcode("IXOR", IXOR, OpcodeGroup.INSN);
    addOpcode("LXOR", LXOR, OpcodeGroup.INSN);
    addOpcode("IINC", IINC, OpcodeGroup.INSN_IINC);
    addOpcode("I2L", I2L, OpcodeGroup.INSN);
    addOpcode("I2F", I2F, OpcodeGroup.INSN);
    addOpcode("I2D", I2D, OpcodeGroup.INSN);
    addOpcode("L2I", L2I, OpcodeGroup.INSN);
    addOpcode("L2F", L2F, OpcodeGroup.INSN);
    addOpcode("L2D", L2D, OpcodeGroup.INSN);
    addOpcode("F2I", F2I, OpcodeGroup.INSN);
    addOpcode("F2L", F2L, OpcodeGroup.INSN);
    addOpcode("F2D", F2D, OpcodeGroup.INSN);
    addOpcode("D2I", D2I, OpcodeGroup.INSN);
    addOpcode("D2L", D2L, OpcodeGroup.INSN);
    addOpcode("D2F", D2F, OpcodeGroup.INSN);
    addOpcode("I2B", I2B, OpcodeGroup.INSN);
    addOpcode("I2C", I2C, OpcodeGroup.INSN);
    addOpcode("I2S", I2S, OpcodeGroup.INSN);
    addOpcode("LCMP", LCMP, OpcodeGroup.INSN);
    addOpcode("FCMPL", FCMPL, OpcodeGroup.INSN);
    addOpcode("FCMPG", FCMPG, OpcodeGroup.INSN);
    addOpcode("DCMPL", DCMPL, OpcodeGroup.INSN);
    addOpcode("DCMPG", DCMPG, OpcodeGroup.INSN);
    addOpcode("IFEQ", IFEQ, OpcodeGroup.INSN_JUMP);
    addOpcode("IFNE", IFNE, OpcodeGroup.INSN_JUMP);
    addOpcode("IFLT", IFLT, OpcodeGroup.INSN_JUMP);
    addOpcode("IFGE", IFGE, OpcodeGroup.INSN_JUMP);
    addOpcode("IFGT", IFGT, OpcodeGroup.INSN_JUMP);
    addOpcode("IFLE", IFLE, OpcodeGroup.INSN_JUMP);
    addOpcode("IF_ICMPEQ", IF_ICMPEQ, OpcodeGroup.INSN_JUMP);
    addOpcode("IF_ICMPNE", IF_ICMPNE, OpcodeGroup.INSN_JUMP);
    addOpcode("IF_ICMPLT", IF_ICMPLT, OpcodeGroup.INSN_JUMP);
    addOpcode("IF_ICMPGE", IF_ICMPGE, OpcodeGroup.INSN_JUMP);
    addOpcode("IF_ICMPGT", IF_ICMPGT, OpcodeGroup.INSN_JUMP);
    addOpcode("IF_ICMPLE", IF_ICMPLE, OpcodeGroup.INSN_JUMP);
    addOpcode("IF_ACMPEQ", IF_ACMPEQ, OpcodeGroup.INSN_JUMP);
    addOpcode("IF_ACMPNE", IF_ACMPNE, OpcodeGroup.INSN_JUMP);
    addOpcode("GOTO", GOTO, OpcodeGroup.INSN_JUMP);
    addOpcode("JSR", JSR, OpcodeGroup.INSN_JUMP);
    addOpcode("RET", RET, OpcodeGroup.INSN_VAR);
    addOpcode("IRETURN", IRETURN, OpcodeGroup.INSN);
    addOpcode("LRETURN", LRETURN, OpcodeGroup.INSN);
    addOpcode("FRETURN", FRETURN, OpcodeGroup.INSN);
    addOpcode("DRETURN", DRETURN, OpcodeGroup.INSN);
    addOpcode("ARETURN", ARETURN, OpcodeGroup.INSN);
    addOpcode("RETURN", RETURN, OpcodeGroup.INSN);
    addOpcode("GETSTATIC", GETSTATIC, OpcodeGroup.INSN_FIELD);
    addOpcode("PUTSTATIC", PUTSTATIC, OpcodeGroup.INSN_FIELD);
    addOpcode("GETFIELD", GETFIELD, OpcodeGroup.INSN_FIELD);
    addOpcode("PUTFIELD", PUTFIELD, OpcodeGroup.INSN_FIELD);
    addOpcode("INVOKEVIRTUAL", INVOKEVIRTUAL, OpcodeGroup.INSN_METHOD);
    addOpcode("INVOKESPECIAL", INVOKESPECIAL, OpcodeGroup.INSN_METHOD);
    addOpcode("INVOKESTATIC", INVOKESTATIC, OpcodeGroup.INSN_METHOD);
    addOpcode("INVOKEINTERFACE", INVOKEINTERFACE, OpcodeGroup.INSN_METHOD);
    addOpcode("NEW", NEW, OpcodeGroup.INSN_TYPE);
    addOpcode("NEWARRAY", NEWARRAY, OpcodeGroup.INSN_INT);
    addOpcode("ANEWARRAY", ANEWARRAY, OpcodeGroup.INSN_TYPE);
    addOpcode("ARRAYLENGTH", ARRAYLENGTH, OpcodeGroup.INSN);
    addOpcode("ATHROW", ATHROW, OpcodeGroup.INSN);
    addOpcode("CHECKCAST", CHECKCAST, OpcodeGroup.INSN_TYPE);
    addOpcode("INSTANCEOF", INSTANCEOF, OpcodeGroup.INSN_TYPE);
    addOpcode("MONITORENTER", MONITORENTER, OpcodeGroup.INSN);
    addOpcode("MONITOREXIT", MONITOREXIT, OpcodeGroup.INSN);
    addOpcode("MULTIANEWARRAY", MULTIANEWARRAY, OpcodeGroup.INSN_MULTIANEWARRAY);
    addOpcode("IFNULL", IFNULL, OpcodeGroup.INSN_JUMP);
    addOpcode("IFNONNULL", IFNONNULL, OpcodeGroup.INSN_JUMP);
  }

  private static void addOpcode(String operStr, int oper, int group) {
    OPCODES.put(operStr, new Opcode(oper, group));
  }

  static final HashMap TYPES = new HashMap();

  static {
    String[] types = SAXCodeAdapter.TYPES;
    for (int i = 0; i < types.length; i++) {
      TYPES.put(types[i], i);
    }
  }

  /**
   * Constructs a new {@link ASMContentHandler ASMContentHandler} object.
   *
   * @param cv class visitor that will be called to reconstruct the classfile using the XML stream.
   */
  public ASMContentHandler(final ClassVisitor cv) {
    this.cv = cv;
  }

  /**
   * Process notification of the start of an XML element being reached.
   *
   * @param ns - The Namespace URI, or the empty string if the element has no Namespace URI or if
   *     Namespace processing is not being performed.
   * @param lName - The local name (without prefix), or the empty string if Namespace processing is
   *     not being performed.
   * @param qName - The qualified name (with prefix), or the empty string if qualified names are not
   *     available.
   * @param list - The attributes attached to the element. If there are no attributes, it shall be
   *     an empty Attributes object.
   * @exception SAXException if a parsing error is to be reported
   */
  @Override
  public final void startElement(
      final String ns, final String lName, final String qName, final Attributes list)
      throws SAXException {
    // the actual element name is either in lName or qName, depending
    // on whether the parser is namespace aware
    String name = lName == null || lName.length() == 0 ? qName : lName;

    // Compute the current matching rule
    StringBuilder sb = new StringBuilder(match);
    if (match.length() > 0) {
      sb.append('/');
    }
    sb.append(name);
    match = sb.toString();

    // Fire "begin" events for all relevant rules
    Rule r = (Rule) RULES.match(match);
    if (r != null) {
      r.begin(name, list);
    }
  }

  /**
   * Process notification of the end of an XML element being reached.
   *
   * @param ns - The Namespace URI, or the empty string if the element has no Namespace URI or if
   *     Namespace processing is not being performed.
   * @param lName - The local name (without prefix), or the empty string if Namespace processing is
   *     not being performed.
   * @param qName - The qualified XML 1.0 name (with prefix), or the empty string if qualified names
   *     are not available.
   * @exception SAXException if a parsing error is to be reported
   */
  @Override
  public final void endElement(final String ns, final String lName, final String qName)
      throws SAXException {
    // the actual element name is either in lName or qName, depending
    // on whether the parser is namespace aware
    String name = lName == null || lName.length() == 0 ? qName : lName;

    // Fire "end" events for all relevant rules in reverse order
    Rule r = (Rule) RULES.match(match);
    if (r != null) {
      r.end(name);
    }

    // Recover the previous match expression
    int slash = match.lastIndexOf('/');
    if (slash >= 0) {
      match = match.substring(0, slash);
    } else {
      match = "";
    }
  }

  /**
   * Return the top object on the stack without removing it. If there are no objects on the stack,
   * return null.
   *
   * @return the top object on the stack without removing it.
   */
  final Object peek() {
    int size = stack.size();
    return size == 0 ? null : stack.get(size - 1);
  }

  /**
   * Pop the top object off of the stack, and return it. If there are no objects on the stack,
   * return null.
   *
   * @return the top object off of the stack.
   */
  final Object pop() {
    int size = stack.size();
    return size == 0 ? null : stack.remove(size - 1);
  }

  /**
   * Push a new object onto the top of the object stack.
   *
   * @param object The new object
   */
  final void push(final Object object) {
    stack.add(object);
  }

  static final class RuleSet {

    private final HashMap rules = new HashMap();

    private final ArrayList lpatterns = new ArrayList();

    private final ArrayList rpatterns = new ArrayList();

    public void add(final String path, final Object rule) {
      String pattern = path;
      if (path.startsWith("*/")) {
        pattern = path.substring(1);
        lpatterns.add(pattern);
      } else if (path.endsWith("/*")) {
        pattern = path.substring(0, path.length() - 1);
        rpatterns.add(pattern);
      }
      rules.put(pattern, rule);
    }

    public Object match(final String path) {
      if (rules.containsKey(path)) {
        return rules.get(path);
      }

      int n = path.lastIndexOf('/');
      for (Iterator it = lpatterns.iterator(); it.hasNext(); ) {
        String pattern = it.next();
        if (path.substring(n).endsWith(pattern)) {
          return rules.get(pattern);
        }
      }

      for (Iterator it = rpatterns.iterator(); it.hasNext(); ) {
        String pattern = it.next();
        if (path.startsWith(pattern)) {
          return rules.get(pattern);
        }
      }

      return null;
    }
  }

  /** Rule */
  protected abstract class Rule {

    public void begin(final String name, final Attributes attrs) throws SAXException {}

    public void end(final String name) {}

    protected final Object getValue(final String desc, final String val) throws SAXException {
      Object value = null;
      if (val != null) {
        if ("Ljava/lang/String;".equals(desc)) {
          value = decode(val);
        } else if ("Ljava/lang/Integer;".equals(desc)
            || "I".equals(desc)
            || "S".equals(desc)
            || "B".equals(desc)
            || "C".equals(desc)
            || "Z".equals(desc)) {
          value = new Integer(val);

        } else if ("Ljava/lang/Short;".equals(desc)) {
          value = new Short(val);

        } else if ("Ljava/lang/Byte;".equals(desc)) {
          value = new Byte(val);

        } else if ("Ljava/lang/Character;".equals(desc)) {
          value = new Character(decode(val).charAt(0));

        } else if ("Ljava/lang/Boolean;".equals(desc)) {
          value = Boolean.valueOf(val);

        } else if ("Ljava/lang/Long;".equals(desc) || "J".equals(desc)) {
          value = new Long(val);
        } else if ("Ljava/lang/Float;".equals(desc) || "F".equals(desc)) {
          value = new Float(val);
        } else if ("Ljava/lang/Double;".equals(desc) || "D".equals(desc)) {
          value = new Double(val);
        } else if (Type.getDescriptor(Type.class).equals(desc)) {
          value = Type.getType(val);

        } else if (Type.getDescriptor(Handle.class).equals(desc)) {
          value = decodeHandle(val);

        } else {
          // TODO use of default toString().
          throw new SAXException("Invalid value:" + val + " desc:" + desc + " ctx:" + this);
        }
      }
      return value;
    }

    Handle decodeHandle(final String val) throws SAXException {
      try {
        int dotIndex = val.indexOf('.');
        int descIndex = val.indexOf('(', dotIndex + 1);
        int tagIndex = val.lastIndexOf('(');
        int itfIndex = val.indexOf(' ', tagIndex + 1);

        boolean itf = itfIndex != -1;
        int tag = Integer.parseInt(val.substring(tagIndex + 1, itf ? itfIndex : val.length() - 1));
        String owner = val.substring(0, dotIndex);
        String name = val.substring(dotIndex + 1, descIndex);
        String desc = val.substring(descIndex, tagIndex - 1);
        return new Handle(tag, owner, name, desc, itf);

      } catch (RuntimeException e) {
        throw new SAXException("Malformed handle " + val, e);
      }
    }

    private final String decode(final String val) throws SAXException {
      StringBuilder sb = new StringBuilder(val.length());
      try {
        int n = 0;
        while (n < val.length()) {
          char c = val.charAt(n);
          if (c == '\\') {
            n++;
            c = val.charAt(n);
            if (c == '\\') {
              sb.append('\\');
            } else {
              n++; // skip 'u'
              sb.append((char) Integer.parseInt(val.substring(n, n + 4), 16));
              n += 3;
            }
          } else {
            sb.append(c);
          }
          n++;
        }

      } catch (RuntimeException ex) {
        throw new SAXException(ex);
      }
      return sb.toString();
    }

    protected final Label getLabel(final Object label) {
      Label lbl = labels.get(label);
      if (lbl == null) {
        lbl = new Label();
        labels.put(label, lbl);
      }
      return lbl;
    }

    // TODO verify move to stack
    protected final MethodVisitor getCodeVisitor() {
      return (MethodVisitor) peek();
    }

    protected final int getAccess(final String s) {
      int access = 0;
      if (s.indexOf("public") != -1) {
        access |= ACC_PUBLIC;
      }
      if (s.indexOf("private") != -1) {
        access |= ACC_PRIVATE;
      }
      if (s.indexOf("protected") != -1) {
        access |= ACC_PROTECTED;
      }
      if (s.indexOf("static") != -1) {
        access |= ACC_STATIC;
      }
      if (s.indexOf("final") != -1) {
        access |= ACC_FINAL;
      }
      if (s.indexOf("super") != -1) {
        access |= ACC_SUPER;
      }
      if (s.indexOf("synchronized") != -1) {
        access |= ACC_SYNCHRONIZED;
      }
      if (s.indexOf("volatile") != -1) {
        access |= ACC_VOLATILE;
      }
      if (s.indexOf("bridge") != -1) {
        access |= ACC_BRIDGE;
      }
      if (s.indexOf("varargs") != -1) {
        access |= ACC_VARARGS;
      }
      if (s.indexOf("transient") != -1) {
        access |= ACC_TRANSIENT;
      }
      if (s.indexOf("native") != -1) {
        access |= ACC_NATIVE;
      }
      if (s.indexOf("interface") != -1) {
        access |= ACC_INTERFACE;
      }
      if (s.indexOf("abstract") != -1) {
        access |= ACC_ABSTRACT;
      }
      if (s.indexOf("strict") != -1) {
        access |= ACC_STRICT;
      }
      if (s.indexOf("synthetic") != -1) {
        access |= ACC_SYNTHETIC;
      }
      if (s.indexOf("annotation") != -1) {
        access |= ACC_ANNOTATION;
      }
      if (s.indexOf("enum") != -1) {
        access |= ACC_ENUM;
      }
      if (s.indexOf("deprecated") != -1) {
        access |= ACC_DEPRECATED;
      }
      if (s.indexOf("mandated") != -1) {
        access |= ACC_MANDATED;
      }
      if (s.indexOf("module") != -1) {
        access |= ACC_MODULE;
      }
      if (s.indexOf("open") != -1) {
        access |= ACC_OPEN;
      }
      if (s.indexOf("transitive") != -1) {
        access |= ACC_TRANSITIVE;
      }
      return access;
    }
  }

  /** ClassRule */
  final class ClassRule extends Rule {

    @Override
    public final void begin(final String name, final Attributes attrs) {
      int major = Integer.parseInt(attrs.getValue("major"));
      int minor = Integer.parseInt(attrs.getValue("minor"));
      HashMap vals = new HashMap();
      vals.put("version", minor << 16 | major);
      vals.put("access", attrs.getValue("access"));
      vals.put("name", attrs.getValue("name"));
      vals.put("parent", attrs.getValue("parent"));
      vals.put("source", attrs.getValue("source"));
      vals.put("signature", attrs.getValue("signature"));
      vals.put("interfaces", new ArrayList());
      push(vals);
      // values will be extracted in InterfacesRule.end();
    }
  }

  final class SourceRule extends Rule {

    @Override
    public void begin(final String name, final Attributes attrs) {
      String file = attrs.getValue("file");
      String debug = attrs.getValue("debug");
      cv.visitSource(file, debug);
    }
  }

  /** InterfaceRule */
  final class InterfaceRule extends Rule {

    @Override
    @SuppressWarnings("unchecked")
    public final void begin(final String name, final Attributes attrs) {
      ((ArrayList) ((HashMap) peek()).get("interfaces")).add(attrs.getValue("name"));
    }
  }

  /** InterfacesRule */
  final class InterfacesRule extends Rule {

    @Override
    public final void end(final String element) {
      HashMap vals = (HashMap) pop();
      int version = ((Integer) vals.get("version")).intValue();
      int access = getAccess((String) vals.get("access"));
      String name = (String) vals.get("name");
      String signature = (String) vals.get("signature");
      String parent = (String) vals.get("parent");
      ArrayList infs = (ArrayList) vals.get("interfaces");
      String[] interfaces = infs.toArray(new String[infs.size()]);
      cv.visit(version, access, name, signature, parent, interfaces);
      push(cv);
    }
  }

  /** ModuleRule: module, requires, exports, opens, uses and provides */
  final class ModuleRule extends Rule {
    @Override
    public final void begin(final String element, final Attributes attrs) throws SAXException {
      if ("module".equals(element)) {
        push(
            cv.visitModule(
                attrs.getValue("name"),
                getAccess(attrs.getValue("access")),
                attrs.getValue("version")));
      } else if ("main-class".equals(element)) {
        ModuleVisitor mv = (ModuleVisitor) peek();
        mv.visitMainClass(attrs.getValue("name"));
      } else if ("packages".equals(element)) {
        ModuleVisitor mv = (ModuleVisitor) peek();
        mv.visitPackage(attrs.getValue("name"));
      } else if ("requires".equals(element)) {
        ModuleVisitor mv = (ModuleVisitor) peek();
        int access = getAccess(attrs.getValue("access"));
        if ((access & Opcodes.ACC_STATIC) != 0) {
          access = access & ~Opcodes.ACC_STATIC | Opcodes.ACC_STATIC_PHASE;
        }
        mv.visitRequire(attrs.getValue("module"), access, attrs.getValue("version"));
      } else if ("exports".equals(element)) {
        push(attrs.getValue("name"));
        push(getAccess(attrs.getValue("access")));
        ArrayList list = new ArrayList();
        push(list);
      } else if ("opens".equals(element)) {
        push(attrs.getValue("name"));
        push(getAccess(attrs.getValue("access")));
        ArrayList list = new ArrayList();
        push(list);
      } else if ("to".equals(element)) {
        @SuppressWarnings("unchecked")
        ArrayList list = (ArrayList) peek();
        list.add(attrs.getValue("module"));
      } else if ("uses".equals(element)) {
        ModuleVisitor mv = (ModuleVisitor) peek();
        mv.visitUse(attrs.getValue("service"));
      } else if ("provides".equals(element)) {
        push(attrs.getValue("service"));
        push(0); // see end() below
        ArrayList list = new ArrayList();
        push(list);
      } else if ("with".equals(element)) {
        @SuppressWarnings("unchecked")
        ArrayList list = (ArrayList) peek();
        list.add(attrs.getValue("provider"));
      }
    }

    @Override
    public void end(final String element) {
      boolean exports = "exports".equals(element);
      boolean opens = "opens".equals(element);
      boolean provides = "provides".equals(element);
      if (exports || opens || provides) {
        @SuppressWarnings("unchecked")
        ArrayList list = (ArrayList) pop();
        int access = (Integer) pop();
        String name = (String) pop();
        String[] tos = null;
        if (!list.isEmpty()) {
          tos = list.toArray(new String[list.size()]);
        }
        ModuleVisitor mv = (ModuleVisitor) peek();
        if (exports) {
          mv.visitExport(name, access, tos);
        } else {
          if (opens) {
            mv.visitOpen(name, access, tos);
          } else {
            mv.visitProvide(name, tos);
          }
        }
      } else if ("module".equals(element)) {
        ((ModuleVisitor) pop()).visitEnd();
      }
    }
  }

  /** OuterClassRule */
  final class OuterClassRule extends Rule {

    @Override
    public final void begin(final String element, final Attributes attrs) {
      String owner = attrs.getValue("owner");
      String name = attrs.getValue("name");
      String desc = attrs.getValue("desc");
      cv.visitOuterClass(owner, name, desc);
    }
  }

  /** InnerClassRule */
  final class InnerClassRule extends Rule {

    @Override
    public final void begin(final String element, final Attributes attrs) {
      int access = getAccess(attrs.getValue("access"));
      String name = attrs.getValue("name");
      String outerName = attrs.getValue("outerName");
      String innerName = attrs.getValue("innerName");
      cv.visitInnerClass(name, outerName, innerName, access);
    }
  }

  /** FieldRule */
  final class FieldRule extends Rule {

    @Override
    public final void begin(final String element, final Attributes attrs) throws SAXException {
      int access = getAccess(attrs.getValue("access"));
      String name = attrs.getValue("name");
      String signature = attrs.getValue("signature");
      String desc = attrs.getValue("desc");
      Object value = getValue(desc, attrs.getValue("value"));
      push(cv.visitField(access, name, desc, signature, value));
    }

    @Override
    public void end(final String name) {
      ((FieldVisitor) pop()).visitEnd();
    }
  }

  /** MethodRule */
  final class MethodRule extends Rule {

    @Override
    public final void begin(final String name, final Attributes attrs) {
      labels = new HashMap();
      HashMap vals = new HashMap();
      vals.put("access", attrs.getValue("access"));
      vals.put("name", attrs.getValue("name"));
      vals.put("desc", attrs.getValue("desc"));
      vals.put("signature", attrs.getValue("signature"));
      vals.put("exceptions", new ArrayList());
      push(vals);
      // values will be extracted in ExceptionsRule.end();
    }

    @Override
    public final void end(final String name) {
      ((MethodVisitor) pop()).visitEnd();
      labels = null;
    }
  }

  /** ExceptionRule */
  final class ExceptionRule extends Rule {

    @Override
    @SuppressWarnings("unchecked")
    public final void begin(final String name, final Attributes attrs) {
      ((ArrayList) ((HashMap) peek()).get("exceptions")).add(attrs.getValue("name"));
    }
  }

  /** ExceptionsRule */
  final class ExceptionsRule extends Rule {

    @Override
    public final void end(final String element) {
      HashMap vals = (HashMap) pop();
      int access = getAccess((String) vals.get("access"));
      String name = (String) vals.get("name");
      String desc = (String) vals.get("desc");
      String signature = (String) vals.get("signature");
      ArrayList excs = (ArrayList) vals.get("exceptions");
      String[] exceptions = excs.toArray(new String[excs.size()]);

      push(cv.visitMethod(access, name, desc, signature, exceptions));
    }
  }

  /** MethodParameterRule */
  final class MethodParameterRule extends Rule {
    @Override
    public void begin(final String nm, final Attributes attrs) {
      String name = attrs.getValue("name");
      int access = getAccess(attrs.getValue("access"));
      getCodeVisitor().visitParameter(name, access);
    }
  }

  /** TableSwitchRule */
  final class TableSwitchRule extends Rule {

    @Override
    public final void begin(final String name, final Attributes attrs) {
      HashMap vals = new HashMap();
      vals.put("min", attrs.getValue("min"));
      vals.put("max", attrs.getValue("max"));
      vals.put("dflt", attrs.getValue("dflt"));
      vals.put("labels", new ArrayList());
      push(vals);
    }

    @Override
    public final void end(final String name) {
      HashMap vals = (HashMap) pop();
      int min = Integer.parseInt((String) vals.get("min"));
      int max = Integer.parseInt((String) vals.get("max"));
      Label dflt = getLabel(vals.get("dflt"));
      ArrayList lbls = (ArrayList) vals.get("labels");
      Label[] labels = lbls.toArray(new Label[lbls.size()]);
      getCodeVisitor().visitTableSwitchInsn(min, max, dflt, labels);
    }
  }

  /** TableSwitchLabelRule */
  final class TableSwitchLabelRule extends Rule {

    @Override
    @SuppressWarnings("unchecked")
    public final void begin(final String name, final Attributes attrs) {
      ((ArrayList