ONJava.com -- The Independent Source for Enterprise Java
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

Introduction to the ASM 2.0 Bytecode Framework
Pages: 1, 2, 3, 4, 5, 6, 7, 8

Tracking Class Dependencies with ASM Visitors

There are already a few articles that explain how to generate bytecode with ASM (see the Resources section for some links). For a change, let's see how ASM can be used to analyze existing classes. One interesting application is to capture information about external classes or packages used by any given module or .jar file. For simplicity, this example will only capture outgoing dependencies and won't keep track of the dependency types (e.g., superclass, method parameters, local variable types, etc.).

Notice that for analysis purposes, we don't need to create new instances of child visitors for annotations, fields, and methods. All of these visitors, including class and signature visitor, could be implemented in a single class:

public class DependencyVisitor implements 
    AnnotationVisitor, SignatureVisitor, 
    ClassVisitor, FieldVisitor, MethodVisitor {
...

For this example, we will track dependencies between packages, so individual classes should be aggregated by the package name:

  private String getGroupKey(String name) {
    int n = name.lastIndexOf('/');
    if(n>-1) name = name.substring(0, n);
    packages.add(name);
    return name;
  }

In order to collect dependencies, visitor interfaces such as ClassVisitor, AnnotationVisitor, FieldVisitor, and MethodVisitor should selectively aggregate parameters of their methods. There are several common cases:

First of all, there are class names in internal form (super class, interfaces, exceptions, field and method owners); e.g., java/lang/String:

  private void addName(String name) {
    if(name==null) return;
    String p = getGroupKey(name);
    if(current.containsKey(p)) {
      current.put(p, current.get(p)+1);
    } else {
      current.put(p, 1);
    }
  }

In this case, current is the current group of dependencies (e.g., package).

Another case is type descriptors (annotations, enum and field types, parameters of the newarray instruction, etc.); e.g., Ljava/lang/String;, J, and [[[I. These can be parsed with Type.getType( desc) to obtain the class name in internal form:

  private void addDesc(String desc) {
    addType(Type.getType(desc));
  }

  private void addType(Type t) {
    switch(t.getSort()) {
      case Type.ARRAY:
        addType(t.getElementType());
        break;
      case Type.OBJECT:
        addName(t.getClassName().replace('.','/'));
        break;
    }
  }

Method descriptors used in method declarations and in invoke instructions describe parameter types and return a type; e.g., ([java/lang/String;II)V. The helper methods Type.getReturnType(methodDescriptor) and Type.getArgumentTypes(methodDescriptor) can parse such descriptors and extract parameter and return types.

  private void addMethodDesc(String desc) {
    addType(Type.getReturnType(desc));
    Type[] types = Type.getArgumentTypes(desc);
    for(int i = 0; i < types.length; i++) {
      addType(types[ i]);
    }
  }

The special case is the signature parameter used in many "visit" methods to specify Java 5 generics info. If it is present (i.e., non-null), this parameter overrides the descriptor parameter and contains an encoded form of the generics information. SignatureReader class could be used to parse this value. So we can implement a SignatureVisitor, which will be called for each signature artifact.

  private void addSignature(String sign) {
    if(sign!=null) {
      new SignatureReader(sign).accept(this);
    }
  }
  
  private void addTypeSignature(String sign) {
    if(sign!=null) {
      new SignatureReader(sign).acceptType(this);
    }
  }

Pages: 1, 2, 3, 4, 5, 6, 7, 8

Next Pagearrow