From ce172887d009fa81c1a9709bae4abd71f4faae10 Mon Sep 17 00:00:00 2001 From: Katelyn Baker Date: Fri, 16 Jun 2017 09:05:47 +0100 Subject: [PATCH] Unit Tests for the amqp -> carpenter schema Squahed commit mesages: * Better tests * WIP * WIP --- .../corda/core/serialization/amqp/Schema.kt | 86 +++- .../serialization/carpenter/ClassCarpenter.kt | 60 +-- .../core/serialization/carpenter/Schema.kt | 44 ++ .../carpenter/ClassCarpenterTest.kt | 29 +- .../carpenter/ClassCarpenterSchemaTests.kt | 263 ------------ ...berCompositeSchemaToClassCarpenterTests.kt | 16 +- .../InheritanceSchemaToClassCarpenterTests.kt | 389 ++++++++++++++++++ ...berCompositeSchemaToClassCarpenterTests.kt | 4 +- ...berCompositeSchemaToClassCarpenterTests.kt | 17 +- 9 files changed, 560 insertions(+), 348 deletions(-) create mode 100644 core/src/main/kotlin/net/corda/core/serialization/carpenter/Schema.kt delete mode 100644 experimental/src/test/kotlin/net/corda/carpenter/ClassCarpenterSchemaTests.kt create mode 100644 experimental/src/test/kotlin/net/corda/carpenter/InheritanceSchemaToClassCarpenterTests.kt diff --git a/core/src/main/kotlin/net/corda/core/serialization/amqp/Schema.kt b/core/src/main/kotlin/net/corda/core/serialization/amqp/Schema.kt index 0117379e6a..2c34698e42 100644 --- a/core/src/main/kotlin/net/corda/core/serialization/amqp/Schema.kt +++ b/core/src/main/kotlin/net/corda/core/serialization/amqp/Schema.kt @@ -15,6 +15,8 @@ import java.lang.reflect.Type import java.lang.reflect.TypeVariable import java.util.* +import net.corda.core.serialization.ClassCarpenterSchema + // TODO: get an assigned number as per AMQP spec val DESCRIPTOR_TOP_32BITS: Long = 0xc0da0000 @@ -87,9 +89,37 @@ data class Schema(val types: List) : DescribedType { override fun getDescribed(): Any = listOf(types) override fun toString(): String = types.joinToString("\n") + + fun carpenterSchema(loaders : List = listOf(ClassLoader.getSystemClassLoader())) + : List + { + var rtn = mutableListOf() + + for (type in types) { + if (type is CompositeType) { + var foundIt = false + for (loader in loaders) { + try { + loader.loadClass(type.name) + foundIt = true + break + } catch (e: ClassNotFoundException) { + continue + } + } + + if (foundIt) continue + else { + rtn.add(type.carpenterSchema()) + } + } + } + + return rtn + } } -data class Descriptor(val name: String?, val code: UnsignedLong? = null) : DescribedType { +data class Descriptor(var name: String?, val code: UnsignedLong? = null) : DescribedType { companion object : DescribedTypeConstructor { val DESCRIPTOR = UnsignedLong(3L or DESCRIPTOR_TOP_32BITS) @@ -127,7 +157,7 @@ data class Descriptor(val name: String?, val code: UnsignedLong? = null) : Descr } } -data class Field(val name: String, val type: String, val requires: List, val default: String?, val label: String?, val mandatory: Boolean, val multiple: Boolean) : DescribedType { +data class Field(var name: String, val type: String, val requires: List, val default: String?, val label: String?, val mandatory: Boolean, val multiple: Boolean) : DescribedType { companion object : DescribedTypeConstructor { val DESCRIPTOR = UnsignedLong(4L or DESCRIPTOR_TOP_32BITS) @@ -169,6 +199,16 @@ data class Field(val name: String, val type: String, val requires: List, return sb.toString() } + inline fun isKnownClass (type : String) : Class<*> { + try { + return ClassLoader.getSystemClassLoader().loadClass(type) + } + catch (e: ClassNotFoundException) { + // call carpenter + throw IllegalArgumentException ("${type} - ${name} - pants") + } + } + fun getPrimType() = when (type) { "int" -> Int::class.javaPrimitiveType!! "string" -> String::class.java @@ -178,14 +218,8 @@ data class Field(val name: String, val type: String, val requires: List, "boolean" -> Boolean::class.javaPrimitiveType!! "double" -> Double::class.javaPrimitiveType!! "float" -> Double::class.javaPrimitiveType!! - else -> { - try { - ClassLoader.getSystemClassLoader().loadClass(type) - } - catch (e: ClassNotFoundException) { - throw IllegalArgumentException ("pants") - } - } + "*" -> isKnownClass(requires[0]) + else -> isKnownClass(type) } } @@ -203,13 +237,13 @@ sealed class TypeNotation : DescribedType { } } - abstract val name: String + abstract var name: String abstract val label: String? abstract val provides: List abstract val descriptor: Descriptor } -data class CompositeType(override val name: String, override val label: String?, override val provides: List, override val descriptor: Descriptor, val fields: List) : TypeNotation() { +data class CompositeType(override var name: String, override val label: String?, override val provides: List, override val descriptor: Descriptor, val fields: List) : TypeNotation() { companion object : DescribedTypeConstructor { val DESCRIPTOR = UnsignedLong(5L or DESCRIPTOR_TOP_32BITS) @@ -253,16 +287,36 @@ data class CompositeType(override val name: String, override val label: String?, return sb.toString() } - fun carpenterSchema() : Map> { + fun carpenterSchema(classLoaders: List = listOf (ClassLoader.getSystemClassLoader())) : ClassCarpenterSchema { var m : MutableMap> = mutableMapOf() fields.forEach { m[it.name] = it.getPrimType() } - return m + var providesList = mutableListOf>() + + for (iface in provides) { + var found = false + for (loader in classLoaders) { + try { + providesList.add (loader.loadClass(iface)) + found = true + break + } + catch (e: ClassNotFoundException) { + continue + } + } + if (found == false) { + println ("This needs to work but it wont - ${iface}") + } + else println ("found it ${iface}") + } + + return ClassCarpenterSchema (name, m, interfaces = providesList) } } -data class RestrictedType(override val name: String, override val label: String?, override val provides: List, val source: String, override val descriptor: Descriptor, val choices: List) : TypeNotation() { +data class RestrictedType(override var name: String, override val label: String?, override val provides: List, val source: String, override val descriptor: Descriptor, val choices: List) : TypeNotation() { companion object : DescribedTypeConstructor { val DESCRIPTOR = UnsignedLong(6L or DESCRIPTOR_TOP_32BITS) @@ -305,7 +359,7 @@ data class RestrictedType(override val name: String, override val label: String? } } -data class Choice(val name: String, val value: String) : DescribedType { +data class Choice(var name: String, val value: String) : DescribedType { companion object : DescribedTypeConstructor { val DESCRIPTOR = UnsignedLong(7L or DESCRIPTOR_TOP_32BITS) diff --git a/core/src/main/kotlin/net/corda/core/serialization/carpenter/ClassCarpenter.kt b/core/src/main/kotlin/net/corda/core/serialization/carpenter/ClassCarpenter.kt index 5f0f1abd99..efda30bea4 100644 --- a/core/src/main/kotlin/net/corda/core/serialization/carpenter/ClassCarpenter.kt +++ b/core/src/main/kotlin/net/corda/core/serialization/carpenter/ClassCarpenter.kt @@ -159,58 +159,18 @@ class ClassCarpenter { } } - /** - * A Schema represents a desired class. - */ - abstract class Schema( - val name: String, - fields: Map, - val superclass: Schema? = null, - val interfaces: List> = emptyList()) - { - private fun Map.descriptors() = - LinkedHashMap(this.mapValues { it.value.descriptor }) - - /* Fix the order up front if the user didn't, inject the name into the field as it's - neater when iterating */ - val fields = LinkedHashMap(fields.mapValues { it.value.copy(it.key, it.value.field) }) - - fun fieldsIncludingSuperclasses(): Map = - (superclass?.fieldsIncludingSuperclasses() ?: emptyMap()) + LinkedHashMap(fields) - - fun descriptorsIncludingSuperclasses(): Map = - (superclass?.descriptorsIncludingSuperclasses() ?: emptyMap()) + fields.descriptors() - - val jvmName: String - get() = name.replace(".", "/") - } - - private val String.jvm: String get() = replace(".", "/") - - class ClassSchema( - name: String, - fields: Map, - superclass: Schema? = null, - interfaces: List> = emptyList() - ) : Schema(name, fields, superclass, interfaces) - - class InterfaceSchema( - name: String, - fields: Map, - superclass: Schema? = null, - interfaces: List> = emptyList() - ) : Schema(name, fields, superclass, interfaces) - private class CarpenterClassLoader : ClassLoader(Thread.currentThread().contextClassLoader) { fun load(name: String, bytes: ByteArray) = defineClass(name, bytes, 0, bytes.size) } private val classloader = CarpenterClassLoader() + fun classLoader() = classloader as ClassLoader + private val _loaded = HashMap>() /** Returns a snapshot of the currently loaded classes as a map of full class name (package names+dots) -> class object */ - val loaded: Map> = HashMap(_loaded) + fun loaded() : Map> = HashMap(_loaded) /** * Generate bytecode for the given schema and load into the JVM. The returned class object can be used to @@ -218,18 +178,17 @@ class ClassCarpenter { * * @throws DuplicateNameException if the schema's name is already taken in this namespace (you can create a new ClassCarpenter if you're OK with ambiguous names) */ - fun build(schema: Schema): Class<*> { + fun build(schema: ClassCarpenterSchema): Class<*> { validateSchema(schema) // Walk up the inheritance hierarchy and then start walking back down once we either hit the top, or // find a class we haven't generated yet. - val hierarchy = ArrayList() + val hierarchy = ArrayList() hierarchy += schema var cursor = schema.superclass while (cursor != null && cursor.name !in _loaded) { hierarchy += cursor cursor = cursor.superclass } - hierarchy.reversed().forEach { when (it) { is InterfaceSchema -> generateInterface(it) @@ -346,6 +305,7 @@ class ClassCarpenter { visitEnd() } } + } private fun ClassWriter.generateAbstractGetters(schema: Schema) { @@ -374,14 +334,16 @@ class ClassCarpenter { // Calculate the super call. val superclassFields = schema.superclass?.fieldsIncludingSuperclasses() ?: emptyMap() visitVarInsn(ALOAD, 0) - if (schema.superclass == null) { + val sc = schema.superclass + if (sc == null) { visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "", "()V", false) } else { var slot = 1 for (fieldType in superclassFields.values) slot += load(slot, fieldType) - val superDesc = schema.superclass.descriptorsIncludingSuperclasses().values.joinToString("") - visitMethodInsn(INVOKESPECIAL, schema.superclass.name.jvm, "", "($superDesc)V", false) + //val superDesc = schema.superclass.descriptorsIncludingSuperclasses().values.joinToString("") + val superDesc = sc.descriptorsIncludingSuperclasses().values.joinToString("") + visitMethodInsn(INVOKESPECIAL, sc.name.jvm, "", "($superDesc)V", false) } // Assign the fields from parameters. diff --git a/core/src/main/kotlin/net/corda/core/serialization/carpenter/Schema.kt b/core/src/main/kotlin/net/corda/core/serialization/carpenter/Schema.kt new file mode 100644 index 0000000000..0a2b73b490 --- /dev/null +++ b/core/src/main/kotlin/net/corda/core/serialization/carpenter/Schema.kt @@ -0,0 +1,44 @@ +package net.corda.core.serialization.carpenter + +import org.objectweb.asm.Type +import java.util.LinkedHashMap + +/** + * A Schema represents a desired class. + */ +abstract class Schema( + val name: String, + fields: Map, + val superclass: Schema? = null, + val interfaces: List> = emptyList()) +{ + private fun Map.descriptors() = + LinkedHashMap(this.mapValues { it.value.descriptor }) + + /* Fix the order up front if the user didn't, inject the name into the field as it's + neater when iterating */ + val fields = LinkedHashMap(fields.mapValues { it.value.copy(it.key, it.value.field) }) + + fun fieldsIncludingSuperclasses(): Map = + (superclass?.fieldsIncludingSuperclasses() ?: emptyMap()) + LinkedHashMap(fields) + + fun descriptorsIncludingSuperclasses(): Map = + (superclass?.descriptorsIncludingSuperclasses() ?: emptyMap()) + fields.descriptors() + + val jvmName: String + get() = name.replace(".", "/") +} + +class ClassSchema( + name: String, + fields: Map>, + superclass: Schema? = null, + interfaces: List> = emptyList() +) : ClassCarpenter.Schema (name, fields, superclass, interfaces) + +class InterfaceSchema( + name: String, + fields: Map>, + superclass: Schema? = null, + interfaces: List> = emptyList() +) : ClassCarpenter.Schema (name, fields, superclass, interfaces) diff --git a/core/src/test/kotlin/net/corda/core/serialization/carpenter/ClassCarpenterTest.kt b/core/src/test/kotlin/net/corda/core/serialization/carpenter/ClassCarpenterTest.kt index 020d907e5f..fbe6fa31b5 100644 --- a/core/src/test/kotlin/net/corda/core/serialization/carpenter/ClassCarpenterTest.kt +++ b/core/src/test/kotlin/net/corda/core/serialization/carpenter/ClassCarpenterTest.kt @@ -8,11 +8,31 @@ import java.beans.Introspector import kotlin.test.assertNotEquals class ClassCarpenterTest { + /* + cw.visitInnerClass( + "net/corda/carpenter/ClassCarpenterTest$DummyInterface", + "net/corda/carpenter/ClassCarpenterTest", + "DummyInterface", + ACC_PUBLIC + ACC_STATIC + ACC_ABSTRACT + ACC_INTERFACE); + */ interface DummyInterface { val a: String val b: Int } + /* + cw.visitInnerClass( + "net/corda/carpenter/ClassCarpenterTest$Dummy", + "net/corda/carpenter/ClassCarpenterTest", + "Dummy", + ACC_PUBLIC + ACC_FINAL + ACC_STATIC); + */ + class Dummy (override val a: String, override val b: Int) : DummyInterface + + val dummy = Dummy ("hi", 1) + val dummy2 = Dummy2 ("hi", 1) + + val cc = ClassCarpenter() // We have to ignore synthetic fields even though ClassCarpenter doesn't create any because the JaCoCo @@ -22,7 +42,7 @@ class ClassCarpenterTest { @Test fun empty() { - val clazz = cc.build(ClassCarpenter.ClassSchema("gen.EmptyClass", emptyMap(), null)) + val clazz = cc.build(ClassSchema("gen.EmptyClass", emptyMap(), null)) assertEquals(0, clazz.nonSyntheticFields.size) assertEquals(2, clazz.nonSyntheticMethods.size) // get, toString assertEquals(0, clazz.declaredConstructors[0].parameterCount) @@ -69,7 +89,7 @@ class ClassCarpenterTest { } private fun genPerson(): Pair, Any> { - val clazz = cc.build(ClassCarpenter.ClassSchema("gen.Person", mapOf( + val clazz = cc.build(ClassSchema("gen.Person", mapOf( "age" to Int::class.javaPrimitiveType!!, "name" to String::class.java ).mapValues { ClassCarpenter.NonNullableField (it.value) } )) @@ -92,8 +112,8 @@ class ClassCarpenterTest { @Test(expected = ClassCarpenter.DuplicateNameException::class) fun duplicates() { - cc.build(ClassCarpenter.ClassSchema("gen.EmptyClass", emptyMap(), null)) - cc.build(ClassCarpenter.ClassSchema("gen.EmptyClass", emptyMap(), null)) + cc.build(ClassSchema("gen.EmptyClass", emptyMap(), null)) + cc.build(ClassSchema("gen.EmptyClass", emptyMap(), null)) } @Test @@ -134,6 +154,7 @@ class ClassCarpenterTest { mapOf("b" to ClassCarpenter.NonNullableField(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) diff --git a/experimental/src/test/kotlin/net/corda/carpenter/ClassCarpenterSchemaTests.kt b/experimental/src/test/kotlin/net/corda/carpenter/ClassCarpenterSchemaTests.kt deleted file mode 100644 index b287706106..0000000000 --- a/experimental/src/test/kotlin/net/corda/carpenter/ClassCarpenterSchemaTests.kt +++ /dev/null @@ -1,263 +0,0 @@ -package net.corda.carpenter - -import net.corda.core.serialization.CordaSerializable -import net.corda.core.serialization.amqp.* -import org.junit.Test -import kotlin.test.assertEquals - - -class ClassCarpenterScehmaTestsSingleMemberComposite { - private var factory = SerializerFactory() - - fun serialise (clazz : Any) = SerializationOutput(factory).serialize(clazz) - - @Test - fun singleInteger() { - val test = 10 - - @CordaSerializable - data class A(val a : Int) - var a = A (test) - - val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise (a)) - - assert (obj.first is A) - val amqpObj = obj.first as A - - assertEquals (test, amqpObj.a) - assertEquals (1, obj.second.schema.types.size) - assert (obj.second.schema.types[0] is CompositeType) - - var amqpSchema = obj.second.schema.types[0] as CompositeType - - assertEquals (1, amqpSchema.fields.size) - assertEquals ("a", amqpSchema.fields[0].name) - assertEquals ("int", amqpSchema.fields[0].type) - - var pinochio = ClassCarpenter().build(ClassCarpenter.Schema(amqpSchema.name, amqpSchema.carpenterSchema())) - - val p = pinochio.constructors[0].newInstance (test) - - assertEquals (pinochio.getMethod("getA").invoke (p), amqpObj.a) - } - - @Test - fun singleString() { - val test = "ten" - - @CordaSerializable - data class A(val a : String) - var a = A (test) - - val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise (a)) - - assert (obj.first is A) - val amqpObj = obj.first as A - - assertEquals (test, amqpObj.a) - assertEquals (1, obj.second.schema.types.size) - assert (obj.second.schema.types[0] is CompositeType) - - var amqpSchema = obj.second.schema.types[0] as CompositeType - - assertEquals (1, amqpSchema.fields.size) - assertEquals ("a", amqpSchema.fields[0].name) - assertEquals ("string", amqpSchema.fields[0].type) - - var pinochio = ClassCarpenter().build(ClassCarpenter.Schema(amqpSchema.name, amqpSchema.carpenterSchema())) - - val p = pinochio.constructors[0].newInstance (test) - - assertEquals (pinochio.getMethod("getA").invoke (p), amqpObj.a) - } - - /* - @Test - fun singleChar () { - val test = 'c' - - @CordaSerializable - data class A(val a : Char) - var a = A (test) - - val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise (a)) - - assert (obj.first is A) - val amqpObj = obj.first as A - - assertEquals (test, amqpObj.a) - assertEquals (1, obj.second.schema.types.size) - assertEquals (1, obj.second.schema.types.size) - assert (obj.second.schema.types[0] is CompositeType) - - var amqpSchema = obj.second.schema.types[0] as CompositeType - - assertEquals (1, amqpSchema.fields.size) - assertEquals ("a", amqpSchema.fields[0].name) - assertEquals ("char", amqpSchema.fields[0].type) - - var pinochio = ClassCarpenter().build(ClassCarpenter.Schema(amqpSchema.name, amqpSchema.carpenterSchema())) - - val p = pinochio.constructors[0].newInstance (test) - - assertEquals (pinochio.getMethod("getA").invoke (p), amqpObj.a) - } - */ - - @Test - fun singleLong() { - val test = 10L - - @CordaSerializable - data class A(val a : Long) - - var a = A (test) - - val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise (a)) - - assert (obj.first is A) - val amqpObj = obj.first as A - - assertEquals (test, amqpObj.a) - assertEquals (1, obj.second.schema.types.size) - assert (obj.second.schema.types[0] is CompositeType) - - var amqpSchema = obj.second.schema.types[0] as CompositeType - - assertEquals (1, amqpSchema.fields.size) - assertEquals ("a", amqpSchema.fields[0].name) - assertEquals ("long", amqpSchema.fields[0].type) - - var pinochio = ClassCarpenter().build(ClassCarpenter.Schema(amqpSchema.name, amqpSchema.carpenterSchema())) - - val p = pinochio.constructors[0].newInstance (test) - - assertEquals (pinochio.getMethod("getA").invoke (p), amqpObj.a) - } - - @Test - fun singleShort() { - val test = 10.toShort() - - @CordaSerializable - data class A(val a : Short) - - var a = A (test) - - val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise (a)) - - assert (obj.first is A) - val amqpObj = obj.first as A - - assertEquals (test, amqpObj.a) - assertEquals (1, obj.second.schema.types.size) - assert (obj.second.schema.types[0] is CompositeType) - - var amqpSchema = obj.second.schema.types[0] as CompositeType - - assertEquals (1, amqpSchema.fields.size) - assertEquals ("a", amqpSchema.fields[0].name) - assertEquals ("short", amqpSchema.fields[0].type) - - var pinochio = ClassCarpenter().build(ClassCarpenter.Schema(amqpSchema.name, amqpSchema.carpenterSchema())) - - val p = pinochio.constructors[0].newInstance (test) - - assertEquals (pinochio.getMethod("getA").invoke (p), amqpObj.a) - } - - /* - @Test - fun singleBool() { - val test = true - - @CordaSerializable - data class A(val a : Boolean) - - var a = A (test) - - val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise (a)) - - assert (obj.first is A) - val amqpObj = obj.first as A - - assertEquals (test, amqpObj.a) - assertEquals (1, obj.second.schema.types.size) - assert (obj.second.schema.types[0] is CompositeType) - - var amqpSchema = obj.second.schema.types[0] as CompositeType - - assertEquals (1, amqpSchema.fields.size) - assertEquals ("a", amqpSchema.fields[0].name) - assertEquals ("boolean", amqpSchema.fields[0].type) - - var pinochio = ClassCarpenter().build(ClassCarpenter.Schema(amqpSchema.name, amqpSchema.carpenterSchema())) - - val p = pinochio.constructors[0].newInstance (test) - - assertEquals (pinochio.getMethod("getA").invoke (p), amqpObj.a) - } - */ - - @Test - fun singleDouble() { - val test = 10.0 - - @CordaSerializable - data class A(val a : Double) - - var a = A (test) - - val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise (a)) - - assert (obj.first is A) - val amqpObj = obj.first as A - - assertEquals (test, amqpObj.a) - assertEquals (1, obj.second.schema.types.size) - assert (obj.second.schema.types[0] is CompositeType) - - var amqpSchema = obj.second.schema.types[0] as CompositeType - - assertEquals (1, amqpSchema.fields.size) - assertEquals ("a", amqpSchema.fields[0].name) - assertEquals ("double", amqpSchema.fields[0].type) - - var pinochio = ClassCarpenter().build(ClassCarpenter.Schema(amqpSchema.name, amqpSchema.carpenterSchema())) - - val p = pinochio.constructors[0].newInstance (test) - - assertEquals (pinochio.getMethod("getA").invoke (p), amqpObj.a) - } - - @Test - fun singleFloat() { - val test = 10.0F - - @CordaSerializable - data class A(val a : Float) - - var a = A (test) - - val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise (a)) - - assert (obj.first is A) - val amqpObj = obj.first as A - - assertEquals (test, amqpObj.a) - assertEquals (1, obj.second.schema.types.size) - assert (obj.second.schema.types[0] is CompositeType) - - var amqpSchema = obj.second.schema.types[0] as CompositeType - - assertEquals (1, amqpSchema.fields.size) - assertEquals ("a", amqpSchema.fields[0].name) - assertEquals ("float", amqpSchema.fields[0].type) - - var pinochio = ClassCarpenter().build(ClassCarpenter.Schema(amqpSchema.name, amqpSchema.carpenterSchema())) - - val p = pinochio.constructors[0].newInstance (test) - -// assertEquals (pinochio.getMethod("getA").invoke (p), amqpObj.a) - } -} \ No newline at end of file diff --git a/experimental/src/test/kotlin/net/corda/carpenter/CompositeMemberCompositeSchemaToClassCarpenterTests.kt b/experimental/src/test/kotlin/net/corda/carpenter/CompositeMemberCompositeSchemaToClassCarpenterTests.kt index b8872dae7a..abbdb5acf5 100644 --- a/experimental/src/test/kotlin/net/corda/carpenter/CompositeMemberCompositeSchemaToClassCarpenterTests.kt +++ b/experimental/src/test/kotlin/net/corda/carpenter/CompositeMemberCompositeSchemaToClassCarpenterTests.kt @@ -2,10 +2,11 @@ package net.corda.carpenter import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.amqp.* +import net.corda.core.serialization.ClassCarpenterSchema import org.junit.Test import kotlin.test.assertEquals -class CompositeMemberCompositeSchemaToClassCarpenterTests { +class Inheritance { private var factory = SerializerFactory() fun serialise(clazz: Any) = SerializationOutput(factory).serialize(clazz) @@ -35,6 +36,9 @@ class CompositeMemberCompositeSchemaToClassCarpenterTests { assert(obj.second.schema.types[0] is CompositeType) assert(obj.second.schema.types[1] is CompositeType) + println (obj.second.schema.types[0] as CompositeType) + println (obj.second.schema.types[1] as CompositeType) + var amqpSchemaA : CompositeType? = null var amqpSchemaB : CompositeType? = null @@ -52,15 +56,15 @@ class CompositeMemberCompositeSchemaToClassCarpenterTests { assertEquals("a", amqpSchemaA!!.fields[0].name) assertEquals("int", amqpSchemaA!!.fields[0].type) + assertEquals(2, amqpSchemaB?.fields?.size) assertEquals("a", amqpSchemaB!!.fields[0].name) - assertEquals("net.corda.carpenter.CompositeMemberCompositeSchemaToClassCarpenterTests\$nestedInts\$A", - amqpSchemaB!!.fields[0].type) + assertEquals("${this.javaClass.name}\$nestedInts\$A", amqpSchemaB!!.fields[0].type) assertEquals("b", amqpSchemaB!!.fields[1].name) assertEquals("int", amqpSchemaB!!.fields[1].type) - var ccA = ClassCarpenter().build(ClassCarpenter.Schema(amqpSchemaA.name, amqpSchemaA.carpenterSchema())) - var ccB = ClassCarpenter().build(ClassCarpenter.Schema(amqpSchemaB.name, amqpSchemaB.carpenterSchema())) + var ccA = ClassCarpenter().build(amqpSchemaA.carpenterSchema()) + var ccB = ClassCarpenter().build(amqpSchemaB.carpenterSchema()) /* * Since A is known to the JVM we can't constuct B with and instance of the carpented A but @@ -72,7 +76,7 @@ class CompositeMemberCompositeSchemaToClassCarpenterTests { assertEquals (ccA.getMethod("getA").invoke(instanceA), amqpObj.a.a) assertEquals ((ccB.getMethod("getA").invoke(instanceB) as A).a, amqpObj.a.a) assertEquals (ccB.getMethod("getB").invoke(instanceB), amqpObj.b) - } + } } diff --git a/experimental/src/test/kotlin/net/corda/carpenter/InheritanceSchemaToClassCarpenterTests.kt b/experimental/src/test/kotlin/net/corda/carpenter/InheritanceSchemaToClassCarpenterTests.kt new file mode 100644 index 0000000000..c009a1d356 --- /dev/null +++ b/experimental/src/test/kotlin/net/corda/carpenter/InheritanceSchemaToClassCarpenterTests.kt @@ -0,0 +1,389 @@ +package net.corda.carpenter + +import net.corda.core.serialization.CordaSerializable +import net.corda.core.serialization.amqp.* +import org.junit.Test +import kotlin.test.assertEquals + +/*******************************************************************************************************/ + +@CordaSerializable +interface J { + val j : Int +} + +/*******************************************************************************************************/ + +@CordaSerializable +interface I { + val i : Int +} + +@CordaSerializable +interface II { + val ii : Int +} + +@CordaSerializable +interface III : I { + val iii : Int + override val i: Int +} + +@CordaSerializable +interface IIII { + val iiii : Int + val i : I +} + +/*******************************************************************************************************/ + +class InheritanceSchemaToClassCarpenterTests { + private var factory = SerializerFactory() + + fun serialise(clazz: Any) = SerializationOutput(factory).serialize(clazz) + + fun curruptName(name: String) = "${name}__carpenter" + + /* given a list of class names work through the amqp envelope schema and alter any that + match in the fashion defined above */ + fun curruptName (schema: Schema, names: List) : Schema { + val types = schema.types + + val newTypes : MutableList = mutableListOf() + + for (type in types) { + val newName = if (type.name in names) curruptName (type.name) else type.name + + val newProvides = type.provides.map { + it -> if (it in names) curruptName (it) else it + } + + val newFields = (type as CompositeType).fields.map { + it -> if ( it.requires.isNotEmpty() && it.requires[0] in names) + it.copy (requires=listOf (curruptName (it.requires[0]))) else it + } + + newTypes.add (type.copy (name=newName, provides=newProvides, fields=newFields)) + } + + return Schema (types=newTypes) + } + + @Test + fun interfaceParent1() { + val testJ = 20 + val testName = "interfaceParent1" + + class A(override val j: Int) : J + + val a = A(testJ) + + assertEquals(testJ, a.j) + + val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise(a)) + + assert(obj.first is A) + + val serSchema = obj.second.schema + + assertEquals(2, serSchema.types.size) + + val l1 = serSchema.carpenterSchema() + + /* since we're using an envelope generated by seilaising classes defined locally + it's extremely unlikely we'd need to carpent any classes */ + assertEquals(0, l1.size) + + val curruptSchema = curruptName (serSchema, listOf ("${this.javaClass.name}\$$testName\$A")) + + val l2 = curruptSchema.carpenterSchema() + + assertEquals(1, l2.size) + + assertEquals(curruptName("${this.javaClass.name}\$$testName\$A"), l2[0].name) + assertEquals(1, l2[0].interfaces.size) + assertEquals(net.corda.carpenter.J::class.java, l2[0].interfaces[0]) + + val aBuilder = ClassCarpenter().build(l2[0]) + + val objJ = aBuilder.constructors[0].newInstance(testJ) + val j = objJ as J + + assertEquals(aBuilder.getMethod("getJ").invoke(objJ), testJ) + assertEquals(a.j, j.j) + } + + + @Test + fun interfaceParent2() { + val testJ = 20 + val testJJ = 40 + val testName = "interfaceParent2" + + class A(override val j: Int, val jj: Int) : J + + val a = A(testJ, testJJ) + + assertEquals(testJ, a.j) + assertEquals(testJJ, a.jj) + + val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise(a)) + + assert(obj.first is A) + + val serSchema = obj.second.schema + + assertEquals(2, serSchema.types.size) + + val l1 = serSchema.carpenterSchema() + + /* since we're using an envelope generated by seilaising classes defined locally + it's extremely unlikely we'd need to carpent any classes */ + assertEquals(0, l1.size) + + val curruptSchema = curruptName (serSchema, listOf ("${this.javaClass.name}\$$testName\$A")) + + val l2 = curruptSchema.carpenterSchema() + + assertEquals(1, l2.size) + + assertEquals(curruptName("${this.javaClass.name}\$$testName\$A"), l2[0].name) + assertEquals(1, l2[0].interfaces.size) + assertEquals(net.corda.carpenter.J::class.java, l2[0].interfaces[0]) + + val aBuilder = ClassCarpenter().build(l2[0]) + + val objJ = aBuilder.constructors[0].newInstance(testJ, testJJ) + val j = objJ as J + + assertEquals(aBuilder.getMethod("getJ").invoke(objJ), testJ) + assertEquals(aBuilder.getMethod("getJj").invoke(objJ), testJJ) + + assertEquals(a.j, j.j) + } + + @Test + fun multipleInterfaces() { + val testI = 20 + val testII = 40 + val testName = "multipleInterfaces" + + class A(override val i: Int, override val ii: Int) : I, II + + val a = A(testI, testII) + val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise(a)) + + assert(obj.first is A) + + val serSchema = obj.second.schema + + assertEquals(3, serSchema.types.size) + + val l1 = serSchema.carpenterSchema() + + /* since we're using an envelope generated by seilaising classes defined locally + it's extremely unlikely we'd need to carpent any classes */ + assertEquals(0, l1.size) + + /* pretend we don't know the class we've been sent, i.e. it's unknown to the class loader, and thus + needs some carpentry */ + + val curruptSchema = curruptName (serSchema, listOf ("${this.javaClass.name}\$$testName\$A")) + val l2 = curruptSchema.carpenterSchema() + + assertEquals(1, l2.size) + + assertEquals(curruptName("${this.javaClass.name}\$$testName\$A"), l2[0].name) + assertEquals(2, l2[0].interfaces.size) + assert (net.corda.carpenter.I::class.java in l2[0].interfaces) + assert (net.corda.carpenter.II::class.java in l2[0].interfaces) + + val aBuilder = ClassCarpenter().build(l2[0]) + + val objA = aBuilder.constructors[0].newInstance(testI, testII) + val i = objA as I + val ii = objA as II + + assertEquals(aBuilder.getMethod("getI").invoke(objA), testI) + assertEquals(aBuilder.getMethod("getIi").invoke(objA), testII) + + assertEquals(a.i, i.i) + assertEquals(a.ii, ii.ii) + } + + @Test + fun nestedInterfaces() { + val testI = 20 + val testIII = 60 + val testName = "nestedInterfaces" + + class A(override val i: Int, override val iii : Int) : III + + val a = A(testI, testIII) + val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise(a)) + + assert(obj.first is A) + + val serSchema = obj.second.schema + + assertEquals(3, serSchema.types.size) + + val l1 = serSchema.carpenterSchema() + + /* since we're using an envelope generated by seilaising classes defined locally + it's extremely unlikely we'd need to carpent any classes */ + assertEquals(0, l1.size) + + val curruptSchema = curruptName (serSchema, listOf ("${this.javaClass.name}\$$testName\$A")) + val l2 = curruptSchema.carpenterSchema() + + assertEquals(1, l2.size) + + assertEquals(curruptName("${this.javaClass.name}\$$testName\$A"), l2[0].name) + assertEquals(2, l2[0].interfaces.size) + assert (net.corda.carpenter.I::class.java in l2[0].interfaces) + assert (net.corda.carpenter.III::class.java in l2[0].interfaces) + + val aBuilder = ClassCarpenter().build(l2[0]) + + val objA = aBuilder.constructors[0].newInstance(testI, testIII) + val i = objA as I + val iii = objA as III + + assertEquals(aBuilder.getMethod("getI").invoke(objA), testI) + assertEquals(aBuilder.getMethod("getIii").invoke(objA), testIII) + + assertEquals(a.i, i.i) + assertEquals(a.i, iii.i) + assertEquals(a.iii, iii.iii) + } + + @Test + fun memberInterface() { + val testI = 25 + val testIIII = 50 + val testName = "memberInterface" + + class A(override val i: Int) : I + class B(override val i : I, override val iiii : Int) : IIII + + val a = A(testI) + val b = B(a, testIIII) + + val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise(b)) + + assert(obj.first is B) + + val serSchema = obj.second.schema + + /* + * class A + * class A's interface (class I) + * class B + * class B's interface (class IIII) + */ + assertEquals(4, serSchema.types.size) + + val curruptSchema = curruptName (serSchema, listOf ("${this.javaClass.name}\$$testName\$A", + "${this.javaClass.name}\$$testName\$B")) + + val cSchema = curruptSchema.carpenterSchema() + assertEquals(2, cSchema.size) + + val aCarpenterSchema = cSchema.find { it.name == curruptName("${this.javaClass.name}\$$testName\$A") } + val bCarpenterSchema = cSchema.find { it.name == curruptName("${this.javaClass.name}\$$testName\$B") } + + assert(aCarpenterSchema != null) + assert(bCarpenterSchema != null) + + val cc = ClassCarpenter() + val cc2 = ClassCarpenter() + + val bBuilder = cc.build(bCarpenterSchema!!) + bBuilder.constructors[0].newInstance(a, testIIII) + + val aBuilder = cc.build(aCarpenterSchema!!) + val objA = aBuilder.constructors[0].newInstance(testI) + + /* build a second B this time using our constructed instane of A and not the + local one we pre defined */ + bBuilder.constructors[0].newInstance(objA, testIIII) + + /* whittle and instantiate a different A with a new class loader */ + val aBuilder2 = cc2.build(aCarpenterSchema) + val objA2 = aBuilder2.constructors[0].newInstance(testI) + + bBuilder.constructors[0].newInstance(objA2, testIIII) + } + + /* this time remove the nested interface from out set of known classes forcing us + to whittle it */ + @Test + fun memberInterface2() { + val testI = 25 + val testIIII = 50 + val testName = "memberInterface2" + + class A(override val i: Int) : I + class B(override val i : I, override val iiii : Int) : IIII + + val a = A(testI) + val b = B(a, testIIII) + + val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise(b)) + + assert(obj.first is B) + + val serSchema = obj.second.schema + + /* + * The classes we're expecting to find: + * class A + * class A's interface (class I) + * class B + * class B's interface (class IIII) + */ + assertEquals(4, serSchema.types.size) + + val curruptSchema = curruptName (serSchema, listOf ( + "${this.javaClass.name}\$$testName\$A", + "${this.javaClass.getPackage().name}.I")) + + + val carpenterSchema = curruptSchema.carpenterSchema() + + val aCarpenterSchema = carpenterSchema.find { it.name == curruptName("${this.javaClass.name}\$$testName\$A") } +// val bCarpenterSchema = carpenterSchema.find { it.name == curruptName("${this.javaClass.getPackage().name}.I") } + + val cc = ClassCarpenter() + + val aBuilder = cc.build (aCarpenterSchema!!) + aBuilder.constructors[0].newInstance(testI) + + + /* + + var cc2 = ClassCarpenter() + + var bBuilder = cc.build(bCarpenterSchema!!) + val objB = bBuilder.constructors[0].newInstance(a, testIIII) + + var aBuilder = cc.build(aCarpenterSchema!!) + val objA = aBuilder.constructors[0].newInstance(testI) + + /* build a second B this time using our constructed instane of A and not the + local one we pre defined */ + val objB2 = bBuilder.constructors[0].newInstance(objA, testIIII) + + /* whittle and instantiate a different A with a new class loader */ + var aBuilder2 = cc2.build(aCarpenterSchema!!) + val objA2 = aBuilder2.constructors[0].newInstance(testI) + + val objB3 = bBuilder.constructors[0].newInstance(objA2, testIIII) + */ + } + +} + + diff --git a/experimental/src/test/kotlin/net/corda/carpenter/MultiMemberCompositeSchemaToClassCarpenterTests.kt b/experimental/src/test/kotlin/net/corda/carpenter/MultiMemberCompositeSchemaToClassCarpenterTests.kt index 51d9a0c128..018ead9912 100644 --- a/experimental/src/test/kotlin/net/corda/carpenter/MultiMemberCompositeSchemaToClassCarpenterTests.kt +++ b/experimental/src/test/kotlin/net/corda/carpenter/MultiMemberCompositeSchemaToClassCarpenterTests.kt @@ -38,7 +38,7 @@ class MultiMemberCompositeSchemaToClassCarpenterTests { assertEquals("b", amqpSchema.fields[1].name) assertEquals("int", amqpSchema.fields[1].type) - var pinochio = ClassCarpenter().build(ClassCarpenter.Schema(amqpSchema.name, amqpSchema.carpenterSchema())) + var pinochio = ClassCarpenter().build(amqpSchema.carpenterSchema()) val p = pinochio.constructors[0].newInstance(testA, testB) @@ -74,7 +74,7 @@ class MultiMemberCompositeSchemaToClassCarpenterTests { assertEquals("b", amqpSchema.fields[1].name) assertEquals("string", amqpSchema.fields[1].type) - var pinochio = ClassCarpenter().build(ClassCarpenter.Schema(amqpSchema.name, amqpSchema.carpenterSchema())) + var pinochio = ClassCarpenter().build(amqpSchema.carpenterSchema()) val p = pinochio.constructors[0].newInstance(testA, testB) diff --git a/experimental/src/test/kotlin/net/corda/carpenter/SingleMemberCompositeSchemaToClassCarpenterTests.kt b/experimental/src/test/kotlin/net/corda/carpenter/SingleMemberCompositeSchemaToClassCarpenterTests.kt index 87f2c51379..d3d26075d5 100644 --- a/experimental/src/test/kotlin/net/corda/carpenter/SingleMemberCompositeSchemaToClassCarpenterTests.kt +++ b/experimental/src/test/kotlin/net/corda/carpenter/SingleMemberCompositeSchemaToClassCarpenterTests.kt @@ -1,6 +1,7 @@ package net.corda.carpenter import net.corda.core.serialization.CordaSerializable +import net.corda.core.serialization.ClassCarpenterSchema import net.corda.core.serialization.amqp.* import org.junit.Test import kotlin.test.assertEquals @@ -17,8 +18,8 @@ class SingleMemberCompositeSchemaToClassCarpenterTests { @CordaSerializable data class A(val a : Int) - var a = A (test) + val a = A (test) val obj = DeserializationInput(factory).deserializeRtnEnvelope(serialise (a)) assert (obj.first is A) @@ -28,13 +29,13 @@ class SingleMemberCompositeSchemaToClassCarpenterTests { assertEquals (1, obj.second.schema.types.size) assert (obj.second.schema.types[0] is CompositeType) - var amqpSchema = obj.second.schema.types[0] as CompositeType + val amqpSchema = obj.second.schema.types[0] as CompositeType assertEquals (1, amqpSchema.fields.size) assertEquals ("a", amqpSchema.fields[0].name) assertEquals ("int", amqpSchema.fields[0].type) - var pinochio = ClassCarpenter().build(ClassCarpenter.Schema(amqpSchema.name, amqpSchema.carpenterSchema())) + val pinochio = ClassCarpenter().build(amqpSchema.carpenterSchema()) val p = pinochio.constructors[0].newInstance (test) @@ -64,7 +65,7 @@ class SingleMemberCompositeSchemaToClassCarpenterTests { assertEquals ("a", amqpSchema.fields[0].name) assertEquals ("string", amqpSchema.fields[0].type) - var pinochio = ClassCarpenter().build(ClassCarpenter.Schema(amqpSchema.name, amqpSchema.carpenterSchema())) + var pinochio = ClassCarpenter().build(amqpSchema.carpenterSchema()) val p = pinochio.constructors[0].newInstance (test) @@ -128,7 +129,7 @@ class SingleMemberCompositeSchemaToClassCarpenterTests { assertEquals ("a", amqpSchema.fields[0].name) assertEquals ("long", amqpSchema.fields[0].type) - var pinochio = ClassCarpenter().build(ClassCarpenter.Schema(amqpSchema.name, amqpSchema.carpenterSchema())) + var pinochio = ClassCarpenter().build(amqpSchema.carpenterSchema()) val p = pinochio.constructors[0].newInstance (test) @@ -159,7 +160,7 @@ class SingleMemberCompositeSchemaToClassCarpenterTests { assertEquals ("a", amqpSchema.fields[0].name) assertEquals ("short", amqpSchema.fields[0].type) - var pinochio = ClassCarpenter().build(ClassCarpenter.Schema(amqpSchema.name, amqpSchema.carpenterSchema())) + var pinochio = ClassCarpenter().build(amqpSchema.carpenterSchema()) val p = pinochio.constructors[0].newInstance (test) @@ -223,7 +224,7 @@ class SingleMemberCompositeSchemaToClassCarpenterTests { assertEquals ("a", amqpSchema.fields[0].name) assertEquals ("double", amqpSchema.fields[0].type) - var pinochio = ClassCarpenter().build(ClassCarpenter.Schema(amqpSchema.name, amqpSchema.carpenterSchema())) + var pinochio = ClassCarpenter().build(amqpSchema.carpenterSchema()) val p = pinochio.constructors[0].newInstance (test) @@ -254,7 +255,7 @@ class SingleMemberCompositeSchemaToClassCarpenterTests { assertEquals ("a", amqpSchema.fields[0].name) assertEquals ("float", amqpSchema.fields[0].type) - var pinochio = ClassCarpenter().build(ClassCarpenter.Schema(amqpSchema.name, amqpSchema.carpenterSchema())) + var pinochio = ClassCarpenter().build(amqpSchema.carpenterSchema()) val p = pinochio.constructors[0].newInstance (test)