End-to-end compilation of a trivial class.
This commit is contained in:
parent
ab4662f86e
commit
e59d214a10
33 changed files with 766 additions and 168 deletions
|
|
@ -1,14 +1,12 @@
|
||||||
package nl.sander.beejava;
|
package nl.sander.beejava;
|
||||||
|
|
||||||
import nl.sander.beejava.constantpool.ConstantPool;
|
import nl.sander.beejava.constantpool.ConstantPool;
|
||||||
import nl.sander.beejava.constantpool.entry.ConstantPoolEntry;
|
|
||||||
import nl.sander.beejava.flags.AccessFlags;
|
import nl.sander.beejava.flags.AccessFlags;
|
||||||
import nl.sander.beejava.util.ByteBuf;
|
import nl.sander.beejava.util.ByteBuf;
|
||||||
|
|
||||||
public class BytecodeGenerator {
|
public class BytecodeGenerator {
|
||||||
|
|
||||||
private final CompiledClass compiledClass; // maybe not a member?
|
private final CompiledClass compiledClass; // maybe not a member?
|
||||||
private final ConstantPoolCreator constantPoolCreator = new ConstantPoolCreator();
|
|
||||||
|
|
||||||
public BytecodeGenerator(CompiledClass compiledClass) {
|
public BytecodeGenerator(CompiledClass compiledClass) {
|
||||||
this.compiledClass = compiledClass;
|
this.compiledClass = compiledClass;
|
||||||
|
|
@ -25,10 +23,8 @@ public class BytecodeGenerator {
|
||||||
buf.addU16(compiledClass.getSource().getClassFileVersion().getMinor());
|
buf.addU16(compiledClass.getSource().getClassFileVersion().getMinor());
|
||||||
buf.addU16(compiledClass.getSource().getClassFileVersion().getMajor());
|
buf.addU16(compiledClass.getSource().getClassFileVersion().getMajor());
|
||||||
|
|
||||||
ConstantPool constantPool = constantPoolCreator.createConstantPool(compiledClass.getConstantTree());
|
buf.addU16(compiledClass.getConstantPool().getLength());
|
||||||
|
compiledClass.getConstantPool().addTo(buf);
|
||||||
buf.addU16(constantPool.getLength());
|
|
||||||
constantPool.addTo(buf);
|
|
||||||
buf.addU16(AccessFlags.combine(compiledClass.getSource().getAccessFlags()));
|
buf.addU16(AccessFlags.combine(compiledClass.getSource().getAccessFlags()));
|
||||||
buf.addU16(compiledClass.geThisIndex());
|
buf.addU16(compiledClass.geThisIndex());
|
||||||
buf.addU16(compiledClass.getSuperIndex());
|
buf.addU16(compiledClass.getSuperIndex());
|
||||||
|
|
@ -36,28 +32,10 @@ public class BytecodeGenerator {
|
||||||
compiledClass.getInterfaces().forEach(interfase -> buf.addU16(interfase.getIndex()));
|
compiledClass.getInterfaces().forEach(interfase -> buf.addU16(interfase.getIndex()));
|
||||||
buf.addU16(compiledClass.getFields().size());
|
buf.addU16(compiledClass.getFields().size());
|
||||||
compiledClass.getFields().forEach(fieldInfo -> fieldInfo.addBytes(buf));
|
compiledClass.getFields().forEach(fieldInfo -> fieldInfo.addBytes(buf));
|
||||||
|
buf.addU16(compiledClass.getMethods().size());
|
||||||
int x = 1;
|
compiledClass.getMethods().forEach(methodInfo -> methodInfo.addBytes(buf));
|
||||||
for (ConstantPoolEntry e : constantPool) {
|
buf.addU16(0); //attributes count
|
||||||
System.out.println((x++) + ":" + e);
|
|
||||||
}
|
|
||||||
printBytes(buf);
|
|
||||||
|
|
||||||
return buf.toBytes();
|
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,15 @@
|
||||||
package nl.sander.beejava;
|
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.api.CodeLine;
|
||||||
|
import nl.sander.beejava.flags.MethodAccessFlags;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* parent of a constructor or a method.
|
* parent of a constructor or a method.
|
||||||
|
|
@ -11,8 +17,41 @@ import java.util.List;
|
||||||
public abstract class CodeContainer {
|
public abstract class CodeContainer {
|
||||||
|
|
||||||
protected final List<CodeLine> code = new LinkedList<>();
|
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() {
|
public List<CodeLine> getCode() {
|
||||||
return code;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,20 +2,25 @@ package nl.sander.beejava;
|
||||||
|
|
||||||
import nl.sander.beejava.api.BeeSource;
|
import nl.sander.beejava.api.BeeSource;
|
||||||
import nl.sander.beejava.classinfo.FieldInfo;
|
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.ClassEntry;
|
||||||
import nl.sander.beejava.constantpool.entry.ConstantPoolEntry;
|
import nl.sander.beejava.constantpool.entry.ConstantPoolEntry;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
class CompiledClass {
|
public class CompiledClass {
|
||||||
private final Set<ConstantPoolEntry> constantTree = new LinkedHashSet<>();
|
private final Set<ConstantPoolEntry> constantTree = new LinkedHashSet<>();
|
||||||
private final Set<ClassEntry> interfaces = new HashSet<>();
|
private final Set<ClassEntry> interfaces = new HashSet<>();
|
||||||
private final Set<FieldInfo> fields = new HashSet<>();
|
private final Set<FieldInfo> fields = new HashSet<>();
|
||||||
|
private final Set<MethodInfo> methods = new HashSet<>();
|
||||||
private final BeeSource beeSource;
|
private final BeeSource beeSource;
|
||||||
private ClassEntry thisClass;
|
private ClassEntry thisClass;
|
||||||
private ClassEntry superClass;
|
private ClassEntry superClass;
|
||||||
|
private ConstantPool constantPool;
|
||||||
|
|
||||||
CompiledClass(BeeSource beeSource) {
|
CompiledClass(BeeSource beeSource) {
|
||||||
this.beeSource = beeSource;
|
this.beeSource = beeSource;
|
||||||
|
|
@ -68,4 +73,20 @@ class CompiledClass {
|
||||||
public Set<FieldInfo> getFields() {
|
public Set<FieldInfo> getFields() {
|
||||||
return fields;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,12 @@ package nl.sander.beejava;
|
||||||
|
|
||||||
import nl.sander.beejava.api.BeeSource;
|
import nl.sander.beejava.api.BeeSource;
|
||||||
import nl.sander.beejava.api.CodeLine;
|
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.
|
* 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 {
|
public class Compiler {
|
||||||
private final CompiledClass compiledClass;
|
private final CompiledClass compiledClass;
|
||||||
private final ConstantPoolEntryCreator constantPoolEntryCreator;
|
private final ConstantPoolEntryCreator constantPoolEntryCreator;
|
||||||
|
private final ConstantPoolCreator constantPoolCreator = new ConstantPoolCreator();
|
||||||
|
private Utf8Entry codeAttributeNameEntry;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* construct a compiler object.
|
* construct a compiler object.
|
||||||
|
|
@ -41,17 +49,58 @@ public class Compiler {
|
||||||
* construct a CompiledClass object that contains all information for generating the bytecode
|
* construct a CompiledClass object that contains all information for generating the bytecode
|
||||||
*/
|
*/
|
||||||
public CompiledClass compile() {
|
public CompiledClass compile() {
|
||||||
compiledClass.getSource().getConstructors().forEach(this::updateConstantPool);
|
compiledClass.setConstantPool(createConstantPool());
|
||||||
compiledClass.getSource().getMethods().forEach(this::updateConstantPool);
|
|
||||||
// TODO update constant pool for fields ?
|
|
||||||
|
|
||||||
compiledClass.setThisClass(constantPoolEntryCreator.addThisClass());
|
|
||||||
constantPoolEntryCreator.addInterfaces();
|
constantPoolEntryCreator.addInterfaces();
|
||||||
constantPoolEntryCreator.addFields();
|
constantPoolEntryCreator.addFields();
|
||||||
|
addConstructors();
|
||||||
|
addMethods();
|
||||||
|
|
||||||
return compiledClass;
|
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
|
* 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) {
|
private void updateConstantPool(CodeLine codeline) {
|
||||||
if (codeline.hasMethodCall()) {
|
if (codeline.hasMethodCall()) {
|
||||||
compiledClass.addConstantPoolEntry(constantPoolEntryCreator.getOrCreateMethodRefEntry(codeline));
|
MethodRefEntry methodRefEntry = constantPoolEntryCreator.getOrCreateMethodRefEntry(codeline);
|
||||||
|
codeline.setAssignedEntry(methodRefEntry);
|
||||||
|
compiledClass.addConstantPoolEntry(methodRefEntry);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (codeline.hasRefToOwnField() || codeline.hasRefToExternalField()) {
|
if (codeline.hasRefToOwnField() || codeline.hasRefToExternalField()) {
|
||||||
compiledClass.addConstantPoolEntry(constantPoolEntryCreator.getOrCreateFieldRefEntry(codeline));
|
FieldRefEntry fieldRefEntry = constantPoolEntryCreator.getOrCreateFieldRefEntry(codeline);
|
||||||
|
codeline.setAssignedEntry(fieldRefEntry);
|
||||||
|
compiledClass.addConstantPoolEntry(fieldRefEntry);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (codeline.hasConstValue()) {
|
if (codeline.hasConstValue()) {
|
||||||
compiledClass.addConstantPoolEntry(constantPoolEntryCreator.getOrCreatePrimitiveEntry(codeline));
|
ConstantPoolEntry primitiveEntry = constantPoolEntryCreator.getOrCreatePrimitiveEntry(codeline);
|
||||||
|
codeline.setAssignedEntry(primitiveEntry);
|
||||||
|
compiledClass.addConstantPoolEntry(primitiveEntry);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,19 @@ public class ConstantPoolCreator {
|
||||||
return new ConstantPoolCreator().createConstantPool(constantTree);
|
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) {
|
public ConstantPool createConstantPool(Set<ConstantPoolEntry> constantTree) {
|
||||||
constantPool = new ConstantPool();
|
constantPool = new ConstantPool();
|
||||||
index = 0;
|
index = 0;
|
||||||
|
|
@ -24,25 +37,34 @@ public class ConstantPoolCreator {
|
||||||
return constantPool;
|
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) {
|
private void updateToplevelElements(Set<ConstantPoolEntry> children) {
|
||||||
for (ConstantPoolEntry child : children) {
|
for (ConstantPoolEntry topElement : children) {
|
||||||
addToPool(child);
|
addToPool(topElement); // grandparents
|
||||||
updateChildElements(child.getChildren());
|
updateChildElements(topElement.getChildren());
|
||||||
// first the complete toplevel element including it's children, then next toplevel element
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateChildElements(Set<ConstantPoolEntry> children) {
|
private void updateChildElements(Set<ConstantPoolEntry> children) {
|
||||||
// first all direct children
|
// parents
|
||||||
for (ConstantPoolEntry child : children) {
|
for (ConstantPoolEntry child : children) {
|
||||||
addToPool(child);
|
addToPool(child);
|
||||||
}
|
}
|
||||||
// then further lineage
|
// then grandchildren
|
||||||
for (ConstantPoolEntry child : children) {
|
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) {
|
private void addToPool(ConstantPoolEntry entry) {
|
||||||
index += 1;
|
index += 1;
|
||||||
entry.setIndex(index);
|
entry.setIndex(index);
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package nl.sander.beejava;
|
||||||
import nl.sander.beejava.api.CodeLine;
|
import nl.sander.beejava.api.CodeLine;
|
||||||
import nl.sander.beejava.api.Ref;
|
import nl.sander.beejava.api.Ref;
|
||||||
import nl.sander.beejava.classinfo.FieldInfo;
|
import nl.sander.beejava.classinfo.FieldInfo;
|
||||||
|
import nl.sander.beejava.classinfo.attributes.CodeAttribute;
|
||||||
import nl.sander.beejava.constantpool.entry.*;
|
import nl.sander.beejava.constantpool.entry.*;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
|
@ -20,38 +21,52 @@ class ConstantPoolEntryCreator {
|
||||||
this.compiledClass = compiledClass;
|
this.compiledClass = compiledClass;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* creates a FieldRefEntry when not found in cache, otherwise gets it from there
|
||||||
|
*/
|
||||||
FieldRefEntry getOrCreateFieldRefEntry(CodeLine codeLine) {
|
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) {
|
MethodRefEntry getOrCreateMethodRefEntry(CodeLine codeline) {
|
||||||
ClassEntry classEntry = getOrCreateClassEntry(codeline);
|
ClassEntry classEntry = getOrCreateClassEntry(codeline);
|
||||||
NameAndTypeEntry nameAndType = getOrCreateMethodNameAndType(codeline);
|
return cache(new MethodRefEntry(classEntry, getOrCreateMethodNameAndType(codeline)));
|
||||||
return cache(new MethodRefEntry(classEntry, nameAndType));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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()) {
|
if (codeline.hasRefToOwnField()) {
|
||||||
return cache(new NameAndTypeEntry(
|
return cache(new NameAndTypeEntry(
|
||||||
cache(new Utf8Entry(codeline.getOwnfield().getName())),
|
cache(new Utf8Entry(codeline.getOwnfield().getName())),
|
||||||
cache(new Utf8Entry(TypeMapper.map(codeline.getOwnfield().getType()))))); // is actually a shortcut
|
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(
|
return cache(new NameAndTypeEntry(
|
||||||
cache(new Utf8Entry(codeline.getExternalfield())),
|
cache(new Utf8Entry(codeline.getExternalfield().getName())),
|
||||||
cache(new Utf8Entry("L" + codeline.getExternalfieldClass()))
|
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) {
|
private NameAndTypeEntry getOrCreateMethodNameAndType(CodeLine codeline) {
|
||||||
return new NameAndTypeEntry(
|
return new NameAndTypeEntry(
|
||||||
cache(new Utf8Entry(codeline.getMethodName())),
|
cache(new Utf8Entry(codeline.getMethodName())),
|
||||||
cache(new Utf8Entry(codeline.getMethodSignature())));
|
cache(new Utf8Entry(codeline.getMethodSignature())));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* creates a ClassEntry when not found in cache, otherwise gets it from there
|
||||||
|
*/
|
||||||
private ClassEntry getOrCreateClassEntry(CodeLine codeline) {
|
private ClassEntry getOrCreateClassEntry(CodeLine codeline) {
|
||||||
if (codeline.hasRef()) {
|
if (codeline.hasRef()) {
|
||||||
if (codeline.getRef() == Ref.SUPER) { //this and super are rather special
|
if (codeline.getRef() == Ref.SUPER) { // this and super are rather special
|
||||||
ClassEntry superClass = getClassEntry(compiledClass.getSource().getSuperClass().getName());
|
ClassEntry superClass = getClassEntry(compiledClass.getSource().getSuperClass().getName());
|
||||||
compiledClass.setSuperClass(superClass);
|
compiledClass.setSuperClass(superClass);
|
||||||
return superClass;
|
return superClass;
|
||||||
|
|
@ -66,12 +81,18 @@ class ConstantPoolEntryCreator {
|
||||||
throw new RuntimeException("shouldn't be here");
|
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 addThisClass() {
|
||||||
ClassEntry classEntry = getClassEntry(compiledClass.getSource().getName());
|
ClassEntry classEntry = getClassEntry(compiledClass.getSource().getName());
|
||||||
compiledClass.addConstantPoolEntry(classEntry);
|
compiledClass.addConstantPoolEntry(classEntry);
|
||||||
return classEntry;
|
return classEntry;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* get or create a ClassEntry
|
||||||
|
*/
|
||||||
private ClassEntry getClassEntry(String externalClassName) {
|
private ClassEntry getClassEntry(String externalClassName) {
|
||||||
return cache(new ClassEntry(cache(new Utf8Entry(internalName(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
|
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() {
|
public void addFields() {
|
||||||
compiledClass.getSource().getFields()
|
compiledClass.getSource().getFields().stream()
|
||||||
.forEach(f -> {
|
.map(field -> new FieldInfo(
|
||||||
Utf8Entry name = cache(new Utf8Entry(f.getName()));
|
cache(new Utf8Entry(field.getName())),
|
||||||
Utf8Entry descriptor = cache(new Utf8Entry(internalName(f.getType().getName())));
|
cache(new Utf8Entry(internalName(field.getType().getName())))
|
||||||
compiledClass.addField(new FieldInfo(name, descriptor).addAccessFlags(f.getAccessFlags()));
|
).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) {
|
private String internalName(String name) {
|
||||||
return name.replaceAll("\\.", "/");
|
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.
|
// A HashSet is a HashMap with entry: key = value, which would work, but I cannot _get_ anything from a set.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
19
src/main/java/nl/sander/beejava/JavaOpcodes.java
Normal file
19
src/main/java/nl/sander/beejava/JavaOpcodes.java
Normal 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;
|
||||||
|
|
||||||
|
}
|
||||||
28
src/main/java/nl/sander/beejava/MethodCodeCreator.java
Normal file
28
src/main/java/nl/sander/beejava/MethodCodeCreator.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
54
src/main/java/nl/sander/beejava/OpcodeMapper.java
Normal file
54
src/main/java/nl/sander/beejava/OpcodeMapper.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -16,6 +16,8 @@ public class TypeMapper {
|
||||||
MAP.put(long.class, "J");
|
MAP.put(long.class, "J");
|
||||||
MAP.put(short.class, "S");
|
MAP.put(short.class, "S");
|
||||||
MAP.put(boolean.class, "Z");
|
MAP.put(boolean.class, "Z");
|
||||||
|
MAP.put(Void.class, "V");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO something with arrays
|
//TODO something with arrays
|
||||||
|
|
@ -24,7 +26,7 @@ public class TypeMapper {
|
||||||
if (type.isArray()) {
|
if (type.isArray()) {
|
||||||
return internalName(type.getName());
|
return internalName(type.getName());
|
||||||
} else {
|
} else {
|
||||||
return "L" + internalName(type.getName());
|
return 'L' + internalName(type.getName() + ';');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,6 @@ import java.util.*;
|
||||||
* Models a constructor
|
* Models a constructor
|
||||||
*/
|
*/
|
||||||
public class BeeConstructor extends CodeContainer {
|
public class BeeConstructor extends CodeContainer {
|
||||||
private final Set<MethodAccessFlags> accessFlags = new HashSet<>();
|
|
||||||
private final Set<BeeParameter> formalParameters = new HashSet<>();
|
|
||||||
|
|
||||||
private BeeConstructor(Set<MethodAccessFlags> accessFlags,
|
private BeeConstructor(Set<MethodAccessFlags> accessFlags,
|
||||||
List<BeeParameter> formalParameters,
|
List<BeeParameter> formalParameters,
|
||||||
|
|
@ -24,12 +22,9 @@ public class BeeConstructor extends CodeContainer {
|
||||||
return new Builder();
|
return new Builder();
|
||||||
}
|
}
|
||||||
|
|
||||||
Set<MethodAccessFlags> getAccessFlags() {
|
|
||||||
return accessFlags;
|
|
||||||
}
|
|
||||||
|
|
||||||
Set<BeeParameter> getFormalParameters() {
|
public String getName() {
|
||||||
return formalParameters;
|
return "<init>";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -39,6 +34,10 @@ public class BeeConstructor extends CodeContainer {
|
||||||
'}';
|
'}';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Class<?> getReturnType() {
|
||||||
|
return Void.class;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (this == o) return true;
|
if (this == o) return true;
|
||||||
|
|
@ -77,7 +76,9 @@ public class BeeConstructor extends CodeContainer {
|
||||||
}
|
}
|
||||||
|
|
||||||
public BeeConstructor build() {
|
public BeeConstructor build() {
|
||||||
return new BeeConstructor(accessFlags, formalParameters, code);
|
BeeConstructor beeConstructor = new BeeConstructor(accessFlags, formalParameters, code);
|
||||||
|
code.forEach(line -> line.setOwner(beeConstructor));
|
||||||
|
return beeConstructor;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,21 @@
|
||||||
package nl.sander.beejava.api;
|
package nl.sander.beejava.api;
|
||||||
|
|
||||||
import nl.sander.beejava.CodeContainer;
|
import nl.sander.beejava.CodeContainer;
|
||||||
|
import nl.sander.beejava.TypeMapper;
|
||||||
import nl.sander.beejava.flags.MethodAccessFlags;
|
import nl.sander.beejava.flags.MethodAccessFlags;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public final class BeeMethod extends CodeContainer {
|
public final class BeeMethod extends CodeContainer {
|
||||||
private final Set<MethodAccessFlags> accessFlags = new HashSet<>();
|
private final String name;
|
||||||
private final Set<BeeParameter> formalParameters = new HashSet<>();
|
|
||||||
private final Class<?> returnType;
|
private final Class<?> returnType;
|
||||||
|
|
||||||
private BeeMethod(Set<MethodAccessFlags> accessFlags,
|
private BeeMethod(String name, Set<MethodAccessFlags> accessFlags,
|
||||||
List<BeeParameter> formalParameters,
|
List<BeeParameter> formalParameters,
|
||||||
Class<?> returnType, List<CodeLine> code) {
|
Class<?> returnType, List<CodeLine> code) {
|
||||||
|
this.name = name;
|
||||||
this.accessFlags.addAll(accessFlags);
|
this.accessFlags.addAll(accessFlags);
|
||||||
this.formalParameters.addAll(formalParameters);
|
this.formalParameters.addAll(formalParameters);
|
||||||
this.returnType = returnType;
|
this.returnType = returnType;
|
||||||
|
|
@ -23,15 +26,38 @@ public final class BeeMethod extends CodeContainer {
|
||||||
return new Builder();
|
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 {
|
public static class Builder {
|
||||||
private final Set<MethodAccessFlags> accessFlags = new HashSet<>();
|
private final Set<MethodAccessFlags> accessFlags = new HashSet<>();
|
||||||
private final List<BeeParameter> formalParameters = new LinkedList<>();
|
private final List<BeeParameter> formalParameters = new LinkedList<>();
|
||||||
private final List<CodeLine> code = new LinkedList<>();
|
private final List<CodeLine> code = new LinkedList<>();
|
||||||
private Class<?> returnType;
|
private String name;
|
||||||
|
private Class<?> returnType = Void.class;
|
||||||
|
|
||||||
private Builder() {
|
private Builder() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Builder withName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public Builder withAccessFlags(MethodAccessFlags... accessFlags) {
|
public Builder withAccessFlags(MethodAccessFlags... accessFlags) {
|
||||||
this.accessFlags.addAll(Arrays.asList(accessFlags));
|
this.accessFlags.addAll(Arrays.asList(accessFlags));
|
||||||
return this;
|
return this;
|
||||||
|
|
@ -53,7 +79,9 @@ public final class BeeMethod extends CodeContainer {
|
||||||
}
|
}
|
||||||
|
|
||||||
public BeeMethod build() {
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -141,7 +141,10 @@ public class BeeSource {
|
||||||
}
|
}
|
||||||
|
|
||||||
public BeeSource build() {
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,32 @@
|
||||||
package nl.sander.beejava.api;
|
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 {
|
public class CodeLine {
|
||||||
private final int linenumber;
|
private final int linenumber;
|
||||||
private final Opcode opcode;
|
private final Opcode opcode;
|
||||||
private Ref ref;
|
private Ref ref;
|
||||||
private BeeParameter parameter;
|
private BeeParameter parameter;
|
||||||
private String className;
|
private Class<?> type;
|
||||||
private String methodName;
|
private String methodName;
|
||||||
private String inputSignature;
|
private List<Class<?>> inputSignature;
|
||||||
private String outputSignature;
|
private String outputSignature;
|
||||||
private BeeField ownfield; // when you create a class with a field, you can refer to it
|
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 Object constValue;
|
||||||
private String externalFieldType;
|
|
||||||
|
private ConstantPoolEntry assignedEntry;
|
||||||
|
private CodeContainer owner;
|
||||||
|
|
||||||
CodeLine(int linenumber, Opcode opcode) {
|
CodeLine(int linenumber, Opcode opcode) {
|
||||||
this.linenumber = linenumber;
|
this.linenumber = linenumber;
|
||||||
|
|
@ -33,16 +45,31 @@ public class CodeLine {
|
||||||
return new CodeLine(nr, opcode).withConstValue(constValue);
|
return new CodeLine(nr, opcode).withConstValue(constValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static CodeLine line(int nr, Opcode opcode, String className, String methodName, String inputSignature) {
|
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(inputSignature).withVoidOutput();
|
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) {
|
private static List<Class<?>> parse(String inputSignature) throws ClassNotFoundException {
|
||||||
return new CodeLine(nr, opcode).withRef(ref).withMethodName(methodNameRef).withInput(inputSignature).withVoidOutput();
|
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) {
|
public static CodeLine line(int nr, Opcode opcode,
|
||||||
return new CodeLine(nr, opcode).withRef(ref).withMethodName(methodNameRef).withInput(inputSignature).withOutput(outputSignature);
|
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) {
|
public static CodeLine line(int nr, Opcode opcode) {
|
||||||
|
|
@ -63,7 +90,7 @@ public class CodeLine {
|
||||||
}
|
}
|
||||||
|
|
||||||
private CodeLine withClassName(String className) {
|
private CodeLine withClassName(String className) {
|
||||||
this.className = className;
|
this.type = loadClass(className);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -72,7 +99,7 @@ public class CodeLine {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
private CodeLine withInput(String inputSignature) {
|
private CodeLine withInput(List<Class<?>> inputSignature) {
|
||||||
this.inputSignature = inputSignature;
|
this.inputSignature = inputSignature;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
@ -102,14 +129,12 @@ public class CodeLine {
|
||||||
}
|
}
|
||||||
|
|
||||||
private CodeLine withExternalFieldRef(String className, String field) {
|
private CodeLine withExternalFieldRef(String className, String field) {
|
||||||
this.className = className;
|
this.type = loadClass(className);
|
||||||
this.externalFieldType = getType(className, field);
|
this.externalfield = loadField(field);
|
||||||
this.externalfield = field;
|
|
||||||
return this;
|
return this;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO decide whether to work with Strings or class objects...
|
// TODO decide whether to work with Strings or class objects...
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Look up the type of a field in a class
|
* Look up the type of a field in a class
|
||||||
|
|
@ -120,21 +145,21 @@ public class CodeLine {
|
||||||
* @param field name of the field
|
* @param field name of the field
|
||||||
* @return the field type
|
* @return the field type
|
||||||
*/
|
*/
|
||||||
private String getType(String className, String field) {
|
private Field loadField(String field) {
|
||||||
try {
|
try {
|
||||||
return Class.forName(className).getField(field).getType().getName();
|
return type.getField(field);
|
||||||
} catch (ClassNotFoundException | NoSuchFieldException e) {
|
} catch (NoSuchFieldException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasClassName() {
|
public boolean hasClassName() {
|
||||||
return className != null;
|
return type != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public String getClassName() {
|
public String getClassName() {
|
||||||
return className;
|
return type.getName();
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getMethodName() {
|
public String getMethodName() {
|
||||||
|
|
@ -146,7 +171,9 @@ public class CodeLine {
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getMethodSignature() {
|
public String getMethodSignature() {
|
||||||
return inputSignature + outputSignature;
|
return inputSignature.stream()
|
||||||
|
.map(TypeMapper::map)
|
||||||
|
.collect(Collectors.joining(",", "(", ")")) + outputSignature;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Ref getRef() {
|
public Ref getRef() {
|
||||||
|
|
@ -183,11 +210,35 @@ public class CodeLine {
|
||||||
return externalfield != null;
|
return externalfield != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getExternalfield() {
|
public Field getExternalfield() {
|
||||||
return externalfield;
|
return externalfield;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getExternalfieldClass() {
|
public ConstantPoolEntry getAssignedEntry() {
|
||||||
return externalFieldType;
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
||||||
|
|
@ -8,18 +8,14 @@ import nl.sander.beejava.util.ByteBuf;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
public class FieldInfo {
|
public class FieldInfo extends Info<FieldInfo> {
|
||||||
private final Set<FieldAccessFlags> accessFlags=new HashSet<>();
|
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) {
|
public FieldInfo(Utf8Entry nameEntry, Utf8Entry descriptorEntry) {
|
||||||
this.nameEntry = nameEntry;
|
super(nameEntry, descriptorEntry);
|
||||||
this.descriptorEntry = descriptorEntry;
|
|
||||||
}
|
}
|
||||||
public FieldInfo addAccessFlags(Set<FieldAccessFlags> accessFlags){
|
|
||||||
|
public FieldInfo addAccessFlags(Set<FieldAccessFlags> accessFlags) {
|
||||||
this.accessFlags.addAll(accessFlags);
|
this.accessFlags.addAll(accessFlags);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
@ -28,18 +24,6 @@ public class FieldInfo {
|
||||||
return accessFlags;
|
return accessFlags;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Utf8Entry getNameEntry() {
|
|
||||||
return nameEntry;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Utf8Entry getDescriptorEntry() {
|
|
||||||
return descriptorEntry;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Set<AttributeInfo> getAttributes() {
|
|
||||||
return attributes;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addBytes(ByteBuf buf) {
|
public void addBytes(ByteBuf buf) {
|
||||||
buf.addU16(AccessFlags.combine(accessFlags));
|
buf.addU16(AccessFlags.combine(accessFlags));
|
||||||
buf.addU16(nameEntry.getIndex());
|
buf.addU16(nameEntry.getIndex());
|
||||||
|
|
|
||||||
36
src/main/java/nl/sander/beejava/classinfo/Info.java
Normal file
36
src/main/java/nl/sander/beejava/classinfo/Info.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
28
src/main/java/nl/sander/beejava/classinfo/MethodInfo.java
Normal file
28
src/main/java/nl/sander/beejava/classinfo/MethodInfo.java
Normal 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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -64,4 +64,5 @@ public abstract class ConstantPoolEntry {
|
||||||
protected byte getByte(long bits, int positions) {
|
protected byte getByte(long bits, int positions) {
|
||||||
return (byte) ((bits >>> (positions * 8)) & 0xFF);
|
return (byte) ((bits >>> (positions * 8)) & 0xFF);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ public class FieldRefEntry extends ConstantPoolEntry {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public byte[] getBytes() {
|
public byte[] getBytes() {
|
||||||
return new byte[]{TAG};
|
return new byte[]{TAG, upperByte(getClassIndex()), lowerByte(getClassIndex()), upperByte(getNameAndTypeIndex()),lowerByte(getNameAndTypeIndex())};
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,10 @@ public class ByteBuf {
|
||||||
addU8((u16 >>> 8) & 0xFF, u16 & 0xFF);
|
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) {
|
private void enlarge(final int size) {
|
||||||
final int length1 = 2 * data.limit();
|
final int length1 = 2 * data.limit();
|
||||||
final int length2 = data.limit() + size;
|
final int length2 = data.limit() + size;
|
||||||
|
|
@ -56,4 +60,26 @@ public class ByteBuf {
|
||||||
data.get(arr);
|
data.get(arr);
|
||||||
return 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,8 @@ import java.io.IOException;
|
||||||
|
|
||||||
public class BytecodeGeneratorTests {
|
public class BytecodeGeneratorTests {
|
||||||
@Test
|
@Test
|
||||||
public void testEmpty() throws IOException {
|
public void testEmpty() throws IOException, ClassNotFoundException {
|
||||||
byte[] bytecode = BytecodeGenerator.generate(Compiler.compile(TestData.emptyClass()));
|
byte[] bytecode = BytecodeGenerator.generate(Compiler.compile(TestData.createEmptyClass()));
|
||||||
File dir = new File("target/nl/sander/beejava/test");
|
File dir = new File("target/nl/sander/beejava/test");
|
||||||
dir.mkdirs();
|
dir.mkdirs();
|
||||||
try (FileOutputStream outputStream = new FileOutputStream(new File(dir, "EmptyBean.class"))) {
|
try (FileOutputStream outputStream = new FileOutputStream(new File(dir, "EmptyBean.class"))) {
|
||||||
|
|
@ -18,12 +18,12 @@ public class BytecodeGeneratorTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testInterface() {
|
public void testInterface() throws ClassNotFoundException {
|
||||||
BytecodeGenerator.generate(Compiler.compile(TestData.emptyClassWithInterface()));
|
BytecodeGenerator.generate(Compiler.compile(TestData.emptyClassWithInterface()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testFields() {
|
public void testFields() throws ClassNotFoundException {
|
||||||
BytecodeGenerator.generate(Compiler.compile(TestData.createClassWithField(int.class)));
|
BytecodeGenerator.generate(Compiler.compile(TestData.createClassWithField(int.class)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,16 +14,16 @@ public class CompilerTests {
|
||||||
|
|
||||||
// creates simplest class possible and checks the tree, that the ConstantTreeCreator emits
|
// creates simplest class possible and checks the tree, that the ConstantTreeCreator emits
|
||||||
@Test // This is not a maintainable test
|
@Test // This is not a maintainable test
|
||||||
public void testMethodRefEntryForSuperConstructor() {
|
public void testMethodRefEntryForSuperConstructor() throws ClassNotFoundException {
|
||||||
// Arrange
|
// Arrange
|
||||||
BeeSource classWithIntField = TestData.emptyClass();
|
BeeSource classWithIntField = TestData.createEmptyClass();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
CompiledClass compiledClass = Compiler.compile(classWithIntField);
|
CompiledClass compiledClass = Compiler.compile(classWithIntField);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Set<ConstantPoolEntry> constantTree = compiledClass.getConstantTree();
|
Set<ConstantPoolEntry> constantTree = compiledClass.getConstantTree();
|
||||||
assertEquals(2, constantTree.size());
|
assertEquals(3, constantTree.size());
|
||||||
ConstantPoolEntry superConstructor = constantTree.iterator().next();
|
ConstantPoolEntry superConstructor = constantTree.iterator().next();
|
||||||
|
|
||||||
assertEquals(MethodRefEntry.class, superConstructor.getClass());
|
assertEquals(MethodRefEntry.class, superConstructor.getClass());
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,11 @@ package nl.sander.beejava;
|
||||||
import nl.sander.beejava.api.BeeSource;
|
import nl.sander.beejava.api.BeeSource;
|
||||||
import nl.sander.beejava.constantpool.ConstantPool;
|
import nl.sander.beejava.constantpool.ConstantPool;
|
||||||
import nl.sander.beejava.constantpool.entry.ClassEntry;
|
import nl.sander.beejava.constantpool.entry.ClassEntry;
|
||||||
|
import nl.sander.beejava.constantpool.entry.ConstantPoolEntry;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
|
@ -12,9 +15,9 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
public class ConstantPoolUniquenessTests {
|
public class ConstantPoolUniquenessTests {
|
||||||
@Test
|
@Test
|
||||||
public void test() {
|
public void test() throws Exception {
|
||||||
// Arrange
|
// Arrange
|
||||||
BeeSource someClass = TestData.createClassWithTwoReferencesToSomeClass();
|
BeeSource someClass = TestData.createEmptyClass();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
CompiledClass compiledClass = Compiler.compile(someClass);
|
CompiledClass compiledClass = Compiler.compile(someClass);
|
||||||
|
|
@ -27,6 +30,48 @@ public class ConstantPoolUniquenessTests {
|
||||||
.filter(ce -> ce.getName().equals("java/lang/System"))
|
.filter(ce -> ce.getName().equals("java/lang/System"))
|
||||||
.collect(Collectors.toList());
|
.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();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,20 +9,21 @@ import java.io.Serializable;
|
||||||
import static nl.sander.beejava.api.CodeLine.line;
|
import static nl.sander.beejava.api.CodeLine.line;
|
||||||
import static nl.sander.beejava.api.Opcode.*;
|
import static nl.sander.beejava.api.Opcode.*;
|
||||||
import static nl.sander.beejava.flags.ClassAccessFlags.PUBLIC;
|
import static nl.sander.beejava.flags.ClassAccessFlags.PUBLIC;
|
||||||
|
import static nl.sander.beejava.flags.ClassAccessFlags.SUPER;
|
||||||
|
|
||||||
public class TestData {
|
public class TestData {
|
||||||
public static BeeSource emptyClass() {
|
public static BeeSource createEmptyClass() throws ClassNotFoundException {
|
||||||
return BeeSource.builder()
|
return BeeSource.builder()
|
||||||
.withClassFileVersion(Version.V14)
|
.withClassFileVersion(Version.V14)
|
||||||
.withPackage("nl.sander.beejava.test")
|
.withPackage("nl.sander.beejava.test")
|
||||||
.withAccessFlags(PUBLIC)
|
.withAccessFlags(PUBLIC, SUPER)
|
||||||
.withSimpleName("EmptyBean")
|
.withSimpleName("EmptyBean")
|
||||||
.withSuperClass(Object.class) // Not mandatory, like in java sourcecode
|
.withSuperClass(Object.class) // Not mandatory, like in java sourcecode
|
||||||
.withConstructors(createConstructor()) // There's no default constructor in beejava. The user must always add them
|
.withConstructors(createConstructor()) // There's no default constructor in beejava. The user must always add them
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static BeeSource emptyClassWithInterface() {
|
public static BeeSource emptyClassWithInterface() throws ClassNotFoundException {
|
||||||
return BeeSource.builder()
|
return BeeSource.builder()
|
||||||
.withClassFileVersion(Version.V14)
|
.withClassFileVersion(Version.V14)
|
||||||
.withPackage("nl.sander.beejava.test")
|
.withPackage("nl.sander.beejava.test")
|
||||||
|
|
@ -34,36 +35,41 @@ public class TestData {
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static BeeSource createClassWithTwoReferencesToSomeClass() {
|
public static BeeSource createClassWithTwoReferencesToSomeClass() throws ClassNotFoundException {
|
||||||
BeeMethod print1 = BeeMethod.builder()
|
BeeMethod print1 = BeeMethod.builder()
|
||||||
|
.withName("print1")
|
||||||
.withAccessFlags(MethodAccessFlags.PUBLIC)
|
.withAccessFlags(MethodAccessFlags.PUBLIC)
|
||||||
.withCode(
|
.withCode(
|
||||||
line(0, GET, "java.lang.System","out"),
|
line(0, GET, "java.lang.System","out"),
|
||||||
line(1, LD_CONST, "1"),
|
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))
|
line(3, RETURN))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
// INVOKE System.out.println("1")
|
||||||
|
|
||||||
|
|
||||||
BeeMethod print2 = BeeMethod.builder()
|
BeeMethod print2 = BeeMethod.builder()
|
||||||
|
.withName("print2")
|
||||||
.withAccessFlags(MethodAccessFlags.PUBLIC)
|
.withAccessFlags(MethodAccessFlags.PUBLIC)
|
||||||
.withCode(
|
.withCode(
|
||||||
line(0, GET, "java.lang.System","out"),
|
line(0, GET, "java.lang.System","out"),
|
||||||
line(1, LD_CONST, "2"),
|
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))
|
line(3, RETURN))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
return BeeSource.builder()
|
return BeeSource.builder()
|
||||||
.withClassFileVersion(Version.V14)
|
.withClassFileVersion(Version.V14)
|
||||||
.withPackage("nl.sander.beejava.test")
|
.withPackage("nl.sander.beejava.test")
|
||||||
.withAccessFlags(PUBLIC)
|
.withAccessFlags(PUBLIC, SUPER)
|
||||||
.withSimpleName("ClassWithReferences")
|
.withSimpleName("ClassWithReferences")
|
||||||
.withConstructors(createConstructor()) // There's no default constructor in beejava. The user must always add them
|
.withConstructors(createConstructor()) // There's no default constructor in beejava. The user must always add them
|
||||||
.withMethods(print1, print2)
|
.withMethods(print1, print2)
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static BeeSource createClassWithField(Class<?> fieldType) {
|
public static BeeSource createClassWithField(Class<?> fieldType) throws ClassNotFoundException {
|
||||||
BeeField field = BeeField.builder()
|
BeeField field = BeeField.builder()
|
||||||
.withAccessFlags(FieldAccessFlags.PRIVATE)
|
.withAccessFlags(FieldAccessFlags.PRIVATE)
|
||||||
.withType(fieldType)
|
.withType(fieldType)
|
||||||
|
|
@ -95,7 +101,7 @@ public class TestData {
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static BeeConstructor createConstructor() {
|
private static BeeConstructor createConstructor() throws ClassNotFoundException {
|
||||||
return BeeConstructor.builder()
|
return BeeConstructor.builder()
|
||||||
.withAccessFlags(MethodAccessFlags.PUBLIC)
|
.withAccessFlags(MethodAccessFlags.PUBLIC)
|
||||||
.withCode(
|
.withCode(
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ import static org.junit.jupiter.api.Assertions.fail;
|
||||||
public class TypeMapperTest {
|
public class TypeMapperTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void test_int() {
|
public void test_int() throws ClassNotFoundException {
|
||||||
// Arrange
|
// Arrange
|
||||||
BeeSource beeSource = TestData.createClassWithField(int.class);
|
BeeSource beeSource = TestData.createClassWithField(int.class);
|
||||||
|
|
||||||
|
|
@ -24,7 +24,7 @@ public class TypeMapperTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void test_double() {
|
public void test_double() throws ClassNotFoundException {
|
||||||
// Arrange
|
// Arrange
|
||||||
BeeSource beeSource = TestData.createClassWithField(double.class);
|
BeeSource beeSource = TestData.createClassWithField(double.class);
|
||||||
|
|
||||||
|
|
@ -37,7 +37,7 @@ public class TypeMapperTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void test_float() {
|
public void test_float() throws ClassNotFoundException {
|
||||||
// Arrange
|
// Arrange
|
||||||
BeeSource beeSource = TestData.createClassWithField(float.class);
|
BeeSource beeSource = TestData.createClassWithField(float.class);
|
||||||
|
|
||||||
|
|
@ -50,7 +50,7 @@ public class TypeMapperTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void test_byte() {
|
public void test_byte() throws ClassNotFoundException {
|
||||||
// Arrange
|
// Arrange
|
||||||
BeeSource beeSource = TestData.createClassWithField(byte.class);
|
BeeSource beeSource = TestData.createClassWithField(byte.class);
|
||||||
|
|
||||||
|
|
@ -63,7 +63,7 @@ public class TypeMapperTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void test_short() {
|
public void test_short() throws ClassNotFoundException {
|
||||||
// Arrange
|
// Arrange
|
||||||
BeeSource beeSource = TestData.createClassWithField(short.class);
|
BeeSource beeSource = TestData.createClassWithField(short.class);
|
||||||
|
|
||||||
|
|
@ -76,7 +76,7 @@ public class TypeMapperTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void test_long() {
|
public void test_long() throws ClassNotFoundException {
|
||||||
// Arrange
|
// Arrange
|
||||||
BeeSource beeSource = TestData.createClassWithField(long.class);
|
BeeSource beeSource = TestData.createClassWithField(long.class);
|
||||||
|
|
||||||
|
|
@ -89,7 +89,7 @@ public class TypeMapperTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void test_char() {
|
public void test_char() throws ClassNotFoundException {
|
||||||
// Arrange
|
// Arrange
|
||||||
BeeSource beeSource = TestData.createClassWithField(char.class);
|
BeeSource beeSource = TestData.createClassWithField(char.class);
|
||||||
|
|
||||||
|
|
@ -102,7 +102,7 @@ public class TypeMapperTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void test_boolean() {
|
public void test_boolean() throws ClassNotFoundException {
|
||||||
// Arrange
|
// Arrange
|
||||||
BeeSource beeSource = TestData.createClassWithField(boolean.class);
|
BeeSource beeSource = TestData.createClassWithField(boolean.class);
|
||||||
|
|
||||||
|
|
@ -115,7 +115,7 @@ public class TypeMapperTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void test_Object() {
|
public void test_Object() throws ClassNotFoundException {
|
||||||
// Arrange
|
// Arrange
|
||||||
BeeSource beeSource = TestData.createClassWithField(Object.class);
|
BeeSource beeSource = TestData.createClassWithField(Object.class);
|
||||||
|
|
||||||
|
|
@ -124,11 +124,11 @@ public class TypeMapperTest {
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
NameAndTypeEntry fieldEntry = getFieldNameAndType(constantPool);
|
NameAndTypeEntry fieldEntry = getFieldNameAndType(constantPool);
|
||||||
assertEquals("Ljava/lang/Object", fieldEntry.getType());
|
assertEquals("Ljava/lang/Object;", fieldEntry.getType());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void test_int_array() {
|
public void test_int_array() throws ClassNotFoundException {
|
||||||
// Arrange
|
// Arrange
|
||||||
BeeSource beeSource = TestData.createClassWithField(int[].class);
|
BeeSource beeSource = TestData.createClassWithField(int[].class);
|
||||||
|
|
||||||
|
|
@ -141,7 +141,7 @@ public class TypeMapperTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void test_Object_array() {
|
public void test_Object_array() throws ClassNotFoundException {
|
||||||
// Arrange
|
// Arrange
|
||||||
BeeSource beeSource = TestData.createClassWithField(String[].class);
|
BeeSource beeSource = TestData.createClassWithField(String[].class);
|
||||||
|
|
||||||
|
|
|
||||||
21
src/test/java/nl/sander/beejava/e2e/ByteClassLoader.java
Normal file
21
src/test/java/nl/sander/beejava/e2e/ByteClassLoader.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
40
src/test/java/nl/sander/beejava/e2e/EmptyBeanTest.java
Normal file
40
src/test/java/nl/sander/beejava/e2e/EmptyBeanTest.java
Normal 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);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -8,12 +8,7 @@ public class BeanWithClassReferences {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void print2() {
|
public void print2() {
|
||||||
System.out.println(create2());
|
System.out.println("2");
|
||||||
}
|
}
|
||||||
|
|
||||||
public String create2() {
|
|
||||||
StringBuilder stringBuilder = new StringBuilder();
|
|
||||||
stringBuilder.append("2");
|
|
||||||
return stringBuilder.toString();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
7
src/test/java/nl/sander/beejava/testclasses/Dummy.java
Normal file
7
src/test/java/nl/sander/beejava/testclasses/Dummy.java
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
package nl.sander.beejava.testclasses;
|
||||||
|
|
||||||
|
public class Dummy {
|
||||||
|
public String hello(String goodbye){
|
||||||
|
return "hello";
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue