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.
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.
This class implements a simple peephole optimizer that removes any NOP
instructions from the given class.
import java.io.*;
import java.util.Iterator;
import org.apache.bcel.classfile.*;
import org.apache.bcel.generic.*;
import org.apache.bcel.Repository;
import org.apache.bcel.util.InstructionFinder;
public class Peephole {
public static void main(String[] argv) {
try {
// Load the class from CLASSPATH.
JavaClass clazz = Repository.lookupClass(argv[0]);
Method[] methods = clazz.getMethods();
ConstantPoolGen cp = new ConstantPoolGen(clazz.getConstantPool());
for (int i = 0; i < methods.length; i++) {
if (!(methods[i].isAbstract() || methods[i].isNative())) {
MethodGen mg = new MethodGen(methods[i], clazz.getClassName(), cp);
Method stripped = removeNOPs(mg);
if (stripped != null) // Any NOPs stripped?
methods[i] = stripped; // Overwrite with stripped method
}
}
// Dump the class to "class name"_.class
clazz.setConstantPool(cp.getFinalConstantPool());
clazz.dump(clazz.getClassName() + "_.class");
} catch (Exception e) {
e.printStackTrace();
}
}
private static Method removeNOPs(MethodGen mg) {
InstructionList il = mg.getInstructionList();
InstructionFinder f = new InstructionFinder(il);
String pat = "NOP+"; // Find at least one NOP
InstructionHandle next = null;
int count = 0;
for (Iterator iter = f.search(pat); iter.hasNext();) {
InstructionHandle[] match = (InstructionHandle[]) iter.next();
InstructionHandle first = match[0];
InstructionHandle last = match[match.length - 1];
// Some nasty Java compilers may add NOP at end of method.
if ((next = last.getNext()) == null) {
break;
}
count += match.length;
/**
* Delete NOPs and redirect any references to them to the following (non-nop) instruction.
*/
try {
il.delete(first, last);
} catch (TargetLostException e) {
for (InstructionHandle target : e.getTargets()) {
for (InstructionTargeter targeter = target.getTargeters()) {
targeter.updateTarget(target, next);
}
}
}
}
Method m = null;
if (count > 0) {
System.out.println("Removed " + count + " NOP instructions from method " + mg.getName());
m = mg.getMethod();
}
il.dispose(); // Reuse instruction handles
return m;
}
}
If you want to learn how certain things are generated using BCEL you
can do the following: Write your program with the needed features in
Java and compile it as usual. Then use BCELifier to create
a class that creates that very input class using BCEL.
(Think about this sentence for a while, or just try it ...)
java org.apache.bcel.verifier.Verifier fully.qualified.class.Name
lets JustIce work standalone.
If you get a "java.lang.OutOfMemoryError", you should increase the
maximum Java heap space. A command like
java -Xmx1887436800 org.apache.bcel.verifier.Verifier f.q.c.Name
will usually resolve the problem. The value above is suitable for
big server machines; if your machine starts swapping to disk, try
to lower the value.
Running a graphics based verifier
If you prefer a graphical application, you should use a command like
java org.apache.bcel.verifier.GraphicalVerifier
to launch one. Again, you may have to resolve a memory issue depending
on the classes to verify.