From 4cfa376d7d0c26b864a3c4aad53d287cd3f54388 Mon Sep 17 00:00:00 2001 From: Katelyn Baker Date: Wed, 12 Jul 2017 16:41:01 +0100 Subject: [PATCH 01/10] Add the carpenter into the deserializer Deseializing any blob that contains a type unknown to the classloader will cause a class object to be carpented up and a suitable object returned --- .../serialization/amqp/SerializationHelper.kt | 1 + .../serialization/amqp/SerializerFactory.kt | 49 ++++++-- .../serialization/carpenter/ClassCarpenter.kt | 2 + .../serialization/carpenter/MetaCarpenter.kt | 5 + .../amqp/DeserializeNeedingCarpentryTests.kt | 105 ++++++++++++++++++ .../carpenter/ClassCarpenterTestUtils.kt | 5 +- 6 files changed, 157 insertions(+), 10 deletions(-) create mode 100644 node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeNeedingCarpentryTests.kt diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationHelper.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationHelper.kt index a8c15461ca..bafd9d5c8c 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationHelper.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationHelper.kt @@ -69,6 +69,7 @@ private fun isConcrete(clazz: Class<*>): Boolean = !(clazz.isInterface || Modifi private fun propertiesForSerializationFromConstructor(kotlinConstructor: KFunction, type: Type, factory: SerializerFactory): Collection { val clazz = (kotlinConstructor.returnType.classifier as KClass<*>).javaObjectType // Kotlin reflection doesn't work with Java getters the way you might expect, so we drop back to good ol' beans. + println (Introspector.getBeanInfo(clazz).propertyDescriptors) val properties = Introspector.getBeanInfo(clazz).propertyDescriptors.filter { it.name != "class" }.groupBy { it.name }.mapValues { it.value[0] } val rc: MutableList = ArrayList(kotlinConstructor.parameters.size) for (param in kotlinConstructor.parameters) { diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializerFactory.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializerFactory.kt index 78a5bf78c5..045d41e9ea 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializerFactory.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializerFactory.kt @@ -5,6 +5,10 @@ import com.google.common.reflect.TypeResolver import net.corda.core.serialization.ClassWhitelist import net.corda.core.serialization.CordaSerializable import net.corda.nodeapi.internal.serialization.AllWhitelist +import net.corda.nodeapi.internal.serialization.carpenter.CarpenterSchemas +import net.corda.nodeapi.internal.serialization.carpenter.ClassCarpenter +import net.corda.nodeapi.internal.serialization.carpenter.MetaCarpenter +import net.corda.nodeapi.internal.serialization.carpenter.carpenterSchema import org.apache.qpid.proton.amqp.* import java.io.NotSerializableException import java.lang.reflect.GenericArrayType @@ -167,15 +171,38 @@ class SerializerFactory(val whitelist: ClassWhitelist = AllWhitelist) { } } - private fun processSchema(schema: Schema) { + private fun processSchema( + schema: Schema, + cl: ClassLoader = DeserializedParameterizedType::class.java.classLoader) { + println ("processSchema cl ${cl::class.java}") + + val retry = cl != DeserializedParameterizedType::class.java.classLoader + val carpenterSchemas = CarpenterSchemas.newInstance() for (typeNotation in schema.types) { - processSchemaEntry(typeNotation) + try { + processSchemaEntry(typeNotation, cl) + } + catch (e: java.lang.ClassNotFoundException) { + println (" CLASS NOT FOUND - $retry") + if (retry) { + throw e + } + println ("add schema ${typeNotation.name}") + (typeNotation as CompositeType).carpenterSchema(carpenterSchemas = carpenterSchemas) + } } + + if (carpenterSchemas.isEmpty()) return + val mc = MetaCarpenter(carpenterSchemas) + mc.build() + + processSchema(schema, mc.classloader) } - private fun processSchemaEntry(typeNotation: TypeNotation) { + private fun processSchemaEntry(typeNotation: TypeNotation, + cl: ClassLoader = DeserializedParameterizedType::class.java.classLoader) { when (typeNotation) { - is CompositeType -> processCompositeType(typeNotation) // java.lang.Class (whether a class or interface) + is CompositeType -> processCompositeType(typeNotation, cl) // java.lang.Class (whether a class or interface) is RestrictedType -> processRestrictedType(typeNotation) // Collection / Map, possibly with generics } } @@ -188,10 +215,13 @@ class SerializerFactory(val whitelist: ClassWhitelist = AllWhitelist) { } } - private fun processCompositeType(typeNotation: CompositeType) { + private fun processCompositeType(typeNotation: CompositeType, + cl: ClassLoader = DeserializedParameterizedType::class.java.classLoader) { + println ("processCompositeType ${typeNotation.name}") serializersByDescriptor.computeIfAbsent(typeNotation.descriptor.name!!) { + println (" no such type") // TODO: class loader logic, and compare the schema. - val type = typeForName(typeNotation.name) + val type = typeForName(typeNotation.name, cl) get(type.asClass() ?: throw NotSerializableException("Unable to build composite type for $type"), type) } } @@ -304,7 +334,9 @@ class SerializerFactory(val whitelist: ClassWhitelist = AllWhitelist) { else -> throw NotSerializableException("Unable to render type $type to a string.") } - private fun typeForName(name: String): Type { + private fun typeForName( + name: String, + cl: ClassLoader = DeserializedParameterizedType::class.java.classLoader): Type { return if (name.endsWith("[]")) { val elementType = typeForName(name.substring(0, name.lastIndex - 1)) if (elementType is ParameterizedType || elementType is GenericArrayType) { @@ -329,7 +361,8 @@ class SerializerFactory(val whitelist: ClassWhitelist = AllWhitelist) { else -> throw NotSerializableException("Not able to deserialize array type: $name") } } else { - DeserializedParameterizedType.make(name) + println ("typeForName: name = $name") + DeserializedParameterizedType.make(name, cl) } } } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/carpenter/ClassCarpenter.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/carpenter/ClassCarpenter.kt index f03e041b69..8909a2de83 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/carpenter/ClassCarpenter.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/carpenter/ClassCarpenter.kt @@ -297,8 +297,10 @@ class ClassCarpenter { // fact that we didn't implement the interface we said we would at the moment the missing method is // actually called, which is a bit too dynamic for my tastes. val allFields = schema.fieldsIncludingSuperclasses() + println (" interfaces = ${schema.interfaces}") for (itf in schema.interfaces) { itf.methods.forEach { + println (" param ${it.name}") val fieldNameFromItf = when { it.name.startsWith("get") -> it.name.substring(3).decapitalize() else -> throw InterfaceMismatchException( diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/carpenter/MetaCarpenter.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/carpenter/MetaCarpenter.kt index 6f66a0abc5..ae3f4e64c9 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/carpenter/MetaCarpenter.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/carpenter/MetaCarpenter.kt @@ -42,6 +42,8 @@ data class CarpenterSchemas ( val size get() = carpenterSchemas.size + + fun isEmpty() = carpenterSchemas.isEmpty() } /** @@ -80,6 +82,9 @@ abstract class MetaCarpenterBase (val schemas : CarpenterSchemas) { } abstract fun build() + + val classloader : ClassLoader + get() = cc.classloader } class MetaCarpenter(schemas: CarpenterSchemas) : MetaCarpenterBase(schemas) { diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeNeedingCarpentryTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeNeedingCarpentryTests.kt new file mode 100644 index 0000000000..80d6ab1c50 --- /dev/null +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeNeedingCarpentryTests.kt @@ -0,0 +1,105 @@ +package net.corda.core.serialization.amqp + +import org.junit.Test +import kotlin.test.* +import net.corda.core.serialization.carpenter.ClassCarpenter +import net.corda.core.serialization.carpenter.ClassSchema +import net.corda.core.serialization.carpenter.NonNullableField + +interface I { + fun getName() : String +} + +class DeserializeNeedingCarpentryTests { + @Test + fun verySimpleType() { + val testVal = 10 + val cc = ClassCarpenter() + val schema = ClassSchema("oneType", mapOf("a" to NonNullableField(Int::class.java))) + val clazz = cc.build (schema) + val classInstance = clazz.constructors[0].newInstance(testVal) + + val serialisedBytes = SerializationOutput().serialize(classInstance) + val deserializedObj = DeserializationInput().deserialize(serialisedBytes) + + assertEquals (testVal, deserializedObj::class.java.getMethod("getA").invoke(deserializedObj)) + } + + @Test + fun simpleTypeKnownInterface() { + val cc = ClassCarpenter() + val schema = ClassSchema("oneType", mapOf("name" to NonNullableField(String::class.java)), + interfaces = listOf (I::class.java)) + val clazz = cc.build (schema) + val testVal = "Andrew Person" + val classInstance = clazz.constructors[0].newInstance(testVal) + + val serialisedBytes = SerializationOutput().serialize(classInstance) + val deserializedObj = DeserializationInput().deserialize(serialisedBytes) + + assertTrue(deserializedObj is I) + assertEquals(testVal, (deserializedObj as I).getName()) + } + + @Test + fun nestedTypes() { + val cc = ClassCarpenter() + val nestedClass = cc.build ( + ClassSchema("nestedType", + mapOf("name" to NonNullableField(String::class.java)))) + + val outerClass = cc.build ( + ClassSchema("outerType", + mapOf("inner" to NonNullableField(nestedClass)))) + + val classInstance = outerClass.constructors.first().newInstance(nestedClass.constructors.first().newInstance("name")) + val serialisedBytes = SerializationOutput().serialize(classInstance) + val deserializedObj = DeserializationInput().deserialize(serialisedBytes) + + val inner = deserializedObj::class.java.getMethod("getInner").invoke(deserializedObj) + assertEquals("name", inner::class.java.getMethod("getName").invoke(inner)) + } + + @Test + fun repeatedNestedTypes() { + val cc = ClassCarpenter() + val nestedClass = cc.build ( + ClassSchema("nestedType", + mapOf("name" to NonNullableField(String::class.java)))) + + data class outer(val a: Any, val b: Any) + + val classInstance = outer ( + nestedClass.constructors.first().newInstance("foo"), + nestedClass.constructors.first().newInstance("bar")) + + val serialisedBytes = SerializationOutput().serialize(classInstance) + val deserializedObj = DeserializationInput().deserialize(serialisedBytes) + + assertEquals ("foo", deserializedObj.a::class.java.getMethod("getName").invoke(deserializedObj.a)) + assertEquals ("bar", deserializedObj.b::class.java.getMethod("getName").invoke(deserializedObj.b)) + } + + @Test + fun listOfType() { + val cc = ClassCarpenter() + val unknownClass = cc.build (ClassSchema("unknownClass", mapOf( + "v1" to NonNullableField(Int::class.java), + "v2" to NonNullableField(Int::class.java)))) + + data class outer (val l : List) + val toSerialise = outer (listOf ( + unknownClass.constructors.first().newInstance(1, 2), + unknownClass.constructors.first().newInstance(3, 4), + unknownClass.constructors.first().newInstance(5, 6), + unknownClass.constructors.first().newInstance(7, 8))) + + val serialisedBytes = SerializationOutput().serialize(toSerialise) + val deserializedObj = DeserializationInput().deserialize(serialisedBytes) + var sentinel = 1 + deserializedObj.l.forEach { + assertEquals(sentinel++, it::class.java.getMethod("getV1").invoke(it)) + assertEquals(sentinel++, it::class.java.getMethod("getV2").invoke(it)) + } + } +} diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/carpenter/ClassCarpenterTestUtils.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/carpenter/ClassCarpenterTestUtils.kt index cc933e13b8..3075f2aec4 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/carpenter/ClassCarpenterTestUtils.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/carpenter/ClassCarpenterTestUtils.kt @@ -1,8 +1,9 @@ package net.corda.nodeapi.internal.serialization.carpenter -import net.corda.nodeapi.internal.serialization.amqp.* -import net.corda.nodeapi.internal.serialization.amqp.Schema import net.corda.nodeapi.internal.serialization.amqp.Field +import net.corda.nodeapi.internal.serialization.amqp.Schema +import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory +import net.corda.nodeapi.internal.serialization.amqp.SerializationOutput fun mangleName(name: String) = "${name}__carpenter" From a5106d74a811de6cff7318cd00624ebf52ccbe55 Mon Sep 17 00:00:00 2001 From: Katelyn Baker Date: Fri, 14 Jul 2017 17:42:58 +0100 Subject: [PATCH 02/10] Integration of the serialiser and carpenter working Tests added --- .../internal/serialization/amqp/Schema.kt | 3 +- .../serialization/amqp/SerializationHelper.kt | 1 - .../serialization/amqp/SerializerFactory.kt | 14 +---- .../carpenter/AMQPSchemaExtensions.kt | 2 + .../serialization/carpenter/ClassCarpenter.kt | 3 +- .../amqp/DeserializeNeedingCarpentryTests.kt | 61 +++++++++++++------ 6 files changed, 49 insertions(+), 35 deletions(-) diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/Schema.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/Schema.kt index 4eea6f4c20..a67cb8e400 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/Schema.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/Schema.kt @@ -14,6 +14,7 @@ import java.lang.reflect.ParameterizedType import java.lang.reflect.Type import java.lang.reflect.TypeVariable import java.util.* + import net.corda.nodeapi.internal.serialization.carpenter.Field as CarpenterField import net.corda.nodeapi.internal.serialization.carpenter.Schema as CarpenterSchema @@ -170,8 +171,6 @@ data class Field(val name: String, val type: String, val requires: List, sb.append("/>") return sb.toString() } - - fun typeAsString() = if (type =="*") requires[0] else type } sealed class TypeNotation : DescribedType { diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationHelper.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationHelper.kt index bafd9d5c8c..a8c15461ca 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationHelper.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationHelper.kt @@ -69,7 +69,6 @@ private fun isConcrete(clazz: Class<*>): Boolean = !(clazz.isInterface || Modifi private fun propertiesForSerializationFromConstructor(kotlinConstructor: KFunction, type: Type, factory: SerializerFactory): Collection { val clazz = (kotlinConstructor.returnType.classifier as KClass<*>).javaObjectType // Kotlin reflection doesn't work with Java getters the way you might expect, so we drop back to good ol' beans. - println (Introspector.getBeanInfo(clazz).propertyDescriptors) val properties = Introspector.getBeanInfo(clazz).propertyDescriptors.filter { it.name != "class" }.groupBy { it.name }.mapValues { it.value[0] } val rc: MutableList = ArrayList(kotlinConstructor.parameters.size) for (param in kotlinConstructor.parameters) { diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializerFactory.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializerFactory.kt index 045d41e9ea..d1b69953b8 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializerFactory.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializerFactory.kt @@ -174,21 +174,16 @@ class SerializerFactory(val whitelist: ClassWhitelist = AllWhitelist) { private fun processSchema( schema: Schema, cl: ClassLoader = DeserializedParameterizedType::class.java.classLoader) { - println ("processSchema cl ${cl::class.java}") - val retry = cl != DeserializedParameterizedType::class.java.classLoader val carpenterSchemas = CarpenterSchemas.newInstance() for (typeNotation in schema.types) { try { processSchemaEntry(typeNotation, cl) } catch (e: java.lang.ClassNotFoundException) { - println (" CLASS NOT FOUND - $retry") - if (retry) { - throw e - } - println ("add schema ${typeNotation.name}") - (typeNotation as CompositeType).carpenterSchema(carpenterSchemas = carpenterSchemas) + if ((cl != DeserializedParameterizedType::class.java.classLoader) + || (typeNotation !is CompositeType)) throw e + typeNotation.carpenterSchema(carpenterSchemas = carpenterSchemas) } } @@ -217,9 +212,7 @@ class SerializerFactory(val whitelist: ClassWhitelist = AllWhitelist) { private fun processCompositeType(typeNotation: CompositeType, cl: ClassLoader = DeserializedParameterizedType::class.java.classLoader) { - println ("processCompositeType ${typeNotation.name}") serializersByDescriptor.computeIfAbsent(typeNotation.descriptor.name!!) { - println (" no such type") // TODO: class loader logic, and compare the schema. val type = typeForName(typeNotation.name, cl) get(type.asClass() ?: throw NotSerializableException("Unable to build composite type for $type"), type) @@ -361,7 +354,6 @@ class SerializerFactory(val whitelist: ClassWhitelist = AllWhitelist) { else -> throw NotSerializableException("Not able to deserialize array type: $name") } } else { - println ("typeForName: name = $name") DeserializedParameterizedType.make(name, cl) } } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/carpenter/AMQPSchemaExtensions.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/carpenter/AMQPSchemaExtensions.kt index f3dac73f33..355e7f4b92 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/carpenter/AMQPSchemaExtensions.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/carpenter/AMQPSchemaExtensions.kt @@ -26,6 +26,8 @@ private fun CompositeType.validatePropertyTypes( } } +fun AMQPField.typeAsString() = if (type =="*") requires[0] else type + /** * based upon this AMQP schema either * a) add the corresponding carpenter schema to the [carpenterSchemas] param diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/carpenter/ClassCarpenter.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/carpenter/ClassCarpenter.kt index 8909a2de83..ad7e8d4ca6 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/carpenter/ClassCarpenter.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/carpenter/ClassCarpenter.kt @@ -297,10 +297,8 @@ class ClassCarpenter { // fact that we didn't implement the interface we said we would at the moment the missing method is // actually called, which is a bit too dynamic for my tastes. val allFields = schema.fieldsIncludingSuperclasses() - println (" interfaces = ${schema.interfaces}") for (itf in schema.interfaces) { itf.methods.forEach { - println (" param ${it.name}") val fieldNameFromItf = when { it.name.startsWith("get") -> it.name.substring(3).decapitalize() else -> throw InterfaceMismatchException( @@ -311,6 +309,7 @@ class ClassCarpenter { // If we're trying to carpent a class that prior to serialisation / deserialisation // was made by a carpenter then we can ignore this (it will implement a plain get // method from SimpleFieldAccess). + // method from SimpleFieldAccess) if (fieldNameFromItf.isEmpty() && SimpleFieldAccess::class.java in schema.interfaces) return@forEach if ((schema is ClassSchema) and (fieldNameFromItf !in allFields)) diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeNeedingCarpentryTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeNeedingCarpentryTests.kt index 80d6ab1c50..29e238361c 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeNeedingCarpentryTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeNeedingCarpentryTests.kt @@ -4,19 +4,24 @@ import org.junit.Test import kotlin.test.* import net.corda.core.serialization.carpenter.ClassCarpenter import net.corda.core.serialization.carpenter.ClassSchema +import net.corda.core.serialization.carpenter.InterfaceSchema import net.corda.core.serialization.carpenter.NonNullableField interface I { fun getName() : String } +/** + * These tests work by having the class carpenter build the classes we serialise and then deserialise. Because + * those classes don't exist within the system's Class Loader the deserialiser will be forced to carpent + * versions of them up using its own internal class carpenter (each carpenter houses it's own loader). This + * replicates the situation where a reciever doesn't have some or all elements of a schema present on it's classpath + */ class DeserializeNeedingCarpentryTests { @Test fun verySimpleType() { val testVal = 10 - val cc = ClassCarpenter() - val schema = ClassSchema("oneType", mapOf("a" to NonNullableField(Int::class.java))) - val clazz = cc.build (schema) + val clazz = ClassCarpenter().build(ClassSchema("oneType", mapOf("a" to NonNullableField(Int::class.java)))) val classInstance = clazz.constructors[0].newInstance(testVal) val serialisedBytes = SerializationOutput().serialize(classInstance) @@ -27,11 +32,10 @@ class DeserializeNeedingCarpentryTests { @Test fun simpleTypeKnownInterface() { - val cc = ClassCarpenter() - val schema = ClassSchema("oneType", mapOf("name" to NonNullableField(String::class.java)), - interfaces = listOf (I::class.java)) - val clazz = cc.build (schema) - val testVal = "Andrew Person" + val clazz = ClassCarpenter().build (ClassSchema( + "oneType", mapOf("name" to NonNullableField(String::class.java)), + interfaces = listOf (I::class.java))) + val testVal = "Some Person" val classInstance = clazz.constructors[0].newInstance(testVal) val serialisedBytes = SerializationOutput().serialize(classInstance) @@ -44,13 +48,11 @@ class DeserializeNeedingCarpentryTests { @Test fun nestedTypes() { val cc = ClassCarpenter() - val nestedClass = cc.build ( - ClassSchema("nestedType", - mapOf("name" to NonNullableField(String::class.java)))) + val nestedClass = cc.build (ClassSchema("nestedType", + mapOf("name" to NonNullableField(String::class.java)))) - val outerClass = cc.build ( - ClassSchema("outerType", - mapOf("inner" to NonNullableField(nestedClass)))) + val outerClass = cc.build (ClassSchema("outerType", + mapOf("inner" to NonNullableField(nestedClass)))) val classInstance = outerClass.constructors.first().newInstance(nestedClass.constructors.first().newInstance("name")) val serialisedBytes = SerializationOutput().serialize(classInstance) @@ -63,9 +65,8 @@ class DeserializeNeedingCarpentryTests { @Test fun repeatedNestedTypes() { val cc = ClassCarpenter() - val nestedClass = cc.build ( - ClassSchema("nestedType", - mapOf("name" to NonNullableField(String::class.java)))) + val nestedClass = cc.build (ClassSchema("nestedType", + mapOf("name" to NonNullableField(String::class.java)))) data class outer(val a: Any, val b: Any) @@ -82,8 +83,7 @@ class DeserializeNeedingCarpentryTests { @Test fun listOfType() { - val cc = ClassCarpenter() - val unknownClass = cc.build (ClassSchema("unknownClass", mapOf( + val unknownClass = ClassCarpenter().build (ClassSchema("unknownClass", mapOf( "v1" to NonNullableField(Int::class.java), "v2" to NonNullableField(Int::class.java)))) @@ -102,4 +102,27 @@ class DeserializeNeedingCarpentryTests { assertEquals(sentinel++, it::class.java.getMethod("getV2").invoke(it)) } } + + @Test + fun unknownInterface() { + val cc = ClassCarpenter() + + val interfaceClass = cc.build (InterfaceSchema( + "gen.Interface", + mapOf("age" to NonNullableField (Int::class.java)))) + + val concreteClass = cc.build (ClassSchema ("gen.Class", mapOf( + "age" to NonNullableField (Int::class.java), + "name" to NonNullableField(String::class.java)), + interfaces = listOf (I::class.java, interfaceClass))) + + val serialisedBytes = SerializationOutput().serialize( + concreteClass.constructors.first().newInstance(12, "timmy")) + val deserializedObj = DeserializationInput().deserialize(serialisedBytes) + + assertTrue(deserializedObj is I) + assertEquals("timmy", (deserializedObj as I).getName()) + assertEquals("timmy", deserializedObj::class.java.getMethod("getName").invoke(deserializedObj)) + assertEquals(12, deserializedObj::class.java.getMethod("getAge").invoke(deserializedObj)) + } } From 0541d128e47cc27a1bc9a3fbadf449e489c096da Mon Sep 17 00:00:00 2001 From: Katelyn Baker Date: Tue, 18 Jul 2017 09:55:14 +0100 Subject: [PATCH 03/10] Fixes for nullable Prim Types Commit messages from Squashed Commits - Carpenter doesn't work with nullable prim types - Fix to get Cherry picks up to date with Master --- .../carpenter/AMQPSchemaExtensions.kt | 2 +- .../amqp/DeserializeNeedingCarpentryTests.kt | 26 ++++++++++++++++--- .../carpenter/ClassCarpenterTestUtils.kt | 1 + 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/carpenter/AMQPSchemaExtensions.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/carpenter/AMQPSchemaExtensions.kt index 355e7f4b92..93cec43ea1 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/carpenter/AMQPSchemaExtensions.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/carpenter/AMQPSchemaExtensions.kt @@ -94,7 +94,7 @@ fun CompositeType.carpenterSchema( fun AMQPField.getTypeAsClass( classLoaders: List = listOf (ClassLoader.getSystemClassLoader()) ) = when (type) { - "int" -> Int::class.javaPrimitiveType!! + "int" -> if (mandatory) Integer::class.java else Int::class.javaPrimitiveType!! "string" -> String::class.java "short" -> Short::class.javaPrimitiveType!! "long" -> Long::class.javaPrimitiveType!! diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeNeedingCarpentryTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeNeedingCarpentryTests.kt index 29e238361c..9b6f572f9a 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeNeedingCarpentryTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeNeedingCarpentryTests.kt @@ -2,10 +2,8 @@ package net.corda.core.serialization.amqp import org.junit.Test import kotlin.test.* -import net.corda.core.serialization.carpenter.ClassCarpenter -import net.corda.core.serialization.carpenter.ClassSchema -import net.corda.core.serialization.carpenter.InterfaceSchema -import net.corda.core.serialization.carpenter.NonNullableField +import net.corda.core.serialization.carpenter.* +import java.lang.Character interface I { fun getName() : String @@ -125,4 +123,24 @@ class DeserializeNeedingCarpentryTests { assertEquals("timmy", deserializedObj::class.java.getMethod("getName").invoke(deserializedObj)) assertEquals(12, deserializedObj::class.java.getMethod("getAge").invoke(deserializedObj)) } + + @Test + fun manyTypes() { + val cc = ClassCarpenter() + + val manyClass = cc.build (ClassSchema( + "many", + mapOf( + "intA" to NonNullableField (Int::class.java), + "intB" to NullableField (Integer::class.java), + "strA" to NonNullableField (String::class.java), + "strB" to NullableField (String::class.java), + "charA" to NonNullableField (Char::class.java), + "charB" to NullableField (Character::class.java)))) + + val serialisedBytes = SerializationOutput().serialize( + manyClass.constructors.first().newInstance(1, 2, "a", "b", 'c', 'd')) + + val deserializedObj = DeserializationInput().deserialize(serialisedBytes) + } } diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/carpenter/ClassCarpenterTestUtils.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/carpenter/ClassCarpenterTestUtils.kt index 3075f2aec4..7601ab1834 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/carpenter/ClassCarpenterTestUtils.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/carpenter/ClassCarpenterTestUtils.kt @@ -2,6 +2,7 @@ package net.corda.nodeapi.internal.serialization.carpenter import net.corda.nodeapi.internal.serialization.amqp.Field import net.corda.nodeapi.internal.serialization.amqp.Schema +import net.corda.nodeapi.internal.serialization.amqp.CompositeType import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory import net.corda.nodeapi.internal.serialization.amqp.SerializationOutput From ae70d73e560f5a680a48913386745287c719dce4 Mon Sep 17 00:00:00 2001 From: Katelyn Baker Date: Thu, 27 Jul 2017 11:35:12 +0100 Subject: [PATCH 04/10] Synthesiser / Serializer integration working --- .../serialization/amqp/SerializerFactory.kt | 2 +- .../carpenter/AMQPSchemaExtensions.kt | 75 ++-- .../amqp/DeserializeNeedingCarpentryTests.kt | 419 ++++++++++++++++-- 3 files changed, 439 insertions(+), 57 deletions(-) diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializerFactory.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializerFactory.kt index d1b69953b8..9dafe348d4 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializerFactory.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializerFactory.kt @@ -293,6 +293,7 @@ class SerializerFactory(val whitelist: ClassWhitelist = AllWhitelist) { private val primitiveTypeNames: Map, String> = mapOf( Character::class.java to "char", + Char::class.java to "char", Boolean::class.java to "boolean", Byte::class.java to "byte", UnsignedByte::class.java to "ubyte", @@ -307,7 +308,6 @@ class SerializerFactory(val whitelist: ClassWhitelist = AllWhitelist) { Decimal32::class.java to "decimal32", Decimal64::class.java to "decimal62", Decimal128::class.java to "decimal128", - Char::class.java to "char", Date::class.java to "timestamp", UUID::class.java to "uuid", ByteArray::class.java to "binary", diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/carpenter/AMQPSchemaExtensions.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/carpenter/AMQPSchemaExtensions.kt index 93cec43ea1..dbe8bb254d 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/carpenter/AMQPSchemaExtensions.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/carpenter/AMQPSchemaExtensions.kt @@ -5,7 +5,7 @@ import net.corda.nodeapi.internal.serialization.amqp.Field as AMQPField import net.corda.nodeapi.internal.serialization.amqp.Schema as AMQPSchema fun AMQPSchema.carpenterSchema( - loaders : List = listOf(ClassLoader.getSystemClassLoader())) + loaders: List = listOf(ClassLoader.getSystemClassLoader())) : CarpenterSchemas { val rtn = CarpenterSchemas.newInstance() @@ -20,13 +20,13 @@ fun AMQPSchema.carpenterSchema( * if we can load the class then we MUST know about all of it's composite elements */ private fun CompositeType.validatePropertyTypes( - classLoaders: List = listOf (ClassLoader.getSystemClassLoader())){ + classLoaders: List = listOf(ClassLoader.getSystemClassLoader())) { fields.forEach { - if (!it.validateType(classLoaders)) throw UncarpentableException (name, it.name, it.type) + if (!it.validateType(classLoaders)) throw UncarpentableException(name, it.name, it.type) } } -fun AMQPField.typeAsString() = if (type =="*") requires[0] else type +fun AMQPField.typeAsString() = if (type == "*") requires[0] else type /** * based upon this AMQP schema either @@ -42,9 +42,9 @@ fun AMQPField.typeAsString() = if (type =="*") requires[0] else type * on the class path. For testing purposes schema generation can be forced */ fun CompositeType.carpenterSchema( - classLoaders: List = listOf (ClassLoader.getSystemClassLoader()), + classLoaders: List = listOf(ClassLoader.getSystemClassLoader()), carpenterSchemas: CarpenterSchemas, - force : Boolean = false) { + force: Boolean = false) { if (classLoaders.exists(name)) { validatePropertyTypes(classLoaders) if (!force) return @@ -62,9 +62,8 @@ fun CompositeType.carpenterSchema( } try { - providesList.add (classLoaders.loadIfExists(it)) - } - catch (e: ClassNotFoundException) { + providesList.add(classLoaders.loadIfExists(it)) + } catch (e: ClassNotFoundException) { carpenterSchemas.addDepPair(this, name, it) isCreatable = false } @@ -75,15 +74,14 @@ fun CompositeType.carpenterSchema( fields.forEach { try { m[it.name] = FieldFactory.newInstance(it.mandatory, it.name, it.getTypeAsClass(classLoaders)) - } - catch (e: ClassNotFoundException) { + } catch (e: ClassNotFoundException) { carpenterSchemas.addDepPair(this, name, it.typeAsString()) isCreatable = false } } if (isCreatable) { - carpenterSchemas.carpenterSchemas.add (CarpenterSchemaFactory.newInstance( + carpenterSchemas.carpenterSchemas.add(CarpenterSchemaFactory.newInstance( name = name, fields = m, interfaces = providesList, @@ -91,33 +89,48 @@ fun CompositeType.carpenterSchema( } } +// map a pair of (typename, mandatory) to the corresponding class type +// where the mandatory AMQP flag maps to the types nullability +val typeStrToType: Map, Class> = mapOf( + Pair("int", true) to Int::class.javaPrimitiveType!!, + Pair("int", false) to Integer::class.javaObjectType, + Pair("short", true) to Short::class.javaPrimitiveType!!, + Pair("short", false) to Short::class.javaObjectType, + Pair("long", true) to Long::class.javaPrimitiveType!!, + Pair("long", false) to Long::class.javaObjectType, + Pair("char", true) to Char::class.javaPrimitiveType!!, + Pair("char", false) to java.lang.Character::class.java, + Pair("boolean", true) to Boolean::class.javaPrimitiveType!!, + Pair("boolean", false) to Boolean::class.javaObjectType, + Pair("double", true) to Double::class.javaPrimitiveType!!, + Pair("double", false) to Double::class.javaObjectType, + Pair("float", true) to Float::class.javaPrimitiveType!!, + Pair("float", false) to Float::class.javaObjectType, + Pair("byte", true) to Byte::class.javaPrimitiveType!!, + Pair("byte", false) to Byte::class.javaObjectType +) + fun AMQPField.getTypeAsClass( - classLoaders: List = listOf (ClassLoader.getSystemClassLoader()) -) = when (type) { - "int" -> if (mandatory) Integer::class.java else Int::class.javaPrimitiveType!! - "string" -> String::class.java - "short" -> Short::class.javaPrimitiveType!! - "long" -> Long::class.javaPrimitiveType!! - "char" -> Char::class.javaPrimitiveType!! - "boolean" -> Boolean::class.javaPrimitiveType!! - "double" -> Double::class.javaPrimitiveType!! - "float" -> Float::class.javaPrimitiveType!! - "*" -> classLoaders.loadIfExists(requires[0]) - else -> classLoaders.loadIfExists(type) + classLoaders: List = listOf(ClassLoader.getSystemClassLoader()) +) = typeStrToType[Pair(type, mandatory)] ?: when (type) { + "string" -> String::class.java + "*" -> classLoaders.loadIfExists(requires[0]) + else -> classLoaders.loadIfExists(type) } fun AMQPField.validateType( - classLoaders: List = listOf (ClassLoader.getSystemClassLoader()) + classLoaders: List = listOf(ClassLoader.getSystemClassLoader()) ) = when (type) { - "int", "string", "short", "long", "char", "boolean", "double", "float" -> true - "*" -> classLoaders.exists(requires[0]) - else -> classLoaders.exists (type) + "byte", "int", "string", "short", "long", "char", "boolean", "double", "float" -> true + "*" -> classLoaders.exists(requires[0]) + else -> classLoaders.exists(type) } -private fun List.exists (clazz: String) = - this.find { try { it.loadClass(clazz); true } catch (e: ClassNotFoundException) { false } } != null +private fun List.exists(clazz: String) = this.find { + try { it.loadClass(clazz); true } catch (e: ClassNotFoundException) { false } +} != null -private fun List.loadIfExists (clazz: String) : Class<*> { +private fun List.loadIfExists(clazz: String): Class<*> { this.forEach { try { return it.loadClass(clazz) diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeNeedingCarpentryTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeNeedingCarpentryTests.kt index 9b6f572f9a..54e3ce915f 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeNeedingCarpentryTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeNeedingCarpentryTests.kt @@ -3,7 +3,7 @@ package net.corda.core.serialization.amqp import org.junit.Test import kotlin.test.* import net.corda.core.serialization.carpenter.* -import java.lang.Character +import org.apache.qpid.proton.codec.Data interface I { fun getName() : String @@ -16,18 +16,331 @@ interface I { * replicates the situation where a reciever doesn't have some or all elements of a schema present on it's classpath */ class DeserializeNeedingCarpentryTests { + class TestSerializationOutput( + private val verbose: Boolean, + serializerFactory: SerializerFactory = SerializerFactory()) : SerializationOutput(serializerFactory) { + + override fun writeSchema(schema: Schema, data: Data) { + if (verbose) println(schema) + super.writeSchema(schema, data) + } + } + + companion object { + /** + * If you want to see the schema encoded into the envelope after serialisation change this to true + */ + private const val VERBOSE = false + } + + val sf = SerializerFactory() + @Test fun verySimpleType() { val testVal = 10 val clazz = ClassCarpenter().build(ClassSchema("oneType", mapOf("a" to NonNullableField(Int::class.java)))) val classInstance = clazz.constructors[0].newInstance(testVal) - val serialisedBytes = SerializationOutput().serialize(classInstance) - val deserializedObj = DeserializationInput().deserialize(serialisedBytes) + val serialisedBytes = TestSerializationOutput(VERBOSE, sf).serialize(classInstance) + val deserializedObj = DeserializationInput(sf).deserialize(serialisedBytes) assertEquals (testVal, deserializedObj::class.java.getMethod("getA").invoke(deserializedObj)) } + @Test + fun singleInt() { + val clazz = ClassCarpenter().build(ClassSchema("single", mapOf( + "int" to NonNullableField(Integer::class.javaPrimitiveType!!) + ))) + + val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(1)) + val db = DeserializationInput(sf).deserialize(sb) + + assertEquals(1, db::class.java.getMethod("getInt").invoke(db)) + } + + @Test + fun singleIntNullable() { + val clazz = ClassCarpenter().build(ClassSchema("single", mapOf( + "int" to NullableField(Integer::class.java) + ))) + + val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(1)) + val db = DeserializationInput(sf).deserialize(sb) + + assertEquals(1, db::class.java.getMethod("getInt").invoke(db)) + } + + @Test + fun singleIntNullableNull() { + val clazz = ClassCarpenter().build(ClassSchema("single", mapOf( + "int" to NullableField(Integer::class.java) + ))) + + val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(null)) + val db = DeserializationInput(sf).deserialize(sb) + + assertEquals(null, db::class.java.getMethod("getInt").invoke(db)) + } + + @Test + fun singleChar() { + val clazz = ClassCarpenter().build(ClassSchema("single", mapOf( + "char" to NonNullableField(Character::class.javaPrimitiveType!!) + ))) + + val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance('a')) + val db = DeserializationInput(sf).deserialize(sb) + + assertEquals('a', db::class.java.getMethod("getChar").invoke(db)) + } + + @Test + fun singleCharNullable() { + val clazz = ClassCarpenter().build(ClassSchema("single", mapOf( + "char" to NullableField(Character::class.javaObjectType) + ))) + + val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance('a')) + val db = DeserializationInput(sf).deserialize(sb) + + assertEquals('a', db::class.java.getMethod("getChar").invoke(db)) + } + + @Test + fun singleCharNullableNull() { + val clazz = ClassCarpenter().build(ClassSchema("single", mapOf( + "char" to NullableField(java.lang.Character::class.java) + ))) + + val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(null)) + val db = DeserializationInput(sf).deserialize(sb) + + assertEquals(null, db::class.java.getMethod("getChar").invoke(db)) + } + + @Test + fun singleLong() { + val clazz = ClassCarpenter().build(ClassSchema("single", mapOf( + "long" to NonNullableField(Long::class.javaPrimitiveType!!) + ))) + + val l : Long = 1 + val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(l)) + val db = DeserializationInput(sf).deserialize(sb) + + assertEquals(l, (db::class.java.getMethod("getLong").invoke(db))) + } + + @Test + fun singleLongNullable() { + val clazz = ClassCarpenter().build(ClassSchema("single", mapOf( + "long" to NullableField(Long::class.javaObjectType) + ))) + + val l : Long = 1 + val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(l)) + val db = DeserializationInput(sf).deserialize(sb) + + assertEquals(l, (db::class.java.getMethod("getLong").invoke(db))) + } + + @Test + fun singleLongNullableNull() { + val clazz = ClassCarpenter().build(ClassSchema("single", mapOf( + "long" to NullableField(Long::class.javaObjectType) + ))) + + val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(null)) + val db = DeserializationInput(sf).deserialize(sb) + + assertEquals(null, (db::class.java.getMethod("getLong").invoke(db))) + } + + @Test + fun singleBoolean() { + val clazz = ClassCarpenter().build(ClassSchema("single", mapOf( + "boolean" to NonNullableField(Boolean::class.javaPrimitiveType!!) + ))) + + val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(true)) + val db = DeserializationInput(sf).deserialize(sb) + + assertEquals(true, db::class.java.getMethod("getBoolean").invoke(db)) + } + + @Test + fun singleBooleanNullable() { + val clazz = ClassCarpenter().build(ClassSchema("single", mapOf( + "boolean" to NullableField(Boolean::class.javaObjectType) + ))) + + val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(true)) + val db = DeserializationInput(sf).deserialize(sb) + + assertEquals(true, db::class.java.getMethod("getBoolean").invoke(db)) + } + + @Test + fun singleBooleanNullableNull() { + val clazz = ClassCarpenter().build(ClassSchema("single", mapOf( + "boolean" to NullableField(Boolean::class.javaObjectType) + ))) + + val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(null)) + val db = DeserializationInput(sf).deserialize(sb) + + assertEquals(null, db::class.java.getMethod("getBoolean").invoke(db)) + } + + @Test + fun singleDouble() { + val clazz = ClassCarpenter().build(ClassSchema("single", mapOf( + "double" to NonNullableField(Double::class.javaPrimitiveType!!) + ))) + + val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(10.0)) + val db = DeserializationInput(sf).deserialize(sb) + + assertEquals(10.0, db::class.java.getMethod("getDouble").invoke(db)) + } + + @Test + fun singleDoubleNullable() { + val clazz = ClassCarpenter().build(ClassSchema("single", mapOf( + "double" to NullableField(Double::class.javaObjectType) + ))) + + val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(10.0)) + val db = DeserializationInput(sf).deserialize(sb) + + assertEquals(10.0, db::class.java.getMethod("getDouble").invoke(db)) + } + + @Test + fun singleDoubleNullableNull() { + val clazz = ClassCarpenter().build(ClassSchema("single", mapOf( + "double" to NullableField(Double::class.javaObjectType) + ))) + + val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(null)) + val db = DeserializationInput(sf).deserialize(sb) + + assertEquals(null, db::class.java.getMethod("getDouble").invoke(db)) + } + + @Test + fun singleShort() { + val clazz = ClassCarpenter().build(ClassSchema("single", mapOf( + "short" to NonNullableField(Short::class.javaPrimitiveType!!) + ))) + + val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(3.toShort())) + val db = DeserializationInput(sf).deserialize(sb) + + assertEquals(3.toShort(), db::class.java.getMethod("getShort").invoke(db)) + } + + @Test + fun singleShortNullable() { + val clazz = ClassCarpenter().build(ClassSchema("single", mapOf( + "short" to NullableField(Short::class.javaObjectType) + ))) + + val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(3.toShort())) + val db = DeserializationInput(sf).deserialize(sb) + + assertEquals(3.toShort(), db::class.java.getMethod("getShort").invoke(db)) + } + + @Test + fun singleShortNullableNull() { + val clazz = ClassCarpenter().build(ClassSchema("single", mapOf( + "short" to NullableField(Short::class.javaObjectType) + ))) + + val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(null)) + val db = DeserializationInput(sf).deserialize(sb) + + assertEquals(null, db::class.java.getMethod("getShort").invoke(db)) + } + + @Test + fun singleFloat() { + val clazz = ClassCarpenter().build(ClassSchema("single", mapOf( + "float" to NonNullableField(Float::class.javaPrimitiveType!!) + ))) + + val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(10.0F)) + val db = DeserializationInput(sf).deserialize(sb) + + assertEquals(10.0F, db::class.java.getMethod("getFloat").invoke(db)) + } + + @Test + fun singleFloatNullable() { + val clazz = ClassCarpenter().build(ClassSchema("single", mapOf( + "float" to NullableField(Float::class.javaObjectType) + ))) + + val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(10.0F)) + val db = DeserializationInput(sf).deserialize(sb) + + assertEquals(10.0F, db::class.java.getMethod("getFloat").invoke(db)) + } + + @Test + fun singleFloatNullableNull() { + val clazz = ClassCarpenter().build(ClassSchema("single", mapOf( + "float" to NullableField(Float::class.javaObjectType) + ))) + + val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(null)) + val db = DeserializationInput(sf).deserialize(sb) + + assertEquals(null, db::class.java.getMethod("getFloat").invoke(db)) + } + + @Test + fun singleByte() { + val clazz = ClassCarpenter().build(ClassSchema("single", mapOf( + "byte" to NonNullableField(Byte::class.javaPrimitiveType!!) + ))) + + val b : Byte = 0b0101 + val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(b)) + val db = DeserializationInput(sf).deserialize(sb) + + assertEquals(b, db::class.java.getMethod("getByte").invoke(db)) + assertEquals(0b0101, (db::class.java.getMethod("getByte").invoke(db) as Byte)) + } + + @Test + fun singleByteNullable() { + val clazz = ClassCarpenter().build(ClassSchema("single", mapOf( + "byte" to NullableField(Byte::class.javaObjectType) + ))) + + val b : Byte = 0b0101 + val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(b)) + val db = DeserializationInput(sf).deserialize(sb) + + assertEquals(b, db::class.java.getMethod("getByte").invoke(db)) + assertEquals(0b0101, (db::class.java.getMethod("getByte").invoke(db) as Byte)) + } + + @Test + fun singleByteNullableNull() { + val clazz = ClassCarpenter().build(ClassSchema("single", mapOf( + "byte" to NullableField(Byte::class.javaObjectType) + ))) + + val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(null)) + val db = DeserializationInput(sf).deserialize(sb) + + assertEquals(null, db::class.java.getMethod("getByte").invoke(db)) + } + @Test fun simpleTypeKnownInterface() { val clazz = ClassCarpenter().build (ClassSchema( @@ -36,8 +349,8 @@ class DeserializeNeedingCarpentryTests { val testVal = "Some Person" val classInstance = clazz.constructors[0].newInstance(testVal) - val serialisedBytes = SerializationOutput().serialize(classInstance) - val deserializedObj = DeserializationInput().deserialize(serialisedBytes) + val serialisedBytes = TestSerializationOutput(VERBOSE, sf).serialize(classInstance) + val deserializedObj = DeserializationInput(sf).deserialize(serialisedBytes) assertTrue(deserializedObj is I) assertEquals(testVal, (deserializedObj as I).getName()) @@ -53,8 +366,8 @@ class DeserializeNeedingCarpentryTests { mapOf("inner" to NonNullableField(nestedClass)))) val classInstance = outerClass.constructors.first().newInstance(nestedClass.constructors.first().newInstance("name")) - val serialisedBytes = SerializationOutput().serialize(classInstance) - val deserializedObj = DeserializationInput().deserialize(serialisedBytes) + val serialisedBytes = TestSerializationOutput(VERBOSE, sf).serialize(classInstance) + val deserializedObj = DeserializationInput(sf).deserialize(serialisedBytes) val inner = deserializedObj::class.java.getMethod("getInner").invoke(deserializedObj) assertEquals("name", inner::class.java.getMethod("getName").invoke(inner)) @@ -72,8 +385,8 @@ class DeserializeNeedingCarpentryTests { nestedClass.constructors.first().newInstance("foo"), nestedClass.constructors.first().newInstance("bar")) - val serialisedBytes = SerializationOutput().serialize(classInstance) - val deserializedObj = DeserializationInput().deserialize(serialisedBytes) + val serialisedBytes = TestSerializationOutput(VERBOSE, sf).serialize(classInstance) + val deserializedObj = DeserializationInput(sf).deserialize(serialisedBytes) assertEquals ("foo", deserializedObj.a::class.java.getMethod("getName").invoke(deserializedObj.a)) assertEquals ("bar", deserializedObj.b::class.java.getMethod("getName").invoke(deserializedObj.b)) @@ -92,8 +405,8 @@ class DeserializeNeedingCarpentryTests { unknownClass.constructors.first().newInstance(5, 6), unknownClass.constructors.first().newInstance(7, 8))) - val serialisedBytes = SerializationOutput().serialize(toSerialise) - val deserializedObj = DeserializationInput().deserialize(serialisedBytes) + val serialisedBytes = TestSerializationOutput(VERBOSE, sf).serialize(toSerialise) + val deserializedObj = DeserializationInput(sf).deserialize(serialisedBytes) var sentinel = 1 deserializedObj.l.forEach { assertEquals(sentinel++, it::class.java.getMethod("getV1").invoke(it)) @@ -114,9 +427,9 @@ class DeserializeNeedingCarpentryTests { "name" to NonNullableField(String::class.java)), interfaces = listOf (I::class.java, interfaceClass))) - val serialisedBytes = SerializationOutput().serialize( + val serialisedBytes = TestSerializationOutput(VERBOSE, sf).serialize( concreteClass.constructors.first().newInstance(12, "timmy")) - val deserializedObj = DeserializationInput().deserialize(serialisedBytes) + val deserializedObj = DeserializationInput(sf).deserialize(serialisedBytes) assertTrue(deserializedObj is I) assertEquals("timmy", (deserializedObj as I).getName()) @@ -128,19 +441,75 @@ class DeserializeNeedingCarpentryTests { fun manyTypes() { val cc = ClassCarpenter() - val manyClass = cc.build (ClassSchema( - "many", - mapOf( - "intA" to NonNullableField (Int::class.java), - "intB" to NullableField (Integer::class.java), - "strA" to NonNullableField (String::class.java), - "strB" to NullableField (String::class.java), - "charA" to NonNullableField (Char::class.java), - "charB" to NullableField (Character::class.java)))) + val manyClass = cc.build (ClassSchema("many", mapOf( + "intA" to NonNullableField (Int::class.java), + "intB" to NullableField (Integer::class.java), + "intC" to NullableField (Integer::class.java), + "strA" to NonNullableField (String::class.java), + "strB" to NullableField (String::class.java), + "strC" to NullableField (String::class.java), + "charA" to NonNullableField (Char::class.java), + "charB" to NullableField (Character::class.javaObjectType), + "charC" to NullableField (Character::class.javaObjectType), + "shortA" to NonNullableField (Short::class.javaPrimitiveType!!), + "shortB" to NullableField (Short::class.javaObjectType), + "shortC" to NullableField (Short::class.javaObjectType), + "longA" to NonNullableField (Long::class.javaPrimitiveType!!), + "longB" to NullableField(Long::class.javaObjectType), + "longC" to NullableField(Long::class.javaObjectType), + "booleanA" to NonNullableField (Boolean::class.javaPrimitiveType!!), + "booleanB" to NullableField (Boolean::class.javaObjectType), + "booleanC" to NullableField (Boolean::class.javaObjectType), + "doubleA" to NonNullableField (Double::class.javaPrimitiveType!!), + "doubleB" to NullableField (Double::class.javaObjectType), + "doubleC" to NullableField (Double::class.javaObjectType), + "floatA" to NonNullableField (Float::class.javaPrimitiveType!!), + "floatB" to NullableField (Float::class.javaObjectType), + "floatC" to NullableField (Float::class.javaObjectType), + "byteA" to NonNullableField (Byte::class.javaPrimitiveType!!), + "byteB" to NullableField (Byte::class.javaObjectType), + "byteC" to NullableField (Byte::class.javaObjectType)))) - val serialisedBytes = SerializationOutput().serialize( - manyClass.constructors.first().newInstance(1, 2, "a", "b", 'c', 'd')) + val serialisedBytes = TestSerializationOutput(VERBOSE, sf).serialize( + manyClass.constructors.first().newInstance( + 1, 2, null, + "a", "b", null, + 'c', 'd', null, + 3.toShort(), 4.toShort(), null, + 100.toLong(), 200.toLong(), null, + true, false, null, + 10.0, 20.0, null, + 10.0F, 20.0F, null, + 0b0101.toByte(), 0b1010.toByte(), null)) - val deserializedObj = DeserializationInput().deserialize(serialisedBytes) + val deserializedObj = DeserializationInput(sf).deserialize(serialisedBytes) + + assertEquals(1, deserializedObj::class.java.getMethod("getIntA").invoke(deserializedObj)) + assertEquals(2, deserializedObj::class.java.getMethod("getIntB").invoke(deserializedObj)) + assertEquals(null, deserializedObj::class.java.getMethod("getIntC").invoke(deserializedObj)) + assertEquals("a", deserializedObj::class.java.getMethod("getStrA").invoke(deserializedObj)) + assertEquals("b", deserializedObj::class.java.getMethod("getStrB").invoke(deserializedObj)) + assertEquals(null, deserializedObj::class.java.getMethod("getStrC").invoke(deserializedObj)) + assertEquals('c', deserializedObj::class.java.getMethod("getCharA").invoke(deserializedObj)) + assertEquals('d', deserializedObj::class.java.getMethod("getCharB").invoke(deserializedObj)) + assertEquals(null, deserializedObj::class.java.getMethod("getCharC").invoke(deserializedObj)) + assertEquals(3.toShort(), deserializedObj::class.java.getMethod("getShortA").invoke(deserializedObj)) + assertEquals(4.toShort(), deserializedObj::class.java.getMethod("getShortB").invoke(deserializedObj)) + assertEquals(null, deserializedObj::class.java.getMethod("getShortC").invoke(deserializedObj)) + assertEquals(100.toLong(), deserializedObj::class.java.getMethod("getLongA").invoke(deserializedObj)) + assertEquals(200.toLong(), deserializedObj::class.java.getMethod("getLongB").invoke(deserializedObj)) + assertEquals(null, deserializedObj::class.java.getMethod("getLongC").invoke(deserializedObj)) + assertEquals(true, deserializedObj::class.java.getMethod("getBooleanA").invoke(deserializedObj)) + assertEquals(false, deserializedObj::class.java.getMethod("getBooleanB").invoke(deserializedObj)) + assertEquals(null, deserializedObj::class.java.getMethod("getBooleanC").invoke(deserializedObj)) + assertEquals(10.0, deserializedObj::class.java.getMethod("getDoubleA").invoke(deserializedObj)) + assertEquals(20.0, deserializedObj::class.java.getMethod("getDoubleB").invoke(deserializedObj)) + assertEquals(null, deserializedObj::class.java.getMethod("getDoubleC").invoke(deserializedObj)) + assertEquals(10.0F, deserializedObj::class.java.getMethod("getFloatA").invoke(deserializedObj)) + assertEquals(20.0F, deserializedObj::class.java.getMethod("getFloatB").invoke(deserializedObj)) + assertEquals(null, deserializedObj::class.java.getMethod("getFloatC").invoke(deserializedObj)) + assertEquals(0b0101.toByte(), deserializedObj::class.java.getMethod("getByteA").invoke(deserializedObj)) + assertEquals(0b1010.toByte(), deserializedObj::class.java.getMethod("getByteB").invoke(deserializedObj)) + assertEquals(null, deserializedObj::class.java.getMethod("getByteC").invoke(deserializedObj)) } } From 92ba0c33e169fc2fa56cea133890654196f1103f Mon Sep 17 00:00:00 2001 From: Katelyn Baker Date: Thu, 27 Jul 2017 15:33:09 +0100 Subject: [PATCH 05/10] Adding more / better tests, splitting existing tests up Squashed commit messages - Split up the serialiser/synthesiser tests into more manageable sizes - WIP - found test which breaks deserialiser Looks to be soemthign to do with synthesising a class with a container of Interfaces --- .../core/serialization/amqp/AMQPTestUtils.kt | 16 + ...erializeNeedingCarpentrySimpleTypesTest.kt | 409 +++++++++++++++++ .../amqp/DeserializeNeedingCarpentryTests.kt | 420 ++---------------- 3 files changed, 464 insertions(+), 381 deletions(-) create mode 100644 core/src/test/kotlin/net/corda/core/serialization/amqp/AMQPTestUtils.kt create mode 100644 core/src/test/kotlin/net/corda/core/serialization/amqp/DeserializeNeedingCarpentrySimpleTypesTest.kt diff --git a/core/src/test/kotlin/net/corda/core/serialization/amqp/AMQPTestUtils.kt b/core/src/test/kotlin/net/corda/core/serialization/amqp/AMQPTestUtils.kt new file mode 100644 index 0000000000..5a19337f3d --- /dev/null +++ b/core/src/test/kotlin/net/corda/core/serialization/amqp/AMQPTestUtils.kt @@ -0,0 +1,16 @@ +package net.corda.core.serialization.amqp.test + +import org.apache.qpid.proton.codec.Data +import net.corda.core.serialization.amqp.Schema +import net.corda.core.serialization.amqp.SerializerFactory +import net.corda.core.serialization.amqp.SerializationOutput + +class TestSerializationOutput( + private val verbose: Boolean, + serializerFactory: SerializerFactory = SerializerFactory()) : SerializationOutput(serializerFactory) { + + override fun writeSchema(schema: Schema, data: Data) { + if (verbose) println(schema) + super.writeSchema(schema, data) + } +} diff --git a/core/src/test/kotlin/net/corda/core/serialization/amqp/DeserializeNeedingCarpentrySimpleTypesTest.kt b/core/src/test/kotlin/net/corda/core/serialization/amqp/DeserializeNeedingCarpentrySimpleTypesTest.kt new file mode 100644 index 0000000000..b36e540d96 --- /dev/null +++ b/core/src/test/kotlin/net/corda/core/serialization/amqp/DeserializeNeedingCarpentrySimpleTypesTest.kt @@ -0,0 +1,409 @@ +package net.corda.core.serialization.amqp + +import org.junit.Test +import kotlin.test.* +import net.corda.core.serialization.carpenter.* +import net.corda.core.serialization.amqp.test.TestSerializationOutput + +/** + * These tests work by having the class carpenter build the classes we serialise and then deserialise. Because + * those classes don't exist within the system's Class Loader the deserialiser will be forced to carpent + * versions of them up using its own internal class carpenter (each carpenter houses it's own loader). This + * replicates the situation where a receiver doesn't have some or all elements of a schema present on it's classpath + */ +class DeserializeNeedingCarpentrySimpleTypesTest { + companion object { + /** + * If you want to see the schema encoded into the envelope after serialisation change this to true + */ + private const val VERBOSE = false + } + + val sf = SerializerFactory() + + @Test + fun singleInt() { + val clazz = ClassCarpenter().build(ClassSchema("single", mapOf( + "int" to NonNullableField(Integer::class.javaPrimitiveType!!) + ))) + + val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(1)) + val db = DeserializationInput(sf).deserialize(sb) + + assertEquals(1, db::class.java.getMethod("getInt").invoke(db)) + } + + @Test + fun singleIntNullable() { + val clazz = ClassCarpenter().build(ClassSchema("single", mapOf( + "int" to NullableField(Integer::class.java) + ))) + + val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(1)) + val db = DeserializationInput(sf).deserialize(sb) + + assertEquals(1, db::class.java.getMethod("getInt").invoke(db)) + } + + @Test + fun singleIntNullableNull() { + val clazz = ClassCarpenter().build(ClassSchema("single", mapOf( + "int" to NullableField(Integer::class.java) + ))) + + val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(null)) + val db = DeserializationInput(sf).deserialize(sb) + + assertEquals(null, db::class.java.getMethod("getInt").invoke(db)) + } + + @Test + fun singleChar() { + val clazz = ClassCarpenter().build(ClassSchema("single", mapOf( + "char" to NonNullableField(Character::class.javaPrimitiveType!!) + ))) + + val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance('a')) + val db = DeserializationInput(sf).deserialize(sb) + + assertEquals('a', db::class.java.getMethod("getChar").invoke(db)) + } + + @Test + fun singleCharNullable() { + val clazz = ClassCarpenter().build(ClassSchema("single", mapOf( + "char" to NullableField(Character::class.javaObjectType) + ))) + + val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance('a')) + val db = DeserializationInput(sf).deserialize(sb) + + assertEquals('a', db::class.java.getMethod("getChar").invoke(db)) + } + + @Test + fun singleCharNullableNull() { + val clazz = ClassCarpenter().build(ClassSchema("single", mapOf( + "char" to NullableField(java.lang.Character::class.java) + ))) + + val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(null)) + val db = DeserializationInput(sf).deserialize(sb) + + assertEquals(null, db::class.java.getMethod("getChar").invoke(db)) + } + + @Test + fun singleLong() { + val clazz = ClassCarpenter().build(ClassSchema("single", mapOf( + "long" to NonNullableField(Long::class.javaPrimitiveType!!) + ))) + + val l : Long = 1 + val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(l)) + val db = DeserializationInput(sf).deserialize(sb) + + assertEquals(l, (db::class.java.getMethod("getLong").invoke(db))) + } + + @Test + fun singleLongNullable() { + val clazz = ClassCarpenter().build(ClassSchema("single", mapOf( + "long" to NullableField(Long::class.javaObjectType) + ))) + + val l : Long = 1 + val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(l)) + val db = DeserializationInput(sf).deserialize(sb) + + assertEquals(l, (db::class.java.getMethod("getLong").invoke(db))) + } + + @Test + fun singleLongNullableNull() { + val clazz = ClassCarpenter().build(ClassSchema("single", mapOf( + "long" to NullableField(Long::class.javaObjectType) + ))) + + val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(null)) + val db = DeserializationInput(sf).deserialize(sb) + + assertEquals(null, (db::class.java.getMethod("getLong").invoke(db))) + } + + @Test + fun singleBoolean() { + val clazz = ClassCarpenter().build(ClassSchema("single", mapOf( + "boolean" to NonNullableField(Boolean::class.javaPrimitiveType!!) + ))) + + val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(true)) + val db = DeserializationInput(sf).deserialize(sb) + + assertEquals(true, db::class.java.getMethod("getBoolean").invoke(db)) + } + + @Test + fun singleBooleanNullable() { + val clazz = ClassCarpenter().build(ClassSchema("single", mapOf( + "boolean" to NullableField(Boolean::class.javaObjectType) + ))) + + val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(true)) + val db = DeserializationInput(sf).deserialize(sb) + + assertEquals(true, db::class.java.getMethod("getBoolean").invoke(db)) + } + + @Test + fun singleBooleanNullableNull() { + val clazz = ClassCarpenter().build(ClassSchema("single", mapOf( + "boolean" to NullableField(Boolean::class.javaObjectType) + ))) + + val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(null)) + val db = DeserializationInput(sf).deserialize(sb) + + assertEquals(null, db::class.java.getMethod("getBoolean").invoke(db)) + } + + @Test + fun singleDouble() { + val clazz = ClassCarpenter().build(ClassSchema("single", mapOf( + "double" to NonNullableField(Double::class.javaPrimitiveType!!) + ))) + + val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(10.0)) + val db = DeserializationInput(sf).deserialize(sb) + + assertEquals(10.0, db::class.java.getMethod("getDouble").invoke(db)) + } + + @Test + fun singleDoubleNullable() { + val clazz = ClassCarpenter().build(ClassSchema("single", mapOf( + "double" to NullableField(Double::class.javaObjectType) + ))) + + val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(10.0)) + val db = DeserializationInput(sf).deserialize(sb) + + assertEquals(10.0, db::class.java.getMethod("getDouble").invoke(db)) + } + + @Test + fun singleDoubleNullableNull() { + val clazz = ClassCarpenter().build(ClassSchema("single", mapOf( + "double" to NullableField(Double::class.javaObjectType) + ))) + + val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(null)) + val db = DeserializationInput(sf).deserialize(sb) + + assertEquals(null, db::class.java.getMethod("getDouble").invoke(db)) + } + + @Test + fun singleShort() { + val clazz = ClassCarpenter().build(ClassSchema("single", mapOf( + "short" to NonNullableField(Short::class.javaPrimitiveType!!) + ))) + + val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(3.toShort())) + val db = DeserializationInput(sf).deserialize(sb) + + assertEquals(3.toShort(), db::class.java.getMethod("getShort").invoke(db)) + } + + @Test + fun singleShortNullable() { + val clazz = ClassCarpenter().build(ClassSchema("single", mapOf( + "short" to NullableField(Short::class.javaObjectType) + ))) + + val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(3.toShort())) + val db = DeserializationInput(sf).deserialize(sb) + + assertEquals(3.toShort(), db::class.java.getMethod("getShort").invoke(db)) + } + + @Test + fun singleShortNullableNull() { + val clazz = ClassCarpenter().build(ClassSchema("single", mapOf( + "short" to NullableField(Short::class.javaObjectType) + ))) + + val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(null)) + val db = DeserializationInput(sf).deserialize(sb) + + assertEquals(null, db::class.java.getMethod("getShort").invoke(db)) + } + + @Test + fun singleFloat() { + val clazz = ClassCarpenter().build(ClassSchema("single", mapOf( + "float" to NonNullableField(Float::class.javaPrimitiveType!!) + ))) + + val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(10.0F)) + val db = DeserializationInput(sf).deserialize(sb) + + assertEquals(10.0F, db::class.java.getMethod("getFloat").invoke(db)) + } + + @Test + fun singleFloatNullable() { + val clazz = ClassCarpenter().build(ClassSchema("single", mapOf( + "float" to NullableField(Float::class.javaObjectType) + ))) + + val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(10.0F)) + val db = DeserializationInput(sf).deserialize(sb) + + assertEquals(10.0F, db::class.java.getMethod("getFloat").invoke(db)) + } + + @Test + fun singleFloatNullableNull() { + val clazz = ClassCarpenter().build(ClassSchema("single", mapOf( + "float" to NullableField(Float::class.javaObjectType) + ))) + + val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(null)) + val db = DeserializationInput(sf).deserialize(sb) + + assertEquals(null, db::class.java.getMethod("getFloat").invoke(db)) + } + + @Test + fun singleByte() { + val clazz = ClassCarpenter().build(ClassSchema("single", mapOf( + "byte" to NonNullableField(Byte::class.javaPrimitiveType!!) + ))) + + val b : Byte = 0b0101 + val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(b)) + val db = DeserializationInput(sf).deserialize(sb) + + assertEquals(b, db::class.java.getMethod("getByte").invoke(db)) + assertEquals(0b0101, (db::class.java.getMethod("getByte").invoke(db) as Byte)) + } + + @Test + fun singleByteNullable() { + val clazz = ClassCarpenter().build(ClassSchema("single", mapOf( + "byte" to NullableField(Byte::class.javaObjectType) + ))) + + val b : Byte = 0b0101 + val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(b)) + val db = DeserializationInput(sf).deserialize(sb) + + assertEquals(b, db::class.java.getMethod("getByte").invoke(db)) + assertEquals(0b0101, (db::class.java.getMethod("getByte").invoke(db) as Byte)) + } + + @Test + fun singleByteNullableNull() { + val clazz = ClassCarpenter().build(ClassSchema("single", mapOf( + "byte" to NullableField(Byte::class.javaObjectType) + ))) + + val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(null)) + val db = DeserializationInput(sf).deserialize(sb) + + assertEquals(null, db::class.java.getMethod("getByte").invoke(db)) + } + + @Test + fun simpleTypeKnownInterface() { + val clazz = ClassCarpenter().build (ClassSchema( + "oneType", mapOf("name" to NonNullableField(String::class.java)), + interfaces = listOf (I::class.java))) + val testVal = "Some Person" + val classInstance = clazz.constructors[0].newInstance(testVal) + + val serialisedBytes = TestSerializationOutput(VERBOSE, sf).serialize(classInstance) + val deserializedObj = DeserializationInput(sf).deserialize(serialisedBytes) + + assertTrue(deserializedObj is I) + assertEquals(testVal, (deserializedObj as I).getName()) + } + + @Test + fun manyTypes() { + val manyClass = ClassCarpenter().build (ClassSchema("many", mapOf( + "intA" to NonNullableField (Int::class.java), + "intB" to NullableField (Integer::class.java), + "intC" to NullableField (Integer::class.java), + "strA" to NonNullableField (String::class.java), + "strB" to NullableField (String::class.java), + "strC" to NullableField (String::class.java), + "charA" to NonNullableField (Char::class.java), + "charB" to NullableField (Character::class.javaObjectType), + "charC" to NullableField (Character::class.javaObjectType), + "shortA" to NonNullableField (Short::class.javaPrimitiveType!!), + "shortB" to NullableField (Short::class.javaObjectType), + "shortC" to NullableField (Short::class.javaObjectType), + "longA" to NonNullableField (Long::class.javaPrimitiveType!!), + "longB" to NullableField(Long::class.javaObjectType), + "longC" to NullableField(Long::class.javaObjectType), + "booleanA" to NonNullableField (Boolean::class.javaPrimitiveType!!), + "booleanB" to NullableField (Boolean::class.javaObjectType), + "booleanC" to NullableField (Boolean::class.javaObjectType), + "doubleA" to NonNullableField (Double::class.javaPrimitiveType!!), + "doubleB" to NullableField (Double::class.javaObjectType), + "doubleC" to NullableField (Double::class.javaObjectType), + "floatA" to NonNullableField (Float::class.javaPrimitiveType!!), + "floatB" to NullableField (Float::class.javaObjectType), + "floatC" to NullableField (Float::class.javaObjectType), + "byteA" to NonNullableField (Byte::class.javaPrimitiveType!!), + "byteB" to NullableField (Byte::class.javaObjectType), + "byteC" to NullableField (Byte::class.javaObjectType)))) + + val serialisedBytes = TestSerializationOutput(VERBOSE, sf).serialize( + manyClass.constructors.first().newInstance( + 1, 2, null, + "a", "b", null, + 'c', 'd', null, + 3.toShort(), 4.toShort(), null, + 100.toLong(), 200.toLong(), null, + true, false, null, + 10.0, 20.0, null, + 10.0F, 20.0F, null, + 0b0101.toByte(), 0b1010.toByte(), null)) + + val deserializedObj = DeserializationInput(sf).deserialize(serialisedBytes) + + assertEquals(1, deserializedObj::class.java.getMethod("getIntA").invoke(deserializedObj)) + assertEquals(2, deserializedObj::class.java.getMethod("getIntB").invoke(deserializedObj)) + assertEquals(null, deserializedObj::class.java.getMethod("getIntC").invoke(deserializedObj)) + assertEquals("a", deserializedObj::class.java.getMethod("getStrA").invoke(deserializedObj)) + assertEquals("b", deserializedObj::class.java.getMethod("getStrB").invoke(deserializedObj)) + assertEquals(null, deserializedObj::class.java.getMethod("getStrC").invoke(deserializedObj)) + assertEquals('c', deserializedObj::class.java.getMethod("getCharA").invoke(deserializedObj)) + assertEquals('d', deserializedObj::class.java.getMethod("getCharB").invoke(deserializedObj)) + assertEquals(null, deserializedObj::class.java.getMethod("getCharC").invoke(deserializedObj)) + assertEquals(3.toShort(), deserializedObj::class.java.getMethod("getShortA").invoke(deserializedObj)) + assertEquals(4.toShort(), deserializedObj::class.java.getMethod("getShortB").invoke(deserializedObj)) + assertEquals(null, deserializedObj::class.java.getMethod("getShortC").invoke(deserializedObj)) + assertEquals(100.toLong(), deserializedObj::class.java.getMethod("getLongA").invoke(deserializedObj)) + assertEquals(200.toLong(), deserializedObj::class.java.getMethod("getLongB").invoke(deserializedObj)) + assertEquals(null, deserializedObj::class.java.getMethod("getLongC").invoke(deserializedObj)) + assertEquals(true, deserializedObj::class.java.getMethod("getBooleanA").invoke(deserializedObj)) + assertEquals(false, deserializedObj::class.java.getMethod("getBooleanB").invoke(deserializedObj)) + assertEquals(null, deserializedObj::class.java.getMethod("getBooleanC").invoke(deserializedObj)) + assertEquals(10.0, deserializedObj::class.java.getMethod("getDoubleA").invoke(deserializedObj)) + assertEquals(20.0, deserializedObj::class.java.getMethod("getDoubleB").invoke(deserializedObj)) + assertEquals(null, deserializedObj::class.java.getMethod("getDoubleC").invoke(deserializedObj)) + assertEquals(10.0F, deserializedObj::class.java.getMethod("getFloatA").invoke(deserializedObj)) + assertEquals(20.0F, deserializedObj::class.java.getMethod("getFloatB").invoke(deserializedObj)) + assertEquals(null, deserializedObj::class.java.getMethod("getFloatC").invoke(deserializedObj)) + assertEquals(0b0101.toByte(), deserializedObj::class.java.getMethod("getByteA").invoke(deserializedObj)) + assertEquals(0b1010.toByte(), deserializedObj::class.java.getMethod("getByteB").invoke(deserializedObj)) + assertEquals(null, deserializedObj::class.java.getMethod("getByteC").invoke(deserializedObj)) + } +} + + + diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeNeedingCarpentryTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeNeedingCarpentryTests.kt index 54e3ce915f..ea74d2ad82 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeNeedingCarpentryTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeNeedingCarpentryTests.kt @@ -3,28 +3,24 @@ package net.corda.core.serialization.amqp import org.junit.Test import kotlin.test.* import net.corda.core.serialization.carpenter.* -import org.apache.qpid.proton.codec.Data +import net.corda.core.serialization.amqp.test.TestSerializationOutput interface I { fun getName() : String } +interface II { + fun getAge() : Int + fun getThingWithName(): I +} + /** * These tests work by having the class carpenter build the classes we serialise and then deserialise. Because * those classes don't exist within the system's Class Loader the deserialiser will be forced to carpent * versions of them up using its own internal class carpenter (each carpenter houses it's own loader). This - * replicates the situation where a reciever doesn't have some or all elements of a schema present on it's classpath + * replicates the situation where a receiver doesn't have some or all elements of a schema present on it's classpath */ class DeserializeNeedingCarpentryTests { - class TestSerializationOutput( - private val verbose: Boolean, - serializerFactory: SerializerFactory = SerializerFactory()) : SerializationOutput(serializerFactory) { - - override fun writeSchema(schema: Schema, data: Data) { - if (verbose) println(schema) - super.writeSchema(schema, data) - } - } companion object { /** @@ -47,300 +43,6 @@ class DeserializeNeedingCarpentryTests { assertEquals (testVal, deserializedObj::class.java.getMethod("getA").invoke(deserializedObj)) } - @Test - fun singleInt() { - val clazz = ClassCarpenter().build(ClassSchema("single", mapOf( - "int" to NonNullableField(Integer::class.javaPrimitiveType!!) - ))) - - val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(1)) - val db = DeserializationInput(sf).deserialize(sb) - - assertEquals(1, db::class.java.getMethod("getInt").invoke(db)) - } - - @Test - fun singleIntNullable() { - val clazz = ClassCarpenter().build(ClassSchema("single", mapOf( - "int" to NullableField(Integer::class.java) - ))) - - val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(1)) - val db = DeserializationInput(sf).deserialize(sb) - - assertEquals(1, db::class.java.getMethod("getInt").invoke(db)) - } - - @Test - fun singleIntNullableNull() { - val clazz = ClassCarpenter().build(ClassSchema("single", mapOf( - "int" to NullableField(Integer::class.java) - ))) - - val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(null)) - val db = DeserializationInput(sf).deserialize(sb) - - assertEquals(null, db::class.java.getMethod("getInt").invoke(db)) - } - - @Test - fun singleChar() { - val clazz = ClassCarpenter().build(ClassSchema("single", mapOf( - "char" to NonNullableField(Character::class.javaPrimitiveType!!) - ))) - - val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance('a')) - val db = DeserializationInput(sf).deserialize(sb) - - assertEquals('a', db::class.java.getMethod("getChar").invoke(db)) - } - - @Test - fun singleCharNullable() { - val clazz = ClassCarpenter().build(ClassSchema("single", mapOf( - "char" to NullableField(Character::class.javaObjectType) - ))) - - val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance('a')) - val db = DeserializationInput(sf).deserialize(sb) - - assertEquals('a', db::class.java.getMethod("getChar").invoke(db)) - } - - @Test - fun singleCharNullableNull() { - val clazz = ClassCarpenter().build(ClassSchema("single", mapOf( - "char" to NullableField(java.lang.Character::class.java) - ))) - - val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(null)) - val db = DeserializationInput(sf).deserialize(sb) - - assertEquals(null, db::class.java.getMethod("getChar").invoke(db)) - } - - @Test - fun singleLong() { - val clazz = ClassCarpenter().build(ClassSchema("single", mapOf( - "long" to NonNullableField(Long::class.javaPrimitiveType!!) - ))) - - val l : Long = 1 - val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(l)) - val db = DeserializationInput(sf).deserialize(sb) - - assertEquals(l, (db::class.java.getMethod("getLong").invoke(db))) - } - - @Test - fun singleLongNullable() { - val clazz = ClassCarpenter().build(ClassSchema("single", mapOf( - "long" to NullableField(Long::class.javaObjectType) - ))) - - val l : Long = 1 - val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(l)) - val db = DeserializationInput(sf).deserialize(sb) - - assertEquals(l, (db::class.java.getMethod("getLong").invoke(db))) - } - - @Test - fun singleLongNullableNull() { - val clazz = ClassCarpenter().build(ClassSchema("single", mapOf( - "long" to NullableField(Long::class.javaObjectType) - ))) - - val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(null)) - val db = DeserializationInput(sf).deserialize(sb) - - assertEquals(null, (db::class.java.getMethod("getLong").invoke(db))) - } - - @Test - fun singleBoolean() { - val clazz = ClassCarpenter().build(ClassSchema("single", mapOf( - "boolean" to NonNullableField(Boolean::class.javaPrimitiveType!!) - ))) - - val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(true)) - val db = DeserializationInput(sf).deserialize(sb) - - assertEquals(true, db::class.java.getMethod("getBoolean").invoke(db)) - } - - @Test - fun singleBooleanNullable() { - val clazz = ClassCarpenter().build(ClassSchema("single", mapOf( - "boolean" to NullableField(Boolean::class.javaObjectType) - ))) - - val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(true)) - val db = DeserializationInput(sf).deserialize(sb) - - assertEquals(true, db::class.java.getMethod("getBoolean").invoke(db)) - } - - @Test - fun singleBooleanNullableNull() { - val clazz = ClassCarpenter().build(ClassSchema("single", mapOf( - "boolean" to NullableField(Boolean::class.javaObjectType) - ))) - - val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(null)) - val db = DeserializationInput(sf).deserialize(sb) - - assertEquals(null, db::class.java.getMethod("getBoolean").invoke(db)) - } - - @Test - fun singleDouble() { - val clazz = ClassCarpenter().build(ClassSchema("single", mapOf( - "double" to NonNullableField(Double::class.javaPrimitiveType!!) - ))) - - val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(10.0)) - val db = DeserializationInput(sf).deserialize(sb) - - assertEquals(10.0, db::class.java.getMethod("getDouble").invoke(db)) - } - - @Test - fun singleDoubleNullable() { - val clazz = ClassCarpenter().build(ClassSchema("single", mapOf( - "double" to NullableField(Double::class.javaObjectType) - ))) - - val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(10.0)) - val db = DeserializationInput(sf).deserialize(sb) - - assertEquals(10.0, db::class.java.getMethod("getDouble").invoke(db)) - } - - @Test - fun singleDoubleNullableNull() { - val clazz = ClassCarpenter().build(ClassSchema("single", mapOf( - "double" to NullableField(Double::class.javaObjectType) - ))) - - val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(null)) - val db = DeserializationInput(sf).deserialize(sb) - - assertEquals(null, db::class.java.getMethod("getDouble").invoke(db)) - } - - @Test - fun singleShort() { - val clazz = ClassCarpenter().build(ClassSchema("single", mapOf( - "short" to NonNullableField(Short::class.javaPrimitiveType!!) - ))) - - val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(3.toShort())) - val db = DeserializationInput(sf).deserialize(sb) - - assertEquals(3.toShort(), db::class.java.getMethod("getShort").invoke(db)) - } - - @Test - fun singleShortNullable() { - val clazz = ClassCarpenter().build(ClassSchema("single", mapOf( - "short" to NullableField(Short::class.javaObjectType) - ))) - - val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(3.toShort())) - val db = DeserializationInput(sf).deserialize(sb) - - assertEquals(3.toShort(), db::class.java.getMethod("getShort").invoke(db)) - } - - @Test - fun singleShortNullableNull() { - val clazz = ClassCarpenter().build(ClassSchema("single", mapOf( - "short" to NullableField(Short::class.javaObjectType) - ))) - - val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(null)) - val db = DeserializationInput(sf).deserialize(sb) - - assertEquals(null, db::class.java.getMethod("getShort").invoke(db)) - } - - @Test - fun singleFloat() { - val clazz = ClassCarpenter().build(ClassSchema("single", mapOf( - "float" to NonNullableField(Float::class.javaPrimitiveType!!) - ))) - - val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(10.0F)) - val db = DeserializationInput(sf).deserialize(sb) - - assertEquals(10.0F, db::class.java.getMethod("getFloat").invoke(db)) - } - - @Test - fun singleFloatNullable() { - val clazz = ClassCarpenter().build(ClassSchema("single", mapOf( - "float" to NullableField(Float::class.javaObjectType) - ))) - - val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(10.0F)) - val db = DeserializationInput(sf).deserialize(sb) - - assertEquals(10.0F, db::class.java.getMethod("getFloat").invoke(db)) - } - - @Test - fun singleFloatNullableNull() { - val clazz = ClassCarpenter().build(ClassSchema("single", mapOf( - "float" to NullableField(Float::class.javaObjectType) - ))) - - val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(null)) - val db = DeserializationInput(sf).deserialize(sb) - - assertEquals(null, db::class.java.getMethod("getFloat").invoke(db)) - } - - @Test - fun singleByte() { - val clazz = ClassCarpenter().build(ClassSchema("single", mapOf( - "byte" to NonNullableField(Byte::class.javaPrimitiveType!!) - ))) - - val b : Byte = 0b0101 - val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(b)) - val db = DeserializationInput(sf).deserialize(sb) - - assertEquals(b, db::class.java.getMethod("getByte").invoke(db)) - assertEquals(0b0101, (db::class.java.getMethod("getByte").invoke(db) as Byte)) - } - - @Test - fun singleByteNullable() { - val clazz = ClassCarpenter().build(ClassSchema("single", mapOf( - "byte" to NullableField(Byte::class.javaObjectType) - ))) - - val b : Byte = 0b0101 - val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(b)) - val db = DeserializationInput(sf).deserialize(sb) - - assertEquals(b, db::class.java.getMethod("getByte").invoke(db)) - assertEquals(0b0101, (db::class.java.getMethod("getByte").invoke(db) as Byte)) - } - - @Test - fun singleByteNullableNull() { - val clazz = ClassCarpenter().build(ClassSchema("single", mapOf( - "byte" to NullableField(Byte::class.javaObjectType) - ))) - - val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(null)) - val db = DeserializationInput(sf).deserialize(sb) - - assertEquals(null, db::class.java.getMethod("getByte").invoke(db)) - } - @Test fun simpleTypeKnownInterface() { val clazz = ClassCarpenter().build (ClassSchema( @@ -414,6 +116,38 @@ class DeserializeNeedingCarpentryTests { } } + @Test + fun mapOfInterfaces() { + val cc = ClassCarpenter() + + val implementsI = cc.build(ClassSchema( + "implementsI", mapOf("name" to NonNullableField(String::class.java)), + interfaces = listOf (I::class.java))) + + val implementsII = cc.build(ClassSchema("ImplementsII", mapOf ( + "age" to NonNullableField(Int::class.java), + "thingWithName" to NullableField(I::class.java)), + interfaces = listOf (II::class.java))) + + val wrapper = cc.build(ClassSchema("wrapper", mapOf ( + "IIs" to NonNullableField(MutableMap::class.java)))) + + val tmp: MutableMap = mutableMapOf() + val toSerialise = wrapper.constructors.first().newInstance(tmp) + val testData = arrayOf(Pair ("Fred", 12), Pair ("Bob", 50), Pair ("Thirsty", 101)) + + testData.forEach { + (wrapper.getMethod("getIIs").invoke(toSerialise) as MutableMap)[it.first] = + implementsII.constructors.first().newInstance(it.second, + implementsI.constructors.first().newInstance(it.first) as I) as II + } + + // Now do the actual test by serialising and deserialising [wrapper] + val serialisedBytes = TestSerializationOutput(VERBOSE, sf).serialize(wrapper) + val deserializedObj = DeserializationInput(sf).deserialize(serialisedBytes) + + } + @Test fun unknownInterface() { val cc = ClassCarpenter() @@ -436,80 +170,4 @@ class DeserializeNeedingCarpentryTests { assertEquals("timmy", deserializedObj::class.java.getMethod("getName").invoke(deserializedObj)) assertEquals(12, deserializedObj::class.java.getMethod("getAge").invoke(deserializedObj)) } - - @Test - fun manyTypes() { - val cc = ClassCarpenter() - - val manyClass = cc.build (ClassSchema("many", mapOf( - "intA" to NonNullableField (Int::class.java), - "intB" to NullableField (Integer::class.java), - "intC" to NullableField (Integer::class.java), - "strA" to NonNullableField (String::class.java), - "strB" to NullableField (String::class.java), - "strC" to NullableField (String::class.java), - "charA" to NonNullableField (Char::class.java), - "charB" to NullableField (Character::class.javaObjectType), - "charC" to NullableField (Character::class.javaObjectType), - "shortA" to NonNullableField (Short::class.javaPrimitiveType!!), - "shortB" to NullableField (Short::class.javaObjectType), - "shortC" to NullableField (Short::class.javaObjectType), - "longA" to NonNullableField (Long::class.javaPrimitiveType!!), - "longB" to NullableField(Long::class.javaObjectType), - "longC" to NullableField(Long::class.javaObjectType), - "booleanA" to NonNullableField (Boolean::class.javaPrimitiveType!!), - "booleanB" to NullableField (Boolean::class.javaObjectType), - "booleanC" to NullableField (Boolean::class.javaObjectType), - "doubleA" to NonNullableField (Double::class.javaPrimitiveType!!), - "doubleB" to NullableField (Double::class.javaObjectType), - "doubleC" to NullableField (Double::class.javaObjectType), - "floatA" to NonNullableField (Float::class.javaPrimitiveType!!), - "floatB" to NullableField (Float::class.javaObjectType), - "floatC" to NullableField (Float::class.javaObjectType), - "byteA" to NonNullableField (Byte::class.javaPrimitiveType!!), - "byteB" to NullableField (Byte::class.javaObjectType), - "byteC" to NullableField (Byte::class.javaObjectType)))) - - val serialisedBytes = TestSerializationOutput(VERBOSE, sf).serialize( - manyClass.constructors.first().newInstance( - 1, 2, null, - "a", "b", null, - 'c', 'd', null, - 3.toShort(), 4.toShort(), null, - 100.toLong(), 200.toLong(), null, - true, false, null, - 10.0, 20.0, null, - 10.0F, 20.0F, null, - 0b0101.toByte(), 0b1010.toByte(), null)) - - val deserializedObj = DeserializationInput(sf).deserialize(serialisedBytes) - - assertEquals(1, deserializedObj::class.java.getMethod("getIntA").invoke(deserializedObj)) - assertEquals(2, deserializedObj::class.java.getMethod("getIntB").invoke(deserializedObj)) - assertEquals(null, deserializedObj::class.java.getMethod("getIntC").invoke(deserializedObj)) - assertEquals("a", deserializedObj::class.java.getMethod("getStrA").invoke(deserializedObj)) - assertEquals("b", deserializedObj::class.java.getMethod("getStrB").invoke(deserializedObj)) - assertEquals(null, deserializedObj::class.java.getMethod("getStrC").invoke(deserializedObj)) - assertEquals('c', deserializedObj::class.java.getMethod("getCharA").invoke(deserializedObj)) - assertEquals('d', deserializedObj::class.java.getMethod("getCharB").invoke(deserializedObj)) - assertEquals(null, deserializedObj::class.java.getMethod("getCharC").invoke(deserializedObj)) - assertEquals(3.toShort(), deserializedObj::class.java.getMethod("getShortA").invoke(deserializedObj)) - assertEquals(4.toShort(), deserializedObj::class.java.getMethod("getShortB").invoke(deserializedObj)) - assertEquals(null, deserializedObj::class.java.getMethod("getShortC").invoke(deserializedObj)) - assertEquals(100.toLong(), deserializedObj::class.java.getMethod("getLongA").invoke(deserializedObj)) - assertEquals(200.toLong(), deserializedObj::class.java.getMethod("getLongB").invoke(deserializedObj)) - assertEquals(null, deserializedObj::class.java.getMethod("getLongC").invoke(deserializedObj)) - assertEquals(true, deserializedObj::class.java.getMethod("getBooleanA").invoke(deserializedObj)) - assertEquals(false, deserializedObj::class.java.getMethod("getBooleanB").invoke(deserializedObj)) - assertEquals(null, deserializedObj::class.java.getMethod("getBooleanC").invoke(deserializedObj)) - assertEquals(10.0, deserializedObj::class.java.getMethod("getDoubleA").invoke(deserializedObj)) - assertEquals(20.0, deserializedObj::class.java.getMethod("getDoubleB").invoke(deserializedObj)) - assertEquals(null, deserializedObj::class.java.getMethod("getDoubleC").invoke(deserializedObj)) - assertEquals(10.0F, deserializedObj::class.java.getMethod("getFloatA").invoke(deserializedObj)) - assertEquals(20.0F, deserializedObj::class.java.getMethod("getFloatB").invoke(deserializedObj)) - assertEquals(null, deserializedObj::class.java.getMethod("getFloatC").invoke(deserializedObj)) - assertEquals(0b0101.toByte(), deserializedObj::class.java.getMethod("getByteA").invoke(deserializedObj)) - assertEquals(0b1010.toByte(), deserializedObj::class.java.getMethod("getByteB").invoke(deserializedObj)) - assertEquals(null, deserializedObj::class.java.getMethod("getByteC").invoke(deserializedObj)) - } } From 45ed78b1a818c3e591fb4d3f7d3617ac69f94b4c Mon Sep 17 00:00:00 2001 From: Katelyn Baker Date: Thu, 27 Jul 2017 18:14:37 +0100 Subject: [PATCH 06/10] Serialiser / Synthesiser work and cleanup - Adding test case that shows it should work AS it works with concrete classes, not if we carpent up the back end - WIP - The current test fails because the carpenter is creating the map (for some reason) as a linked hash map and not a normal map that we can't serialise. Added a test to confirm that it's not the carpenter here. Two questions really, why are we creating it as a linked has map, AFAIK the carpenter should just be whipping up what it's told to and it's not being given a linked map Secondly, should we support serialisation of these objects - Remove debug - Remove yet more debug --- .../serialization/amqp/SerializerFactory.kt | 9 +- .../serialization/carpenter/ClassCarpenter.kt | 3 +- .../amqp/DeserializeNeedingCarpentryTests.kt | 82 ++++++++++++++++--- 3 files changed, 76 insertions(+), 18 deletions(-) diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializerFactory.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializerFactory.kt index 9dafe348d4..1eba19c13f 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializerFactory.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializerFactory.kt @@ -219,8 +219,8 @@ class SerializerFactory(val whitelist: ClassWhitelist = AllWhitelist) { } } - private fun makeClassSerializer(clazz: Class<*>, type: Type, declaredType: Type): AMQPSerializer { - return serializersByType.computeIfAbsent(type) { + private fun makeClassSerializer(clazz: Class<*>, type: Type, declaredType: Type): AMQPSerializer = + serializersByType.computeIfAbsent(type) { if (isPrimitive(clazz)) { AMQPPrimitiveSerializer(clazz) } else { @@ -238,7 +238,6 @@ class SerializerFactory(val whitelist: ClassWhitelist = AllWhitelist) { } } } - } } internal fun findCustomSerializer(clazz: Class<*>, declaredType: Type): AMQPSerializer? { @@ -319,10 +318,10 @@ class SerializerFactory(val whitelist: ClassWhitelist = AllWhitelist) { fun nameForType(type: Type) : String = when (type) { is Class<*> -> { primitiveTypeName(type) ?: if (type.isArray) { - "${nameForType(type.componentType)}${if(type.componentType.isPrimitive)"[p]" else "[]"}" + "${nameForType(type.componentType)}${if (type.componentType.isPrimitive) "[p]" else "[]"}" } else type.name } - is ParameterizedType -> "${nameForType(type.rawType)}<${type.actualTypeArguments.joinToString { nameForType(it) }}>" + is ParameterizedType -> "${nameForType(type.rawType)ype.actualTypeArguments.joinToString { nameForType(it) }}>" is GenericArrayType -> "${nameForType(type.genericComponentType)}[]" else -> throw NotSerializableException("Unable to render type $type to a string.") } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/carpenter/ClassCarpenter.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/carpenter/ClassCarpenter.kt index ad7e8d4ca6..21641a0ae1 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/carpenter/ClassCarpenter.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/carpenter/ClassCarpenter.kt @@ -129,7 +129,7 @@ class ClassCarpenter { private fun generateClass(classSchema: Schema): Class<*> { return generate(classSchema) { cw, schema -> val superName = schema.superclass?.jvmName ?: "java/lang/Object" - var interfaces = schema.interfaces.map { it.name.jvm }.toMutableList() + val interfaces = schema.interfaces.map { it.name.jvm }.toMutableList() if (SimpleFieldAccess::class.java !in schema.interfaces) interfaces.add(SimpleFieldAccess::class.java.name.jvm) @@ -309,7 +309,6 @@ class ClassCarpenter { // If we're trying to carpent a class that prior to serialisation / deserialisation // was made by a carpenter then we can ignore this (it will implement a plain get // method from SimpleFieldAccess). - // method from SimpleFieldAccess) if (fieldNameFromItf.isEmpty() && SimpleFieldAccess::class.java in schema.interfaces) return@forEach if ((schema is ClassSchema) and (fieldNameFromItf !in allFields)) diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeNeedingCarpentryTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeNeedingCarpentryTests.kt index ea74d2ad82..096d2fa7a0 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeNeedingCarpentryTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeNeedingCarpentryTests.kt @@ -10,8 +10,21 @@ interface I { } interface II { + fun returnName() : String +} + +interface III { + fun returnAge() : Int + fun returnThingWithName(): II +} + +interface B { + fun getName() : String +} + +interface BB { fun getAge() : Int - fun getThingWithName(): I + fun getThingWithName(): II } /** @@ -21,7 +34,6 @@ interface II { * replicates the situation where a receiver doesn't have some or all elements of a schema present on it's classpath */ class DeserializeNeedingCarpentryTests { - companion object { /** * If you want to see the schema encoded into the envelope after serialisation change this to true @@ -116,37 +128,85 @@ class DeserializeNeedingCarpentryTests { } } + // technically this test doesn't test anything (relevent to carpanter / serialiser interaction) since + // all the classes are knwon, what does do is replicate the test below to demonstrate it should + // all work + @Test + fun mapOfKnown() { + class lII (val name: String) : II { + override fun returnName() = name + } + + class lIII (val age: Int, val thingWithName: II): III { + override fun returnAge(): Int = age + override fun returnThingWithName() = thingWithName + } + + data class Wrapper(val IIIs: MutableMap) + val wrapper = Wrapper (mutableMapOf()) + val testData = arrayOf(Pair ("Fred", 12), Pair ("Bob", 50), Pair ("Thirsty", 101)) + + testData.forEach { + wrapper.IIIs[it.first] = lIII(it.second, lII(it.first)) + } + + // Now do the actual test by serialising and deserialising [wrapper] + val serialisedBytes = TestSerializationOutput(VERBOSE, sf).serialize(wrapper) + val deserializedObj = DeserializationInput(sf).deserialize(serialisedBytes) + } + + // TODO This class shows that the problem isn't with the carpented class but a general + // TODO Bug / feature of the code... + /* + @Test + fun linkedHashMapTest() { + data class C(val c : LinkedHashMap) + val c = C (LinkedHashMap (mapOf("A" to 1, "B" to 2))) + + val serialisedBytes = TestSerializationOutput(VERBOSE, sf).serialize(c) + val deserializedObj = DeserializationInput(sf).deserialize(serialisedBytes) + + } + */ + + // TODO the problem here is that the wrapper class as created by the serialiser + // TODO contains a [LinkedHashMap] and not a [Map] and we thus can't serialise + // TODO it - Talk to Rick about weather we should be able to or not + /* @Test fun mapOfInterfaces() { val cc = ClassCarpenter() val implementsI = cc.build(ClassSchema( "implementsI", mapOf("name" to NonNullableField(String::class.java)), - interfaces = listOf (I::class.java))) + interfaces = listOf (B::class.java))) val implementsII = cc.build(ClassSchema("ImplementsII", mapOf ( "age" to NonNullableField(Int::class.java), - "thingWithName" to NullableField(I::class.java)), - interfaces = listOf (II::class.java))) + "thingWithName" to NullableField(B::class.java)), + interfaces = listOf (BB::class.java))) + + // inline fun getval(reified T : Any) : return T::class.java val wrapper = cc.build(ClassSchema("wrapper", mapOf ( - "IIs" to NonNullableField(MutableMap::class.java)))) + "BBs" to NonNullableField(mutableMapOf()::class.java + )))) - val tmp: MutableMap = mutableMapOf() + val tmp : MutableMap = mutableMapOf() val toSerialise = wrapper.constructors.first().newInstance(tmp) val testData = arrayOf(Pair ("Fred", 12), Pair ("Bob", 50), Pair ("Thirsty", 101)) testData.forEach { - (wrapper.getMethod("getIIs").invoke(toSerialise) as MutableMap)[it.first] = + (wrapper.getMethod("getBBs").invoke(toSerialise) as MutableMap)[it.first] = implementsII.constructors.first().newInstance(it.second, - implementsI.constructors.first().newInstance(it.first) as I) as II + implementsI.constructors.first().newInstance(it.first) as B) as BB } // Now do the actual test by serialising and deserialising [wrapper] - val serialisedBytes = TestSerializationOutput(VERBOSE, sf).serialize(wrapper) + val serialisedBytes = TestSerializationOutput(VERBOSE, sf).serialize(toSerialise) val deserializedObj = DeserializationInput(sf).deserialize(serialisedBytes) - } + */ @Test fun unknownInterface() { From 36cbcae99c7c1b3e1ae9d1254931b408394edd6f Mon Sep 17 00:00:00 2001 From: Katelyn Baker Date: Thu, 3 Aug 2017 14:01:59 +0100 Subject: [PATCH 07/10] Review Comments --- .../serialization/amqp/SerializerFactory.kt | 20 +++++++++---------- .../serialization/carpenter/MetaCarpenter.kt | 1 + 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializerFactory.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializerFactory.kt index 1eba19c13f..f19e9c88b2 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializerFactory.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializerFactory.kt @@ -171,27 +171,25 @@ class SerializerFactory(val whitelist: ClassWhitelist = AllWhitelist) { } } - private fun processSchema( - schema: Schema, - cl: ClassLoader = DeserializedParameterizedType::class.java.classLoader) { - + private fun processSchema(schema: Schema, cl: ClassLoader = DeserializedParameterizedType::class.java.classLoader) { val carpenterSchemas = CarpenterSchemas.newInstance() for (typeNotation in schema.types) { try { processSchemaEntry(typeNotation, cl) } - catch (e: java.lang.ClassNotFoundException) { + catch (e: ClassNotFoundException) { if ((cl != DeserializedParameterizedType::class.java.classLoader) || (typeNotation !is CompositeType)) throw e typeNotation.carpenterSchema(carpenterSchemas = carpenterSchemas) } } - if (carpenterSchemas.isEmpty()) return - val mc = MetaCarpenter(carpenterSchemas) - mc.build() + if (carpenterSchemas.isNotEmpty()) { + val mc = MetaCarpenter(carpenterSchemas) + mc.build() - processSchema(schema, mc.classloader) + processSchema(schema, mc.classloader) + } } private fun processSchemaEntry(typeNotation: TypeNotation, @@ -318,10 +316,10 @@ class SerializerFactory(val whitelist: ClassWhitelist = AllWhitelist) { fun nameForType(type: Type) : String = when (type) { is Class<*> -> { primitiveTypeName(type) ?: if (type.isArray) { - "${nameForType(type.componentType)}${if (type.componentType.isPrimitive) "[p]" else "[]"}" + "${nameForType(type.componentType)}${if(type.componentType.isPrimitive) "[p]" else "[]"}" } else type.name } - is ParameterizedType -> "${nameForType(type.rawType)ype.actualTypeArguments.joinToString { nameForType(it) }}>" + is ParameterizedType -> "${nameForType(type.rawType)}<${type.actualTypeArguments.joinToString { nameForType(it) }}>" is GenericArrayType -> "${nameForType(type.genericComponentType)}[]" else -> throw NotSerializableException("Unable to render type $type to a string.") } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/carpenter/MetaCarpenter.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/carpenter/MetaCarpenter.kt index ae3f4e64c9..460284e078 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/carpenter/MetaCarpenter.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/carpenter/MetaCarpenter.kt @@ -44,6 +44,7 @@ data class CarpenterSchemas ( get() = carpenterSchemas.size fun isEmpty() = carpenterSchemas.isEmpty() + fun isNotEmpty() = carpenterSchemas.isNotEmpty() } /** From 1f9c04544d57fd68a43c118f362575234944d2ee Mon Sep 17 00:00:00 2001 From: Katelyn Baker Date: Fri, 4 Aug 2017 12:44:14 +0100 Subject: [PATCH 08/10] Make the carpenter a member of the serialiser factory If it's not then the caroenter's class loader isn't persistent and thus we will be constantly recarpenting objects --- .../serialization/amqp/SerializerFactory.kt | 16 +- .../serialization/carpenter/MetaCarpenter.kt | 8 +- .../amqp/DeserializeNeedingCarpentryTests.kt | 179 ++++++++---------- 3 files changed, 94 insertions(+), 109 deletions(-) diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializerFactory.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializerFactory.kt index f19e9c88b2..40b557f8f4 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializerFactory.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializerFactory.kt @@ -48,6 +48,7 @@ class SerializerFactory(val whitelist: ClassWhitelist = AllWhitelist) { private val serializersByType = ConcurrentHashMap>() private val serializersByDescriptor = ConcurrentHashMap>() private val customSerializers = CopyOnWriteArrayList>() + private val classCarpenter = ClassCarpenter() /** * Look up, and manufacture if necessary, a serializer for the given type. @@ -171,24 +172,23 @@ class SerializerFactory(val whitelist: ClassWhitelist = AllWhitelist) { } } - private fun processSchema(schema: Schema, cl: ClassLoader = DeserializedParameterizedType::class.java.classLoader) { + private fun processSchema(schema: Schema, sentinal: Boolean = false) { val carpenterSchemas = CarpenterSchemas.newInstance() for (typeNotation in schema.types) { try { - processSchemaEntry(typeNotation, cl) + processSchemaEntry(typeNotation, classCarpenter.classloader) } catch (e: ClassNotFoundException) { - if ((cl != DeserializedParameterizedType::class.java.classLoader) - || (typeNotation !is CompositeType)) throw e - typeNotation.carpenterSchema(carpenterSchemas = carpenterSchemas) + if (sentinal || (typeNotation !is CompositeType)) throw e + typeNotation.carpenterSchema( + classLoaders = listOf (classCarpenter.classloader), carpenterSchemas = carpenterSchemas) } } if (carpenterSchemas.isNotEmpty()) { - val mc = MetaCarpenter(carpenterSchemas) + val mc = MetaCarpenter(carpenterSchemas, classCarpenter) mc.build() - - processSchema(schema, mc.classloader) + processSchema(schema, true) } } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/carpenter/MetaCarpenter.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/carpenter/MetaCarpenter.kt index 460284e078..a334fc9e40 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/carpenter/MetaCarpenter.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/carpenter/MetaCarpenter.kt @@ -56,8 +56,7 @@ data class CarpenterSchemas ( * @property cc a reference to the actual class carpenter we're using to constuct classes * @property objects a list of carpented classes loaded into the carpenters class loader */ -abstract class MetaCarpenterBase (val schemas : CarpenterSchemas) { - private val cc = ClassCarpenter() +abstract class MetaCarpenterBase (val schemas : CarpenterSchemas, val cc : ClassCarpenter = ClassCarpenter()) { val objects = mutableMapOf>() fun step(newObject: Schema) { @@ -88,7 +87,8 @@ abstract class MetaCarpenterBase (val schemas : CarpenterSchemas) { get() = cc.classloader } -class MetaCarpenter(schemas: CarpenterSchemas) : MetaCarpenterBase(schemas) { +class MetaCarpenter(schemas : CarpenterSchemas, + cc : ClassCarpenter = ClassCarpenter()) : MetaCarpenterBase(schemas, cc) { override fun build() { while (schemas.carpenterSchemas.isNotEmpty()) { val newObject = schemas.carpenterSchemas.removeAt(0) @@ -98,6 +98,8 @@ class MetaCarpenter(schemas: CarpenterSchemas) : MetaCarpenterBase(schemas) { } class TestMetaCarpenter(schemas: CarpenterSchemas) : MetaCarpenterBase(schemas) { +class TestMetaCarpenter(schemas : CarpenterSchemas, + cc : ClassCarpenter = ClassCarpenter()) : MetaCarpenterBase(schemas, cc) { override fun build() { if (schemas.carpenterSchemas.isEmpty()) return step (schemas.carpenterSchemas.removeAt(0)) diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeNeedingCarpentryTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeNeedingCarpentryTests.kt index 096d2fa7a0..18186bbc6f 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeNeedingCarpentryTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeNeedingCarpentryTests.kt @@ -9,24 +9,6 @@ interface I { fun getName() : String } -interface II { - fun returnName() : String -} - -interface III { - fun returnAge() : Int - fun returnThingWithName(): II -} - -interface B { - fun getName() : String -} - -interface BB { - fun getAge() : Int - fun getThingWithName(): II -} - /** * These tests work by having the class carpenter build the classes we serialise and then deserialise. Because * those classes don't exist within the system's Class Loader the deserialiser will be forced to carpent @@ -55,6 +37,36 @@ class DeserializeNeedingCarpentryTests { assertEquals (testVal, deserializedObj::class.java.getMethod("getA").invoke(deserializedObj)) } + @Test + fun repeatedTypesAreRecognised() { + val testValA = 10 + val testValB = 20 + val testValC = 20 + val clazz = ClassCarpenter().build(ClassSchema("oneType", mapOf("a" to NonNullableField(Int::class.java)))) + val concreteA = clazz.constructors[0].newInstance(testValA) + val concreteB = clazz.constructors[0].newInstance(testValB) + val concreteC = clazz.constructors[0].newInstance(testValC) + + val deserialisedA = DeserializationInput(sf).deserialize(TestSerializationOutput(VERBOSE, sf).serialize(concreteA)) + + assertEquals (testValA, deserialisedA::class.java.getMethod("getA").invoke(deserialisedA)) + + val deserialisedB = DeserializationInput(sf).deserialize(TestSerializationOutput(VERBOSE, sf).serialize(concreteB)) + + assertEquals (testValB, deserialisedA::class.java.getMethod("getA").invoke(deserialisedB)) + assertEquals (deserialisedA::class.java, deserialisedB::class.java) + + // C is deseriliased with a different factory, meaning a different class carpenter, so the type + // won't already exist and it will be carpented a second time showing that when A and B are the + // same underlying class that we didn't create a second instance of the class with the + // second deserialisation + val lsf = SerializerFactory() + val deserialisedC = DeserializationInput(lsf).deserialize(TestSerializationOutput(VERBOSE, lsf).serialize(concreteC)) + assertEquals (testValC, deserialisedC::class.java.getMethod("getA").invoke(deserialisedC)) + assertNotEquals (deserialisedA::class.java, deserialisedC::class.java) + assertNotEquals (deserialisedB::class.java, deserialisedC::class.java) + } + @Test fun simpleTypeKnownInterface() { val clazz = ClassCarpenter().build (ClassSchema( @@ -70,6 +82,57 @@ class DeserializeNeedingCarpentryTests { assertEquals(testVal, (deserializedObj as I).getName()) } + @Test + fun arrayOfTypes() { + val clazz = ClassCarpenter().build(ClassSchema("oneType", mapOf("a" to NonNullableField(Int::class.java)))) + + data class Outer (val a : Array) + + val outer = Outer (arrayOf ( + clazz.constructors[0].newInstance(1), + clazz.constructors[0].newInstance(2), + clazz.constructors[0].newInstance(3))) + + val deserializedObj = DeserializationInput(sf).deserialize(TestSerializationOutput(VERBOSE, sf).serialize(outer)) + + assertNotEquals((deserializedObj.a[0])::class.java, (outer.a[0])::class.java) + assertNotEquals((deserializedObj.a[1])::class.java, (outer.a[1])::class.java) + assertNotEquals((deserializedObj.a[2])::class.java, (outer.a[2])::class.java) + + assertEquals((deserializedObj.a[0])::class.java, (deserializedObj.a[1])::class.java) + assertEquals((deserializedObj.a[0])::class.java, (deserializedObj.a[2])::class.java) + assertEquals((deserializedObj.a[1])::class.java, (deserializedObj.a[2])::class.java) + + assertEquals( + outer.a[0]::class.java.getMethod("getA").invoke(outer.a[0]), + deserializedObj.a[0]::class.java.getMethod("getA").invoke(deserializedObj.a[0])) + assertEquals( + outer.a[1]::class.java.getMethod("getA").invoke(outer.a[1]), + deserializedObj.a[1]::class.java.getMethod("getA").invoke(deserializedObj.a[1])) + assertEquals( + outer.a[2]::class.java.getMethod("getA").invoke(outer.a[2]), + deserializedObj.a[2]::class.java.getMethod("getA").invoke(deserializedObj.a[2])) + } + + @Test + fun reusedClasses() { + val cc = ClassCarpenter() + + val innerType = cc.build(ClassSchema("inner", mapOf("a" to NonNullableField(Int::class.java)))) + val outerType = cc.build(ClassSchema("outer", mapOf("a" to NonNullableField(innerType)))) + val inner = innerType.constructors[0].newInstance(1) + val outer = outerType.constructors[0].newInstance(innerType.constructors[0].newInstance(2)) + + val serializedI = TestSerializationOutput(VERBOSE, sf).serialize(inner) + val deserialisedI = DeserializationInput(sf).deserialize(serializedI) + val serialisedO = TestSerializationOutput(VERBOSE, sf).serialize(outer) + val deserialisedO = DeserializationInput(sf).deserialize(serialisedO) + + // ensure out carpented version of inner is reused + assertEquals (deserialisedI::class.java, + (deserialisedO::class.java.getMethod("getA").invoke(deserialisedO))::class.java) + } + @Test fun nestedTypes() { val cc = ClassCarpenter() @@ -128,86 +191,6 @@ class DeserializeNeedingCarpentryTests { } } - // technically this test doesn't test anything (relevent to carpanter / serialiser interaction) since - // all the classes are knwon, what does do is replicate the test below to demonstrate it should - // all work - @Test - fun mapOfKnown() { - class lII (val name: String) : II { - override fun returnName() = name - } - - class lIII (val age: Int, val thingWithName: II): III { - override fun returnAge(): Int = age - override fun returnThingWithName() = thingWithName - } - - data class Wrapper(val IIIs: MutableMap) - val wrapper = Wrapper (mutableMapOf()) - val testData = arrayOf(Pair ("Fred", 12), Pair ("Bob", 50), Pair ("Thirsty", 101)) - - testData.forEach { - wrapper.IIIs[it.first] = lIII(it.second, lII(it.first)) - } - - // Now do the actual test by serialising and deserialising [wrapper] - val serialisedBytes = TestSerializationOutput(VERBOSE, sf).serialize(wrapper) - val deserializedObj = DeserializationInput(sf).deserialize(serialisedBytes) - } - - // TODO This class shows that the problem isn't with the carpented class but a general - // TODO Bug / feature of the code... - /* - @Test - fun linkedHashMapTest() { - data class C(val c : LinkedHashMap) - val c = C (LinkedHashMap (mapOf("A" to 1, "B" to 2))) - - val serialisedBytes = TestSerializationOutput(VERBOSE, sf).serialize(c) - val deserializedObj = DeserializationInput(sf).deserialize(serialisedBytes) - - } - */ - - // TODO the problem here is that the wrapper class as created by the serialiser - // TODO contains a [LinkedHashMap] and not a [Map] and we thus can't serialise - // TODO it - Talk to Rick about weather we should be able to or not - /* - @Test - fun mapOfInterfaces() { - val cc = ClassCarpenter() - - val implementsI = cc.build(ClassSchema( - "implementsI", mapOf("name" to NonNullableField(String::class.java)), - interfaces = listOf (B::class.java))) - - val implementsII = cc.build(ClassSchema("ImplementsII", mapOf ( - "age" to NonNullableField(Int::class.java), - "thingWithName" to NullableField(B::class.java)), - interfaces = listOf (BB::class.java))) - - // inline fun getval(reified T : Any) : return T::class.java - - val wrapper = cc.build(ClassSchema("wrapper", mapOf ( - "BBs" to NonNullableField(mutableMapOf()::class.java - )))) - - val tmp : MutableMap = mutableMapOf() - val toSerialise = wrapper.constructors.first().newInstance(tmp) - val testData = arrayOf(Pair ("Fred", 12), Pair ("Bob", 50), Pair ("Thirsty", 101)) - - testData.forEach { - (wrapper.getMethod("getBBs").invoke(toSerialise) as MutableMap)[it.first] = - implementsII.constructors.first().newInstance(it.second, - implementsI.constructors.first().newInstance(it.first) as B) as BB - } - - // Now do the actual test by serialising and deserialising [wrapper] - val serialisedBytes = TestSerializationOutput(VERBOSE, sf).serialize(toSerialise) - val deserializedObj = DeserializationInput(sf).deserialize(serialisedBytes) - } - */ - @Test fun unknownInterface() { val cc = ClassCarpenter() From 4d6230f22c3dd5c47bb6737f33e4d36291f33ffa Mon Sep 17 00:00:00 2001 From: Katelyn Baker Date: Fri, 4 Aug 2017 16:56:27 +0100 Subject: [PATCH 09/10] Post rebase fixes - Add missing import - Deal with files that need to move from core to nodeapi.internal - Fix up the imports --- .../internal/serialization/carpenter/MetaCarpenter.kt | 1 - .../internal}/serialization/amqp/AMQPTestUtils.kt | 5 +---- .../amqp/DeserializeAndReturnEnvelopeTests.kt | 1 + .../amqp/DeserializeNeedingCarpentrySimpleTypesTest.kt | 5 ++--- .../amqp/DeserializeNeedingCarpentryTests.kt | 5 ++--- .../serialization/amqp/DeserializeSimpleTypesTests.kt | 10 ---------- .../serialization/carpenter/ClassCarpenterTestUtils.kt | 1 + 7 files changed, 7 insertions(+), 21 deletions(-) rename {core/src/test/kotlin/net/corda/core => node-api/src/test/kotlin/net/corda/nodeapi/internal}/serialization/amqp/AMQPTestUtils.kt (63%) rename {core/src/test/kotlin/net/corda/core => node-api/src/test/kotlin/net/corda/nodeapi/internal}/serialization/amqp/DeserializeNeedingCarpentrySimpleTypesTest.kt (99%) diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/carpenter/MetaCarpenter.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/carpenter/MetaCarpenter.kt index a334fc9e40..77e866fb32 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/carpenter/MetaCarpenter.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/carpenter/MetaCarpenter.kt @@ -97,7 +97,6 @@ class MetaCarpenter(schemas : CarpenterSchemas, } } -class TestMetaCarpenter(schemas: CarpenterSchemas) : MetaCarpenterBase(schemas) { class TestMetaCarpenter(schemas : CarpenterSchemas, cc : ClassCarpenter = ClassCarpenter()) : MetaCarpenterBase(schemas, cc) { override fun build() { diff --git a/core/src/test/kotlin/net/corda/core/serialization/amqp/AMQPTestUtils.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPTestUtils.kt similarity index 63% rename from core/src/test/kotlin/net/corda/core/serialization/amqp/AMQPTestUtils.kt rename to node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPTestUtils.kt index 5a19337f3d..19b93b6542 100644 --- a/core/src/test/kotlin/net/corda/core/serialization/amqp/AMQPTestUtils.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPTestUtils.kt @@ -1,9 +1,6 @@ -package net.corda.core.serialization.amqp.test +package net.corda.nodeapi.internal.serialization.amqp import org.apache.qpid.proton.codec.Data -import net.corda.core.serialization.amqp.Schema -import net.corda.core.serialization.amqp.SerializerFactory -import net.corda.core.serialization.amqp.SerializationOutput class TestSerializationOutput( private val verbose: Boolean, diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeAndReturnEnvelopeTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeAndReturnEnvelopeTests.kt index d20edd0997..b91fa3fe9e 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeAndReturnEnvelopeTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeAndReturnEnvelopeTests.kt @@ -8,6 +8,7 @@ import kotlin.test.assertTrue class DeserializeAndReturnEnvelopeTests { fun testName(): String = Thread.currentThread().stackTrace[2].methodName + @Suppress("NOTHING_TO_INLINE") inline fun classTestName(clazz: String) = "${this.javaClass.name}\$${testName()}\$$clazz" diff --git a/core/src/test/kotlin/net/corda/core/serialization/amqp/DeserializeNeedingCarpentrySimpleTypesTest.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeNeedingCarpentrySimpleTypesTest.kt similarity index 99% rename from core/src/test/kotlin/net/corda/core/serialization/amqp/DeserializeNeedingCarpentrySimpleTypesTest.kt rename to node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeNeedingCarpentrySimpleTypesTest.kt index b36e540d96..1a5cf62c2c 100644 --- a/core/src/test/kotlin/net/corda/core/serialization/amqp/DeserializeNeedingCarpentrySimpleTypesTest.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeNeedingCarpentrySimpleTypesTest.kt @@ -1,9 +1,8 @@ -package net.corda.core.serialization.amqp +package net.corda.nodeapi.internal.serialization.amqp import org.junit.Test import kotlin.test.* -import net.corda.core.serialization.carpenter.* -import net.corda.core.serialization.amqp.test.TestSerializationOutput +import net.corda.nodeapi.internal.serialization.carpenter.* /** * These tests work by having the class carpenter build the classes we serialise and then deserialise. Because diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeNeedingCarpentryTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeNeedingCarpentryTests.kt index 18186bbc6f..7a5ba64457 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeNeedingCarpentryTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeNeedingCarpentryTests.kt @@ -1,9 +1,8 @@ -package net.corda.core.serialization.amqp +package net.corda.nodeapi.internal.serialization.amqp import org.junit.Test import kotlin.test.* -import net.corda.core.serialization.carpenter.* -import net.corda.core.serialization.amqp.test.TestSerializationOutput +import net.corda.nodeapi.internal.serialization.carpenter.* interface I { fun getName() : String diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeSimpleTypesTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeSimpleTypesTests.kt index 6ed4c6471c..61628705af 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeSimpleTypesTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeSimpleTypesTests.kt @@ -1,6 +1,5 @@ package net.corda.nodeapi.internal.serialization.amqp -import org.apache.qpid.proton.codec.Data import org.junit.Test import kotlin.test.assertEquals @@ -9,15 +8,6 @@ import kotlin.test.assertEquals // char property of the class would've been treated as an Integer and given to the constructor // as such class DeserializeSimpleTypesTests { - class TestSerializationOutput( - private val verbose: Boolean, - serializerFactory: SerializerFactory = SerializerFactory()) : SerializationOutput(serializerFactory) { - - override fun writeSchema(schema: Schema, data: Data) { - if (verbose) println(schema) - super.writeSchema(schema, data) - } - } companion object { /** diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/carpenter/ClassCarpenterTestUtils.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/carpenter/ClassCarpenterTestUtils.kt index 7601ab1834..3aae840918 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/carpenter/ClassCarpenterTestUtils.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/carpenter/ClassCarpenterTestUtils.kt @@ -2,6 +2,7 @@ package net.corda.nodeapi.internal.serialization.carpenter import net.corda.nodeapi.internal.serialization.amqp.Field import net.corda.nodeapi.internal.serialization.amqp.Schema +import net.corda.nodeapi.internal.serialization.amqp.TypeNotation import net.corda.nodeapi.internal.serialization.amqp.CompositeType import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory import net.corda.nodeapi.internal.serialization.amqp.SerializationOutput From 52aa145c9181f27976df55e47e14eae30d4b1b26 Mon Sep 17 00:00:00 2001 From: Katelyn Baker Date: Thu, 10 Aug 2017 12:17:10 +0100 Subject: [PATCH 10/10] Ensure deserialised obj isn't cached serialised version if it is (it's not) then we wouldn't be testing the carpetner --- .../serialization/amqp/DeserializeNeedingCarpentryTests.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeNeedingCarpentryTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeNeedingCarpentryTests.kt index 7a5ba64457..f1a44aef7b 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeNeedingCarpentryTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeNeedingCarpentryTests.kt @@ -33,7 +33,12 @@ class DeserializeNeedingCarpentryTests { val serialisedBytes = TestSerializationOutput(VERBOSE, sf).serialize(classInstance) val deserializedObj = DeserializationInput(sf).deserialize(serialisedBytes) + assertNotEquals(clazz::class.java, deserializedObj::class.java) assertEquals (testVal, deserializedObj::class.java.getMethod("getA").invoke(deserializedObj)) + + val deserializedObj2 = DeserializationInput(sf).deserialize(serialisedBytes) + + assertEquals(deserializedObj::class.java, deserializedObj2::class.java) } @Test