Package org.biojava.utils.bytecode

A Java object-model for a Java Bytecode Macro-Assembler.

Description

The ByteCode package is able to generate Java Bytecode that can be used to define Java classes. It is not restricted to things that can be expressed as Java code (as in .java files), and is more flexible than basic Java bytecode.

This package supports generating classes, interfaces, methods, fields, and the like. It follows the macro-assembler pattern, where users can effectively extend the bytecode instruction set with their own instructions that at compile time expand to real bytecode instructions. Many useful pseudo-instructions are provided, such as IfExpression. This package also supports some advanced functionality such as templating. Templating supports both object and primitive types and types can be resolved at the level of complete methods or single expressions.

The ByteCode package has no support whatsoever for editing Java Bytecode. In particular, there is no support for search-and-replace or optimization.

The main classes for generating bytecode are CodeUtils, ByteCode, InstructionVector and GeneratedCodeClass. You will probably also use GeneratedClassLoader, as it does the actual work of building and loading classes.

It is probably a requirement of using this package effectively that you have memorised or have handy a copy of the Java VM specification. This is available from the Sun web site.

Class Reflection

The ByteCode package represents classes, methods and fields using its own APIs. These, for obvious reasons, closely resemble those provided by the core Java APIs. The three interfaces CodeClass, CodeMethod and CodeField represent these basic concepts.

Classes from the Java VM are reflected into this model using IntrospectedCodeClass. To get an instance, use the factory method IntrospectedCodeClass.forClass(Class) or IntrospectedCodeClass.forClass(String).

Class Generation

Classes that you generate yourself will be represented by instances of GeneratedCodeClass. Methods are created by calling GeneratedCodeClass.createMethod(java.lang.String, org.biojava.utils.bytecode.CodeClass, org.biojava.utils.bytecode.CodeClass[], java.lang.String[], int), which will return a GeneratedCodeMethod. The method can then be implemented by associating a CodeGenerator with the method on that particular class using GeneratedCodeClass.setCodeGenerator(org.biojava.utils.bytecode.CodeMethod, org.biojava.utils.bytecode.CodeGenerator).

Fields are generated by calling GeneratedCodeClass.createField(java.lang.String, org.biojava.utils.bytecode.CodeClass, int).

The classes themselves are instantiated into the VM using GeneratedClassLoader. This implements ClassLoader.defineClass(byte[], int, int) and over-rides the other necessary book-keeping methods. You may wish to sub-class or wrap this class in an application to generate the desired behavior.

An example

We are going to implement HelloWorld in bytecode. Here is the (annotated) code.

Firstly, lets define our application structure...

public class BCHelloWorld {
  public static void main(String[] args)
  throws Throwable {
    createHelloWorld().run();
  }

  public static Runnable createHelloWorld() {
    ...
  }
}
We will fill in the createHelloWorld method to actualy do the work of printing out hello world by implementing a Runnable with a run() method. Firstly, we need a load of types, methods and a field or two.
CodeClass cl_Object = IntrospectedCodeClass.forClass(Object.class);
CodeClass cl_String = IntrospectedCodeClass.forClass(String.class);
CodeClass cl_System = IntrospectedCodeClass.forClass(System.class);
CodeClass cl_PrintStream = IntrospectedCodeClass.forClass(PrintStream.class);
CodeClass cl_Void = IntrospectedCodeClass.forClass(Void.TYPE);
CodeClass cl_Runnable = IntrospectedCodeClass.forClass(Runnable.class);

CodeField f_System_out = cl_System.getFieldByName("out");

CodeMethod m_PrintStream_printLn = cl_PrintStream.getMethod(
    "printLn",
    new CodeClass[] { cl_String } );
CodeMethod m_Object_init = cl_Object.getConstructor(CodeUtils.EMPTY_LIST);
Now we are ready to create our code class. It will be called HelloWorldRunnable, inherit directly from Object, implement Runnable, and be a public class.
GeneratedCodeClass ourClass = new GeneratedCodeClass(
    "HelloWorldRunnable",
    cl_Object,
    new CodeClass[] { cl_Runnable },
    CodeUtils.ACC_PUBLIC | CodeUtils.ACC_SUPER);
This will need a constructor. Remember, constructors have the name >init<:, and you must be sure to call the super-constructor explicitly. Normaly the javac compiler does this for you.
GeneratedCodeMethod init = ourClass.createMethod(
    "<init>",
    cl_Void,
    CodeUtils.EMPTY_LIST,
    CodeUtils.ACC_PUBLIC);
InstructionVector initIV = new InstructionVector();
initIV.add(ByteCode.make_aload(init.getThis()));
initIV.add(ByteCode.make_invokespecial(m_Object_init));
initIV.add(ByteCode.make_return());
ourClass.setCodeGenerator(init, initIV);
To be a Runnable implementation, we must also provide a run() method. This will take no arguments, return void and be publically accessible. Also, the body of this method will print out "Hello World" and then we can all feel pleased with ourselves.
GeneratedCodeMethod run = ourClass.createMethod(
    "run",
    cl_Void,
    CodeUtils.EMPTY_LIST,
    CodeUtils.ACC_PUBLIC);
InstructionVector runIV = new InstructionVector();
runIV.add(ByteCode.make_getstatic(f_System_out));
runIV.add(ByteCode.make_sconst("Hello World");
runIV.add(ByteCode.make_invokevirtual(m_PrintStream_printLn));
runIV.add(ByteCode.make_return());
ourClass.setCodeGenerator(run, runIV);
Now we want to load in a class with this deffinition and instantiate it.
GeneratedClassLoader gcl = new GeneratedClassLoader(class.getClassLoader());
Class newClass = gcl.defineClass(ourClass);
return (Runnable) newClass.newInstance();
And there you are. With any luck, if you type all of this in, and the fates smile on you, and biojava.jar is arround for linking against, you should have generated your very own unuque HelloWorld class.

Good luck.