Review Comments

Refactor generator calls to use a visitor type pattern to avoid duplicating
the class writer boiler plate. Each generate<Type> call passes its own
function to the wrapping class that writes the boiler plate and then
calls back into the type specific methods before returning bacl to
finlaise the class writer and load it into the class loader

Remove unkotlinesq spaces
This commit is contained in:
Katelyn Baker 2017-06-22 16:02:02 +01:00
parent a330360834
commit 9d2905e125
2 changed files with 60 additions and 50 deletions

View File

@ -77,8 +77,13 @@ class ClassCarpenter {
fun fieldsIncludingSuperclasses(): Map<String, Class<out Any?>> = (superclass?.fieldsIncludingSuperclasses() ?: emptyMap()) + LinkedHashMap(fields) fun fieldsIncludingSuperclasses(): Map<String, Class<out Any?>> = (superclass?.fieldsIncludingSuperclasses() ?: emptyMap()) + LinkedHashMap(fields)
fun descriptorsIncludingSuperclasses(): Map<String, String> = (superclass?.descriptorsIncludingSuperclasses() ?: emptyMap()) + LinkedHashMap(descriptors) fun descriptorsIncludingSuperclasses(): Map<String, String> = (superclass?.descriptorsIncludingSuperclasses() ?: emptyMap()) + LinkedHashMap(descriptors)
val jvmName : String
get() = name.replace (".", "/")
} }
private val String.jvm: String get() = replace(".", "/")
class ClassSchema( class ClassSchema(
name: String, name: String,
fields: Map<String, Class<out Any?>>, fields: Map<String, Class<out Any?>>,
@ -106,8 +111,6 @@ class ClassCarpenter {
/** Returns a snapshot of the currently loaded classes as a map of full class name (package names+dots) -> class object */ /** Returns a snapshot of the currently loaded classes as a map of full class name (package names+dots) -> class object */
val loaded: Map<String, Class<*>> = HashMap(_loaded) val loaded: Map<String, Class<*>> = HashMap(_loaded)
private val String.jvm: String get() = replace(".", "/")
/** /**
* Generate bytecode for the given schema and load into the JVM. The returned class object can be used to * Generate bytecode for the given schema and load into the JVM. The returned class object can be used to
* construct instances of the generated class. * construct instances of the generated class.
@ -125,52 +128,59 @@ class ClassCarpenter {
hierarchy += cursor hierarchy += cursor
cursor = cursor.superclass cursor = cursor.superclass
} }
hierarchy.reversed().forEach { hierarchy.reversed().forEach {
when (it) { when (it) {
is ClassSchema -> generateClass(it)
is InterfaceSchema -> generateInterface(it) is InterfaceSchema -> generateInterface(it)
is ClassSchema -> generateClass(it)
} }
} }
return _loaded[schema.name]!! return _loaded[schema.name]!!
} }
private fun generateClass(schema: ClassSchema): Class<*> { private fun generateInterface (schema: Schema): Class<*> {
val jvmName = schema.name.jvm return generate (schema) { cw, schema ->
// Lazy: we could compute max locals/max stack ourselves, it'd be faster.
val cw = ClassWriter(ClassWriter.COMPUTE_FRAMES or ClassWriter.COMPUTE_MAXS)
with(cw) {
// public class Name implements SimpleFieldAccess {
val superName = schema.superclass?.name?.jvm ?: "java/lang/Object"
val interfaces = arrayOf(SimpleFieldAccess::class.java.name.jvm) + schema.interfaces.map { it.name.jvm }
visit(52, ACC_PUBLIC + ACC_SUPER, jvmName, null, superName, interfaces)
generateFields(schema)
generateConstructor(jvmName, schema)
generateGetters(jvmName, schema)
if (schema.superclass == null)
generateGetMethod() // From SimplePropertyAccess
generateToString(jvmName, schema)
visitEnd()
}
val clazz = classloader.load(schema.name, cw.toByteArray())
_loaded[schema.name] = clazz
return clazz
}
private fun generateInterface(schema: InterfaceSchema): Class<*> {
val jvmName = schema.name.jvm
// Lazy: we could compute max locals/max stack ourselves, it'd be faster.
val cw = ClassWriter (ClassWriter.COMPUTE_FRAMES or ClassWriter.COMPUTE_MAXS)
with(cw) {
val interfaces = schema.interfaces.map { it.name.jvm }.toTypedArray() val interfaces = schema.interfaces.map { it.name.jvm }.toTypedArray()
visit(V1_8, ACC_PUBLIC + ACC_ABSTRACT + ACC_INTERFACE, jvmName, null, "java/lang/Object", interfaces) with (cw) {
visit(V1_8, ACC_PUBLIC + ACC_ABSTRACT + ACC_INTERFACE, schema.jvmName, null, "java/lang/Object", interfaces)
generateAbstractGetters(schema) generateAbstractGetters(schema)
visitEnd() visitEnd()
}
} }
val clazz = classloader.load(schema.name, cw.toByteArray()) }
private fun generateClass (schema: Schema): Class<*> {
return generate (schema) { cw, schema ->
val superName = schema.superclass?.jvmName ?: "java/lang/Object"
val interfaces = arrayOf(SimpleFieldAccess::class.java.name.jvm) + schema.interfaces.map { it.name.jvm }
with (cw) {
visit(V1_8, ACC_PUBLIC + ACC_SUPER, schema.jvmName, null, superName, interfaces)
generateFields(schema)
generateConstructor(schema)
generateGetters(schema)
if (schema.superclass == null)
generateGetMethod() // From SimplePropertyAccess
generateToString(schema)
visitEnd()
}
}
}
private fun generate(schema: Schema, generator : (ClassWriter, Schema) -> Unit): Class<*> {
// Lazy: we could compute max locals/max stack ourselves, it'd be faster.
val cw = ClassWriter (ClassWriter.COMPUTE_FRAMES or ClassWriter.COMPUTE_MAXS)
generator (cw, schema)
val clazz = classloader.load(schema.name, cw.toByteArray())
_loaded[schema.name] = clazz _loaded[schema.name] = clazz
return clazz return clazz
} }
@ -181,7 +191,7 @@ class ClassCarpenter {
} }
} }
private fun ClassWriter.generateToString(jvmName: String, schema: Schema) { private fun ClassWriter.generateToString(schema: Schema) {
val toStringHelper = "com/google/common/base/MoreObjects\$ToStringHelper" val toStringHelper = "com/google/common/base/MoreObjects\$ToStringHelper"
with(visitMethod(ACC_PUBLIC, "toString", "()Ljava/lang/String;", "", null)) { with(visitMethod(ACC_PUBLIC, "toString", "()Ljava/lang/String;", "", null)) {
visitCode() visitCode()
@ -192,7 +202,7 @@ class ClassCarpenter {
for ((name, type) in schema.fieldsIncludingSuperclasses().entries) { for ((name, type) in schema.fieldsIncludingSuperclasses().entries) {
visitLdcInsn(name) visitLdcInsn(name)
visitVarInsn(ALOAD, 0) // this visitVarInsn(ALOAD, 0) // this
visitFieldInsn(GETFIELD, jvmName, name, schema.descriptorsIncludingSuperclasses()[name]) visitFieldInsn(GETFIELD, schema.jvmName, name, schema.descriptorsIncludingSuperclasses()[name])
val desc = if (type.isPrimitive) schema.descriptors[name] else "Ljava/lang/Object;" val desc = if (type.isPrimitive) schema.descriptors[name] else "Ljava/lang/Object;"
visitMethodInsn(INVOKEVIRTUAL, toStringHelper, "add", "(Ljava/lang/String;$desc)L$toStringHelper;", false) visitMethodInsn(INVOKEVIRTUAL, toStringHelper, "add", "(Ljava/lang/String;$desc)L$toStringHelper;", false)
} }
@ -220,13 +230,13 @@ class ClassCarpenter {
} }
} }
private fun ClassWriter.generateGetters(jvmName: String, schema: Schema) { private fun ClassWriter.generateGetters(schema: Schema) {
for ((name, type) in schema.fields) { for ((name, type) in schema.fields) {
val descriptor = schema.descriptors[name] val descriptor = schema.descriptors[name]
with(visitMethod(ACC_PUBLIC, "get" + name.capitalize(), "()" + descriptor, null, null)) { with(visitMethod(ACC_PUBLIC, "get" + name.capitalize(), "()" + descriptor, null, null)) {
visitCode() visitCode()
visitVarInsn(ALOAD, 0) // Load 'this' visitVarInsn(ALOAD, 0) // Load 'this'
visitFieldInsn(GETFIELD, jvmName, name, descriptor) visitFieldInsn(GETFIELD, schema.jvmName, name, descriptor)
when (type) { when (type) {
java.lang.Boolean.TYPE, Integer.TYPE, java.lang.Short.TYPE, java.lang.Byte.TYPE, TYPE -> visitInsn(IRETURN) java.lang.Boolean.TYPE, Integer.TYPE, java.lang.Short.TYPE, java.lang.Byte.TYPE, TYPE -> visitInsn(IRETURN)
java.lang.Long.TYPE -> visitInsn(LRETURN) java.lang.Long.TYPE -> visitInsn(LRETURN)
@ -240,7 +250,7 @@ class ClassCarpenter {
} }
} }
private fun ClassWriter.generateAbstractGetters(schema: InterfaceSchema) { private fun ClassWriter.generateAbstractGetters(schema: Schema) {
for ((name, type) in schema.fields) { for ((name, type) in schema.fields) {
val descriptor = schema.descriptors[name] val descriptor = schema.descriptors[name]
val opcodes = ACC_ABSTRACT + ACC_PUBLIC val opcodes = ACC_ABSTRACT + ACC_PUBLIC
@ -251,7 +261,7 @@ class ClassCarpenter {
} }
} }
private fun ClassWriter.generateConstructor(jvmName: String, schema: Schema) { private fun ClassWriter.generateConstructor(schema: Schema) {
with(visitMethod(ACC_PUBLIC, "<init>", "(" + schema.descriptorsIncludingSuperclasses().values.joinToString("") + ")V", null, null)) { with(visitMethod(ACC_PUBLIC, "<init>", "(" + schema.descriptorsIncludingSuperclasses().values.joinToString("") + ")V", null, null)) {
visitCode() visitCode()
// Calculate the super call. // Calculate the super call.
@ -273,7 +283,7 @@ class ClassCarpenter {
throw UnsupportedOperationException("Array types are not implemented yet") throw UnsupportedOperationException("Array types are not implemented yet")
visitVarInsn(ALOAD, 0) // Load 'this' onto the stack visitVarInsn(ALOAD, 0) // Load 'this' onto the stack
slot += load(slot, type) // Load the contents of the parameter onto the stack. slot += load(slot, type) // Load the contents of the parameter onto the stack.
visitFieldInsn(PUTFIELD, jvmName, name, schema.descriptors[name]) visitFieldInsn(PUTFIELD, schema.jvmName, name, schema.descriptors[name])
} }
visitInsn(RETURN) visitInsn(RETURN)
visitMaxs(0, 0) visitMaxs(0, 0)

View File

@ -137,12 +137,12 @@ class ClassCarpenterTest {
val schema1 = ClassCarpenter.InterfaceSchema("gen.Interface", mapOf("a" to Int::class.java)) val schema1 = ClassCarpenter.InterfaceSchema("gen.Interface", mapOf("a" to Int::class.java))
val iface = cc.build(schema1) val iface = cc.build(schema1)
assert (iface.isInterface()) assert(iface.isInterface())
assert (iface.constructors.isEmpty()) assert(iface.constructors.isEmpty())
assertEquals (iface.declaredMethods.size, 1) assertEquals(iface.declaredMethods.size, 1)
assertEquals (iface.declaredMethods[0].name, "getA") assertEquals(iface.declaredMethods[0].name, "getA")
val schema2 = ClassCarpenter.ClassSchema("gen.Derived", mapOf("a" to Int::class.java), interfaces = listOf (iface)) val schema2 = ClassCarpenter.ClassSchema("gen.Derived", mapOf("a" to Int::class.java), interfaces = listOf(iface))
val clazz = cc.build(schema2) val clazz = cc.build(schema2)
val testA = 42 val testA = 42
val i = clazz.constructors[0].newInstance(testA) as SimpleFieldAccess val i = clazz.constructors[0].newInstance(testA) as SimpleFieldAccess
@ -162,7 +162,7 @@ class ClassCarpenterTest {
"b" to String::class.java, "b" to String::class.java,
"c" to Int::class.java, "c" to Int::class.java,
"d" to String::class.java), "d" to String::class.java),
interfaces = listOf (cc.build (iFace1), cc.build (iFace2))) interfaces = listOf(cc.build(iFace1), cc.build(iFace2)))
val clazz = cc.build(class1) val clazz = cc.build(class1)
val testA = 42 val testA = 42
@ -181,7 +181,7 @@ class ClassCarpenterTest {
fun `interface implementing interface`() { fun `interface implementing interface`() {
val iFace1 = ClassCarpenter.InterfaceSchema( val iFace1 = ClassCarpenter.InterfaceSchema(
"gen.Interface1", "gen.Interface1",
mapOf ( mapOf(
"a" to Int::class.java, "a" to Int::class.java,
"b" to String::class.java)) "b" to String::class.java))
@ -190,7 +190,7 @@ class ClassCarpenterTest {
mapOf( mapOf(
"c" to Int::class.java, "c" to Int::class.java,
"d" to String::class.java), "d" to String::class.java),
interfaces = listOf (cc.build (iFace1))) interfaces = listOf(cc.build(iFace1)))
val class1 = ClassCarpenter.ClassSchema( val class1 = ClassCarpenter.ClassSchema(
"gen.Derived", "gen.Derived",
@ -199,7 +199,7 @@ class ClassCarpenterTest {
"b" to String::class.java, "b" to String::class.java,
"c" to Int::class.java, "c" to Int::class.java,
"d" to String::class.java), "d" to String::class.java),
interfaces = listOf (cc.build (iFace2))) interfaces = listOf(cc.build(iFace2)))
val clazz = cc.build(class1) val clazz = cc.build(class1)
val testA = 99 val testA = 99