End-to-end compilation of a trivial class.

This commit is contained in:
Sander Hautvast 2020-11-16 17:24:28 +01:00
parent ab4662f86e
commit e59d214a10
33 changed files with 766 additions and 168 deletions

View file

@ -1,14 +1,12 @@
package nl.sander.beejava;
import nl.sander.beejava.constantpool.ConstantPool;
import nl.sander.beejava.constantpool.entry.ConstantPoolEntry;
import nl.sander.beejava.flags.AccessFlags;
import nl.sander.beejava.util.ByteBuf;
public class BytecodeGenerator {
private final CompiledClass compiledClass; // maybe not a member?
private final ConstantPoolCreator constantPoolCreator = new ConstantPoolCreator();
public BytecodeGenerator(CompiledClass compiledClass) {
this.compiledClass = compiledClass;
@ -25,10 +23,8 @@ public class BytecodeGenerator {
buf.addU16(compiledClass.getSource().getClassFileVersion().getMinor());
buf.addU16(compiledClass.getSource().getClassFileVersion().getMajor());
ConstantPool constantPool = constantPoolCreator.createConstantPool(compiledClass.getConstantTree());
buf.addU16(constantPool.getLength());
constantPool.addTo(buf);
buf.addU16(compiledClass.getConstantPool().getLength());
compiledClass.getConstantPool().addTo(buf);
buf.addU16(AccessFlags.combine(compiledClass.getSource().getAccessFlags()));
buf.addU16(compiledClass.geThisIndex());
buf.addU16(compiledClass.getSuperIndex());
@ -36,28 +32,10 @@ public class BytecodeGenerator {
compiledClass.getInterfaces().forEach(interfase -> buf.addU16(interfase.getIndex()));
buf.addU16(compiledClass.getFields().size());
compiledClass.getFields().forEach(fieldInfo -> fieldInfo.addBytes(buf));
int x = 1;
for (ConstantPoolEntry e : constantPool) {
System.out.println((x++) + ":" + e);
}
printBytes(buf);
buf.addU16(compiledClass.getMethods().size());
compiledClass.getMethods().forEach(methodInfo -> methodInfo.addBytes(buf));
buf.addU16(0); //attributes count
return buf.toBytes();
}
//TODO remove
private void printBytes(ByteBuf buf) {
byte[] bytes = buf.toBytes();
int count = 0;
for (byte b : bytes) {
System.out.print(String.format("%2s", Integer.toHexString(b & 0xFF)).replace(' ', '0') + (count % 2 == 0 ? "" : " "));
if (++count > 15) {
count = 0;
System.out.println();
}
}
}
}

View file

@ -1,9 +1,15 @@
package nl.sander.beejava;
import nl.sander.beejava.api.BeeParameter;
import nl.sander.beejava.api.BeeSource;
import nl.sander.beejava.api.CodeLine;
import nl.sander.beejava.flags.MethodAccessFlags;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/**
* parent of a constructor or a method.
@ -11,8 +17,41 @@ import java.util.List;
public abstract class CodeContainer {
protected final List<CodeLine> code = new LinkedList<>();
protected final Set<BeeParameter> formalParameters = new HashSet<>();
protected final Set<MethodAccessFlags> accessFlags = new HashSet<>();
private BeeSource owner;
public List<CodeLine> getCode() {
return code;
}
public String getSignature() {
return getParametersSignature() + TypeMapper.map(getReturnType());
}
public abstract String getName();
public abstract Class<?> getReturnType();
private String getParametersSignature() {
return formalParameters.stream()
.map(BeeParameter::getType)
.map(TypeMapper::map)
.collect(Collectors.joining(",", "(", ")"));
}
public Set<MethodAccessFlags> getAccessFlags() {
return accessFlags;
}
public BeeSource getOwner() {
return owner;
}
public void setOwner(BeeSource beeSource) {
if (owner != null) {
throw new IllegalStateException("Owner set twice. Sue the developer!");
}
this.owner = beeSource;
}
}

View file

@ -2,20 +2,25 @@ package nl.sander.beejava;
import nl.sander.beejava.api.BeeSource;
import nl.sander.beejava.classinfo.FieldInfo;
import nl.sander.beejava.classinfo.MethodInfo;
import nl.sander.beejava.constantpool.ConstantPool;
import nl.sander.beejava.constantpool.entry.ClassEntry;
import nl.sander.beejava.constantpool.entry.ConstantPoolEntry;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;
class CompiledClass {
public class CompiledClass {
private final Set<ConstantPoolEntry> constantTree = new LinkedHashSet<>();
private final Set<ClassEntry> interfaces = new HashSet<>();
private final Set<FieldInfo> fields = new HashSet<>();
private final Set<MethodInfo> methods = new HashSet<>();
private final BeeSource beeSource;
private ClassEntry thisClass;
private ClassEntry superClass;
private ConstantPool constantPool;
CompiledClass(BeeSource beeSource) {
this.beeSource = beeSource;
@ -68,4 +73,20 @@ class CompiledClass {
public Set<FieldInfo> getFields() {
return fields;
}
public void addMethod(MethodInfo methodInfo) {
methods.add(methodInfo);
}
public Set<MethodInfo> getMethods() {
return methods;
}
public void setConstantPool(ConstantPool constantPool) {
this.constantPool = constantPool;
}
public ConstantPool getConstantPool() {
return constantPool;
}
}

View file

@ -2,6 +2,12 @@ package nl.sander.beejava;
import nl.sander.beejava.api.BeeSource;
import nl.sander.beejava.api.CodeLine;
import nl.sander.beejava.classinfo.MethodInfo;
import nl.sander.beejava.constantpool.ConstantPool;
import nl.sander.beejava.constantpool.entry.ConstantPoolEntry;
import nl.sander.beejava.constantpool.entry.FieldRefEntry;
import nl.sander.beejava.constantpool.entry.MethodRefEntry;
import nl.sander.beejava.constantpool.entry.Utf8Entry;
/**
* Builds a set of a tree of constant pool entries that refer to each other.
@ -15,6 +21,8 @@ import nl.sander.beejava.api.CodeLine;
public class Compiler {
private final CompiledClass compiledClass;
private final ConstantPoolEntryCreator constantPoolEntryCreator;
private final ConstantPoolCreator constantPoolCreator = new ConstantPoolCreator();
private Utf8Entry codeAttributeNameEntry;
/**
* construct a compiler object.
@ -41,17 +49,58 @@ public class Compiler {
* construct a CompiledClass object that contains all information for generating the bytecode
*/
public CompiledClass compile() {
compiledClass.getSource().getConstructors().forEach(this::updateConstantPool);
compiledClass.getSource().getMethods().forEach(this::updateConstantPool);
// TODO update constant pool for fields ?
compiledClass.setConstantPool(createConstantPool());
compiledClass.setThisClass(constantPoolEntryCreator.addThisClass());
constantPoolEntryCreator.addInterfaces();
constantPoolEntryCreator.addFields();
addConstructors();
addMethods();
return compiledClass;
}
private ConstantPool createConstantPool() {
compiledClass.getSource().getConstructors().forEach(this::updateConstantPool);
compiledClass.getSource().getMethods().forEach(this::updateConstantPool); // compiledClass.getSource() ?
compiledClass.setThisClass(constantPoolEntryCreator.addThisClass());
this.codeAttributeNameEntry = constantPoolEntryCreator.getOrCreateUtf8("Code");
compiledClass.addConstantPoolEntry(codeAttributeNameEntry);
ConstantPool constantPool = constantPoolCreator.createConstantPool(compiledClass.getConstantTree());
return constantPool;
}
public void addConstructors() {
compiledClass.getSource().getConstructors().stream()
.map(this::createMethod)
.forEach(compiledClass::addMethod);
}
/*
* maps methods from the source to a MethodInfo and adds that to the CompiledClass.
*/
public void addMethods() {
compiledClass.addConstantPoolEntry(codeAttributeNameEntry);
compiledClass.getSource().getMethods().stream()
.map(this::createMethod)
.forEach(compiledClass::addMethod);
}
/*
* create bytecode for method
* create methodInfo object for classfile
*/
private MethodInfo createMethod(CodeContainer method) {
return new MethodInfo(
constantPoolEntryCreator.getOrCreateUtf8(method.getName()),
constantPoolEntryCreator.getOrCreateUtf8(method.getSignature()))
.addAccessFlags(method.getAccessFlags())
.addAttribute(MethodCodeCreator.createCodeAttribute(codeAttributeNameEntry, method.getCode()));
}
/*
* inspect a method or constructor, extract items that need to be added, and add them to the constant pool
*/
@ -70,15 +119,21 @@ public class Compiler {
*/
private void updateConstantPool(CodeLine codeline) {
if (codeline.hasMethodCall()) {
compiledClass.addConstantPoolEntry(constantPoolEntryCreator.getOrCreateMethodRefEntry(codeline));
MethodRefEntry methodRefEntry = constantPoolEntryCreator.getOrCreateMethodRefEntry(codeline);
codeline.setAssignedEntry(methodRefEntry);
compiledClass.addConstantPoolEntry(methodRefEntry);
}
if (codeline.hasRefToOwnField() || codeline.hasRefToExternalField()) {
compiledClass.addConstantPoolEntry(constantPoolEntryCreator.getOrCreateFieldRefEntry(codeline));
FieldRefEntry fieldRefEntry = constantPoolEntryCreator.getOrCreateFieldRefEntry(codeline);
codeline.setAssignedEntry(fieldRefEntry);
compiledClass.addConstantPoolEntry(fieldRefEntry);
}
if (codeline.hasConstValue()) {
compiledClass.addConstantPoolEntry(constantPoolEntryCreator.getOrCreatePrimitiveEntry(codeline));
ConstantPoolEntry primitiveEntry = constantPoolEntryCreator.getOrCreatePrimitiveEntry(codeline);
codeline.setAssignedEntry(primitiveEntry);
compiledClass.addConstantPoolEntry(primitiveEntry);
}
}
}

View file

@ -17,6 +17,19 @@ public class ConstantPoolCreator {
return new ConstantPoolCreator().createConstantPool(constantTree);
}
/**
* Creates a ConstantPool from the tree structure provided by the {@link Compiler}
*
* @param constantTree a Set of CP entries assumed to be laid out correctly
* @return a ConstantPool, a list of entries
*/
/*
* resets the current state to create a new constant pool
*
* This method is halfway at memory efficiency. Next step could be to always have just 1 ConstantPool.
* Downsides: no parallelism, maybe keep more track of lingering references
*/
//@NotThreadSafe
public ConstantPool createConstantPool(Set<ConstantPoolEntry> constantTree) {
constantPool = new ConstantPool();
index = 0;
@ -24,25 +37,34 @@ public class ConstantPoolCreator {
return constantPool;
}
/*
* javac:
* - start with toplevel element (like MethodRef) -> grandparents
* - then add direct children -> the parents
* - then add grandchildren (I don't think there's more levels)
*/
private void updateToplevelElements(Set<ConstantPoolEntry> children) {
for (ConstantPoolEntry child : children) {
addToPool(child);
updateChildElements(child.getChildren());
// first the complete toplevel element including it's children, then next toplevel element
for (ConstantPoolEntry topElement : children) {
addToPool(topElement); // grandparents
updateChildElements(topElement.getChildren());
}
}
private void updateChildElements(Set<ConstantPoolEntry> children) {
// first all direct children
// parents
for (ConstantPoolEntry child : children) {
addToPool(child);
}
// then further lineage
// then grandchildren
for (ConstantPoolEntry child : children) {
updateChildElements(child.getChildren());
updateChildElements(child.getChildren()); // no problem if there are great grand children
}
}
/*
* This just adds the next index to an entry and adds the entry to a flat structure.
* The tree structure makes sure all references by index will be correct
*/
private void addToPool(ConstantPoolEntry entry) {
index += 1;
entry.setIndex(index);

View file

@ -3,6 +3,7 @@ package nl.sander.beejava;
import nl.sander.beejava.api.CodeLine;
import nl.sander.beejava.api.Ref;
import nl.sander.beejava.classinfo.FieldInfo;
import nl.sander.beejava.classinfo.attributes.CodeAttribute;
import nl.sander.beejava.constantpool.entry.*;
import java.util.HashMap;
@ -20,35 +21,49 @@ class ConstantPoolEntryCreator {
this.compiledClass = compiledClass;
}
/*
* creates a FieldRefEntry when not found in cache, otherwise gets it from there
*/
FieldRefEntry getOrCreateFieldRefEntry(CodeLine codeLine) {
return cache(new FieldRefEntry(getOrCreateClassEntry(codeLine), createFieldNameAndType(codeLine)));
return cache(new FieldRefEntry(getOrCreateClassEntry(codeLine), getOrCreateFieldNameAndType(codeLine)));
}
/*
* creates a MethodRefEntry when not found in cache, otherwise gets it from there
*/
MethodRefEntry getOrCreateMethodRefEntry(CodeLine codeline) {
ClassEntry classEntry = getOrCreateClassEntry(codeline);
NameAndTypeEntry nameAndType = getOrCreateMethodNameAndType(codeline);
return cache(new MethodRefEntry(classEntry, nameAndType));
return cache(new MethodRefEntry(classEntry, getOrCreateMethodNameAndType(codeline)));
}
private NameAndTypeEntry createFieldNameAndType(CodeLine codeline) {
/*
* creates a NamAndTypeEntry for a field when not found in cache, otherwise gets it from there
*/
private NameAndTypeEntry getOrCreateFieldNameAndType(CodeLine codeline) {
if (codeline.hasRefToOwnField()) {
return cache(new NameAndTypeEntry(
cache(new Utf8Entry(codeline.getOwnfield().getName())),
cache(new Utf8Entry(TypeMapper.map(codeline.getOwnfield().getType()))))); // is actually a shortcut
} else {
} else {//TODO this method may need some work
return cache(new NameAndTypeEntry(
cache(new Utf8Entry(codeline.getExternalfield())),
cache(new Utf8Entry("L" + codeline.getExternalfieldClass()))
cache(new Utf8Entry(codeline.getExternalfield().getName())),
cache(new Utf8Entry(TypeMapper.map(codeline.getExternalfield().getType())))
));
}
}
/*
* creates a NamAndTypeEntry for a method when not found in cache, otherwise gets it from there
*/
private NameAndTypeEntry getOrCreateMethodNameAndType(CodeLine codeline) {
return new NameAndTypeEntry(
cache(new Utf8Entry(codeline.getMethodName())),
cache(new Utf8Entry(codeline.getMethodSignature())));
}
/*
* creates a ClassEntry when not found in cache, otherwise gets it from there
*/
private ClassEntry getOrCreateClassEntry(CodeLine codeline) {
if (codeline.hasRef()) {
if (codeline.getRef() == Ref.SUPER) { // this and super are rather special
@ -66,12 +81,18 @@ class ConstantPoolEntryCreator {
throw new RuntimeException("shouldn't be here");
}
/*
* this method gives me a headache. It adds, but also creates a classEntry for the this class
*/
ClassEntry addThisClass() {
ClassEntry classEntry = getClassEntry(compiledClass.getSource().getName());
compiledClass.addConstantPoolEntry(classEntry);
return classEntry;
}
/*
* get or create a ClassEntry
*/
private ClassEntry getClassEntry(String externalClassName) {
return cache(new ClassEntry(cache(new Utf8Entry(internalName(externalClassName)))));
}
@ -109,15 +130,24 @@ class ConstantPoolEntryCreator {
throw new RuntimeException(); // TODO find out why are you here
}
/*
* maps a field from the source to a FieldInfo and adds that to the CompiledClass.
*/
public void addFields() {
compiledClass.getSource().getFields()
.forEach(f -> {
Utf8Entry name = cache(new Utf8Entry(f.getName()));
Utf8Entry descriptor = cache(new Utf8Entry(internalName(f.getType().getName())));
compiledClass.addField(new FieldInfo(name, descriptor).addAccessFlags(f.getAccessFlags()));
});
compiledClass.getSource().getFields().stream()
.map(field -> new FieldInfo(
cache(new Utf8Entry(field.getName())),
cache(new Utf8Entry(internalName(field.getType().getName())))
).addAccessFlags(field.getAccessFlags())
)
.forEach(compiledClass::addField);
}
public Utf8Entry getOrCreateUtf8(String utf8) {
return cache(new Utf8Entry(utf8));
}
// why not put this everywhere, it's not like it's ever going to change
private String internalName(String name) {
return name.replaceAll("\\.", "/");
}
@ -132,5 +162,4 @@ class ConstantPoolEntryCreator {
// A HashSet is a HashMap with entry: key = value, which would work, but I cannot _get_ anything from a set.
}
}

View file

@ -0,0 +1,19 @@
package nl.sander.beejava;
public class JavaOpcodes {
public static final int LDC = 0x12;
public static final int LDC_W = 0x13;
public static final int LDC2_W = 0x14;
public static final int ALOAD_0 = 0x2a;
public static final int RETURN = 0xb1;
public static final int GETSTATIC = 0xb2;
public static final int GETFIELD = 0xb4;
public static final int INVOKEINTERFACE = 0xb5;
public static final int INVOKEVIRTUAL = 0xb6;
public static final int INVOKESPECIAL = 0xb7;
public static final int INVOKESTATIC = 0xb8;
public static final int INVOKEDYNAMIC = 0xba;
}

View file

@ -0,0 +1,28 @@
package nl.sander.beejava;
import nl.sander.beejava.api.CodeLine;
import nl.sander.beejava.classinfo.attributes.CodeAttribute;
import nl.sander.beejava.constantpool.entry.ConstantPoolEntry;
import nl.sander.beejava.constantpool.entry.Utf8Entry;
import nl.sander.beejava.util.ByteBuf;
import java.util.List;
public class MethodCodeCreator {
public static CodeAttribute createCodeAttribute(Utf8Entry codeAttributeNameEntry, List<CodeLine> code) {
CodeAttribute codeAttribute = new CodeAttribute(codeAttributeNameEntry);
codeAttribute.setMaxStack(1);
codeAttribute.setMaxLocals(1);
ByteBuf byteBuf = new ByteBuf();
code.forEach(codeLine -> {
ConstantPoolEntry constantPoolEntry = codeLine.getAssignedEntry();
byteBuf.addU8(OpcodeMapper.mapOpcode(codeLine));
if (constantPoolEntry != null) {
byteBuf.addU16(constantPoolEntry.getIndex());
}
});
codeAttribute.setCode(byteBuf.toBytes());
return codeAttribute;
}
}

View file

@ -0,0 +1,54 @@
package nl.sander.beejava;
import nl.sander.beejava.api.BeeSource;
import nl.sander.beejava.api.CodeLine;
import nl.sander.beejava.api.Opcode;
import nl.sander.beejava.flags.ClassAccessFlags;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import static nl.sander.beejava.JavaOpcodes.*;
public class OpcodeMapper {
public static int mapOpcode(CodeLine codeLine) {
Opcode opcode = codeLine.getOpcode();
return switch (opcode) {
case GET -> isStatic(codeLine.getExternalfield()) ? GETSTATIC : GETFIELD;
case LD_VAR -> ALOAD_0;
case LD_CONST -> loadConst(codeLine);
case INVOKE -> invoke(codeLine);
case RETURN -> JavaOpcodes.RETURN; //TODO not complete yet
default -> 0;
};
}
/* TODO cover more cases */
private static int invoke(CodeLine codeLine) {
BeeSource source = codeLine.getOwner().getOwner();
if (source.getAccessFlags().contains(ClassAccessFlags.SUPER)) {
return INVOKESPECIAL;
} else {
return INVOKEVIRTUAL;
}
}
private static int loadConst(CodeLine codeLine) {
Object constValue = codeLine.getConstValue();
int index = codeLine.getAssignedEntry().getIndex();
if (constValue instanceof Double || constValue instanceof Long) {
return LDC2_W;
} else {
if (index > 0xff) {
return LDC_W;
} else {
return LDC;
}
}
}
private static boolean isStatic(Field field) {
return (field.getModifiers() & Modifier.STATIC) == Modifier.STATIC;
}
}

View file

@ -16,6 +16,8 @@ public class TypeMapper {
MAP.put(long.class, "J");
MAP.put(short.class, "S");
MAP.put(boolean.class, "Z");
MAP.put(Void.class, "V");
}
//TODO something with arrays
@ -24,7 +26,7 @@ public class TypeMapper {
if (type.isArray()) {
return internalName(type.getName());
} else {
return "L" + internalName(type.getName());
return 'L' + internalName(type.getName() + ';');
}
});
}

View file

@ -9,8 +9,6 @@ import java.util.*;
* Models a constructor
*/
public class BeeConstructor extends CodeContainer {
private final Set<MethodAccessFlags> accessFlags = new HashSet<>();
private final Set<BeeParameter> formalParameters = new HashSet<>();
private BeeConstructor(Set<MethodAccessFlags> accessFlags,
List<BeeParameter> formalParameters,
@ -24,12 +22,9 @@ public class BeeConstructor extends CodeContainer {
return new Builder();
}
Set<MethodAccessFlags> getAccessFlags() {
return accessFlags;
}
Set<BeeParameter> getFormalParameters() {
return formalParameters;
public String getName() {
return "<init>";
}
@Override
@ -39,6 +34,10 @@ public class BeeConstructor extends CodeContainer {
'}';
}
public Class<?> getReturnType() {
return Void.class;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
@ -77,7 +76,9 @@ public class BeeConstructor extends CodeContainer {
}
public BeeConstructor build() {
return new BeeConstructor(accessFlags, formalParameters, code);
BeeConstructor beeConstructor = new BeeConstructor(accessFlags, formalParameters, code);
code.forEach(line -> line.setOwner(beeConstructor));
return beeConstructor;
}
}
}

View file

@ -1,18 +1,21 @@
package nl.sander.beejava.api;
import nl.sander.beejava.CodeContainer;
import nl.sander.beejava.TypeMapper;
import nl.sander.beejava.flags.MethodAccessFlags;
import java.util.*;
import java.util.stream.Collectors;
public final class BeeMethod extends CodeContainer {
private final Set<MethodAccessFlags> accessFlags = new HashSet<>();
private final Set<BeeParameter> formalParameters = new HashSet<>();
private final String name;
private final Class<?> returnType;
private BeeMethod(Set<MethodAccessFlags> accessFlags,
private BeeMethod(String name, Set<MethodAccessFlags> accessFlags,
List<BeeParameter> formalParameters,
Class<?> returnType, List<CodeLine> code) {
this.name = name;
this.accessFlags.addAll(accessFlags);
this.formalParameters.addAll(formalParameters);
this.returnType = returnType;
@ -23,15 +26,38 @@ public final class BeeMethod extends CodeContainer {
return new Builder();
}
public String getName() {
return name;
}
public Class<?> getReturnType() {
return returnType;
}
public void validate() {
//TODO
/*
* here we could add checks like:
* -If this method is in a class rather than an interface, and the name of the method is <init>, then the descriptor must denote a void method.
* -If the name of the method is <clinit>, then the descriptor must denote avoid method, and, in a class file whose version number is 51.0 or above,a method that takes no arguments
*/
}
public static class Builder {
private final Set<MethodAccessFlags> accessFlags = new HashSet<>();
private final List<BeeParameter> formalParameters = new LinkedList<>();
private final List<CodeLine> code = new LinkedList<>();
private Class<?> returnType;
private String name;
private Class<?> returnType = Void.class;
private Builder() {
}
public Builder withName(String name) {
this.name = name;
return this;
}
public Builder withAccessFlags(MethodAccessFlags... accessFlags) {
this.accessFlags.addAll(Arrays.asList(accessFlags));
return this;
@ -53,7 +79,9 @@ public final class BeeMethod extends CodeContainer {
}
public BeeMethod build() {
return new BeeMethod(accessFlags, formalParameters, returnType, code);
BeeMethod beeMethod = new BeeMethod(name, accessFlags, formalParameters, returnType, code);
code.forEach(line -> line.setOwner(beeMethod));
return beeMethod;
}
}
}

View file

@ -141,7 +141,10 @@ public class BeeSource {
}
public BeeSource build() {
return new BeeSource(version, beePackage, accessFlags, simpleName, superClass, interfaces, fields, constructors, methods);
BeeSource beeSource = new BeeSource(version, beePackage, accessFlags, simpleName, superClass, interfaces, fields, constructors, methods);
constructors.forEach(c -> c.setOwner(beeSource));
methods.forEach(m -> m.setOwner(beeSource));
return beeSource;
}
}

View file

@ -1,20 +1,32 @@
package nl.sander.beejava.api;
import nl.sander.beejava.CodeContainer;
import nl.sander.beejava.TypeMapper;
import nl.sander.beejava.constantpool.entry.ConstantPoolEntry;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
public class CodeLine {
private final int linenumber;
private final Opcode opcode;
private Ref ref;
private BeeParameter parameter;
private String className;
private Class<?> type;
private String methodName;
private String inputSignature;
private List<Class<?>> inputSignature;
private String outputSignature;
private BeeField ownfield; // when you create a class with a field, you can refer to it
private String externalfield; // when you refer to a field from another class
private Field externalfield; // when you refer to a field from another class
private Object constValue;
private String externalFieldType;
private ConstantPoolEntry assignedEntry;
private CodeContainer owner;
CodeLine(int linenumber, Opcode opcode) {
this.linenumber = linenumber;
@ -33,16 +45,31 @@ public class CodeLine {
return new CodeLine(nr, opcode).withConstValue(constValue);
}
public static CodeLine line(int nr, Opcode opcode, String className, String methodName, String inputSignature) {
return new CodeLine(nr, opcode).withClassName(className).withMethodName(methodName).withInput(inputSignature).withVoidOutput();
public static CodeLine line(int nr, Opcode opcode, String className, String methodName, String inputSignature) throws ClassNotFoundException {
return new CodeLine(nr, opcode).withClassName(className).withMethodName(methodName).withInput(parse(inputSignature)).withVoidOutput();
}
public static CodeLine line(int nr, Opcode opcode, Ref ref, String methodNameRef, String inputSignature) {
return new CodeLine(nr, opcode).withRef(ref).withMethodName(methodNameRef).withInput(inputSignature).withVoidOutput();
private static List<Class<?>> parse(String inputSignature) throws ClassNotFoundException {
if ("()".equals(inputSignature)) {
return Collections.emptyList();
} else {
String[] params = inputSignature.split(",");
List<Class<?>> paramClasses = new ArrayList<>();
for (String param : params) {
paramClasses.add(Class.forName(param));
}
return paramClasses;
}
}
public static CodeLine line(int nr, Opcode opcode, Ref ref, String methodNameRef, String inputSignature, String outputSignature) {
return new CodeLine(nr, opcode).withRef(ref).withMethodName(methodNameRef).withInput(inputSignature).withOutput(outputSignature);
public static CodeLine line(int nr, Opcode opcode,
Ref ref, String methodNameRef, String inputSignature) throws ClassNotFoundException {
return new CodeLine(nr, opcode).withRef(ref).withMethodName(methodNameRef).withInput(parse(inputSignature)).withVoidOutput();
}
public static CodeLine line(int nr, Opcode opcode,
Ref ref, String methodNameRef, String inputSignature, String outputSignature) throws ClassNotFoundException {
return new CodeLine(nr, opcode).withRef(ref).withMethodName(methodNameRef).withInput(parse(inputSignature)).withOutput(outputSignature);
}
public static CodeLine line(int nr, Opcode opcode) {
@ -63,7 +90,7 @@ public class CodeLine {
}
private CodeLine withClassName(String className) {
this.className = className;
this.type = loadClass(className);
return this;
}
@ -72,7 +99,7 @@ public class CodeLine {
return this;
}
private CodeLine withInput(String inputSignature) {
private CodeLine withInput(List<Class<?>> inputSignature) {
this.inputSignature = inputSignature;
return this;
}
@ -102,11 +129,9 @@ public class CodeLine {
}
private CodeLine withExternalFieldRef(String className, String field) {
this.className = className;
this.externalFieldType = getType(className, field);
this.externalfield = field;
this.type = loadClass(className);
this.externalfield = loadField(field);
return this;
}
// TODO decide whether to work with Strings or class objects...
@ -120,21 +145,21 @@ public class CodeLine {
* @param field name of the field
* @return the field type
*/
private String getType(String className, String field) {
private Field loadField(String field) {
try {
return Class.forName(className).getField(field).getType().getName();
} catch (ClassNotFoundException | NoSuchFieldException e) {
return type.getField(field);
} catch (NoSuchFieldException e) {
throw new RuntimeException(e);
}
}
public boolean hasClassName() {
return className != null;
return type != null;
}
public String getClassName() {
return className;
return type.getName();
}
public String getMethodName() {
@ -146,7 +171,9 @@ public class CodeLine {
}
public String getMethodSignature() {
return inputSignature + outputSignature;
return inputSignature.stream()
.map(TypeMapper::map)
.collect(Collectors.joining(",", "(", ")")) + outputSignature;
}
public Ref getRef() {
@ -183,11 +210,35 @@ public class CodeLine {
return externalfield != null;
}
public String getExternalfield() {
public Field getExternalfield() {
return externalfield;
}
public String getExternalfieldClass() {
return externalFieldType;
public ConstantPoolEntry getAssignedEntry() {
return assignedEntry;
}
public void setAssignedEntry(ConstantPoolEntry assignedEntry) {
this.assignedEntry = assignedEntry;
}
public Opcode getOpcode() {
return opcode;
}
private Class<?> loadClass(String className) {
try {
return Class.forName(className);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e); //TODO specific exception
}
}
public CodeContainer getOwner() {
return owner;
}
public void setOwner(CodeContainer codeContainer) {
this.owner = codeContainer;
}
}

View file

@ -1,11 +0,0 @@
package nl.sander.beejava.classinfo;
import nl.sander.beejava.constantpool.entry.Utf8Entry;
import nl.sander.beejava.util.ByteBuf;
public abstract class AttributeInfo {
private Utf8Entry nameEntry;
private int length;
public abstract void addBytes(ByteBuf buf);
}

View file

@ -8,17 +8,13 @@ import nl.sander.beejava.util.ByteBuf;
import java.util.HashSet;
import java.util.Set;
public class FieldInfo {
public class FieldInfo extends Info<FieldInfo> {
private final Set<FieldAccessFlags> accessFlags = new HashSet<>();
private final Utf8Entry nameEntry;
private final Utf8Entry descriptorEntry;
private final Set<AttributeInfo> attributes=new HashSet<>();
public FieldInfo(Utf8Entry nameEntry, Utf8Entry descriptorEntry) {
this.nameEntry = nameEntry;
this.descriptorEntry = descriptorEntry;
super(nameEntry, descriptorEntry);
}
public FieldInfo addAccessFlags(Set<FieldAccessFlags> accessFlags) {
this.accessFlags.addAll(accessFlags);
return this;
@ -28,18 +24,6 @@ public class FieldInfo {
return accessFlags;
}
public Utf8Entry getNameEntry() {
return nameEntry;
}
public Utf8Entry getDescriptorEntry() {
return descriptorEntry;
}
public Set<AttributeInfo> getAttributes() {
return attributes;
}
public void addBytes(ByteBuf buf) {
buf.addU16(AccessFlags.combine(accessFlags));
buf.addU16(nameEntry.getIndex());

View file

@ -0,0 +1,36 @@
package nl.sander.beejava.classinfo;
import nl.sander.beejava.classinfo.attributes.Attribute;
import nl.sander.beejava.constantpool.entry.Utf8Entry;
import java.util.HashSet;
import java.util.Set;
public abstract class Info<T extends Info> {
protected final Utf8Entry nameEntry;
protected final Utf8Entry descriptorEntry;
protected final Set<Attribute> attributes = new HashSet<>();
public Info(Utf8Entry nameEntry, Utf8Entry descriptorEntry) {
this.nameEntry = nameEntry;
this.descriptorEntry = descriptorEntry;
}
public Utf8Entry getNameEntry() {
return nameEntry;
}
public Utf8Entry getDescriptorEntry() {
return descriptorEntry;
}
public Set<Attribute> getAttributes() {
return attributes;
}
public T addAttribute(Attribute attribute) {
attributes.add(attribute);
return (T) this;
}
}

View file

@ -0,0 +1,28 @@
package nl.sander.beejava.classinfo;
import nl.sander.beejava.constantpool.entry.Utf8Entry;
import nl.sander.beejava.flags.AccessFlags;
import nl.sander.beejava.flags.MethodAccessFlags;
import nl.sander.beejava.util.ByteBuf;
import java.util.HashSet;
import java.util.Set;
public class MethodInfo extends Info<MethodInfo> {
private final Set<MethodAccessFlags> accessFlags = new HashSet<>();
public MethodInfo(Utf8Entry nameEntry, Utf8Entry descriptorEntry) {
super(nameEntry, descriptorEntry);
}
public MethodInfo addAccessFlags(Set<MethodAccessFlags> accessFlags) {
this.accessFlags.addAll(accessFlags);
return this;
}
public void addBytes(ByteBuf buf) {
buf.addU16(AccessFlags.combine(accessFlags));
buf.addU16(nameEntry.getIndex());
buf.addU16(descriptorEntry.getIndex());
buf.addU16(attributes.size());
attributes.forEach(ai -> ai.addBytes(buf));
}
}

View file

@ -0,0 +1,15 @@
package nl.sander.beejava.classinfo.attributes;
import nl.sander.beejava.constantpool.entry.Utf8Entry;
import nl.sander.beejava.util.ByteBuf;
public abstract class Attribute {
protected final Utf8Entry nameEntry;
protected int length;
protected Attribute(Utf8Entry nameEntry) {
this.nameEntry = nameEntry;
}
public abstract void addBytes(ByteBuf buf);
}

View file

@ -0,0 +1,64 @@
package nl.sander.beejava.classinfo.attributes;
import nl.sander.beejava.constantpool.entry.Utf8Entry;
import nl.sander.beejava.util.ByteBuf;
import java.util.HashSet;
import java.util.Set;
/**
* §4.7.3 The Code Attribute
*/
public class CodeAttribute extends Attribute {
private static final int MAX_STACK_LEN = 2; // nr of bytes for max_stack
private static final int MAX_LOCALS_LEN = 2; // nr of bytes for max_locals
private static final int CODE_LEN = 4; // nr of bytes for code_length
private static final int NUM_EXCEPTION_HANDLERS_LEN = 2; // nr of bytes for number of exception_handlers
private static final int NUM_ATTRIBUTES_LEN = 2; // nr of bytes for number of method attributes
private static final int EXCEPTION_HANDLER_SIZE = 8; // nr of bytes per exception_hander
private final Set<Attribute> attributes = new HashSet<>();
private final Set<ExceptionHandler> exceptionHandlers = new HashSet<>();
private int maxStack; // u2
private int maxLocals; // u2
private byte[] code;
public CodeAttribute(Utf8Entry nameEntry) {
super(nameEntry);
}
public byte[] getCode() {
return code;
}
public void setCode(byte[] bytes) {
this.code = bytes;
}
@Override
public void addBytes(ByteBuf buf) {
buf.addU16(nameEntry.getIndex());
buf.addU32(MAX_STACK_LEN +
MAX_LOCALS_LEN +
CODE_LEN +
code.length +
NUM_EXCEPTION_HANDLERS_LEN +
exceptionHandlers.size() * EXCEPTION_HANDLER_SIZE +
NUM_ATTRIBUTES_LEN); //TODO works only if attributes are empty
buf.addU16(maxStack);
buf.addU16(maxLocals);
buf.addU32(code.length);
buf.addU8(getCode());
buf.addU16(exceptionHandlers.size());
buf.addU16(attributes.size());
}
public void setMaxStack(int maxStack) {
this.maxStack = maxStack;
}
public void setMaxLocals(int maxLocals) {
this.maxLocals = maxLocals;
}
}

View file

@ -0,0 +1,11 @@
package nl.sander.beejava.classinfo.attributes;
/**
* see §4.7.3 The code attribute
*/
public class ExceptionHandler {
private int startPc; //u2
private int endPc; //u2
private int handlerPc; //u2
private int catchType; //u2
}

View file

@ -64,4 +64,5 @@ public abstract class ConstantPoolEntry {
protected byte getByte(long bits, int positions) {
return (byte) ((bits >>> (positions * 8)) & 0xFF);
}
}

View file

@ -31,7 +31,7 @@ public class FieldRefEntry extends ConstantPoolEntry {
@Override
public byte[] getBytes() {
return new byte[]{TAG};
return new byte[]{TAG, upperByte(getClassIndex()), lowerByte(getClassIndex()), upperByte(getNameAndTypeIndex()),lowerByte(getNameAndTypeIndex())};
}
@Override

View file

@ -40,6 +40,10 @@ public class ByteBuf {
addU8((u16 >>> 8) & 0xFF, u16 & 0xFF);
}
public void addU32(int u32) {
addU8((u32 >>> 24) & 0xFF,(u32 >>> 16) & 0xFF, (u32 >>> 8) & 0xFF, u32 & 0xFF);
}
private void enlarge(final int size) {
final int length1 = 2 * data.limit();
final int length2 = data.limit() + size;
@ -56,4 +60,26 @@ public class ByteBuf {
data.get(arr);
return arr;
}
public String toString() {
return toString(StandardCharsets.UTF_8);
}
public String toString(Charset charset) {
data.flip();
CharsetDecoder decoder = charset.newDecoder(); // decode is not threadsafe, might put it in threadlocal
// but I don't think this (newDecoder()+config) is expensive
decoder.onMalformedInput(CodingErrorAction.REPLACE)
.onUnmappableCharacter(CodingErrorAction.REPLACE);
try {
return decoder.decode(data).toString();
} catch (CharacterCodingException e) {
throw new RuntimeException(e);
}
}
}

View file

@ -8,8 +8,8 @@ import java.io.IOException;
public class BytecodeGeneratorTests {
@Test
public void testEmpty() throws IOException {
byte[] bytecode = BytecodeGenerator.generate(Compiler.compile(TestData.emptyClass()));
public void testEmpty() throws IOException, ClassNotFoundException {
byte[] bytecode = BytecodeGenerator.generate(Compiler.compile(TestData.createEmptyClass()));
File dir = new File("target/nl/sander/beejava/test");
dir.mkdirs();
try (FileOutputStream outputStream = new FileOutputStream(new File(dir, "EmptyBean.class"))) {
@ -18,12 +18,12 @@ public class BytecodeGeneratorTests {
}
@Test
public void testInterface() {
public void testInterface() throws ClassNotFoundException {
BytecodeGenerator.generate(Compiler.compile(TestData.emptyClassWithInterface()));
}
@Test
public void testFields() {
public void testFields() throws ClassNotFoundException {
BytecodeGenerator.generate(Compiler.compile(TestData.createClassWithField(int.class)));
}
}

View file

@ -14,16 +14,16 @@ public class CompilerTests {
// creates simplest class possible and checks the tree, that the ConstantTreeCreator emits
@Test // This is not a maintainable test
public void testMethodRefEntryForSuperConstructor() {
public void testMethodRefEntryForSuperConstructor() throws ClassNotFoundException {
// Arrange
BeeSource classWithIntField = TestData.emptyClass();
BeeSource classWithIntField = TestData.createEmptyClass();
// Act
CompiledClass compiledClass = Compiler.compile(classWithIntField);
// Assert
Set<ConstantPoolEntry> constantTree = compiledClass.getConstantTree();
assertEquals(2, constantTree.size());
assertEquals(3, constantTree.size());
ConstantPoolEntry superConstructor = constantTree.iterator().next();
assertEquals(MethodRefEntry.class, superConstructor.getClass());

View file

@ -3,8 +3,11 @@ package nl.sander.beejava;
import nl.sander.beejava.api.BeeSource;
import nl.sander.beejava.constantpool.ConstantPool;
import nl.sander.beejava.constantpool.entry.ClassEntry;
import nl.sander.beejava.constantpool.entry.ConstantPoolEntry;
import org.junit.jupiter.api.Test;
import java.io.File;
import java.io.FileOutputStream;
import java.util.List;
import java.util.stream.Collectors;
@ -12,9 +15,9 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
public class ConstantPoolUniquenessTests {
@Test
public void test() {
public void test() throws Exception {
// Arrange
BeeSource someClass = TestData.createClassWithTwoReferencesToSomeClass();
BeeSource someClass = TestData.createEmptyClass();
// Act
CompiledClass compiledClass = Compiler.compile(someClass);
@ -27,6 +30,48 @@ public class ConstantPoolUniquenessTests {
.filter(ce -> ce.getName().equals("java/lang/System"))
.collect(Collectors.toList());
assertEquals(1, refsToSystem.size());
assertEquals(0, refsToSystem.size());
byte[] bytecode = BytecodeGenerator.generate(compiledClass);
int x = 1;
for (ConstantPoolEntry e : constantPool) {
System.out.println((x++) + ":" + e);
}
x = 1;
for (ConstantPoolEntry e : constantPool) {
System.out.print((x++) + ":");
printBytes(e.getBytes());
}
File dir = new File("target/nl/sander/beejava/test");
dir.mkdirs();
try (FileOutputStream outputStream = new FileOutputStream(new File(dir, "EmptyBean.class"))) {
outputStream.write(bytecode);
}
printBytes2(bytecode);
}
//TODO remove
private void printBytes(byte[] bytes) {
for (byte b : bytes) {
System.out.print(String.format("%2s", Integer.toHexString(b & 0xFF)).replace(' ', '0') + " ");
}
System.out.println();
}
private void printBytes2(byte[] bytes) {
int count = 0;
for (byte b : bytes) {
System.out.print(String.format("%2s", Integer.toHexString(b & 0xFF)).replace(' ', '0') + (count % 2 == 0 ? "" : " "));
count += 1;
if (count > 15) {
count = 0;
System.out.println();
}
}
}
}

View file

@ -9,20 +9,21 @@ import java.io.Serializable;
import static nl.sander.beejava.api.CodeLine.line;
import static nl.sander.beejava.api.Opcode.*;
import static nl.sander.beejava.flags.ClassAccessFlags.PUBLIC;
import static nl.sander.beejava.flags.ClassAccessFlags.SUPER;
public class TestData {
public static BeeSource emptyClass() {
public static BeeSource createEmptyClass() throws ClassNotFoundException {
return BeeSource.builder()
.withClassFileVersion(Version.V14)
.withPackage("nl.sander.beejava.test")
.withAccessFlags(PUBLIC)
.withAccessFlags(PUBLIC, SUPER)
.withSimpleName("EmptyBean")
.withSuperClass(Object.class) // Not mandatory, like in java sourcecode
.withConstructors(createConstructor()) // There's no default constructor in beejava. The user must always add them
.build();
}
public static BeeSource emptyClassWithInterface() {
public static BeeSource emptyClassWithInterface() throws ClassNotFoundException {
return BeeSource.builder()
.withClassFileVersion(Version.V14)
.withPackage("nl.sander.beejava.test")
@ -34,36 +35,41 @@ public class TestData {
.build();
}
public static BeeSource createClassWithTwoReferencesToSomeClass() {
public static BeeSource createClassWithTwoReferencesToSomeClass() throws ClassNotFoundException {
BeeMethod print1 = BeeMethod.builder()
.withName("print1")
.withAccessFlags(MethodAccessFlags.PUBLIC)
.withCode(
line(0, GET, "java.lang.System","out"),
line(1, LD_CONST, "1"),
line(2, INVOKE, "java.io.PrintStream", "println", "(java.lang.String)"),
line(2, INVOKE, "java.io.PrintStream", "println", "java.lang.String"),
line(3, RETURN))
.build();
// INVOKE System.out.println("1")
BeeMethod print2 = BeeMethod.builder()
.withName("print2")
.withAccessFlags(MethodAccessFlags.PUBLIC)
.withCode(
line(0, GET, "java.lang.System","out"),
line(1, LD_CONST, "2"),
line(2, INVOKE, "java.io.PrintStream", "println", "(java.lang.String)"),
line(2, INVOKE, "java.io.PrintStream", "println", "java.lang.String"),
line(3, RETURN))
.build();
return BeeSource.builder()
.withClassFileVersion(Version.V14)
.withPackage("nl.sander.beejava.test")
.withAccessFlags(PUBLIC)
.withAccessFlags(PUBLIC, SUPER)
.withSimpleName("ClassWithReferences")
.withConstructors(createConstructor()) // There's no default constructor in beejava. The user must always add them
.withMethods(print1, print2)
.build();
}
public static BeeSource createClassWithField(Class<?> fieldType) {
public static BeeSource createClassWithField(Class<?> fieldType) throws ClassNotFoundException {
BeeField field = BeeField.builder()
.withAccessFlags(FieldAccessFlags.PRIVATE)
.withType(fieldType)
@ -95,7 +101,7 @@ public class TestData {
.build();
}
private static BeeConstructor createConstructor() {
private static BeeConstructor createConstructor() throws ClassNotFoundException {
return BeeConstructor.builder()
.withAccessFlags(MethodAccessFlags.PUBLIC)
.withCode(

View file

@ -11,7 +11,7 @@ import static org.junit.jupiter.api.Assertions.fail;
public class TypeMapperTest {
@Test
public void test_int() {
public void test_int() throws ClassNotFoundException {
// Arrange
BeeSource beeSource = TestData.createClassWithField(int.class);
@ -24,7 +24,7 @@ public class TypeMapperTest {
}
@Test
public void test_double() {
public void test_double() throws ClassNotFoundException {
// Arrange
BeeSource beeSource = TestData.createClassWithField(double.class);
@ -37,7 +37,7 @@ public class TypeMapperTest {
}
@Test
public void test_float() {
public void test_float() throws ClassNotFoundException {
// Arrange
BeeSource beeSource = TestData.createClassWithField(float.class);
@ -50,7 +50,7 @@ public class TypeMapperTest {
}
@Test
public void test_byte() {
public void test_byte() throws ClassNotFoundException {
// Arrange
BeeSource beeSource = TestData.createClassWithField(byte.class);
@ -63,7 +63,7 @@ public class TypeMapperTest {
}
@Test
public void test_short() {
public void test_short() throws ClassNotFoundException {
// Arrange
BeeSource beeSource = TestData.createClassWithField(short.class);
@ -76,7 +76,7 @@ public class TypeMapperTest {
}
@Test
public void test_long() {
public void test_long() throws ClassNotFoundException {
// Arrange
BeeSource beeSource = TestData.createClassWithField(long.class);
@ -89,7 +89,7 @@ public class TypeMapperTest {
}
@Test
public void test_char() {
public void test_char() throws ClassNotFoundException {
// Arrange
BeeSource beeSource = TestData.createClassWithField(char.class);
@ -102,7 +102,7 @@ public class TypeMapperTest {
}
@Test
public void test_boolean() {
public void test_boolean() throws ClassNotFoundException {
// Arrange
BeeSource beeSource = TestData.createClassWithField(boolean.class);
@ -115,7 +115,7 @@ public class TypeMapperTest {
}
@Test
public void test_Object() {
public void test_Object() throws ClassNotFoundException {
// Arrange
BeeSource beeSource = TestData.createClassWithField(Object.class);
@ -124,11 +124,11 @@ public class TypeMapperTest {
// Assert
NameAndTypeEntry fieldEntry = getFieldNameAndType(constantPool);
assertEquals("Ljava/lang/Object", fieldEntry.getType());
assertEquals("Ljava/lang/Object;", fieldEntry.getType());
}
@Test
public void test_int_array() {
public void test_int_array() throws ClassNotFoundException {
// Arrange
BeeSource beeSource = TestData.createClassWithField(int[].class);
@ -141,7 +141,7 @@ public class TypeMapperTest {
}
@Test
public void test_Object_array() {
public void test_Object_array() throws ClassNotFoundException {
// Arrange
BeeSource beeSource = TestData.createClassWithField(String[].class);

View file

@ -0,0 +1,21 @@
package nl.sander.beejava.e2e;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
public class ByteClassLoader extends ClassLoader {
private final ConcurrentMap<String, byte[]> classByteCode = new ConcurrentHashMap<>();
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] bytecode = Optional.ofNullable(classByteCode.get(name)).orElseThrow(() -> new ClassNotFoundException(name));
return defineClass(name, bytecode, 0, bytecode.length);
}
public void setByteCode(String className, byte[] bytecode) {
classByteCode.putIfAbsent(className, bytecode);
}
}

View file

@ -0,0 +1,40 @@
package nl.sander.beejava.e2e;
import nl.sander.beejava.BytecodeGenerator;
import nl.sander.beejava.CompiledClass;
import nl.sander.beejava.Compiler;
import nl.sander.beejava.TestData;
import nl.sander.beejava.api.BeeSource;
import org.junit.jupiter.api.Test;
import java.lang.reflect.Constructor;
import static org.junit.jupiter.api.Assertions.assertNotNull;
/**
* A rather simple case, still meaningful nonetheless.
* Green means class can be loaded, so the class file structure is valid.
* The EmptyBean class just contains a default constructor.
*/
public class EmptyBeanTest {
@Test
public void testEmptyBean() throws Exception {
// Arrange
BeeSource emptyClass = TestData.createEmptyClass();
// Act
CompiledClass compiledClass = Compiler.compile(emptyClass);
byte[] bytecode = BytecodeGenerator.generate(compiledClass);
ByteClassLoader classLoader = new ByteClassLoader();
classLoader.setByteCode("nl.sander.beejava.test.EmptyBean", bytecode);
// Assert
Constructor<?> constructor = classLoader.loadClass("nl.sander.beejava.test.EmptyBean").getConstructor();
assertNotNull(constructor);
Object instance = constructor.newInstance();
assertNotNull(instance);
}
}

View file

@ -8,12 +8,7 @@ public class BeanWithClassReferences {
}
public void print2() {
System.out.println(create2());
System.out.println("2");
}
public String create2() {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("2");
return stringBuilder.toString();
}
}

View file

@ -0,0 +1,7 @@
package nl.sander.beejava.testclasses;
public class Dummy {
public String hello(String goodbye){
return "hello";
}
}