Please use NetBeans version 6.5 or above to open the attached source code. You need to update the jCFL library reference of the NetBeans project since your folder path may be different.

You may go to the jCFL download page to get the latest library.

Introduction

Java Class File is one of the key reasons that Java can run on various platforms. The Java class file is designed as a byte stream, with a specific structure as described in the JVM Spec, chapter 4.

Well, it is not easy for a beginner to understand the VM Spec; Java Class Viewer is a visual yet powerful application which can show the meaning of each byte of the class file.

Java Class Viewer - Main Window

Background

You can ignore this "Background" section if you are not interested in the history of Java Class Viewer.

1. The Reason for Java Class Viewer (jCV)

Years ago, I was asked to write a plug-in which can hook the Java applications. The principle of the plug-in was simple: Find the object/class at run time, and try to change its behavior. At that time, I had to read the class file byte by byte via the binary file reader like UltraEdit. It is really not interesting at all; it is boring.

So, I decided to write a Java Class Viewer, which can display the class file visually, and it can display the meaning of each byte of the class file. This is the reason for creating the Java Class Viewer (jCV) application.

In the very beginning (September, 2007), jCV was a command line tool. And one and a half years later (I am lazy enough...), the graphical version of this application was created.

2. Java Class File Library (jCFL)

When creating the graphical application, I noticed that this application can be divided into two parts:

  1. Java Class File Library. It parses the class byte array file, provides various helper classes which can help to get the information of the parsed class; and also provides some swing UI controls for the Java class parser. It may provide more developer friendly information that the Sun JDK javacerror message if the class file has any problem.
  2. Java Class Viewer. It is a swing application, uses the jCFL, to give a graphical view of the class file.

So, for now, there are two components: jCFL, and jCV.

Another reason for this is that, jCFL can also be used in other areas, like class file Meta data analysis.

Related Libraries

There is some other library available for class file manufacturing and verifying, like the Byte Code Engineering Library (BCEL) from Apache. The design principle of jCFL is quite different from BCEL. BCEL is a powerful library for edit/change Java class file, and there is also a class file verifier available in BCEL.

Well, it is (almost) impossible to use BCEL to write a Class File Viewer which can show each byte's meaning, since it does not record the location when parsing the class file; BCEL is designed following the author's idea, the idea and naming convention is not following the JVM Spec, Class File Structure strictly.

jCFL could edit class file until now; jCFL records the offset of the class file when parsing; jCFL follows the ClassFilestructure in JVM Spec strictly.

If you are running jCV, while reading the JVM Spec, it will be much easier for a beginner to understand the Class file.

The Class File Format

The Class file has the following structure:

ClassFile {
    	u4 magic;
    	u2 minor_version;
    	u2 major_version;
    	u2 constant_pool_count;
    	cp_info constant_pool[constant_pool_count-1];
    	u2 access_flags;
    	u2 this_class;
    	u2 super_class;
    	u2 interfaces_count;
    	u2 interfaces[interfaces_count];
    	u2 fields_count;
    	field_info fields[fields_count];
    	u2 methods_count;
    	method_info methods[methods_count];
    	u2 attributes_count;
    	attribute_info attributes[attributes_count];
} 

You may want to view the section The class File Format in the JVM Spec for a detailed description for the ClassFilestructure. Here is a brief description:

magic- 0xCAFEBABE, the magic number of class file. If the first 4 bytes are not 0xCAFEBABE, it is not recognized as a class file.

minor_version, major_version- The major version and minor version determine the class file version together.

constant_pool_count, cp_info constant_pool[constant_pool_count-1]- Constant pool of the class file. The constant pool may contain eleven types of constants:

01. class/interface info
02. field reference info
03. method reference info
04. interface method reference info
05. String 
06. Integer
07. Float
08. Long
09. Double
10. NameAndType
11. Utf8 

access_flags- The access flag of the class

this_class super_class- The class info of current class and super class. Only the java.lang.Objectclass's super class is null if the super class is not specified for this class, the super class is java.lang.Object

interfaces_count, interfaces[interfaces_count]- The direct super interfaces.

fields_count, field_info fields[fields_count]- The fields of this class, if there are any.

methods_count, method_info methods[methods_count]- The methods of this class. The Java compiler will generate a default constructor for a class (inner class excluded) if there is none. So, there will be at least one method in this class.

attributes_count, attribute_info attributes[attributes_count]- The attribute of this class. There is at least one attribute named "SourceFilequot; for the file name of the source code.

Parse a Class File using Java Class File Library

1. Parse the Class File

The class org.freeinternals.classfile.core.ClassFileis the parser of class file. It accepts a byte array as input parameter; the byte array contains the class file. The byte array may come from a .class file, a .jar file, a .war file, etc. Or alternatively, the byte array may be built by libraries like BCEL.

// ArticleCodeDemo.src.zip - org.freeinternals.demo.jCFL_CodeDemo.extractClassFile()
File file = new File("C:/Temp/File.class");
byte[] classByteArray = Tool.readClassFile(file);
ClassFile classfile = new ClassFile(classByteArray);
// ArticleCodeDemo.src.zip - org.freeinternals.demo.jCFL_CodeDemo.extractJarFile()
File file = new File("C:/Temp/tools.jar");
JarFile jarFile = new JarFile(file, false, JarFile.OPEN_READ);
ZipFile zipFile = jarFile;
 final Enumeration zipEntries = zipFile.entries();
while (zipEntries.hasMoreElements()) {
    ZipEntry zipEntry = (ZipEntry) zipEntries.nextElement();
    if (!zipEntry.getName().endsWith(".class")) {
        continue;
    }
     byte[] classByteArray = Tool.readClassFile(zipFile, zipEntry);
    ClassFile classfile = new ClassFile(classByteArray);
     System.out.println();
    System.out.println(zipEntry.getName());
    jCFL_CodeDemo.printClassFile(classfile);
}

There is a tool class org.freeinternals.classfile.ui.Toolwhich can help us to read from a file or a zip file. We know that .jar and .war files are both in fact in zip format.

The constructor of ClassFile hrows an org.freeinternals.classfile.core.ClassFormatExceptionif the byte array is not a valid class file, or a java.io.IOExceptionif there is some error in IO. You may surround the statement with try...catchblock or add a throwsclause in the method declaration.

2. Get the Information of the Class File

Once we get the ClassFile nstance successfully, we can get the information of the class file via the getXxxxxmethods.

Here is an example to print out all of the component information of the class file:

// ArticleCodeDemo.src.zip - org.freeinternals.demo.jCFL_CodeDemo.printClassFile()
 // Minor & Major version
MinorVersion minorVersion = classfile.getMinorVersion();
System.out.println("Class File Minor Version: " + minorVersion.getValue());
 MajorVersion majorVersion = classfile.getMajorVersion();
System.out.println("Class File Major Version: " + majorVersion.getValue());
 // Constant Pool
CPCount cpCount = classfile.getCPCount();
System.out.println("Constant Pool size: " + cpCount.getValue());
 AbstractCPInfo[] cpArray = classfile.getConstantPool();
for (int i = 1; i < cpCount.getValue(); i++) {
    System.out.println(
            String.format("Constant Pool [%d]: %s", i, classfile.getCPDescription(i)));
    short tag = cpArray[i].getTag();
    if ((tag == AbstractCPInfo.CONSTANT_Double) || 
			(tag == AbstractCPInfo.CONSTANT_Long)) {
        i++;
    }
}
 // Access flag, this & super class
AccessFlags accessFlags = classfile.getAccessFlags();
System.out.println("Class Modifier: " + accessFlags.getModifiers());
 ThisClass thisClass = classfile.getThisClass();
System.out.println("This Class Name Index: " + thisClass.getValue());
System.out.println("This Class Name: " + 
	classfile.getCPDescription(thisClass.getValue()));
 SuperClass superClass = classfile.getSuperClass();
System.out.println("Super Class Name Index: " + superClass.getValue());
if (superClass.getValue() == 0) {
    System.out.println("Super Class Name: java.lang.Object");
} else {
    System.out.println("Super Class Name: " + 
		classfile.getCPDescription(superClass.getValue()));
}
 // Interfaces
InterfaceCount interfactCount = classfile.getInterfacesCount();
System.out.println("Interface Count: " + interfactCount.getValue());
 if (interfactCount.getValue() > 0) {
    Interface[] interfaceArray = classfile.getInterfaces();
    for (int i = 0; i < interfaceArray.length; i++) {
        System.out.println(
                String.format("Interface [%d] Name Index: %d", i, 
				interfaceArray[i].getValue()));
        System.out.println(
                String.format("Interface [%d] Name: %s", i, 
		classfile.getCPDescription(interfaceArray[i].getValue())));
    }
}
 // Fields
FieldCount fieldCount = classfile.getFieldCount();
System.out.println("Field count: " + fieldCount.getValue());
 if (fieldCount.getValue() > 0) {
    FieldInfo[] fieldArray = classfile.getFields();
    for (int i = 0; i < fieldArray.length; i++) {
        System.out.println(String.format("Field [%d]: %s", i, 
				fieldArray[i].getDeclaration()));
    }
}
 // Methods
MethodCount methodCount = classfile.getMethodCount();
System.out.println("Method count: " + methodCount.getValue());
 if (methodCount.getValue() > 0) {
    MethodInfo[] methodArray = classfile.getMethods();
    for (int i = 0; i < methodArray.length; i++) {
        System.out.println(String.format("Method [%d]: %s", i, 
				methodArray[i].getDeclaration()));
    }
}
 // Attributes
AttributeCount attributeCount = classfile.getAttributeCount();
System.out.println("Attribute count: " + attributeCount.getValue());
 AttributeInfo[] attributeArray = classfile.getAttributes();
for (int i = 0; i < attributeArray.length; i++) {
    System.out.println(String.format("Attribute [%d]: %s", i, 
				attributeArray[i].getName()));
}

Here are some special notes for the code above.

Constant Pool: The constant pool array is from index 1to (constant_pool_count-1; and the CONSTANT_Long_infoand CONSTANT_Double_infowill take two indexes position while all other types will take only one position.

Super Class: The super class index will be zero only when the current class is java.lang.Object Otherwise, it should be an item in the constant pool.

Interfaces & Fields: One class may have no interfaces or fields, so we need to check the InterfaceCountand FieldCountvariable before getting the array for interface/field.

Methods: For a non-inner class, one class must have at least one method, which is the default instance constructor created by javac but for inner class, there can be no method. So we should check the MethodCountvariable is not zero.

Attributes: One class must have at least one attribute, the SourceFileattribute; we don't have to add similar logic for it.

By using some code just like the above, it is not hard work to write a visual UI control for a class file. And it will be very easy for us to write any application to analyse the meta data in the class.

Add the Parsed Class File to the Swing Control

To reduce the effort to write a Java Class Viewer, the jCFL provides a set of swing UI controls.

1. A Tree Control for the Class File Component Hierarchy

The class org.freeinternals.classfile.ui.JTreeClassFileis a subclass of JTree which accepts a ClassFile bject in the constructor. It will add all components of a class file into the tree control.

2. A Split Container for the Class File Interactive Binary Viewer

The class org.freeinternals.classfile.ui.JSplitPaneClassFileis a subclass of JSplitPane which is divided into two panels: the left panel is the JTreeClassFile while the right panel is a binary viewer for class file.

While we select each component in the tree, the corresponding bytes will be highlighted; this is the reason for the word "interactive".

// JavaClassViewer.src.zip - org.freeinternals.javaclassviewer.Main.open_ClassFile()
private JSplitPaneClassFile cfPane;

private void open_ClassFile(final File file) {
    this.cfPane = new JSplitPaneClassFile(Tool.readClassFile(file));
    this.add(this.cfPane, BorderLayout.CENTER);
    this.resizeForContent();
}

For example, after opening the class file File.class (java.io.File, when we select the node for the method getName() the corresponding bytes will be highlighted.

If we select the name_index descriptor_indexnode for this method, only the index section (for the value 119, 24) will be highlighted.

If we select the codenode in the Codeattribute, and open the Opcode tab, the opcodes of this method are extracted.

Java Class Viewer is not intended to be a decompiler, it only displays the raw code and some comments according to the context. You may refer to the JVM Spec for the meaning of the opcode. The extracted opcode is partly human readable.

3. A Tree Control for the ZipFile (jar, war, etc.)

The class org.freeinternals.classfile.ui.JTreeZipFileis a subclass of JTree which accepts a ZipFile n the constructor. It will build a tree for all entries in the zip file. The .jar/.war file is in fact a zip file.

// JavaClassViewer.src.zip - org.freeinternals.javaclassviewer.Main.open_JarFile()
// Only key logic is left here

private JTreeZipFile zftree;

private void open_JarFile(final File file) {
    this.zftree = new JTreeZipFile(new JarFile(file, false, JarFile.OPEN_READ));
    this.zftreeContainer = new JPanelForTree(this.zftree);
    this.add(this.zftreeContainer, BorderLayout.CENTER);
    this.resizeForContent();
}

Here is a screen shot of the ZipFile ree control.

BTW, if we double click an xxxxx.class node, a new window will be opened for the class file.

Build a Java Class Viewer

By using the swing controls provided by jCFL, it is very easy to write a class file viewer. All we need to do is to add menu/toolbar, and layout the controls onto a JFrame

You may need to refer to the source code at the top of this article for details.

Points of Interest

The class file viewer is only the starting point to understand Java. We may use it as a tool to study the new feathers provided by new versions of Java; as a tool to study AOP, to study some framework/platform code, etc.

Sun (may be Oracle later) is trying to keep the compatibility of the class file format. For example, annotation in fact is adding some attribute to the class file, and uses reflection mechanism to read and analyse annotation; thus annotation's performance is poor, but this is how we can keep compatibility.

History

  • Apr 26, 2009: Created article
  • Apr 27-29, 2009: Updated article
  • Aug 17, 2010: Revised links in this article
  • Dec 08, 2010: Revised links in this article
推荐.NET配套的通用数据层ORM框架:CYQ.Data 通用数据层框架
新浪微博粉丝精灵,刷粉丝、刷评论、刷转发、企业商家微博营销必备工具"