HelloWorldBuilder
The following program reads a name from the standard input and
prints a friendly "Hello". Since the readLine()
method may
throw an IOException
it is enclosed by a try-catch
clause.
import java.io.*;
public class HelloWorld {
public static void main(String[] argv) {
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
String name = null;
try {
System.out.print("Please enter your name> ");
name = in.readLine();
} catch (IOException e) {
return;
}
System.out.println("Hello, " + name);
}
}
We will sketch here how the above Java class can be created from the
scratch using the BCEL API. For
ease of reading we will use textual signatures and not create them
dynamically. For example, the signature
"(Ljava/lang/String;)Ljava/lang/StringBuffer;"
actually be created with
Type.getMethodSignature(Type.STRINGBUFFER, new Type[] { Type.STRING });
Initialization:
First we create an empty class and an instruction list:
ClassGen cg = new ClassGen("HelloWorld", "java.lang.Object",
"<generated>", ACC_PUBLIC | ACC_SUPER, null);
ConstantPoolGen cp = cg.getConstantPool(); // cg creates constant pool
InstructionList il = new InstructionList();
We then create the main method, supplying the method's name and the
symbolic type signature encoded with Type
objects.
MethodGen mg = new MethodGen(ACC_STATIC | ACC_PUBLIC, // access flags
Type.VOID, // return type
new Type[] { // argument types
new ArrayType(Type.STRING, 1) },
new String[] { "argv" }, // arg names
"main", "HelloWorld", // method, class
il, cp);
InstructionFactory factory = new InstructionFactory(cg);
We now define some often used types:
ObjectType i_stream = new ObjectType("java.io.InputStream");
ObjectType p_stream = new ObjectType("java.io.PrintStream");
Create variables in
and name
: We call
the constructors, i.e., execute
BufferedReader(InputStreamReader(System.in))
. The reference
to the BufferedReader
object stays on top of the stack and
is stored in the newly allocated in
variable.
il.append(factory.createNew("java.io.BufferedReader"));
il.append(InstructionConstants.DUP); // Use predefined constant
il.append(factory.createNew("java.io.InputStreamReader"));
il.append(InstructionConstants.DUP);
il.append(factory.createFieldAccess("java.lang.System", "in", i_stream, Constants.GETSTATIC));
il.append(factory.createInvoke("java.io.InputStreamReader", "<init>",
Type.VOID, new Type[] { i_stream },
Constants.INVOKESPECIAL));
il.append(factory.createInvoke("java.io.BufferedReader", "<init>", Type.VOID,
new Type[] {new ObjectType("java.io.Reader")},
Constants.INVOKESPECIAL));
LocalVariableGen lg = mg.addLocalVariable("in",
new ObjectType("java.io.BufferedReader"), null, null);
int in = lg.getIndex();
lg.setStart(il.append(new ASTORE(in))); // "i" valid from here
Create local variable name
and initialize it to null
.
lg = mg.addLocalVariable("name", Type.STRING, null, null);
int name = lg.getIndex();
il.append(InstructionConstants.ACONST_NULL);
lg.setStart(il.append(new ASTORE(name))); // "name" valid from here
Create try-catch block: We remember the start of the
block, read a line from the standard input and store it into the
variable name
.
InstructionHandle try_start =
il.append(factory.createFieldAccess("java.lang.System", "out", p_stream, Constants.GETSTATIC));
il.append(new PUSH(cp, "Please enter your name> "));
il.append(factory.createInvoke("java.io.PrintStream", "print", Type.VOID,
new Type[] { Type.STRING },
Constants.INVOKEVIRTUAL));
il.append(new ALOAD(in));
il.append(factory.createInvoke("java.io.BufferedReader", "readLine",
Type.STRING, Type.NO_ARGS,
Constants.INVOKEVIRTUAL));
il.append(new ASTORE(name));
Upon normal execution we jump behind exception handler, the target
address is not known yet.
GOTO g = new GOTO(null);
InstructionHandle try_end = il.append(g);
We add the exception handler which simply returns from the method.
InstructionHandle handler = il.append(InstructionConstants.RETURN);
mg.addExceptionHandler(try_start, try_end, handler, "java.io.IOException");
"Normal" code continues, now we can set the branch target of the GOTO
.
InstructionHandle ih =
il.append(factory.createFieldAccess("java.lang.System", "out", p_stream, Constants.GETSTATIC));
g.setTarget(ih);
Printing "Hello":
String concatenation compiles to StringBuffer
operations.
il.append(factory.createNew(Type.STRINGBUFFER));
il.append(InstructionConstants.DUP);
il.append(new PUSH(cp, "Hello, "));
il.append(factory.createInvoke("java.lang.StringBuffer", "<init>",
Type.VOID, new Type[] { Type.STRING },
Constants.INVOKESPECIAL));
il.append(new ALOAD(name));
il.append(factory.createInvoke("java.lang.StringBuffer", "append",
Type.STRINGBUFFER, new Type[] { Type.STRING },
Constants.INVOKEVIRTUAL));
il.append(factory.createInvoke("java.lang.StringBuffer", "toString",
Type.STRING, Type.NO_ARGS,
Constants.INVOKEVIRTUAL));
il.append(factory.createInvoke("java.io.PrintStream", "println",
Type.VOID, new Type[] { Type.STRING },
Constants.INVOKEVIRTUAL));
il.append(InstructionConstants.RETURN);
Finalization: Finally, we have to set the stack size,
which normally would have to be computed on the fly and add a
default constructor method to the class, which is empty in this
case.
mg.setMaxStack();
cg.addMethod(mg.getMethod());
il.dispose(); // Allow instruction handles to be reused
cg.addEmptyConstructor(ACC_PUBLIC);
Last but not least we dump the JavaClass
object to a file.
try {
cg.getJavaClass().dump("HelloWorld.class");
} catch (IOException e) {
System.err.println(e);
}