diff --git a/src/main/java/nl/sander/beejava/BytecodeGenerator.java b/src/main/java/nl/sander/beejava/BytecodeGenerator.java index ac6ecdc..10ff698 100644 --- a/src/main/java/nl/sander/beejava/BytecodeGenerator.java +++ b/src/main/java/nl/sander/beejava/BytecodeGenerator.java @@ -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(); - } - } - } - - } diff --git a/src/main/java/nl/sander/beejava/CodeContainer.java b/src/main/java/nl/sander/beejava/CodeContainer.java index 79f3dd6..63404da 100644 --- a/src/main/java/nl/sander/beejava/CodeContainer.java +++ b/src/main/java/nl/sander/beejava/CodeContainer.java @@ -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 code = new LinkedList<>(); + protected final Set formalParameters = new HashSet<>(); + protected final Set accessFlags = new HashSet<>(); + private BeeSource owner; public List 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 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; + } } diff --git a/src/main/java/nl/sander/beejava/CompiledClass.java b/src/main/java/nl/sander/beejava/CompiledClass.java index 83dc265..df75aba 100644 --- a/src/main/java/nl/sander/beejava/CompiledClass.java +++ b/src/main/java/nl/sander/beejava/CompiledClass.java @@ -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 constantTree = new LinkedHashSet<>(); private final Set interfaces = new HashSet<>(); private final Set fields = new HashSet<>(); + private final Set 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 getFields() { return fields; } + + public void addMethod(MethodInfo methodInfo) { + methods.add(methodInfo); + } + + public Set getMethods() { + return methods; + } + + public void setConstantPool(ConstantPool constantPool) { + this.constantPool = constantPool; + } + + public ConstantPool getConstantPool() { + return constantPool; + } } diff --git a/src/main/java/nl/sander/beejava/Compiler.java b/src/main/java/nl/sander/beejava/Compiler.java index c43cb85..6308a75 100644 --- a/src/main/java/nl/sander/beejava/Compiler.java +++ b/src/main/java/nl/sander/beejava/Compiler.java @@ -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); } } } diff --git a/src/main/java/nl/sander/beejava/ConstantPoolCreator.java b/src/main/java/nl/sander/beejava/ConstantPoolCreator.java index 366c13a..24d6e15 100644 --- a/src/main/java/nl/sander/beejava/ConstantPoolCreator.java +++ b/src/main/java/nl/sander/beejava/ConstantPoolCreator.java @@ -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 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 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 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); diff --git a/src/main/java/nl/sander/beejava/ConstantPoolEntryCreator.java b/src/main/java/nl/sander/beejava/ConstantPoolEntryCreator.java index 4a71d6b..ed4a5b0 100644 --- a/src/main/java/nl/sander/beejava/ConstantPoolEntryCreator.java +++ b/src/main/java/nl/sander/beejava/ConstantPoolEntryCreator.java @@ -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,38 +21,52 @@ 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 + if (codeline.getRef() == Ref.SUPER) { // this and super are rather special ClassEntry superClass = getClassEntry(compiledClass.getSource().getSuperClass().getName()); compiledClass.setSuperClass(superClass); return superClass; @@ -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. } - } diff --git a/src/main/java/nl/sander/beejava/JavaOpcodes.java b/src/main/java/nl/sander/beejava/JavaOpcodes.java new file mode 100644 index 0000000..53e68b1 --- /dev/null +++ b/src/main/java/nl/sander/beejava/JavaOpcodes.java @@ -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; + +} diff --git a/src/main/java/nl/sander/beejava/MethodCodeCreator.java b/src/main/java/nl/sander/beejava/MethodCodeCreator.java new file mode 100644 index 0000000..1a330d2 --- /dev/null +++ b/src/main/java/nl/sander/beejava/MethodCodeCreator.java @@ -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 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; + } +} diff --git a/src/main/java/nl/sander/beejava/OpcodeMapper.java b/src/main/java/nl/sander/beejava/OpcodeMapper.java new file mode 100644 index 0000000..a464a67 --- /dev/null +++ b/src/main/java/nl/sander/beejava/OpcodeMapper.java @@ -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; + } +} diff --git a/src/main/java/nl/sander/beejava/TypeMapper.java b/src/main/java/nl/sander/beejava/TypeMapper.java index d12199f..cbd5aee 100644 --- a/src/main/java/nl/sander/beejava/TypeMapper.java +++ b/src/main/java/nl/sander/beejava/TypeMapper.java @@ -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() + ';'); } }); } diff --git a/src/main/java/nl/sander/beejava/api/BeeConstructor.java b/src/main/java/nl/sander/beejava/api/BeeConstructor.java index 0c9b554..01fedfa 100644 --- a/src/main/java/nl/sander/beejava/api/BeeConstructor.java +++ b/src/main/java/nl/sander/beejava/api/BeeConstructor.java @@ -9,8 +9,6 @@ import java.util.*; * Models a constructor */ public class BeeConstructor extends CodeContainer { - private final Set accessFlags = new HashSet<>(); - private final Set formalParameters = new HashSet<>(); private BeeConstructor(Set accessFlags, List formalParameters, @@ -24,12 +22,9 @@ public class BeeConstructor extends CodeContainer { return new Builder(); } - Set getAccessFlags() { - return accessFlags; - } - Set getFormalParameters() { - return formalParameters; + public String getName() { + return ""; } @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; } } } diff --git a/src/main/java/nl/sander/beejava/api/BeeMethod.java b/src/main/java/nl/sander/beejava/api/BeeMethod.java index aa0f2ba..bfc043b 100644 --- a/src/main/java/nl/sander/beejava/api/BeeMethod.java +++ b/src/main/java/nl/sander/beejava/api/BeeMethod.java @@ -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 accessFlags = new HashSet<>(); - private final Set formalParameters = new HashSet<>(); + private final String name; + private final Class returnType; - private BeeMethod(Set accessFlags, + private BeeMethod(String name, Set accessFlags, List formalParameters, Class returnType, List 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 , then the descriptor must denote a void method. + * -If the name of the method is , 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 accessFlags = new HashSet<>(); private final List formalParameters = new LinkedList<>(); private final List 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; } } } diff --git a/src/main/java/nl/sander/beejava/api/BeeSource.java b/src/main/java/nl/sander/beejava/api/BeeSource.java index 5dc57fd..a32b25d 100644 --- a/src/main/java/nl/sander/beejava/api/BeeSource.java +++ b/src/main/java/nl/sander/beejava/api/BeeSource.java @@ -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; } } diff --git a/src/main/java/nl/sander/beejava/api/CodeLine.java b/src/main/java/nl/sander/beejava/api/CodeLine.java index ca83150..42ee36c 100644 --- a/src/main/java/nl/sander/beejava/api/CodeLine.java +++ b/src/main/java/nl/sander/beejava/api/CodeLine.java @@ -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> 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> parse(String inputSignature) throws ClassNotFoundException { + if ("()".equals(inputSignature)) { + return Collections.emptyList(); + } else { + String[] params = inputSignature.split(","); + List> 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> inputSignature) { this.inputSignature = inputSignature; return this; } @@ -102,14 +129,12 @@ 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... +// TODO decide whether to work with Strings or class objects... /* * Look up the type of a field in a class @@ -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; } } diff --git a/src/main/java/nl/sander/beejava/classinfo/AttributeInfo.java b/src/main/java/nl/sander/beejava/classinfo/AttributeInfo.java deleted file mode 100644 index 089ed97..0000000 --- a/src/main/java/nl/sander/beejava/classinfo/AttributeInfo.java +++ /dev/null @@ -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); -} diff --git a/src/main/java/nl/sander/beejava/classinfo/FieldInfo.java b/src/main/java/nl/sander/beejava/classinfo/FieldInfo.java index 68ffd2a..b4e824a 100644 --- a/src/main/java/nl/sander/beejava/classinfo/FieldInfo.java +++ b/src/main/java/nl/sander/beejava/classinfo/FieldInfo.java @@ -8,18 +8,14 @@ import nl.sander.beejava.util.ByteBuf; import java.util.HashSet; import java.util.Set; -public class FieldInfo { - private final Set accessFlags=new HashSet<>(); - private final Utf8Entry nameEntry; - private final Utf8Entry descriptorEntry; - private final Set attributes=new HashSet<>(); - +public class FieldInfo extends Info { + private final Set accessFlags = new HashSet<>(); public FieldInfo(Utf8Entry nameEntry, Utf8Entry descriptorEntry) { - this.nameEntry = nameEntry; - this.descriptorEntry = descriptorEntry; + super(nameEntry, descriptorEntry); } - public FieldInfo addAccessFlags(Set accessFlags){ + + public FieldInfo addAccessFlags(Set 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 getAttributes() { - return attributes; - } - public void addBytes(ByteBuf buf) { buf.addU16(AccessFlags.combine(accessFlags)); buf.addU16(nameEntry.getIndex()); diff --git a/src/main/java/nl/sander/beejava/classinfo/Info.java b/src/main/java/nl/sander/beejava/classinfo/Info.java new file mode 100644 index 0000000..67a6a56 --- /dev/null +++ b/src/main/java/nl/sander/beejava/classinfo/Info.java @@ -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 { + + protected final Utf8Entry nameEntry; + protected final Utf8Entry descriptorEntry; + protected final Set 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 getAttributes() { + return attributes; + } + + public T addAttribute(Attribute attribute) { + attributes.add(attribute); + return (T) this; + } +} diff --git a/src/main/java/nl/sander/beejava/classinfo/MethodInfo.java b/src/main/java/nl/sander/beejava/classinfo/MethodInfo.java new file mode 100644 index 0000000..377ae7b --- /dev/null +++ b/src/main/java/nl/sander/beejava/classinfo/MethodInfo.java @@ -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 { + private final Set accessFlags = new HashSet<>(); + + public MethodInfo(Utf8Entry nameEntry, Utf8Entry descriptorEntry) { + super(nameEntry, descriptorEntry); + } + public MethodInfo addAccessFlags(Set 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)); + } +} diff --git a/src/main/java/nl/sander/beejava/classinfo/attributes/Attribute.java b/src/main/java/nl/sander/beejava/classinfo/attributes/Attribute.java new file mode 100644 index 0000000..7be52b7 --- /dev/null +++ b/src/main/java/nl/sander/beejava/classinfo/attributes/Attribute.java @@ -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); +} diff --git a/src/main/java/nl/sander/beejava/classinfo/attributes/CodeAttribute.java b/src/main/java/nl/sander/beejava/classinfo/attributes/CodeAttribute.java new file mode 100644 index 0000000..1ae13ed --- /dev/null +++ b/src/main/java/nl/sander/beejava/classinfo/attributes/CodeAttribute.java @@ -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 attributes = new HashSet<>(); + private final Set 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; + } +} diff --git a/src/main/java/nl/sander/beejava/classinfo/attributes/ExceptionHandler.java b/src/main/java/nl/sander/beejava/classinfo/attributes/ExceptionHandler.java new file mode 100644 index 0000000..494ec0d --- /dev/null +++ b/src/main/java/nl/sander/beejava/classinfo/attributes/ExceptionHandler.java @@ -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 +} diff --git a/src/main/java/nl/sander/beejava/constantpool/entry/ConstantPoolEntry.java b/src/main/java/nl/sander/beejava/constantpool/entry/ConstantPoolEntry.java index 66a964e..0cced65 100644 --- a/src/main/java/nl/sander/beejava/constantpool/entry/ConstantPoolEntry.java +++ b/src/main/java/nl/sander/beejava/constantpool/entry/ConstantPoolEntry.java @@ -64,4 +64,5 @@ public abstract class ConstantPoolEntry { protected byte getByte(long bits, int positions) { return (byte) ((bits >>> (positions * 8)) & 0xFF); } + } diff --git a/src/main/java/nl/sander/beejava/constantpool/entry/FieldRefEntry.java b/src/main/java/nl/sander/beejava/constantpool/entry/FieldRefEntry.java index 3c79854..b668892 100644 --- a/src/main/java/nl/sander/beejava/constantpool/entry/FieldRefEntry.java +++ b/src/main/java/nl/sander/beejava/constantpool/entry/FieldRefEntry.java @@ -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 diff --git a/src/main/java/nl/sander/beejava/util/ByteBuf.java b/src/main/java/nl/sander/beejava/util/ByteBuf.java index 1f3d606..41136cb 100644 --- a/src/main/java/nl/sander/beejava/util/ByteBuf.java +++ b/src/main/java/nl/sander/beejava/util/ByteBuf.java @@ -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); + } + } + + } diff --git a/src/test/java/nl/sander/beejava/BytecodeGeneratorTests.java b/src/test/java/nl/sander/beejava/BytecodeGeneratorTests.java index 7388a67..43ca295 100644 --- a/src/test/java/nl/sander/beejava/BytecodeGeneratorTests.java +++ b/src/test/java/nl/sander/beejava/BytecodeGeneratorTests.java @@ -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))); } } diff --git a/src/test/java/nl/sander/beejava/CompilerTests.java b/src/test/java/nl/sander/beejava/CompilerTests.java index ae54faa..528888f 100644 --- a/src/test/java/nl/sander/beejava/CompilerTests.java +++ b/src/test/java/nl/sander/beejava/CompilerTests.java @@ -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 constantTree = compiledClass.getConstantTree(); - assertEquals(2, constantTree.size()); + assertEquals(3, constantTree.size()); ConstantPoolEntry superConstructor = constantTree.iterator().next(); assertEquals(MethodRefEntry.class, superConstructor.getClass()); diff --git a/src/test/java/nl/sander/beejava/ConstantPoolUniquenessTests.java b/src/test/java/nl/sander/beejava/ConstantPoolUniquenessTests.java index 134dd6d..f995170 100644 --- a/src/test/java/nl/sander/beejava/ConstantPoolUniquenessTests.java +++ b/src/test/java/nl/sander/beejava/ConstantPoolUniquenessTests.java @@ -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(); + } + } } } diff --git a/src/test/java/nl/sander/beejava/TestData.java b/src/test/java/nl/sander/beejava/TestData.java index 13f64ac..f438b81 100644 --- a/src/test/java/nl/sander/beejava/TestData.java +++ b/src/test/java/nl/sander/beejava/TestData.java @@ -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( diff --git a/src/test/java/nl/sander/beejava/TypeMapperTest.java b/src/test/java/nl/sander/beejava/TypeMapperTest.java index a31f18a..519bc45 100644 --- a/src/test/java/nl/sander/beejava/TypeMapperTest.java +++ b/src/test/java/nl/sander/beejava/TypeMapperTest.java @@ -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); diff --git a/src/test/java/nl/sander/beejava/e2e/ByteClassLoader.java b/src/test/java/nl/sander/beejava/e2e/ByteClassLoader.java new file mode 100644 index 0000000..ec07c65 --- /dev/null +++ b/src/test/java/nl/sander/beejava/e2e/ByteClassLoader.java @@ -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 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); + } +} diff --git a/src/test/java/nl/sander/beejava/e2e/EmptyBeanTest.java b/src/test/java/nl/sander/beejava/e2e/EmptyBeanTest.java new file mode 100644 index 0000000..30017fb --- /dev/null +++ b/src/test/java/nl/sander/beejava/e2e/EmptyBeanTest.java @@ -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); + + } +} diff --git a/src/test/java/nl/sander/beejava/testclasses/BeanWithClassReferences.java b/src/test/java/nl/sander/beejava/testclasses/BeanWithClassReferences.java index 7e816e6..9122074 100644 --- a/src/test/java/nl/sander/beejava/testclasses/BeanWithClassReferences.java +++ b/src/test/java/nl/sander/beejava/testclasses/BeanWithClassReferences.java @@ -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(); - } } diff --git a/src/test/java/nl/sander/beejava/testclasses/Dummy.java b/src/test/java/nl/sander/beejava/testclasses/Dummy.java new file mode 100644 index 0000000..53f052b --- /dev/null +++ b/src/test/java/nl/sander/beejava/testclasses/Dummy.java @@ -0,0 +1,7 @@ +package nl.sander.beejava.testclasses; + +public class Dummy { + public String hello(String goodbye){ + return "hello"; + } +}