Skip to content
This repository has been archived by the owner on Jul 22, 2024. It is now read-only.

Commit

Permalink
Declare BCBClass to build classes composed of methods
Browse files Browse the repository at this point in the history
Signed-off-by: Brad Wood <bradley.wood@ibm.com>
  • Loading branch information
BradleyWood committed May 3, 2022
1 parent 581e5f7 commit a964b0e
Show file tree
Hide file tree
Showing 3 changed files with 123 additions and 41 deletions.
77 changes: 77 additions & 0 deletions bcb/src/main/java/com/ibm/bcb/BCBClass.java
Original file line number Diff line number Diff line change
@@ -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<BCBMethod> 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("/", "."));
}
}
52 changes: 11 additions & 41 deletions bcb/src/main/java/com/ibm/bcb/BCBMethod.java
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
35 changes: 35 additions & 0 deletions bcb/src/test/java/com/ibm/bcb/TestBCBClass.java
Original file line number Diff line number Diff line change
@@ -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));
}
}

0 comments on commit a964b0e

Please sign in to comment.