tags:

views:

94

answers:

4

What's the best way to (programmatically) parse a compiled Java (.class) file and generate a list of any and all other Java classes it references?

+1  A: 

Have you tried JDepend? It analyzes class files to determine references.

Jeff Storey
A: 

I don't know what the best way is. I do know there are 2 APIs that might help.

You can try jclasslib, for inspecting .class files
http://www.ej-technologies.com/products/jclasslib/resources.html
or BCEL (byte code engineering library) for analyzing and manipulating .class files.
http://jakarta.apache.org/bcel/

My understanding is that any class to be used within a .class (as far as a field, parameter, return type, etc) is defined in the constant_pool. So you may want to read up on that.
http://java.sun.com/docs/books/jvms/second%5Fedition/html/ClassFile.doc.html

z5h
+1  A: 

This is possible to do with Apache's BCEL.

brianegge
+1  A: 

It's easy do it by yourself if you know the spec.

Heres a quick and dirty demo program how to do it:

package test;

import java.io.*;
import java.util.*;

public class Main {

  private static final String FILE_NAME = "D:/Arne/workspaces/IDEDeluxe/TestParser/bin/test/Main.class";

  private static Map<Integer, String> strings = new HashMap<Integer, String>();
  private static Set<Integer> classes = new HashSet<Integer>();
  private static int indexCorrection = 0; // for correcting indexes to constant pools with long and double entries

  public static void main(String[] args) throws Exception {
    printReferencedClassesFromFile(FILE_NAME);
  }

  private static void printReferencedClassesFromFile(String fileName) throws IOException {
    printReferencedClassesFromFile(new File(fileName));
  }

  private static void printReferencedClassesFromFile(File file) throws IOException {
    DataInputStream stream = new DataInputStream(new BufferedInputStream(new FileInputStream(file)));
    try {
      saveReferencedClassesFromStream(stream);
    } finally {
      stream.close();
    }
    printReferencedClasses();
  }

  private static void printReferencedClasses() {
    for(Integer index : classes)
      System.out.println(strings.get(index));
  }

  private static void saveReferencedClassesFromStream(DataInputStream stream) throws IOException {
    skipHeader(stream);
    saveReferencedClassesFromConstantPool(stream);
  }

  private static void skipHeader(DataInputStream dataInputStream) throws IOException {
    readU4(dataInputStream); // magic byte
    readU2(dataInputStream); // minor version
    readU2(dataInputStream); // major version
  }

  private static void saveReferencedClassesFromConstantPool(DataInputStream stream) throws IOException {
    int poolSize = readU2(stream);
    for(int n = 1; n < poolSize; n++)
      savePoolEntryIfIsClassReference(n, stream);
  }

  private static void savePoolEntryIfIsClassReference(int index, DataInputStream stream) throws IOException {
    int tag = readU1(stream);
    switch(tag) {
    case 1: // Utf8
      saveStringFromUtf8Entry(index, stream);
      break;
    case 7: // Class
      saveClassEntry(stream);
      break;
    case 8: // String
      readU2(stream);
      break;
    case 3: // Integer
    case 4: // Float
      readU4(stream);
      break;
    case 5: // Long
    case 6: // Double
      readU4(stream);
      readU4(stream);
      indexCorrection++;
      break;
    case 9: // Fieldref
    case 10: // Methodref
    case 11: // InterfaceMethodref
    case 12: // NameAndType
      readU2(stream);
      readU2(stream);
      break;
    }
  }

  private static void saveClassEntry(DataInputStream stream) throws IOException {
    int nameIndex = readU2(stream);
    classes.add(nameIndex - indexCorrection);
  }

  private static void saveStringFromUtf8Entry(int index, DataInputStream stream) throws IOException {
    String content = readString(stream);
    strings.put(index, content);
  }

  private static String readString(DataInputStream stream) throws IOException {
    return stream.readUTF();
  }

  private static int readU1(DataInputStream stream) throws IOException {
    return stream.readUnsignedByte();
  }

  private static int readU2(DataInputStream stream) throws IOException {
    return stream.readUnsignedShort();
  }

  private static int readU4(DataInputStream stream) throws IOException {
    return stream.readInt();
  }

}

Output is:

test/Main
java/lang/Object
java/io/BufferedInputStream
java/util/Iterator
java/io/FileInputStream
java/lang/System
java/lang/Integer
java/lang/Exception
java/lang/String
java/io/File
java/util/Map
java/util/HashMap
java/io/IOException
java/util/Set
java/io/DataInputStream
java/io/PrintStream
java/util/HashSet
java/lang/Throwable
Arne