From a964b0e136df177f783ca27f2a1dc1696d4ca14a Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Mon, 2 May 2022 21:33:44 -0400 Subject: [PATCH] Declare BCBClass to build classes composed of methods Signed-off-by: Brad Wood --- bcb/src/main/java/com/ibm/bcb/BCBClass.java | 77 +++++++++++++++++++ bcb/src/main/java/com/ibm/bcb/BCBMethod.java | 52 +++---------- .../test/java/com/ibm/bcb/TestBCBClass.java | 35 +++++++++ 3 files changed, 123 insertions(+), 41 deletions(-) create mode 100644 bcb/src/main/java/com/ibm/bcb/BCBClass.java create mode 100644 bcb/src/test/java/com/ibm/bcb/TestBCBClass.java diff --git a/bcb/src/main/java/com/ibm/bcb/BCBClass.java b/bcb/src/main/java/com/ibm/bcb/BCBClass.java new file mode 100644 index 0000000..554b57e --- /dev/null +++ b/bcb/src/main/java/com/ibm/bcb/BCBClass.java @@ -0,0 +1,77 @@ +package com.ibm.bcb; + +import com.ibm.bcb.tree.Block; +import com.ibm.bcb.tree.MethodContext; +import lombok.*; +import org.objectweb.asm.*; + +import java.util.List; + +import static org.objectweb.asm.Opcodes.*; + +@Data +@Builder +@EqualsAndHashCode +public class BCBClass { + + @Builder.Default + private final String name = "AnonymousClass"; + @Builder.Default + private final String parent = "java/lang/Object"; + @Singular + private final List methods; + + public byte[] toByteArray() { + final ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); + cw.visit(V1_8, ACC_PUBLIC + ACC_SUPER, name, null, parent, null); + + // Todo; Add default constructor if required + // Modify existing default constructor to initial default fields + + // Todo; Add class initializer if required + // Modify existing initializer to initial static fields + + for (final BCBMethod method : methods) { + writeToClass(method, cw); + } + + cw.visitEnd(); + return cw.toByteArray(); + } + + public void writeToClass(final BCBMethod method, final ClassVisitor cv) { + final Label methodEnd = new Label(); + final MethodVisitor mv = cv.visitMethod(method.getModifiers(), method.getName(), method.getMethodType().getDescriptor(), null, null); + final Type clazzType = Type.getType("L" + name + ";"); + final MethodContext ctx = new MethodContext(clazzType, method.getName(), ACC_PUBLIC + ACC_STATIC, method.getMethodType(), method.getArgNames(), methodEnd); + + Block.of(method.getStatements()).evaluate(ctx, mv); + + mv.visitLabel(methodEnd); + + for (final String argName : method.getArgNames()) { + mv.visitParameter(argName, 0); + } + + mv.visitMaxs(0, 0); + mv.visitEnd(); + } + + @SneakyThrows + public Class loadClass() { + final byte[] bytes = toByteArray(); + + final ClassLoader loader = new ClassLoader() { + @Override + protected Class findClass(final String name) { + if (name.equals(name.replace("/", "."))) { + return defineClass(name, bytes, 0, bytes.length); + } + + return null; + } + }; + + return loader.loadClass(name.replace("/", ".")); + } +} diff --git a/bcb/src/main/java/com/ibm/bcb/BCBMethod.java b/bcb/src/main/java/com/ibm/bcb/BCBMethod.java index 88a7545..bc3729c 100644 --- a/bcb/src/main/java/com/ibm/bcb/BCBMethod.java +++ b/bcb/src/main/java/com/ibm/bcb/BCBMethod.java @@ -1,12 +1,8 @@ package com.ibm.bcb; import com.ibm.bcb.tree.Block; -import com.ibm.bcb.tree.MethodContext; import com.ibm.bcb.tree.Statement; import lombok.*; -import org.objectweb.asm.ClassWriter; -import org.objectweb.asm.Label; -import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Type; import java.io.File; @@ -17,7 +13,8 @@ import java.lang.reflect.Method; import java.util.List; -import static org.objectweb.asm.Opcodes.*; +import static org.objectweb.asm.Opcodes.ACC_PUBLIC; +import static org.objectweb.asm.Opcodes.ACC_STATIC; @Builder @AllArgsConstructor @@ -87,46 +84,19 @@ public Type getMethodType() { return Type.getMethodType(returnType, argTypes.toArray(new Type[0])); } - public byte[] toByteArray() { - final ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); - cw.visit(V1_8, ACC_PUBLIC + ACC_SUPER, declaringClass, null, "java/lang/Object", null); - - final Label methodEnd = new Label(); - final MethodVisitor mv = cw.visitMethod(modifiers, name, getMethodType().getDescriptor(), null, null); - final Type clazzType = Type.getType("L" + declaringClass + ";"); - final MethodContext ctx = new MethodContext(clazzType, name, ACC_PUBLIC + ACC_STATIC, getMethodType(), argNames, methodEnd); - - Block.of(statements).evaluate(ctx, mv); - - mv.visitLabel(methodEnd); - - for (final String argName : argNames) { - mv.visitParameter(argName, 0); - } - - mv.visitMaxs(0, 0); - mv.visitEnd(); - - cw.visitEnd(); - return cw.toByteArray(); + private byte[] toByteArray() { + return BCBClass.builder() + .name(declaringClass) + .method(this) + .build().toByteArray(); } @SneakyThrows private Class loadClass() { - final byte[] bytes = toByteArray(); - - final ClassLoader loader = new ClassLoader() { - @Override - protected Class findClass(final String name) { - if (name.equals(declaringClass.replace("/", "."))) { - return defineClass(name, bytes, 0, bytes.length); - } - - return null; - } - }; - - return loader.loadClass(declaringClass.replace("/", ".")); + return BCBClass.builder() + .name(declaringClass) + .method(this) + .build().loadClass(); } @SneakyThrows diff --git a/bcb/src/test/java/com/ibm/bcb/TestBCBClass.java b/bcb/src/test/java/com/ibm/bcb/TestBCBClass.java new file mode 100644 index 0000000..c6c1f3d --- /dev/null +++ b/bcb/src/test/java/com/ibm/bcb/TestBCBClass.java @@ -0,0 +1,35 @@ +package com.ibm.bcb; + +import lombok.SneakyThrows; +import org.junit.Assert; +import org.junit.Test; +import org.objectweb.asm.Type; + +import static com.ibm.bcb.BCB.constant; +import static com.ibm.bcb.BCB.ret; + +public class TestBCBClass { + + @Test + @SneakyThrows + public void testInvokeMR() { + final Class clazz = BCBClass.builder() + .method(BCBMethod.builder() + .name("test") + .returnType(Type.INT_TYPE) + .body( + ret(constant(100)) + ).build()) + .method(BCBMethod.builder() + .name("test2") + .returnType(Type.INT_TYPE) + .body( + ret(constant(200)) + ).build()) + .build() + .loadClass(); + + Assert.assertEquals(100, (int) clazz.getMethod("test").invoke(null)); + Assert.assertEquals(200, (int) clazz.getMethod("test2").invoke(null)); + } +}