Tech Ads

Back to Article List

Originally published June 2004 [ Publisher Link ]

BCEL("Byte Code Engineering Library") for Java


Java, being a cross-platform language, achieves its interoperability through an abstraction layer in the way of a binary format named byte code. While the actual process of building the intermediate layer is the work of a compiler, and the execution itself is done by the Java Virtual Machine through .class files, there is often a need to inspect and even modify the compiled byte code for purposes of optimization or run-time analysis. The Byte Code Engineering Library (BCEL) provides a comprehensive API for these tasks.

One of the main benefits of using a tool like BCEL for modifying or inspecting Java byte code is that cryptic knowledge like that of .class file structures or binary format is not a necessity, given that it is these low-level concepts that BCEL tries to abstract away from a developer through its high-level API. BCEL can inspect and generate a complete list of the methods, fields, inheritance hierarchy, and compiler version, among other information on any given byte code. BCEL logo

As an example, let's analyze the contents of a Java class. You might want to do this when both source code and documentation are lost for a compiled class -- an unlikely, albeit possible, situation. Here's the source code for our to-be-inspected compiled Java Bean:


public class OS implements java.io.Serializable { 
    
    protected String name;

    public OS(String name) { 
	this.name = name;
    } 
    
    public String getName() { 
	return name;
    }
    
    public void setName() { 
	this.name = name;
        

        
    }
}


After we compile this snippet, we can feed it -- or any other class file -- through the following BCEL program, which should be invoked from the command line as follows: java -cp InspectClass <Class to Inspect without extension>.


import org.apache.bcel.*;
import org.apache.bcel.classfile.*;

public class InspectClass {

    protected JavaClass specimen;

    public InspectClass(String specimen){
	this.specimen = Repository.lookupClass(specimen);
		
    }

    public static void main(String args[]){
	if(args.length != 1) { 
	    System.out.println("Usage : java InspectClass 
               <Class_to_Inspect_without_extension>");
	    System.exit(-1);
	}
	
	try { 
		
	  InspectClass inspector = new InspectClass(args[0]);
	    
           if (inspector.specimen  == null)  { 
		// Java Class not found or unreadable 
		throw new Exception(); 
	    } else { 
		//Print general information on class file
		System.out.println(inspector.specimen);
	    }
	    
	} catch (Exception ex) { 
	    System.err.println("Are you sure you are feeding 
                                     in a .class file ?");
	    
	}
    }
    
}

After importing two BCEL packages and declaring our root class, we define a field (specimen) in the form of a JavaClass used to hold our parsed byte code. We later define a constructor employed to assign the class file given at run time to the aforementioned field. Notice that the actual reading of the byte code file is achieved through the lookupClass method in the Repository BCEL class.

Our main method is used to read the input provided at run time, and searches for a byte code file in every directory defined in the CLASSPATH environment variable. This search behaviour is the default for the Repository class. Finally, we define our root class instance through our constructor, and print out a complete list of methods, fields, and other details for our JavaClass field.

It should be noted that the BCEL JavaClass structure has a series of more refined methods for outputting its contents on an individual basis, such as: getMethods() or getFields().

Using BCEL to modify compiled code

Now that we have inspected byte code files, we will illustrate how to modify compiled code with BCEL's other packages. You might have noticed that our initial Java Bean lacked its default constructor, forcing us to instantiate the bean with its one field constructor. Through BCEL we will add its default constructor so we can create a bean instance and later use its getter/setter methods for defining its field value.


import org.apache.bcel.*;
import org.apache.bcel.generic.*;
import org.apache.bcel.classfile.*;

public class AddingConstructor implements Constants {

    protected JavaClass oldImplementation;
    protected ClassGen newImplementation;

    public AddingConstructor(String oldImplementation){
      this.oldImplementation = 
               Repository.lookupClass(oldImplementation);
      this.newImplementation = 
               new ClassGen(this.oldImplementation);
    }
    
    public static void main(String args[]){
      if(args.length != 1) { 
       System.out.println("Usage : java InspectClass
       <Class_to_Inspect_without_extension>");
       System.exit(-1);
	}
	
     try { 	
	    
     AddingConstructor modifiedByteCode =
               new AddingConstructor(args[0]);
	    
       if (modifiedByteCode.oldImplementation  == null)  { 
	 // Java Class not found or unreadable 
	throw new Exception(); 
       }  else { 

modifiedByteCode.newImplementation.
                   addEmptyConstructor(ACC_PUBLIC);
modifiedByteCode.newImplementation.
                   getJavaClass().dump("NewOS.class");
		
	}
	    
     } catch (Exception ex) { 

System.err.println("Are you sure you are feeding in a .class file ?");

	}
    }
    
}

This newly created BCEL program defines two fields: JavaClass, which will hold the original byte code, and ClassGen, for placing the newly generated byte code.

The constructor and first part of our main method are very similar in nature to our initial program, reading a class file with the lookupClass in the Repository class. However, in this case, we assign this same byte code to our ClassGen field. Our main method then invokes the addEmptyConstructor to our newImplementation field, which, as its name implies, is used to place an empty constructor into the byte code.

Finally, we use the dump method available through our JavaClass instance to place the newly modified byte code in a file named NewOS.class, which, if inspected with our initial BCEL program, will illustrate the new constructor.

As with any other comprehensive Java API, BCEL's depth cannot be illustrated in a few examples. BCEL's power is evident in processes like reverse-engineering and code optimization, due to its reach within a Java program's internal structure. Current mainstream projects that make use of BCEL include JBoss, AspectJ ("Aspect-oriented Java") and Xalan, among others, making it a worthwhile API to know.


Originally published June 2004 [ Publisher Link ]

Back to Article List