-
Notifications
You must be signed in to change notification settings - Fork 2
Ease of use
Brian S. O'Neill edited this page Dec 20, 2024
·
22 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.
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);
}
}
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);
}
With the Class-File API
public static void main(String[] args) throws Exception {
byte[] bytes = ClassFile.of().build(ClassDesc.of("HelloWorld"), (ClassBuilder cb) -> {
cb.withFlags(ClassFile.ACC_PUBLIC);
// Make the 'main' method.
cb.withMethod
("main", MethodTypeDesc.of(ConstantDescs.CD_void,
ConstantDescs.CD_String.arrayType()),
ClassFile.ACC_PUBLIC | ClassFile.ACC_STATIC,
(MethodBuilder mb) -> mb.withCode((CodeBuilder b) -> {
// Access System.in, wrap it in a BufferedReader, and assign to variable 1 (in).
ClassDesc brDesc = ClassDesc.of("java.io.BufferedReader");
b.new_(brDesc);
b.dup();
ClassDesc isrDesc = ClassDesc.of("java.io.InputStreamReader");
b.new_(isrDesc);
b.dup();
ClassDesc sysDesc = ClassDesc.of("java.lang.System");
ClassDesc isDesc = ClassDesc.of("java.io.InputStream");
b.getstatic(sysDesc, "in", isDesc);
b.invokespecial(isrDesc, "<init>",
MethodTypeDesc.of(ConstantDescs.CD_void, isDesc));
b.invokespecial(brDesc, "<init>",
MethodTypeDesc.of(ConstantDescs.CD_void,
ClassDesc.of("java.io.Reader")));
b.astore(1);
// Create local variable 2 (name), initially set to null.
b.aconst_null();
b.astore(2);
// Print the initial message and read the response.
ClassDesc psDesc = ClassDesc.of("java.io.PrintStream");
ClassDesc strDesc = ClassDesc.of("java.lang.String");
b.trying((CodeBuilder.BlockCodeBuilder tb) -> {
tb.getstatic(sysDesc, "out", psDesc);
tb.loadConstant("Please enter your name> ");
tb.invokevirtual(psDesc, "print",
MethodTypeDesc.of(ConstantDescs.CD_void, strDesc));
tb.aload(1);
tb.invokevirtual(brDesc, "readLine", MethodTypeDesc.of(strDesc));
tb.astore(2);
}, (CodeBuilder.CatchBuilder cb2) ->
cb2.catching(ClassDesc.of("java.io.IOException"),
(CodeBuilder.BlockCodeBuilder cb3) -> {
cb3.astore(3);
cb3.getstatic(sysDesc, "out", psDesc);
cb3.aload(3);
cb3.invokevirtual
(psDesc, "println",
MethodTypeDesc.of(ConstantDescs.CD_void,
ConstantDescs.CD_Object));
cb3.return_();
})
);
// Print the hello message.
b.getstatic(sysDesc, "out", psDesc);
b.aload(2);
DirectMethodHandleDesc bootDesc = MethodHandleDesc.ofMethod
(DirectMethodHandleDesc.Kind.STATIC,
ClassDesc.of("java.lang.invoke.StringConcatFactory"),
"makeConcatWithConstants",
MethodTypeDesc.of
(ClassDesc.of("java.lang.invoke.CallSite"),
ClassDesc.of("java.lang.invoke.MethodHandles$Lookup"),
strDesc,
ClassDesc.of("java.lang.invoke.MethodType"),
strDesc,
ConstantDescs.CD_Object.arrayType()));
b.invokedynamic(DynamicCallSiteDesc.of(bootDesc, "makeConcatWithConstants",
MethodTypeDesc.of(strDesc, strDesc),
"Hello, \u0001"));
b.invokevirtual(psDesc, "println",
MethodTypeDesc.of(ConstantDescs.CD_void, strDesc));
b.return_();
}));
// Make the default public constructor.
cb.withMethod("<init>", MethodTypeDesc.of(ConstantDescs.CD_void), ClassFile.ACC_PUBLIC,
(MethodBuilder mb) -> mb.withCode((CodeBuilder b) -> {
b.aload(0);
b.invokespecial(ConstantDescs.CD_Object, "<init>",
MethodTypeDesc.of(ConstantDescs.CD_void));
b.return_();
}));
});
// Load the class and invoke the main method.
Class<?> clazz = MethodHandles.lookup().defineClass(bytes);
clazz.getMethod("main", String[].class).invoke(null, (Object) args);
}