-
Notifications
You must be signed in to change notification settings - Fork 2
Ease of use
Brian S. O'Neill edited this page Aug 28, 2024
·
16 revisions
The primary goal of Cojen/Maker is to make code generation easy, but without compromising any JVM functionality.
The Apache Commons BCEL project contains a HelloWorld example which generates the following Java code:
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) {
System.out.println(e);
return;
}
System.out.println("Hello, " + name);
}
}
Here's how to use Cojen/Maker to generate and directly run the example:
public static void main(String[] argv) throws Exception {
// Begin making the class with an automatic name, since it will run directly.
ClassMaker cm = ClassMaker.begin().public_();
// Begin making the 'main' method.
MethodMaker mm = cm.addMethod(null, "main", String[].class).public_().static_();
// Quick reference to the System class, for input and output.
var sys = mm.var(System.class);
// Access System.in, wrap it in a BufferedReader, and assign to the 'in' variable.
var in = mm.new_(BufferedReader.class, mm.new_(InputStreamReader.class, sys.field("in")));
// Create local variable 'name', initially set to null.
var name = mm.var(String.class).set(null);
// Label the start of the exception handler.
Label tryStart = mm.label().here();
// Print the initial message and read the response.
sys.field("out").invoke("print", "Please enter your name> ");
name.set(in.invoke("readLine"));
// Make the exception handler.
mm.catch_(tryStart, IOException.class, ex -> {
sys.field("out").invoke("println", ex);
mm.return_();
});
// Print the hello message.
sys.field("out").invoke("println", mm.concat("Hello, ", name));
// Add the default public constructor, which will be auto-finished as empty.
cm.addConstructor().public_();
// Finish making the class and invoke the 'main' method.
cm.finish().getMethod("main", String[].class).invoke(null, (Object) argv);
}
For comparison, what follows is the BCEL code which generates the example and writes it to a file. It should be apparent that the code is much larger because it requires explicit JVM instructions and operand stack manipulation. Cojen/Maker is easier because this complexity is completely hidden. In fact, no knowledge of the JVM specification is required at all.
With BCEL:
public static void main(final String[] argv) {
final ClassGen cg = new ClassGen("HelloWorld", "java.lang.Object",
"<generated>", Const.ACC_PUBLIC |
Const.ACC_SUPER,
null);
final ConstantPoolGen cp = cg.getConstantPool(); // cg creates constant pool
final InstructionList il = new InstructionList();
final MethodGen mg = new MethodGen(Const.ACC_STATIC |
Const.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);
final InstructionFactory factory = new InstructionFactory(cg);
final ObjectType i_stream = new ObjectType("java.io.InputStream");
final ObjectType p_stream = new ObjectType("java.io.PrintStream");
// Create BufferedReader object and store it in local variable `in'.
il.append(factory.createNew("java.io.BufferedReader"));
il.append(InstructionConst.DUP); // Use predefined constant, i.e. flyweight
il.append(factory.createNew("java.io.InputStreamReader"));
il.append(InstructionConst.DUP);
il.append(factory.createFieldAccess("java.lang.System", "in", i_stream, Const.GETSTATIC));
// Call constructors, i.e. BufferedReader(InputStreamReader())
il.append(factory.createInvoke("java.io.InputStreamReader", "<init>",
Type.VOID, new Type[]{i_stream},
Const.INVOKESPECIAL));
il.append(factory.createInvoke("java.io.BufferedReader", "<init>", Type.VOID,
new Type[]{new ObjectType("java.io.Reader")},
Const.INVOKESPECIAL));
// Create local variable `in'
LocalVariableGen lg = mg.addLocalVariable("in", new ObjectType("java.io.BufferedReader"), null, null);
final int in = lg.getIndex();
lg.setStart(il.append(new ASTORE(in))); // `i' valid from here
// Create local variable `name'
lg = mg.addLocalVariable("name", Type.STRING, null, null);
final int name = lg.getIndex();
il.append(InstructionConst.ACONST_NULL);
lg.setStart(il.append(new ASTORE(name))); // `name' valid from here
// try { ...
final InstructionHandle try_start =
il.append(factory.createFieldAccess("java.lang.System", "out", p_stream, Const.GETSTATIC));
il.append(new PUSH(cp, "Please enter your name> "));
il.append(factory.createInvoke("java.io.PrintStream", "print", Type.VOID,
new Type[]{Type.STRING}, Const.INVOKEVIRTUAL));
il.append(new ALOAD(in));
il.append(factory.createInvoke("java.io.BufferedReader", "readLine",
Type.STRING, Type.NO_ARGS, Const.INVOKEVIRTUAL));
il.append(new ASTORE(name));
// Upon normal execution we jump behind exception handler, the target address is not known yet.
final GOTO g = new GOTO(null);
final InstructionHandle try_end = il.append(g);
/* } catch() { ... }
* Add exception handler: print exception and return from method
*/
final InstructionHandle handler =
il.append(factory.createFieldAccess("java.lang.System", "out", p_stream, Const.GETSTATIC));
// Little trick in order not to save exception object temporarily
il.append(InstructionConst.SWAP);
il.append(factory.createInvoke("java.io.PrintStream", "println", Type.VOID, new Type[]{Type.OBJECT}, Const.INVOKEVIRTUAL));
il.append(InstructionConst.RETURN);
mg.addExceptionHandler(try_start, try_end, handler, new ObjectType("java.io.IOException"));
// Normal code continues, now we can set the branch target of the GOTO that jumps over the handler code.
final InstructionHandle ih =
il.append(factory.createFieldAccess("java.lang.System", "out", p_stream, Const.GETSTATIC));
g.setTarget(ih);
// String concatenation compiles to StringBuffer operations.
il.append(factory.createNew(Type.STRINGBUFFER));
il.append(InstructionConst.DUP);
il.append(new PUSH(cp, "Hello, "));
il.append(factory.createInvoke("java.lang.StringBuffer", "<init>",
Type.VOID, new Type[]{Type.STRING},
Const.INVOKESPECIAL));
il.append(new ALOAD(name));
// Concatenate strings using a StringBuffer and print them.
il.append(factory.createInvoke("java.lang.StringBuffer", "append",
Type.STRINGBUFFER, new Type[]{Type.STRING},
Const.INVOKEVIRTUAL));
il.append(factory.createInvoke("java.lang.StringBuffer", "toString",
Type.STRING, Type.NO_ARGS,
Const.INVOKEVIRTUAL));
il.append(factory.createInvoke("java.io.PrintStream", "println",
Type.VOID, new Type[]{Type.STRING},
Const.INVOKEVIRTUAL));
il.append(InstructionConst.RETURN);
mg.setMaxStack(5); // Needed stack size
cg.addMethod(mg.getMethod());
il.dispose(); // Reuse instruction handles
// Add public <init> method, i.e. empty constructor
cg.addEmptyConstructor(Const.ACC_PUBLIC);
// Get JavaClass object and dump it to file.
try {
cg.getJavaClass().dump("HelloWorld.class");
} catch (final IOException e) {
System.err.println(e);
}
}
With ASM:
public static void main(String[] argv) throws Exception {
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
cw.visit(V12, ACC_PUBLIC | ACC_SUPER, "maker/HelloWorld", null, "java/lang/Object", null);
// Add the default constructor.
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
mv.visitInsn(RETURN);
mv.visitMaxs(0, 0);
// Add the main method.
mv = cw.visitMethod(ACC_PUBLIC | ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
Label tryStart = new Label();
Label tryEnd = new Label();
Label tryHandler = new Label();
mv.visitTryCatchBlock(tryStart, tryEnd, tryHandler, "java/io/IOException");
mv.visitTypeInsn(NEW, "java/io/BufferedReader");
mv.visitInsn(DUP);
mv.visitTypeInsn(NEW, "java/io/InputStreamReader");
mv.visitInsn(DUP);
mv.visitFieldInsn(GETSTATIC, "java/lang/System", "in", "Ljava/io/InputStream;");
mv.visitMethodInsn(INVOKESPECIAL, "java/io/InputStreamReader", "<init>", "(Ljava/io/InputStream;)V", false);
mv.visitMethodInsn(INVOKESPECIAL, "java/io/BufferedReader", "<init>", "(Ljava/io/Reader;)V", false);
mv.visitVarInsn(ASTORE, 1);
mv.visitInsn(ACONST_NULL);
mv.visitVarInsn(ASTORE, 2);
mv.visitLabel(tryStart);
mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitLdcInsn("Please enter your name> ");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "print", "(Ljava/lang/String;)V", false);
mv.visitVarInsn(ALOAD, 1);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/BufferedReader", "readLine", "()Ljava/lang/String;", false);
mv.visitVarInsn(ASTORE, 2);
mv.visitLabel(tryEnd);
Label label3 = new Label();
mv.visitJumpInsn(GOTO, label3);
mv.visitLabel(tryHandler);
mv.visitVarInsn(ASTORE, 3);
mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitVarInsn(ALOAD, 3);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/Object;)V", false);
mv.visitInsn(RETURN);
mv.visitLabel(label3);
mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitVarInsn(ALOAD, 2);
mv.visitInvokeDynamicInsn("makeConcatWithConstants", "(Ljava/lang/String;)Ljava/lang/String;",
new Handle(H_INVOKESTATIC, "java/lang/invoke/StringConcatFactory",
"makeConcatWithConstants",
"(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;",
false),
new Object[]{"Hello, \u0001"});
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
mv.visitInsn(RETURN);
mv.visitMaxs(0, 0);
byte[] bytes = cw.toByteArray();
// Load the class and invoke the main method.
Class<?> clazz = MethodHandles.lookup().defineClass(bytes);
clazz.getMethod("main", String[].class).invoke(null, (Object) argv);
}
Also see JEP-484.