mirror of
https://github.com/corda/corda.git
synced 2024-12-19 21:17:58 +00:00
Add Interface synthesis to the carpenter
Currently the carpenter only generates concreate classes, add the ability to also synthesise interfaces and have those interfaces used by other gnerated classes
This commit is contained in:
parent
aaf7de0d02
commit
a330360834
@ -71,7 +71,7 @@ class ClassCarpenter {
|
||||
/**
|
||||
* A Schema represents a desired class.
|
||||
*/
|
||||
class Schema(val name: String, fields: Map<String, Class<out Any?>>, val superclass: Schema? = null, val interfaces: List<Class<*>> = emptyList()) {
|
||||
open class Schema(val name: String, fields: Map<String, Class<out Any?>>, val superclass: Schema? = null, val interfaces: List<Class<*>> = emptyList()) {
|
||||
val fields = LinkedHashMap(fields) // Fix the order up front if the user didn't.
|
||||
val descriptors = fields.map { it.key to Type.getDescriptor(it.value) }.toMap()
|
||||
|
||||
@ -79,6 +79,20 @@ class ClassCarpenter {
|
||||
fun descriptorsIncludingSuperclasses(): Map<String, String> = (superclass?.descriptorsIncludingSuperclasses() ?: emptyMap()) + LinkedHashMap(descriptors)
|
||||
}
|
||||
|
||||
class ClassSchema(
|
||||
name: String,
|
||||
fields: Map<String, Class<out Any?>>,
|
||||
superclass: Schema? = null,
|
||||
interfaces: List<Class<*>> = emptyList()
|
||||
) : Schema (name, fields, superclass, interfaces)
|
||||
|
||||
class InterfaceSchema(
|
||||
name: String,
|
||||
fields: Map<String, Class<out Any?>>,
|
||||
superclass: Schema? = null,
|
||||
interfaces: List<Class<*>> = emptyList()
|
||||
) : Schema (name, fields, superclass, interfaces)
|
||||
|
||||
class DuplicateName : RuntimeException("An attempt was made to register two classes with the same name within the same ClassCarpenter namespace.")
|
||||
class InterfaceMismatch(msg: String) : RuntimeException(msg)
|
||||
|
||||
@ -111,11 +125,16 @@ class ClassCarpenter {
|
||||
hierarchy += cursor
|
||||
cursor = cursor.superclass
|
||||
}
|
||||
hierarchy.reversed().forEach { generateClass(it) }
|
||||
hierarchy.reversed().forEach {
|
||||
when (it) {
|
||||
is ClassSchema -> generateClass(it)
|
||||
is InterfaceSchema -> generateInterface(it)
|
||||
}
|
||||
}
|
||||
return _loaded[schema.name]!!
|
||||
}
|
||||
|
||||
private fun generateClass(schema: Schema): Class<*> {
|
||||
private fun generateClass(schema: ClassSchema): 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)
|
||||
@ -137,6 +156,25 @@ class ClassCarpenter {
|
||||
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()
|
||||
|
||||
visit(V1_8, ACC_PUBLIC + ACC_ABSTRACT + ACC_INTERFACE, jvmName, null, "java/lang/Object", interfaces)
|
||||
|
||||
generateAbstractGetters(schema)
|
||||
|
||||
visitEnd()
|
||||
}
|
||||
val clazz = classloader.load(schema.name, cw.toByteArray())
|
||||
|
||||
_loaded[schema.name] = clazz
|
||||
return clazz
|
||||
}
|
||||
|
||||
private fun ClassWriter.generateFields(schema: Schema) {
|
||||
for ((name, desc) in schema.descriptors) {
|
||||
visitField(ACC_PROTECTED + ACC_FINAL, name, desc, null, null).visitEnd()
|
||||
@ -202,6 +240,17 @@ class ClassCarpenter {
|
||||
}
|
||||
}
|
||||
|
||||
private fun ClassWriter.generateAbstractGetters(schema: InterfaceSchema) {
|
||||
for ((name, type) in schema.fields) {
|
||||
val descriptor = schema.descriptors[name]
|
||||
val opcodes = ACC_ABSTRACT + ACC_PUBLIC
|
||||
with (visitMethod(opcodes, "get" + name.capitalize(), "()" + descriptor, null, null)) {
|
||||
// abstract method doesn't have any implementation so just end
|
||||
visitEnd()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun ClassWriter.generateConstructor(jvmName: String, schema: Schema) {
|
||||
with(visitMethod(ACC_PUBLIC, "<init>", "(" + schema.descriptorsIncludingSuperclasses().values.joinToString("") + ")V", null, null)) {
|
||||
visitCode()
|
||||
@ -262,7 +311,8 @@ class ClassCarpenter {
|
||||
method.name.startsWith("get") -> method.name.substring(3).decapitalize()
|
||||
else -> throw InterfaceMismatch("Requested interfaces must consist only of methods that start with 'get': ${itf.name}.${method.name}")
|
||||
}
|
||||
if (fieldNameFromItf !in allFields)
|
||||
|
||||
if ((schema is ClassSchema) and (fieldNameFromItf !in allFields))
|
||||
throw InterfaceMismatch("Interface ${itf.name} requires a field named ${fieldNameFromItf} but that isn't found in the schema or any superclass schemas")
|
||||
}
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ class ClassCarpenterTest {
|
||||
|
||||
@Test
|
||||
fun empty() {
|
||||
val clazz = cc.build(ClassCarpenter.Schema("gen.EmptyClass", emptyMap(), null))
|
||||
val clazz = cc.build(ClassCarpenter.ClassSchema("gen.EmptyClass", emptyMap(), null))
|
||||
assertEquals(0, clazz.nonSyntheticFields.size)
|
||||
assertEquals(2, clazz.nonSyntheticMethods.size) // get, toString
|
||||
assertEquals(0, clazz.declaredConstructors[0].parameterCount)
|
||||
@ -30,7 +30,7 @@ class ClassCarpenterTest {
|
||||
|
||||
@Test
|
||||
fun prims() {
|
||||
val clazz = cc.build(ClassCarpenter.Schema("gen.Prims", mapOf(
|
||||
val clazz = cc.build(ClassCarpenter.ClassSchema("gen.Prims", mapOf(
|
||||
"anIntField" to Int::class.javaPrimitiveType!!,
|
||||
"aLongField" to Long::class.javaPrimitiveType!!,
|
||||
"someCharField" to Char::class.javaPrimitiveType!!,
|
||||
@ -65,7 +65,7 @@ class ClassCarpenterTest {
|
||||
}
|
||||
|
||||
private fun genPerson(): Pair<Class<*>, Any> {
|
||||
val clazz = cc.build(ClassCarpenter.Schema("gen.Person", mapOf(
|
||||
val clazz = cc.build(ClassCarpenter.ClassSchema("gen.Person", mapOf(
|
||||
"age" to Int::class.javaPrimitiveType!!,
|
||||
"name" to String::class.java
|
||||
)))
|
||||
@ -88,14 +88,14 @@ class ClassCarpenterTest {
|
||||
|
||||
@Test(expected = ClassCarpenter.DuplicateName::class)
|
||||
fun duplicates() {
|
||||
cc.build(ClassCarpenter.Schema("gen.EmptyClass", emptyMap(), null))
|
||||
cc.build(ClassCarpenter.Schema("gen.EmptyClass", emptyMap(), null))
|
||||
cc.build(ClassCarpenter.ClassSchema("gen.EmptyClass", emptyMap(), null))
|
||||
cc.build(ClassCarpenter.ClassSchema("gen.EmptyClass", emptyMap(), null))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `can refer to each other`() {
|
||||
val (clazz1, i) = genPerson()
|
||||
val clazz2 = cc.build(ClassCarpenter.Schema("gen.Referee", mapOf(
|
||||
val clazz2 = cc.build(ClassCarpenter.ClassSchema("gen.Referee", mapOf(
|
||||
"ref" to clazz1
|
||||
)))
|
||||
val i2 = clazz2.constructors[0].newInstance(i)
|
||||
@ -104,8 +104,8 @@ class ClassCarpenterTest {
|
||||
|
||||
@Test
|
||||
fun superclasses() {
|
||||
val schema1 = ClassCarpenter.Schema("gen.A", mapOf("a" to String::class.java))
|
||||
val schema2 = ClassCarpenter.Schema("gen.B", mapOf("b" to String::class.java), schema1)
|
||||
val schema1 = ClassCarpenter.ClassSchema("gen.A", mapOf("a" to String::class.java))
|
||||
val schema2 = ClassCarpenter.ClassSchema("gen.B", mapOf("b" to String::class.java), schema1)
|
||||
val clazz = cc.build(schema2)
|
||||
val i = clazz.constructors[0].newInstance("xa", "xb") as SimpleFieldAccess
|
||||
assertEquals("xa", i["a"])
|
||||
@ -115,8 +115,8 @@ class ClassCarpenterTest {
|
||||
|
||||
@Test
|
||||
fun interfaces() {
|
||||
val schema1 = ClassCarpenter.Schema("gen.A", mapOf("a" to String::class.java))
|
||||
val schema2 = ClassCarpenter.Schema("gen.B", mapOf("b" to Int::class.java), schema1, interfaces = listOf(DummyInterface::class.java))
|
||||
val schema1 = ClassCarpenter.ClassSchema("gen.A", mapOf("a" to String::class.java))
|
||||
val schema2 = ClassCarpenter.ClassSchema("gen.B", mapOf("b" to Int::class.java), schema1, interfaces = listOf(DummyInterface::class.java))
|
||||
val clazz = cc.build(schema2)
|
||||
val i = clazz.constructors[0].newInstance("xa", 1) as DummyInterface
|
||||
assertEquals("xa", i.a)
|
||||
@ -125,10 +125,92 @@ class ClassCarpenterTest {
|
||||
|
||||
@Test(expected = ClassCarpenter.InterfaceMismatch::class)
|
||||
fun `mismatched interface`() {
|
||||
val schema1 = ClassCarpenter.Schema("gen.A", mapOf("a" to String::class.java))
|
||||
val schema2 = ClassCarpenter.Schema("gen.B", mapOf("c" to Int::class.java), schema1, interfaces = listOf(DummyInterface::class.java))
|
||||
val schema1 = ClassCarpenter.ClassSchema("gen.A", mapOf("a" to String::class.java))
|
||||
val schema2 = ClassCarpenter.ClassSchema("gen.B", mapOf("c" to Int::class.java), schema1, interfaces = listOf(DummyInterface::class.java))
|
||||
val clazz = cc.build(schema2)
|
||||
val i = clazz.constructors[0].newInstance("xa", 1) as DummyInterface
|
||||
assertEquals(1, i.b)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `generate interface`() {
|
||||
val schema1 = ClassCarpenter.InterfaceSchema("gen.Interface", mapOf("a" to Int::class.java))
|
||||
val iface = cc.build(schema1)
|
||||
|
||||
assert (iface.isInterface())
|
||||
assert (iface.constructors.isEmpty())
|
||||
assertEquals (iface.declaredMethods.size, 1)
|
||||
assertEquals (iface.declaredMethods[0].name, "getA")
|
||||
|
||||
val schema2 = ClassCarpenter.ClassSchema("gen.Derived", mapOf("a" to Int::class.java), interfaces = listOf (iface))
|
||||
val clazz = cc.build(schema2)
|
||||
val testA = 42
|
||||
val i = clazz.constructors[0].newInstance(testA) as SimpleFieldAccess
|
||||
|
||||
assertEquals(testA, i["a"])
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `generate multiple interfaces`() {
|
||||
val iFace1 = ClassCarpenter.InterfaceSchema("gen.Interface1", mapOf("a" to Int::class.java, "b" to String::class.java))
|
||||
val iFace2 = ClassCarpenter.InterfaceSchema("gen.Interface2", mapOf("c" to Int::class.java, "d" to String::class.java))
|
||||
|
||||
val class1 = ClassCarpenter.ClassSchema(
|
||||
"gen.Derived",
|
||||
mapOf(
|
||||
"a" to Int::class.java,
|
||||
"b" to String::class.java,
|
||||
"c" to Int::class.java,
|
||||
"d" to String::class.java),
|
||||
interfaces = listOf (cc.build (iFace1), cc.build (iFace2)))
|
||||
|
||||
val clazz = cc.build(class1)
|
||||
val testA = 42
|
||||
val testB = "don't touch me, I'm scared"
|
||||
val testC = 0xDEAD
|
||||
val testD = "wibble"
|
||||
val i = clazz.constructors[0].newInstance(testA, testB, testC, testD) as SimpleFieldAccess
|
||||
|
||||
assertEquals(testA, i["a"])
|
||||
assertEquals(testB, i["b"])
|
||||
assertEquals(testC, i["c"])
|
||||
assertEquals(testD, i["d"])
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `interface implementing interface`() {
|
||||
val iFace1 = ClassCarpenter.InterfaceSchema(
|
||||
"gen.Interface1",
|
||||
mapOf (
|
||||
"a" to Int::class.java,
|
||||
"b" to String::class.java))
|
||||
|
||||
val iFace2 = ClassCarpenter.InterfaceSchema(
|
||||
"gen.Interface2",
|
||||
mapOf(
|
||||
"c" to Int::class.java,
|
||||
"d" to String::class.java),
|
||||
interfaces = listOf (cc.build (iFace1)))
|
||||
|
||||
val class1 = ClassCarpenter.ClassSchema(
|
||||
"gen.Derived",
|
||||
mapOf(
|
||||
"a" to Int::class.java,
|
||||
"b" to String::class.java,
|
||||
"c" to Int::class.java,
|
||||
"d" to String::class.java),
|
||||
interfaces = listOf (cc.build (iFace2)))
|
||||
|
||||
val clazz = cc.build(class1)
|
||||
val testA = 99
|
||||
val testB = "green is not a creative colour"
|
||||
val testC = 7
|
||||
val testD = "I like jam"
|
||||
val i = clazz.constructors[0].newInstance(testA, testB, testC, testD) as SimpleFieldAccess
|
||||
|
||||
assertEquals(testA, i["a"])
|
||||
assertEquals(testB, i["b"])
|
||||
assertEquals(testC, i["c"])
|
||||
assertEquals(testD, i["d"])
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user