Merge pull request #1005 from corda/kat-serialization

Integrate class synthesis with the serialisation framework
This commit is contained in:
Katelyn Baker 2017-07-17 12:58:53 +01:00 committed by GitHub
commit eeb66b68b8
15 changed files with 1639 additions and 234 deletions

1
.gitignore vendored
View File

@ -32,6 +32,7 @@ lib/dokka.jar
.idea/libraries
.idea/shelf
.idea/dataSources
.idea/markdown-navigator
/gradle-plugins/.idea/
# Include the -parameters compiler option by default in IntelliJ required for serialization.

View File

@ -15,6 +15,9 @@ import java.lang.reflect.Type
import java.lang.reflect.TypeVariable
import java.util.*
import net.corda.core.serialization.carpenter.Schema as CarpenterSchema
import net.corda.core.serialization.carpenter.Field as CarpenterField
// TODO: get an assigned number as per AMQP spec
val DESCRIPTOR_TOP_32BITS: Long = 0xc0da0000
@ -168,6 +171,8 @@ data class Field(val name: String, val type: String, val requires: List<String>,
sb.append("/>")
return sb.toString()
}
fun typeAsString() = if (type =="*") requires[0] else type
}
sealed class TypeNotation : DescribedType {
@ -402,4 +407,4 @@ private fun fingerprintForObject(type: Type, contextType: Type?, alreadySeen: Mu
}
interfacesForSerialization(type).map { fingerprintForType(it, type, alreadySeen, hasher, factory) }
return hasher
}
}

View File

@ -269,6 +269,7 @@ class SerializerFactory(val whitelist: ClassWhitelist = AllWhitelist) {
}
private val primitiveTypeNames: Map<Class<*>, String> = mapOf(
Character::class.java to "char",
Boolean::class.java to "boolean",
Byte::class.java to "byte",
UnsignedByte::class.java to "ubyte",

View File

@ -0,0 +1,127 @@
package net.corda.core.serialization.carpenter
import net.corda.core.serialization.amqp.Schema as AMQPSchema
import net.corda.core.serialization.amqp.Field as AMQPField
import net.corda.core.serialization.amqp.CompositeType
fun AMQPSchema.carpenterSchema(
loaders : List<ClassLoader> = listOf<ClassLoader>(ClassLoader.getSystemClassLoader()))
: CarpenterSchemas {
val rtn = CarpenterSchemas.newInstance()
types.filterIsInstance<CompositeType>().forEach {
it.carpenterSchema(classLoaders = loaders, carpenterSchemas = rtn)
}
return rtn
}
/**
* if we can load the class then we MUST know about all of it's composite elements
*/
private fun CompositeType.validatePropertyTypes(
classLoaders: List<ClassLoader> = listOf<ClassLoader> (ClassLoader.getSystemClassLoader())){
fields.forEach {
if (!it.validateType(classLoaders)) throw UncarpentableException (name, it.name, it.type)
}
}
/**
* based upon this AMQP schema either
* a) add the corresponding carpenter schema to the [carpenterSchemas] param
* b) add the class to the dependency tree in [carpenterSchemas] if it cannot be instantiated
* at this time
*
* @param classLoaders list of classLoaders, defaulting toe the system class loader, that might
* be used to load objects
* @param carpenterSchemas structure that holds the dependency tree and list of classes that
* need constructing
* @param force by default a schema is not added to [carpenterSchemas] if it already exists
* on the class path. For testing purposes schema generation can be forced
*/
fun CompositeType.carpenterSchema(
classLoaders: List<ClassLoader> = listOf<ClassLoader> (ClassLoader.getSystemClassLoader()),
carpenterSchemas : CarpenterSchemas,
force : Boolean = false) {
if (classLoaders.exists(name)) {
validatePropertyTypes(classLoaders)
if (!force) return
}
val providesList = mutableListOf<Class<*>>()
var isInterface = false
var isCreatable = true
provides.forEach {
if (name == it) {
isInterface = true
return@forEach
}
try {
providesList.add (classLoaders.loadIfExists(it))
}
catch (e: ClassNotFoundException) {
carpenterSchemas.addDepPair(this, name, it)
isCreatable = false
}
}
val m : MutableMap<String, Field> = mutableMapOf()
fields.forEach {
try {
m[it.name] = FieldFactory.newInstance(it.mandatory, it.name, it.getTypeAsClass(classLoaders))
}
catch (e: ClassNotFoundException) {
carpenterSchemas.addDepPair(this, name, it.typeAsString())
isCreatable = false
}
}
if (isCreatable) {
carpenterSchemas.carpenterSchemas.add (CarpenterSchemaFactory.newInstance(
name = name,
fields = m,
interfaces = providesList,
isInterface = isInterface))
}
}
fun AMQPField.getTypeAsClass(
classLoaders: List<ClassLoader> = listOf<ClassLoader> (ClassLoader.getSystemClassLoader())
) = when (type) {
"int" -> 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)
}
fun AMQPField.validateType(
classLoaders: List<ClassLoader> = listOf<ClassLoader> (ClassLoader.getSystemClassLoader())
) = when (type) {
"int", "string", "short", "long", "char", "boolean", "double", "float" -> true
"*" -> classLoaders.exists(requires[0])
else -> classLoaders.exists (type)
}
private fun List<ClassLoader>.exists (clazz: String) =
this.find { try { it.loadClass(clazz); true } catch (e: ClassNotFoundException) { false } } != null
private fun List<ClassLoader>.loadIfExists (clazz: String) : Class<*> {
this.forEach {
try {
return it.loadClass(clazz)
} catch (e: ClassNotFoundException) {
return@forEach
}
}
throw ClassNotFoundException(clazz)
}

View File

@ -3,7 +3,6 @@ package net.corda.core.serialization.carpenter
import org.objectweb.asm.ClassWriter
import org.objectweb.asm.MethodVisitor
import org.objectweb.asm.Opcodes.*
import org.objectweb.asm.Type
import java.lang.Character.isJavaIdentifierPart
import java.lang.Character.isJavaIdentifierStart
@ -19,6 +18,9 @@ interface SimpleFieldAccess {
operator fun get(name: String): Any?
}
class CarpenterClassLoader : ClassLoader(Thread.currentThread().contextClassLoader) {
fun load(name: String, bytes: ByteArray) = defineClass(name, bytes, 0, bytes.size)
}
/**
* A class carpenter generates JVM bytecodes for a class given a schema and then loads it into a sub-classloader.
@ -71,143 +73,10 @@ class ClassCarpenter {
// TODO: Support annotations.
// TODO: isFoo getter patterns for booleans (this is what Kotlin generates)
class DuplicateNameException : RuntimeException("An attempt was made to register two classes with the same name within the same ClassCarpenter namespace.")
class InterfaceMismatchException(msg: String) : RuntimeException(msg)
class NullablePrimitiveException(msg: String) : RuntimeException(msg)
abstract class Field(val field: Class<out Any?>) {
companion object {
const val unsetName = "Unset"
}
var name: String = unsetName
abstract val nullabilityAnnotation: String
val descriptor: String
get() = Type.getDescriptor(this.field)
val type: String
get() = if (this.field.isPrimitive) this.descriptor else "Ljava/lang/Object;"
fun generateField(cw: ClassWriter) {
val fieldVisitor = cw.visitField(ACC_PROTECTED + ACC_FINAL, name, descriptor, null, null)
fieldVisitor.visitAnnotation(nullabilityAnnotation, true).visitEnd()
fieldVisitor.visitEnd()
}
fun addNullabilityAnnotation(mv: MethodVisitor) {
mv.visitAnnotation(nullabilityAnnotation, true).visitEnd()
}
fun visitParameter(mv: MethodVisitor, idx: Int) {
with(mv) {
visitParameter(name, 0)
if (!field.isPrimitive) {
visitParameterAnnotation(idx, nullabilityAnnotation, true).visitEnd()
}
}
}
abstract fun copy(name: String, field: Class<out Any?>): Field
abstract fun nullTest(mv: MethodVisitor, slot: Int)
}
class NonNullableField(field: Class<out Any?>) : Field(field) {
override val nullabilityAnnotation = "Ljavax/annotation/Nonnull;"
constructor(name: String, field: Class<out Any?>) : this(field) {
this.name = name
}
override fun copy(name: String, field: Class<out Any?>) = NonNullableField(name, field)
override fun nullTest(mv: MethodVisitor, slot: Int) {
assert(name != unsetName)
if (!field.isPrimitive) {
with(mv) {
visitVarInsn(ALOAD, 0) // load this
visitVarInsn(ALOAD, slot) // load parameter
visitLdcInsn("param \"$name\" cannot be null")
visitMethodInsn(INVOKESTATIC,
"java/util/Objects",
"requireNonNull",
"(Ljava/lang/Object;Ljava/lang/String;)Ljava/lang/Object;", false)
visitInsn(POP)
}
}
}
}
class NullableField(field: Class<out Any?>) : Field(field) {
override val nullabilityAnnotation = "Ljavax/annotation/Nullable;"
constructor(name: String, field: Class<out Any?>) : this(field) {
if (field.isPrimitive) {
throw NullablePrimitiveException (
"Field $name is primitive type ${Type.getDescriptor(field)} and thus cannot be nullable")
}
this.name = name
}
override fun copy(name: String, field: Class<out Any?>) = NullableField(name, field)
override fun nullTest(mv: MethodVisitor, slot: Int) {
assert(name != unsetName)
}
}
/**
* A Schema represents a desired class.
*/
abstract class Schema(
val name: String,
fields: Map<String, Field>,
val superclass: Schema? = null,
val interfaces: List<Class<*>> = emptyList())
{
private fun Map<String, ClassCarpenter.Field>.descriptors() =
LinkedHashMap(this.mapValues { it.value.descriptor })
/* Fix the order up front if the user didn't, inject the name into the field as it's
neater when iterating */
val fields = LinkedHashMap(fields.mapValues { it.value.copy(it.key, it.value.field) })
fun fieldsIncludingSuperclasses(): Map<String, Field> =
(superclass?.fieldsIncludingSuperclasses() ?: emptyMap()) + LinkedHashMap(fields)
fun descriptorsIncludingSuperclasses(): Map<String, String> =
(superclass?.descriptorsIncludingSuperclasses() ?: emptyMap()) + fields.descriptors()
val jvmName: String
get() = name.replace(".", "/")
}
private val String.jvm: String get() = replace(".", "/")
class ClassSchema(
name: String,
fields: Map<String, Field>,
superclass: Schema? = null,
interfaces: List<Class<*>> = emptyList()
) : Schema(name, fields, superclass, interfaces)
class InterfaceSchema(
name: String,
fields: Map<String, Field>,
superclass: Schema? = null,
interfaces: List<Class<*>> = emptyList()
) : Schema(name, fields, superclass, interfaces)
private class CarpenterClassLoader : ClassLoader(Thread.currentThread().contextClassLoader) {
fun load(name: String, bytes: ByteArray) = defineClass(name, bytes, 0, bytes.size)
}
private val classloader = CarpenterClassLoader()
val classloader = CarpenterClassLoader()
private val _loaded = HashMap<String, Class<*>>()
private val String.jvm: String get() = replace(".", "/")
/** Returns a snapshot of the currently loaded classes as a map of full class name (package names+dots) -> class object */
val loaded: Map<String, Class<*>> = HashMap(_loaded)
@ -216,7 +85,8 @@ class ClassCarpenter {
* Generate bytecode for the given schema and load into the JVM. The returned class object can be used to
* construct instances of the generated class.
*
* @throws DuplicateNameException if the schema's name is already taken in this namespace (you can create a new ClassCarpenter if you're OK with ambiguous names)
* @throws DuplicateNameException if the schema's name is already taken in this namespace (you can create a
* new ClassCarpenter if you're OK with ambiguous names)
*/
fun build(schema: Schema): Class<*> {
validateSchema(schema)
@ -237,6 +107,8 @@ class ClassCarpenter {
}
}
assert (schema.name in _loaded)
return _loaded[schema.name]!!
}
@ -374,14 +246,14 @@ class ClassCarpenter {
// Calculate the super call.
val superclassFields = schema.superclass?.fieldsIncludingSuperclasses() ?: emptyMap()
visitVarInsn(ALOAD, 0)
if (schema.superclass == null) {
val sc = schema.superclass
if (sc == null) {
visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false)
} else {
var slot = 1
for (fieldType in superclassFields.values)
slot += load(slot, fieldType)
val superDesc = schema.superclass.descriptorsIncludingSuperclasses().values.joinToString("")
visitMethodInsn(INVOKESPECIAL, schema.superclass.name.jvm, "<init>", "($superDesc)V", false)
superclassFields.values.forEach { slot += load(slot, it) }
val superDesc = sc.descriptorsIncludingSuperclasses().values.joinToString("")
visitMethodInsn(INVOKESPECIAL, sc.name.jvm, "<init>", "($superDesc)V", false)
}
// Assign the fields from parameters.

View File

@ -0,0 +1,11 @@
package net.corda.core.serialization.carpenter
class DuplicateNameException : RuntimeException (
"An attempt was made to register two classes with the same name within the same ClassCarpenter namespace.")
class InterfaceMismatchException(msg: String) : RuntimeException(msg)
class NullablePrimitiveException(msg: String) : RuntimeException(msg)
class UncarpentableException (name: String, field: String, type: String) :
Exception ("Class $name is loadable yet contains field $field of unknown type $type")

View File

@ -0,0 +1,100 @@
package net.corda.core.serialization.carpenter
import net.corda.core.serialization.amqp.CompositeType
import net.corda.core.serialization.amqp.TypeNotation
/**
* Generated from an AMQP schema this class represents the classes unknown to the deserialiser and that thusly
* require carpenting up in bytecode form. This is a multi step process as carpenting one object may be depedent
* upon the creation of others, this information is tracked in the dependency tree represented by
* [dependencies] and [dependsOn]. Creatable classes are stored in [carpenterSchemas].
*
* The state of this class after initial generation is expected to mutate as classes are built by the carpenter
* enablaing the resolution of dependencies and thus new carpenter schemas added whilst those already
* carpented schemas are removed.
*
* @property carpenterSchemas The list of carpentable classes
* @property dependencies Maps a class to a list of classes that depend on it being built first
* @property dependsOn Maps a class to a list of classes it depends on being built before it
*
* Once a class is constructed we can quickly check for resolution by first looking at all of its dependents in the
* [dependencies] map. This will give us a list of classes that depended on that class being carpented. We can then
* in turn look up all of those classes in the [dependsOn] list, remove their dependency on the newly created class,
* and if that list is reduced to zero know we can now generate a [Schema] for them and carpent them up
*/
data class CarpenterSchemas (
val carpenterSchemas : MutableList<Schema>,
val dependencies : MutableMap<String, Pair<TypeNotation, MutableList<String>>>,
val dependsOn : MutableMap<String, MutableList<String>>) {
companion object CarpenterSchemaConstructor {
fun newInstance(): CarpenterSchemas {
return CarpenterSchemas(
mutableListOf<Schema>(),
mutableMapOf<String, Pair<TypeNotation, MutableList<String>>>(),
mutableMapOf<String, MutableList<String>>())
}
}
fun addDepPair(type: TypeNotation, dependant: String, dependee: String) {
dependsOn.computeIfAbsent(dependee, { mutableListOf<String>() }).add(dependant)
dependencies.computeIfAbsent(dependant, { Pair(type, mutableListOf<String>()) }).second.add(dependee)
}
val size
get() = carpenterSchemas.size
}
/**
* Take a dependency tree of [CarpenterSchemas] and reduce it to zero by carpenting those classes that
* require it. As classes are carpented check for depdency resolution, if now free generate a [Schema] for
* that class and add it to the list of classes ([CarpenterSchemas.carpenterSchemas]) that require
* carpenting
*
* @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()
val objects = mutableMapOf<String, Class<*>>()
fun step (newObject : Schema) {
objects[newObject.name] = cc.build (newObject)
// go over the list of everything that had a dependency on the newly
// carpented class existing and remove it from their dependency list, If that
// list is now empty we have no impediment to carpenting that class up
schemas.dependsOn.remove(newObject.name)?.forEach { dependent ->
assert (newObject.name in schemas.dependencies[dependent]!!.second)
schemas.dependencies[dependent]?.second?.remove(newObject.name)
// we're out of blockers so we can now create the type
if (schemas.dependencies[dependent]?.second?.isEmpty() ?: false) {
(schemas.dependencies.remove (dependent)?.first as CompositeType).carpenterSchema (
classLoaders = listOf<ClassLoader> (
ClassLoader.getSystemClassLoader(),
cc.classloader),
carpenterSchemas = schemas)
}
}
}
abstract fun build()
}
class MetaCarpenter (schemas : CarpenterSchemas) : MetaCarpenterBase (schemas) {
override fun build() {
while (schemas.carpenterSchemas.isNotEmpty()) {
val newObject = schemas.carpenterSchemas.removeAt(0)
step (newObject)
}
}
}
class TestMetaCarpenter (schemas : CarpenterSchemas) : MetaCarpenterBase (schemas) {
override fun build() {
if (schemas.carpenterSchemas.isEmpty()) return
step (schemas.carpenterSchemas.removeAt(0))
}
}

View File

@ -0,0 +1,150 @@
package net.corda.core.serialization.carpenter
import jdk.internal.org.objectweb.asm.Opcodes.*
import org.objectweb.asm.ClassWriter
import org.objectweb.asm.MethodVisitor
import org.objectweb.asm.Type
import java.util.LinkedHashMap
/**
* A Schema represents a desired class.
*/
abstract class Schema(
val name: String,
fields: Map<String, Field>,
val superclass: Schema? = null,
val interfaces: List<Class<*>> = emptyList())
{
private fun Map<String, Field>.descriptors() =
LinkedHashMap(this.mapValues { it.value.descriptor })
/* Fix the order up front if the user didn't, inject the name into the field as it's
neater when iterating */
val fields = LinkedHashMap(fields.mapValues { it.value.copy(it.key, it.value.field) })
fun fieldsIncludingSuperclasses(): Map<String, Field> =
(superclass?.fieldsIncludingSuperclasses() ?: emptyMap()) + LinkedHashMap(fields)
fun descriptorsIncludingSuperclasses(): Map<String, String> =
(superclass?.descriptorsIncludingSuperclasses() ?: emptyMap()) + fields.descriptors()
val jvmName: String
get() = name.replace(".", "/")
}
class ClassSchema(
name: String,
fields: Map<String, Field>,
superclass: Schema? = null,
interfaces: List<Class<*>> = emptyList()
) : Schema (name, fields, superclass, interfaces)
class InterfaceSchema(
name: String,
fields: Map<String, Field>,
superclass: Schema? = null,
interfaces: List<Class<*>> = emptyList()
) : Schema (name, fields, superclass, interfaces)
object CarpenterSchemaFactory {
fun newInstance (
name: String,
fields: Map<String, Field>,
superclass: Schema? = null,
interfaces: List<Class<*>> = emptyList(),
isInterface: Boolean = false
) : Schema =
if (isInterface) InterfaceSchema (name, fields, superclass, interfaces)
else ClassSchema (name, fields, superclass, interfaces)
}
abstract class Field(val field: Class<out Any?>) {
companion object {
const val unsetName = "Unset"
}
var name: String = unsetName
abstract val nullabilityAnnotation: String
val descriptor: String
get() = Type.getDescriptor(this.field)
val type: String
get() = if (this.field.isPrimitive) this.descriptor else "Ljava/lang/Object;"
fun generateField(cw: ClassWriter) {
val fieldVisitor = cw.visitField(ACC_PROTECTED + ACC_FINAL, name, descriptor, null, null)
fieldVisitor.visitAnnotation(nullabilityAnnotation, true).visitEnd()
fieldVisitor.visitEnd()
}
fun addNullabilityAnnotation(mv: MethodVisitor) {
mv.visitAnnotation(nullabilityAnnotation, true).visitEnd()
}
fun visitParameter(mv: MethodVisitor, idx: Int) {
with(mv) {
visitParameter(name, 0)
if (!field.isPrimitive) {
visitParameterAnnotation(idx, nullabilityAnnotation, true).visitEnd()
}
}
}
abstract fun copy(name: String, field: Class<out Any?>): Field
abstract fun nullTest(mv: MethodVisitor, slot: Int)
}
class NonNullableField(field: Class<out Any?>) : Field(field) {
override val nullabilityAnnotation = "Ljavax/annotation/Nonnull;"
constructor(name: String, field: Class<out Any?>) : this(field) {
this.name = name
}
override fun copy(name: String, field: Class<out Any?>) = NonNullableField(name, field)
override fun nullTest(mv: MethodVisitor, slot: Int) {
assert(name != unsetName)
if (!field.isPrimitive) {
with(mv) {
visitVarInsn(ALOAD, 0) // load this
visitVarInsn(ALOAD, slot) // load parameter
visitLdcInsn("param \"$name\" cannot be null")
visitMethodInsn(INVOKESTATIC,
"java/util/Objects",
"requireNonNull",
"(Ljava/lang/Object;Ljava/lang/String;)Ljava/lang/Object;", false)
visitInsn(POP)
}
}
}
}
class NullableField(field: Class<out Any?>) : Field(field) {
override val nullabilityAnnotation = "Ljavax/annotation/Nullable;"
constructor(name: String, field: Class<out Any?>) : this(field) {
if (field.isPrimitive) {
throw NullablePrimitiveException (
"Field $name is primitive type ${Type.getDescriptor(field)} and thus cannot be nullable")
}
this.name = name
}
override fun copy(name: String, field: Class<out Any?>) = NullableField(name, field)
override fun nullTest(mv: MethodVisitor, slot: Int) {
assert(name != unsetName)
}
}
object FieldFactory {
fun newInstance (mandatory: Boolean, name: String, field: Class<out Any?>) =
if (mandatory) NonNullableField (name, field) else NullableField (name, field)
}

View File

@ -7,10 +7,12 @@ import net.corda.core.identity.AbstractParty
import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.EmptyWhitelist
import net.corda.core.serialization.KryoAMQPSerializer
import net.corda.core.serialization.amqp.SerializerFactory.Companion.isPrimitive
import net.corda.core.CordaRuntimeException
import net.corda.nodeapi.RPCException
import net.corda.testing.MEGA_CORP
import net.corda.testing.MEGA_CORP_PUBKEY
import org.apache.qpid.proton.amqp.*
import org.apache.qpid.proton.codec.DecoderImpl
import org.apache.qpid.proton.codec.EncoderImpl
import org.junit.Test
@ -27,6 +29,14 @@ import kotlin.test.assertTrue
class SerializationOutputTests {
data class Foo(val bar: String, val pub: Int)
data class testFloat(val f: Float)
data class testDouble(val d: Double)
data class testShort(val s: Short)
data class testBoolean(val b : Boolean)
interface FooInterface {
val pub: Int
}
@ -159,12 +169,61 @@ class SerializationOutputTests {
return desObj2
}
@Test
fun isPrimitive() {
assertTrue(isPrimitive(Character::class.java))
assertTrue(isPrimitive(Boolean::class.java))
assertTrue(isPrimitive(Byte::class.java))
assertTrue(isPrimitive(UnsignedByte::class.java))
assertTrue(isPrimitive(Short::class.java))
assertTrue(isPrimitive(UnsignedShort::class.java))
assertTrue(isPrimitive(Int::class.java))
assertTrue(isPrimitive(UnsignedInteger::class.java))
assertTrue(isPrimitive(Long::class.java))
assertTrue(isPrimitive(UnsignedLong::class.java))
assertTrue(isPrimitive(Float::class.java))
assertTrue(isPrimitive(Double::class.java))
assertTrue(isPrimitive(Decimal32::class.java))
assertTrue(isPrimitive(Decimal64::class.java))
assertTrue(isPrimitive(Decimal128::class.java))
assertTrue(isPrimitive(Char::class.java))
assertTrue(isPrimitive(Date::class.java))
assertTrue(isPrimitive(UUID::class.java))
assertTrue(isPrimitive(ByteArray::class.java))
assertTrue(isPrimitive(String::class.java))
assertTrue(isPrimitive(Symbol::class.java))
}
@Test
fun `test foo`() {
val obj = Foo("Hello World!", 123)
serdes(obj)
}
@Test
fun `test float`() {
val obj = testFloat(10.0F)
serdes(obj)
}
@Test
fun `test double`() {
val obj = testDouble(10.0)
serdes(obj)
}
@Test
fun `test short`() {
val obj = testShort(1)
serdes(obj)
}
@Test
fun `test bool`() {
val obj = testBoolean(true)
serdes(obj)
}
@Test
fun `test foo implements`() {
val obj = FooImplements("Hello World!", 123)
@ -524,4 +583,4 @@ class SerializationOutputTests {
val obj = StateRef(SecureHash.randomSHA256(), 0)
serdes(obj, factory, factory2)
}
}
}

View File

@ -22,7 +22,7 @@ class ClassCarpenterTest {
@Test
fun empty() {
val clazz = cc.build(ClassCarpenter.ClassSchema("gen.EmptyClass", emptyMap(), null))
val clazz = cc.build(ClassSchema("gen.EmptyClass", emptyMap(), null))
assertEquals(0, clazz.nonSyntheticFields.size)
assertEquals(2, clazz.nonSyntheticMethods.size) // get, toString
assertEquals(0, clazz.declaredConstructors[0].parameterCount)
@ -31,7 +31,7 @@ class ClassCarpenterTest {
@Test
fun prims() {
val clazz = cc.build(ClassCarpenter.ClassSchema(
val clazz = cc.build(ClassSchema(
"gen.Prims",
mapOf(
"anIntField" to Int::class.javaPrimitiveType!!,
@ -42,7 +42,7 @@ class ClassCarpenterTest {
"floatMyBoat" to Float::class.javaPrimitiveType!!,
"byteMe" to Byte::class.javaPrimitiveType!!,
"booleanField" to Boolean::class.javaPrimitiveType!!).mapValues {
ClassCarpenter.NonNullableField (it.value)
NonNullableField (it.value)
}))
assertEquals(8, clazz.nonSyntheticFields.size)
assertEquals(10, clazz.nonSyntheticMethods.size)
@ -69,10 +69,10 @@ class ClassCarpenterTest {
}
private fun genPerson(): Pair<Class<*>, Any> {
val clazz = cc.build(ClassCarpenter.ClassSchema("gen.Person", mapOf(
val clazz = cc.build(ClassSchema("gen.Person", mapOf(
"age" to Int::class.javaPrimitiveType!!,
"name" to String::class.java
).mapValues { ClassCarpenter.NonNullableField (it.value) } ))
).mapValues { NonNullableField (it.value) } ))
val i = clazz.constructors[0].newInstance(32, "Mike")
return Pair(clazz, i)
}
@ -90,17 +90,17 @@ class ClassCarpenterTest {
assertEquals("Person{age=32, name=Mike}", i.toString())
}
@Test(expected = ClassCarpenter.DuplicateNameException::class)
@Test(expected = DuplicateNameException::class)
fun duplicates() {
cc.build(ClassCarpenter.ClassSchema("gen.EmptyClass", emptyMap(), null))
cc.build(ClassCarpenter.ClassSchema("gen.EmptyClass", emptyMap(), null))
cc.build(ClassSchema("gen.EmptyClass", emptyMap(), null))
cc.build(ClassSchema("gen.EmptyClass", emptyMap(), null))
}
@Test
fun `can refer to each other`() {
val (clazz1, i) = genPerson()
val clazz2 = cc.build(ClassCarpenter.ClassSchema("gen.Referee", mapOf(
"ref" to ClassCarpenter.NonNullableField (clazz1)
val clazz2 = cc.build(ClassSchema("gen.Referee", mapOf(
"ref" to NonNullableField (clazz1)
)))
val i2 = clazz2.constructors[0].newInstance(i)
assertEquals(i, (i2 as SimpleFieldAccess)["ref"])
@ -108,13 +108,13 @@ class ClassCarpenterTest {
@Test
fun superclasses() {
val schema1 = ClassCarpenter.ClassSchema(
val schema1 = ClassSchema(
"gen.A",
mapOf("a" to ClassCarpenter.NonNullableField (String::class.java)))
mapOf("a" to NonNullableField (String::class.java)))
val schema2 = ClassCarpenter.ClassSchema(
val schema2 = ClassSchema(
"gen.B",
mapOf("b" to ClassCarpenter.NonNullableField (String::class.java)),
mapOf("b" to NonNullableField (String::class.java)),
schema1)
val clazz = cc.build(schema2)
@ -126,29 +126,30 @@ class ClassCarpenterTest {
@Test
fun interfaces() {
val schema1 = ClassCarpenter.ClassSchema(
val schema1 = ClassSchema(
"gen.A",
mapOf("a" to ClassCarpenter.NonNullableField(String::class.java)))
mapOf("a" to NonNullableField(String::class.java)))
val schema2 = ClassCarpenter.ClassSchema("gen.B",
mapOf("b" to ClassCarpenter.NonNullableField(Int::class.java)),
val schema2 = ClassSchema("gen.B",
mapOf("b" to NonNullableField(Int::class.java)),
schema1,
interfaces = listOf(DummyInterface::class.java))
val clazz = cc.build(schema2)
val i = clazz.constructors[0].newInstance("xa", 1) as DummyInterface
assertEquals("xa", i.a)
assertEquals(1, i.b)
}
@Test(expected = ClassCarpenter.InterfaceMismatchException::class)
@Test(expected = InterfaceMismatchException::class)
fun `mismatched interface`() {
val schema1 = ClassCarpenter.ClassSchema(
val schema1 = ClassSchema(
"gen.A",
mapOf("a" to ClassCarpenter.NonNullableField(String::class.java)))
mapOf("a" to NonNullableField(String::class.java)))
val schema2 = ClassCarpenter.ClassSchema(
val schema2 = ClassSchema(
"gen.B",
mapOf("c" to ClassCarpenter.NonNullableField(Int::class.java)),
mapOf("c" to NonNullableField(Int::class.java)),
schema1,
interfaces = listOf(DummyInterface::class.java))
@ -159,9 +160,9 @@ class ClassCarpenterTest {
@Test
fun `generate interface`() {
val schema1 = ClassCarpenter.InterfaceSchema(
val schema1 = InterfaceSchema(
"gen.Interface",
mapOf("a" to ClassCarpenter.NonNullableField (Int::class.java)))
mapOf("a" to NonNullableField (Int::class.java)))
val iface = cc.build(schema1)
@ -170,9 +171,9 @@ class ClassCarpenterTest {
assertEquals(iface.declaredMethods.size, 1)
assertEquals(iface.declaredMethods[0].name, "getA")
val schema2 = ClassCarpenter.ClassSchema(
val schema2 = ClassSchema(
"gen.Derived",
mapOf("a" to ClassCarpenter.NonNullableField (Int::class.java)),
mapOf("a" to NonNullableField (Int::class.java)),
interfaces = listOf(iface))
val clazz = cc.build(schema2)
@ -184,25 +185,25 @@ class ClassCarpenterTest {
@Test
fun `generate multiple interfaces`() {
val iFace1 = ClassCarpenter.InterfaceSchema(
val iFace1 = InterfaceSchema(
"gen.Interface1",
mapOf(
"a" to ClassCarpenter.NonNullableField(Int::class.java),
"b" to ClassCarpenter.NonNullableField(String::class.java)))
"a" to NonNullableField(Int::class.java),
"b" to NonNullableField(String::class.java)))
val iFace2 = ClassCarpenter.InterfaceSchema(
val iFace2 = InterfaceSchema(
"gen.Interface2",
mapOf(
"c" to ClassCarpenter.NonNullableField(Int::class.java),
"d" to ClassCarpenter.NonNullableField(String::class.java)))
"c" to NonNullableField(Int::class.java),
"d" to NonNullableField(String::class.java)))
val class1 = ClassCarpenter.ClassSchema(
val class1 = ClassSchema(
"gen.Derived",
mapOf(
"a" to ClassCarpenter.NonNullableField(Int::class.java),
"b" to ClassCarpenter.NonNullableField(String::class.java),
"c" to ClassCarpenter.NonNullableField(Int::class.java),
"d" to ClassCarpenter.NonNullableField(String::class.java)),
"a" to NonNullableField(Int::class.java),
"b" to NonNullableField(String::class.java),
"c" to NonNullableField(Int::class.java),
"d" to NonNullableField(String::class.java)),
interfaces = listOf(cc.build(iFace1), cc.build(iFace2)))
val clazz = cc.build(class1)
@ -220,26 +221,26 @@ class ClassCarpenterTest {
@Test
fun `interface implementing interface`() {
val iFace1 = ClassCarpenter.InterfaceSchema(
val iFace1 = InterfaceSchema(
"gen.Interface1",
mapOf(
"a" to ClassCarpenter.NonNullableField (Int::class.java),
"b" to ClassCarpenter.NonNullableField(String::class.java)))
"a" to NonNullableField (Int::class.java),
"b" to NonNullableField(String::class.java)))
val iFace2 = ClassCarpenter.InterfaceSchema(
val iFace2 = InterfaceSchema(
"gen.Interface2",
mapOf(
"c" to ClassCarpenter.NonNullableField(Int::class.java),
"d" to ClassCarpenter.NonNullableField(String::class.java)),
"c" to NonNullableField(Int::class.java),
"d" to NonNullableField(String::class.java)),
interfaces = listOf(cc.build(iFace1)))
val class1 = ClassCarpenter.ClassSchema(
val class1 = ClassSchema(
"gen.Derived",
mapOf(
"a" to ClassCarpenter.NonNullableField(Int::class.java),
"b" to ClassCarpenter.NonNullableField(String::class.java),
"c" to ClassCarpenter.NonNullableField(Int::class.java),
"d" to ClassCarpenter.NonNullableField(String::class.java)),
"a" to NonNullableField(Int::class.java),
"b" to NonNullableField(String::class.java),
"c" to NonNullableField(Int::class.java),
"d" to NonNullableField(String::class.java)),
interfaces = listOf(cc.build(iFace2)))
val clazz = cc.build(class1)
@ -258,22 +259,21 @@ class ClassCarpenterTest {
@Test(expected = java.lang.IllegalArgumentException::class)
fun `null parameter small int`() {
val className = "iEnjoySwede"
val schema = ClassCarpenter.ClassSchema(
val schema = ClassSchema(
"gen.$className",
mapOf("a" to ClassCarpenter.NonNullableField (Int::class.java)))
mapOf("a" to NonNullableField (Int::class.java)))
val clazz = cc.build(schema)
val a : Int? = null
clazz.constructors[0].newInstance(a)
}
@Test(expected = ClassCarpenter.NullablePrimitiveException::class)
@Test(expected = NullablePrimitiveException::class)
fun `nullable parameter small int`() {
val className = "iEnjoySwede"
val schema = ClassCarpenter.ClassSchema(
val schema = ClassSchema(
"gen.$className",
mapOf("a" to ClassCarpenter.NullableField (Int::class.java)))
mapOf("a" to NullableField (Int::class.java)))
cc.build(schema)
}
@ -281,9 +281,9 @@ class ClassCarpenterTest {
@Test
fun `nullable parameter integer`() {
val className = "iEnjoyWibble"
val schema = ClassCarpenter.ClassSchema(
val schema = ClassSchema(
"gen.$className",
mapOf("a" to ClassCarpenter.NullableField (Integer::class.java)))
mapOf("a" to NullableField (Integer::class.java)))
val clazz = cc.build(schema)
val a1 : Int? = null
@ -296,9 +296,9 @@ class ClassCarpenterTest {
@Test
fun `non nullable parameter integer with non null`() {
val className = "iEnjoyWibble"
val schema = ClassCarpenter.ClassSchema(
val schema = ClassSchema(
"gen.$className",
mapOf("a" to ClassCarpenter.NonNullableField (Integer::class.java)))
mapOf("a" to NonNullableField (Integer::class.java)))
val clazz = cc.build(schema)
@ -309,9 +309,9 @@ class ClassCarpenterTest {
@Test(expected = java.lang.reflect.InvocationTargetException::class)
fun `non nullable parameter integer with null`() {
val className = "iEnjoyWibble"
val schema = ClassCarpenter.ClassSchema(
val schema = ClassSchema(
"gen.$className",
mapOf("a" to ClassCarpenter.NonNullableField (Integer::class.java)))
mapOf("a" to NonNullableField (Integer::class.java)))
val clazz = cc.build(schema)
@ -323,9 +323,9 @@ class ClassCarpenterTest {
@Suppress("UNCHECKED_CAST")
fun `int array`() {
val className = "iEnjoyPotato"
val schema = ClassCarpenter.ClassSchema(
val schema = ClassSchema(
"gen.$className",
mapOf("a" to ClassCarpenter.NonNullableField(IntArray::class.java)))
mapOf("a" to NonNullableField(IntArray::class.java)))
val clazz = cc.build(schema)
@ -342,9 +342,9 @@ class ClassCarpenterTest {
@Test(expected = java.lang.reflect.InvocationTargetException::class)
fun `nullable int array throws`() {
val className = "iEnjoySwede"
val schema = ClassCarpenter.ClassSchema(
val schema = ClassSchema(
"gen.$className",
mapOf("a" to ClassCarpenter.NonNullableField(IntArray::class.java)))
mapOf("a" to NonNullableField(IntArray::class.java)))
val clazz = cc.build(schema)
@ -356,14 +356,13 @@ class ClassCarpenterTest {
@Suppress("UNCHECKED_CAST")
fun `integer array`() {
val className = "iEnjoyFlan"
val schema = ClassCarpenter.ClassSchema(
val schema = ClassSchema(
"gen.$className",
mapOf("a" to ClassCarpenter.NonNullableField(Array<Int>::class.java)))
mapOf("a" to NonNullableField(Array<Int>::class.java)))
val clazz = cc.build(schema)
val i = clazz.constructors[0].newInstance(arrayOf(1, 2, 3)) as SimpleFieldAccess
val arr = clazz.getMethod("getA").invoke(i)
assertEquals(1, (arr as Array<Int>)[0])
@ -376,21 +375,19 @@ class ClassCarpenterTest {
@Suppress("UNCHECKED_CAST")
fun `int array with ints`() {
val className = "iEnjoyCrumble"
val schema = ClassCarpenter.ClassSchema(
val schema = ClassSchema(
"gen.$className", mapOf(
"a" to Int::class.java,
"b" to IntArray::class.java,
"c" to Int::class.java).mapValues { ClassCarpenter.NonNullableField(it.value) })
"c" to Int::class.java).mapValues { NonNullableField(it.value) })
val clazz = cc.build(schema)
val i = clazz.constructors[0].newInstance(2, intArrayOf(4, 8), 16) as SimpleFieldAccess
assertEquals(2, clazz.getMethod("getA").invoke(i))
assertEquals(4, (clazz.getMethod("getB").invoke(i) as IntArray)[0])
assertEquals(8, (clazz.getMethod("getB").invoke(i) as IntArray)[1])
assertEquals(16, clazz.getMethod("getC").invoke(i))
assertEquals("$className{a=2, b=[4, 8], c=16}", i.toString())
}
@ -398,11 +395,11 @@ class ClassCarpenterTest {
@Suppress("UNCHECKED_CAST")
fun `multiple int arrays`() {
val className = "iEnjoyJam"
val schema = ClassCarpenter.ClassSchema(
val schema = ClassSchema(
"gen.$className", mapOf(
"a" to IntArray::class.java,
"b" to Int::class.java,
"c" to IntArray::class.java).mapValues { ClassCarpenter.NonNullableField(it.value) })
"c" to IntArray::class.java).mapValues { NonNullableField(it.value) })
val clazz = cc.build(schema)
val i = clazz.constructors[0].newInstance(intArrayOf(1, 2), 3, intArrayOf(4, 5, 6))
@ -413,7 +410,6 @@ class ClassCarpenterTest {
assertEquals(4, (clazz.getMethod("getC").invoke(i) as IntArray)[0])
assertEquals(5, (clazz.getMethod("getC").invoke(i) as IntArray)[1])
assertEquals(6, (clazz.getMethod("getC").invoke(i) as IntArray)[2])
assertEquals("$className{a=[1, 2], b=3, c=[4, 5, 6]}", i.toString())
}
@ -421,9 +417,9 @@ class ClassCarpenterTest {
@Suppress("UNCHECKED_CAST")
fun `string array`() {
val className = "iEnjoyToast"
val schema = ClassCarpenter.ClassSchema(
val schema = ClassSchema(
"gen.$className",
mapOf("a" to ClassCarpenter.NullableField(Array<String>::class.java)))
mapOf("a" to NullableField(Array<String>::class.java)))
val clazz = cc.build(schema)
@ -439,12 +435,12 @@ class ClassCarpenterTest {
@Suppress("UNCHECKED_CAST")
fun `string arrays`() {
val className = "iEnjoyToast"
val schema = ClassCarpenter.ClassSchema(
val schema = ClassSchema(
"gen.$className",
mapOf(
"a" to Array<String>::class.java,
"b" to String::class.java,
"c" to Array<String>::class.java).mapValues { ClassCarpenter.NullableField (it.value) })
"c" to Array<String>::class.java).mapValues { NullableField (it.value) })
val clazz = cc.build(schema)
@ -453,7 +449,6 @@ class ClassCarpenterTest {
"and on the side",
arrayOf("some pickles", "some fries"))
val arr1 = clazz.getMethod("getA").invoke(i) as Array<String>
val arr2 = clazz.getMethod("getC").invoke(i) as Array<String>
@ -468,33 +463,29 @@ class ClassCarpenterTest {
@Test
fun `nullable sets annotations`() {
val className = "iEnjoyJam"
val schema = ClassCarpenter.ClassSchema(
val schema = ClassSchema(
"gen.$className",
mapOf("a" to ClassCarpenter.NullableField(String::class.java),
"b" to ClassCarpenter.NonNullableField(String::class.java)))
mapOf("a" to NullableField(String::class.java),
"b" to NonNullableField(String::class.java)))
val clazz = cc.build(schema)
assertEquals (2, clazz.declaredFields.size)
assertEquals (1, clazz.getDeclaredField("a").annotations.size)
assertEquals (javax.annotation.Nullable::class.java, clazz.getDeclaredField("a").annotations[0].annotationClass.java)
assertEquals (1, clazz.getDeclaredField("b").annotations.size)
assertEquals (javax.annotation.Nonnull::class.java, clazz.getDeclaredField("b").annotations[0].annotationClass.java)
assertEquals (1, clazz.getMethod("getA").annotations.size)
assertEquals (javax.annotation.Nullable::class.java, clazz.getMethod("getA").annotations[0].annotationClass.java)
assertEquals (1, clazz.getMethod("getB").annotations.size)
assertEquals (javax.annotation.Nonnull::class.java, clazz.getMethod("getB").annotations[0].annotationClass.java)
}
@Test
fun beanTest() {
val schema = ClassCarpenter.ClassSchema(
val schema = ClassSchema(
"pantsPantsPants",
mapOf("a" to ClassCarpenter.NonNullableField(Integer::class.java)))
mapOf("a" to NonNullableField(Integer::class.java)))
val clazz = cc.build(schema)
val descriptors = Introspector.getBeanInfo(clazz).propertyDescriptors
@ -502,5 +493,4 @@ class ClassCarpenterTest {
assertNotEquals(null, descriptors.find { it.name == "a" })
assertNotEquals(null, descriptors.find { it.name == "class" })
}
}

View File

@ -0,0 +1,48 @@
package net.corda.core.serialization.carpenter.test
import net.corda.core.serialization.amqp.CompositeType
import net.corda.core.serialization.amqp.Field
import net.corda.core.serialization.amqp.Schema
import net.corda.core.serialization.amqp.TypeNotation
import net.corda.core.serialization.amqp.SerializerFactory
import net.corda.core.serialization.amqp.SerializationOutput
fun mangleName(name: String) = "${name}__carpenter"
/**
* given a list of class names work through the amqp envelope schema and alter any that
* match in the fashion defined above
*/
fun Schema.mangleNames(names: List<String>): Schema {
val newTypes: MutableList<TypeNotation> = mutableListOf()
for (type in types) {
val newName = if (type.name in names) mangleName(type.name) else type.name
val newProvides = type.provides.map { it -> if (it in names) mangleName(it) else it }
val newFields = mutableListOf<Field>()
(type as CompositeType).fields.forEach {
val fieldType = if (it.type in names) mangleName(it.type) else it.type
val requires =
if (it.requires.isNotEmpty() && (it.requires[0] in names)) listOf(mangleName(it.requires[0]))
else it.requires
newFields.add(it.copy(type = fieldType, requires = requires))
}
newTypes.add(type.copy(name = newName, provides = newProvides, fields = newFields))
}
return Schema(types = newTypes)
}
open class AmqpCarpenterBase {
var factory = SerializerFactory()
fun serialise(clazz: Any) = SerializationOutput(factory).serialize(clazz)
fun testName() = Thread.currentThread().stackTrace[2].methodName
inline fun classTestName(clazz: String) = "${this.javaClass.name}\$${testName()}\$$clazz"
}
/**********************************************************************************************************************/

View File

@ -0,0 +1,286 @@
package net.corda.core.serialization.carpenter
import net.corda.core.serialization.carpenter.test.*
import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.amqp.*
import org.junit.Test
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue
@CordaSerializable
interface I_ {
val a: Int
}
class CompositeMembers : AmqpCarpenterBase() {
@Test
fun bothKnown() {
val testA = 10
val testB = 20
@CordaSerializable
data class A(val a: Int)
@CordaSerializable
data class B(val a: A, var b: Int)
val b = B(A(testA), testB)
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(b))
assert(obj.obj is B)
val amqpObj = obj.obj as B
assertEquals(testB, amqpObj.b)
assertEquals(testA, amqpObj.a.a)
assertEquals(2, obj.envelope.schema.types.size)
assert(obj.envelope.schema.types[0] is CompositeType)
assert(obj.envelope.schema.types[1] is CompositeType)
var amqpSchemaA: CompositeType? = null
var amqpSchemaB: CompositeType? = null
for (type in obj.envelope.schema.types) {
when (type.name.split ("$").last()) {
"A" -> amqpSchemaA = type as CompositeType
"B" -> amqpSchemaB = type as CompositeType
}
}
assert(amqpSchemaA != null)
assert(amqpSchemaB != null)
// Just ensure the amqp schema matches what we want before we go messing
// around with the internals
assertEquals(1, amqpSchemaA?.fields?.size)
assertEquals("a", amqpSchemaA!!.fields[0].name)
assertEquals("int", amqpSchemaA.fields[0].type)
assertEquals(2, amqpSchemaB?.fields?.size)
assertEquals("a", amqpSchemaB!!.fields[0].name)
assertEquals(classTestName("A"), amqpSchemaB.fields[0].type)
assertEquals("b", amqpSchemaB.fields[1].name)
assertEquals("int", amqpSchemaB.fields[1].type)
val metaSchema = obj.envelope.schema.carpenterSchema()
// if we know all the classes there is nothing to really achieve here
assert(metaSchema.carpenterSchemas.isEmpty())
assert(metaSchema.dependsOn.isEmpty())
assert(metaSchema.dependencies.isEmpty())
}
// you cannot have an element of a composite class we know about
// that is unknown as that should be impossible. If we have the containing
// class in the class path then we must have all of it's constituent elements
@Test(expected = UncarpentableException::class)
fun nestedIsUnknown() {
val testA = 10
val testB = 20
@CordaSerializable
data class A(override val a: Int) : I_
@CordaSerializable
data class B(val a: A, var b: Int)
val b = B(A(testA), testB)
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(b))
val amqpSchema = obj.envelope.schema.mangleNames(listOf (classTestName ("A")))
assert(obj.obj is B)
amqpSchema.carpenterSchema()
}
@Test
fun ParentIsUnknown() {
val testA = 10
val testB = 20
@CordaSerializable
data class A(override val a: Int) : I_
@CordaSerializable
data class B(val a: A, var b: Int)
val b = B(A(testA), testB)
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(b))
assert(obj.obj is B)
val amqpSchema = obj.envelope.schema.mangleNames(listOf(classTestName("B")))
val carpenterSchema = amqpSchema.carpenterSchema()
assertEquals(1, carpenterSchema.size)
val metaCarpenter = MetaCarpenter(carpenterSchema)
metaCarpenter.build()
assert(mangleName(classTestName("B")) in metaCarpenter.objects)
}
@Test
fun BothUnknown() {
val testA = 10
val testB = 20
@CordaSerializable
data class A(override val a: Int) : I_
@CordaSerializable
data class B(val a: A, var b: Int)
val b = B(A(testA), testB)
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(b))
assert(obj.obj is B)
val amqpSchema = obj.envelope.schema.mangleNames(listOf(classTestName("A"), classTestName("B")))
val carpenterSchema = amqpSchema.carpenterSchema()
// just verify we're in the expected initial state, A is carpentable, B is not because
// it depends on A and the dependency chains are in place
assertEquals(1, carpenterSchema.size)
assertEquals(mangleName(classTestName("A")), carpenterSchema.carpenterSchemas.first().name)
assertEquals(1, carpenterSchema.dependencies.size)
assert(mangleName(classTestName("B")) in carpenterSchema.dependencies)
assertEquals(1, carpenterSchema.dependsOn.size)
assert(mangleName(classTestName("A")) in carpenterSchema.dependsOn)
val metaCarpenter = TestMetaCarpenter(carpenterSchema)
assertEquals(0, metaCarpenter.objects.size)
// first iteration, carpent A, resolve deps and mark B as carpentable
metaCarpenter.build()
// one build iteration should have carpetned up A and worked out that B is now buildable
// given it's depedencies have been satisfied
assertTrue(mangleName(classTestName("A")) in metaCarpenter.objects)
assertFalse(mangleName(classTestName("B")) in metaCarpenter.objects)
assertEquals(1, carpenterSchema.carpenterSchemas.size)
assertEquals(mangleName(classTestName("B")), carpenterSchema.carpenterSchemas.first().name)
assertTrue(carpenterSchema.dependencies.isEmpty())
assertTrue(carpenterSchema.dependsOn.isEmpty())
// second manual iteration, will carpent B
metaCarpenter.build()
assert(mangleName(classTestName("A")) in metaCarpenter.objects)
assert(mangleName(classTestName("B")) in metaCarpenter.objects)
// and we must be finished
assertTrue(carpenterSchema.carpenterSchemas.isEmpty())
}
@Test(expected = UncarpentableException::class)
@Suppress("UNUSED")
fun nestedIsUnknownInherited() {
val testA = 10
val testB = 20
val testC = 30
@CordaSerializable
open class A(val a: Int)
@CordaSerializable
class B(a: Int, var b: Int) : A(a)
@CordaSerializable
data class C(val b: B, var c: Int)
val c = C(B(testA, testB), testC)
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(c))
assert(obj.obj is C)
val amqpSchema = obj.envelope.schema.mangleNames(listOf(classTestName("A"), classTestName("B")))
amqpSchema.carpenterSchema()
}
@Test(expected = UncarpentableException::class)
@Suppress("UNUSED")
fun nestedIsUnknownInheritedUnknown() {
val testA = 10
val testB = 20
val testC = 30
@CordaSerializable
open class A(val a: Int)
@CordaSerializable
class B(a: Int, var b: Int) : A(a)
@CordaSerializable
data class C(val b: B, var c: Int)
val c = C(B(testA, testB), testC)
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(c))
assert(obj.obj is C)
val amqpSchema = obj.envelope.schema.mangleNames(listOf(classTestName("A"), classTestName("B")))
amqpSchema.carpenterSchema()
}
@Suppress("UNUSED")
@Test(expected = UncarpentableException::class)
fun parentsIsUnknownWithUnknownInheritedMember() {
val testA = 10
val testB = 20
val testC = 30
@CordaSerializable
open class A(val a: Int)
@CordaSerializable
class B(a: Int, var b: Int) : A(a)
@CordaSerializable
data class C(val b: B, var c: Int)
val c = C(B(testA, testB), testC)
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(c))
assert(obj.obj is C)
val carpenterSchema = obj.envelope.schema.mangleNames(listOf(classTestName("A"), classTestName("B")))
TestMetaCarpenter(carpenterSchema.carpenterSchema())
}
/*
* TODO serializer doesn't support inheritnace at the moment, when it does this should work
@Test
fun `inheritance`() {
val testA = 10
val testB = 20
@CordaSerializable
open class A(open val a: Int)
@CordaSerializable
class B(override val a: Int, val b: Int) : A (a)
val b = B(testA, testB)
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(b))
assert(obj.obj is B)
val carpenterSchema = obj.envelope.schema.mangleNames(listOf(classTestName("A"), classTestName("B")))
val metaCarpenter = TestMetaCarpenter(carpenterSchema.carpenterSchema())
assertEquals(1, metaCarpenter.schemas.carpenterSchemas.size)
assertEquals(mangleNames(classTestName("B")), metaCarpenter.schemas.carpenterSchemas.first().name)
assertEquals(1, metaCarpenter.schemas.dependencies.size)
assertTrue(mangleNames(classTestName("A")) in metaCarpenter.schemas.dependencies)
}
*/
}

View File

@ -0,0 +1,461 @@
package net.corda.core.serialization.carpenter
import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.carpenter.test.*
import net.corda.core.serialization.amqp.*
import org.junit.Test
import kotlin.test.*
@CordaSerializable
interface J {
val j: Int
}
@CordaSerializable
interface I {
val i: Int
}
@CordaSerializable
interface II {
val ii: Int
}
@CordaSerializable
interface III : I {
val iii: Int
override val i: Int
}
@CordaSerializable
interface IIII {
val iiii: Int
val i: I
}
class InheritanceSchemaToClassCarpenterTests : AmqpCarpenterBase() {
@Test
fun interfaceParent1() {
class A(override val j: Int) : J
val testJ = 20
val a = A(testJ)
assertEquals(testJ, a.j)
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a))
assert(obj.obj is A)
val serSchema = obj.envelope.schema
assertEquals(2, serSchema.types.size)
val l1 = serSchema.carpenterSchema()
// since we're using an envelope generated by seilaising classes defined locally
// it's extremely unlikely we'd need to carpent any classes
assertEquals(0, l1.size)
val mangleSchema = serSchema.mangleNames(listOf(classTestName("A")))
val l2 = mangleSchema.carpenterSchema()
assertEquals(1, l2.size)
val aSchema = l2.carpenterSchemas.find { it.name == mangleName(classTestName("A")) }
assertNotEquals(null, aSchema)
assertEquals(mangleName(classTestName("A")), aSchema!!.name)
assertEquals(1, aSchema.interfaces.size)
assertEquals(net.corda.core.serialization.carpenter.J::class.java, aSchema.interfaces[0])
val aBuilder = ClassCarpenter().build(aSchema)
val objJ = aBuilder.constructors[0].newInstance(testJ)
val j = objJ as J
assertEquals(aBuilder.getMethod("getJ").invoke(objJ), testJ)
assertEquals(a.j, j.j)
}
@Test
fun interfaceParent2() {
class A(override val j: Int, val jj: Int) : J
val testJ = 20
val testJJ = 40
val a = A(testJ, testJJ)
assertEquals(testJ, a.j)
assertEquals(testJJ, a.jj)
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a))
assert(obj.obj is A)
val serSchema = obj.envelope.schema
assertEquals(2, serSchema.types.size)
val l1 = serSchema.carpenterSchema()
assertEquals(0, l1.size)
val mangleSchema = serSchema.mangleNames(listOf(classTestName("A")))
val aName = mangleName(classTestName("A"))
val l2 = mangleSchema.carpenterSchema()
assertEquals(1, l2.size)
val aSchema = l2.carpenterSchemas.find { it.name == aName }
assertNotEquals(null, aSchema)
assertEquals(aName, aSchema!!.name)
assertEquals(1, aSchema.interfaces.size)
assertEquals(net.corda.core.serialization.carpenter.J::class.java, aSchema.interfaces[0])
val aBuilder = ClassCarpenter().build(aSchema)
val objJ = aBuilder.constructors[0].newInstance(testJ, testJJ)
val j = objJ as J
assertEquals(aBuilder.getMethod("getJ").invoke(objJ), testJ)
assertEquals(aBuilder.getMethod("getJj").invoke(objJ), testJJ)
assertEquals(a.j, j.j)
}
@Test
fun multipleInterfaces() {
val testI = 20
val testII = 40
class A(override val i: Int, override val ii: Int) : I, II
val a = A(testI, testII)
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a))
assert(obj.obj is A)
val serSchema = obj.envelope.schema
assertEquals(3, serSchema.types.size)
val l1 = serSchema.carpenterSchema()
// since we're using an envelope generated by serialising classes defined locally
// it's extremely unlikely we'd need to carpent any classes
assertEquals(0, l1.size)
// pretend we don't know the class we've been sent, i.e. it's unknown to the class loader, and thus
// needs some carpentry
val mangleSchema = serSchema.mangleNames(listOf(classTestName("A")))
val l2 = mangleSchema.carpenterSchema()
val aName = mangleName(classTestName("A"))
assertEquals(1, l2.size)
val aSchema = l2.carpenterSchemas.find { it.name == aName }
assertNotEquals(null, aSchema)
assertEquals(aName, aSchema!!.name)
assertEquals(2, aSchema.interfaces.size)
assert(net.corda.core.serialization.carpenter.I::class.java in aSchema.interfaces)
assert(net.corda.core.serialization.carpenter.II::class.java in aSchema.interfaces)
val aBuilder = ClassCarpenter().build(aSchema)
val objA = aBuilder.constructors[0].newInstance(testI, testII)
val i = objA as I
val ii = objA as II
assertEquals(aBuilder.getMethod("getI").invoke(objA), testI)
assertEquals(aBuilder.getMethod("getIi").invoke(objA), testII)
assertEquals(a.i, i.i)
assertEquals(a.ii, ii.ii)
}
@Test
fun nestedInterfaces() {
class A(override val i: Int, override val iii: Int) : III
val testI = 20
val testIII = 60
val a = A(testI, testIII)
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a))
assert(obj.obj is A)
val serSchema = obj.envelope.schema
assertEquals(3, serSchema.types.size)
val l1 = serSchema.carpenterSchema()
// since we're using an envelope generated by serialising classes defined locally
// it's extremely unlikely we'd need to carpent any classes
assertEquals(0, l1.size)
val mangleSchema = serSchema.mangleNames(listOf(classTestName("A")))
val l2 = mangleSchema.carpenterSchema()
val aName = mangleName(classTestName("A"))
assertEquals(1, l2.size)
val aSchema = l2.carpenterSchemas.find { it.name == aName }
assertNotEquals(null, aSchema)
assertEquals(aName, aSchema!!.name)
assertEquals(2, aSchema.interfaces.size)
assert(net.corda.core.serialization.carpenter.I::class.java in aSchema.interfaces)
assert(net.corda.core.serialization.carpenter.III::class.java in aSchema.interfaces)
val aBuilder = ClassCarpenter().build(aSchema)
val objA = aBuilder.constructors[0].newInstance(testI, testIII)
val i = objA as I
val iii = objA as III
assertEquals(aBuilder.getMethod("getI").invoke(objA), testI)
assertEquals(aBuilder.getMethod("getIii").invoke(objA), testIII)
assertEquals(a.i, i.i)
assertEquals(a.i, iii.i)
assertEquals(a.iii, iii.iii)
}
@Test
fun memberInterface() {
class A(override val i: Int) : I
class B(override val i: I, override val iiii: Int) : IIII
val testI = 25
val testIIII = 50
val a = A(testI)
val b = B(a, testIIII)
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(b))
assert(obj.obj is B)
val serSchema = obj.envelope.schema
// Expected classes are
// * class A
// * class A's interface (class I)
// * class B
// * class B's interface (class IIII)
assertEquals(4, serSchema.types.size)
val mangleSchema = serSchema.mangleNames(listOf(classTestName("A"), classTestName("B")))
val cSchema = mangleSchema.carpenterSchema()
val aName = mangleName(classTestName("A"))
val bName = mangleName(classTestName("B"))
assertEquals(2, cSchema.size)
val aCarpenterSchema = cSchema.carpenterSchemas.find { it.name == aName }
val bCarpenterSchema = cSchema.carpenterSchemas.find { it.name == bName }
assertNotEquals(null, aCarpenterSchema)
assertNotEquals(null, bCarpenterSchema)
val cc = ClassCarpenter()
val cc2 = ClassCarpenter()
val bBuilder = cc.build(bCarpenterSchema!!)
bBuilder.constructors[0].newInstance(a, testIIII)
val aBuilder = cc.build(aCarpenterSchema!!)
val objA = aBuilder.constructors[0].newInstance(testI)
// build a second B this time using our constructed instance of A and not the
// local one we pre defined
bBuilder.constructors[0].newInstance(objA, testIIII)
// whittle and instantiate a different A with a new class loader
val aBuilder2 = cc2.build(aCarpenterSchema)
val objA2 = aBuilder2.constructors[0].newInstance(testI)
bBuilder.constructors[0].newInstance(objA2, testIIII)
}
// if we remove the nested interface we should get an error as it's impossible
// to have a concrete class loaded without having access to all of it's elements
@Test(expected = UncarpentableException::class)
fun memberInterface2() {
class A(override val i: Int) : I
class B(override val i: I, override val iiii: Int) : IIII
val testI = 25
val testIIII = 50
val a = A(testI)
val b = B(a, testIIII)
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(b))
assert(obj.obj is B)
val serSchema = obj.envelope.schema
// The classes we're expecting to find:
// * class A
// * class A's interface (class I)
// * class B
// * class B's interface (class IIII)
assertEquals(4, serSchema.types.size)
// ignore the return as we expect this to throw
serSchema.mangleNames(listOf(
classTestName("A"), "${this.javaClass.`package`.name}.I")).carpenterSchema()
}
@Test
fun interfaceAndImplementation() {
class A(override val i: Int) : I
val testI = 25
val a = A(testI)
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a))
assert(obj.obj is A)
val serSchema = obj.envelope.schema
// The classes we're expecting to find:
// * class A
// * class A's interface (class I)
assertEquals(2, serSchema.types.size)
val amqpSchema = serSchema.mangleNames(listOf(classTestName("A"), "${this.javaClass.`package`.name}.I"))
val aName = mangleName(classTestName("A"))
val iName = mangleName("${this.javaClass.`package`.name}.I")
val carpenterSchema = amqpSchema.carpenterSchema()
// whilst there are two unknown classes within the envelope A depends on I so we can't construct a
// schema for A until we have for I
assertEquals(1, carpenterSchema.size)
assertNotEquals(null, carpenterSchema.carpenterSchemas.find { it.name == iName })
// since we can't build A it should list I as a dependency
assert(aName in carpenterSchema.dependencies)
assertEquals(1, carpenterSchema.dependencies[aName]!!.second.size)
assertEquals(iName, carpenterSchema.dependencies[aName]!!.second[0])
// and conversly I should have A listed as a dependent
assert(iName in carpenterSchema.dependsOn)
assertEquals(1, carpenterSchema.dependsOn[iName]!!.size)
assertEquals(aName, carpenterSchema.dependsOn[iName]!![0])
val mc = MetaCarpenter(carpenterSchema)
mc.build()
assertEquals(0, mc.schemas.carpenterSchemas.size)
assertEquals(0, mc.schemas.dependencies.size)
assertEquals(0, mc.schemas.dependsOn.size)
assertEquals(2, mc.objects.size)
assert(aName in mc.objects)
assert(iName in mc.objects)
mc.objects[aName]!!.constructors[0].newInstance(testI)
}
@Test
fun twoInterfacesAndImplementation() {
class A(override val i: Int, override val ii: Int) : I, II
val testI = 69
val testII = 96
val a = A(testI, testII)
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a))
val amqpSchema = obj.envelope.schema.mangleNames(listOf(
classTestName("A"),
"${this.javaClass.`package`.name}.I",
"${this.javaClass.`package`.name}.II"))
val aName = mangleName(classTestName("A"))
val iName = mangleName("${this.javaClass.`package`.name}.I")
val iiName = mangleName("${this.javaClass.`package`.name}.II")
val carpenterSchema = amqpSchema.carpenterSchema()
// there is nothing preventing us from carpenting up the two interfaces so
// our initial list should contain both interface with A being dependent on both
// and each having A as a dependent
assertEquals(2, carpenterSchema.carpenterSchemas.size)
assertNotNull(carpenterSchema.carpenterSchemas.find { it.name == iName })
assertNotNull(carpenterSchema.carpenterSchemas.find { it.name == iiName })
assertNull(carpenterSchema.carpenterSchemas.find { it.name == aName })
assert(iName in carpenterSchema.dependsOn)
assertEquals(1, carpenterSchema.dependsOn[iName]?.size)
assertNotNull(carpenterSchema.dependsOn[iName]?.find({ it == aName }))
assert(iiName in carpenterSchema.dependsOn)
assertEquals(1, carpenterSchema.dependsOn[iiName]?.size)
assertNotNull(carpenterSchema.dependsOn[iiName]?.find { it == aName })
assert(aName in carpenterSchema.dependencies)
assertEquals(2, carpenterSchema.dependencies[aName]!!.second.size)
assertNotNull(carpenterSchema.dependencies[aName]!!.second.find { it == iName })
assertNotNull(carpenterSchema.dependencies[aName]!!.second.find { it == iiName })
val mc = MetaCarpenter(carpenterSchema)
mc.build()
assertEquals(0, mc.schemas.carpenterSchemas.size)
assertEquals(0, mc.schemas.dependencies.size)
assertEquals(0, mc.schemas.dependsOn.size)
assertEquals(3, mc.objects.size)
assert(aName in mc.objects)
assert(iName in mc.objects)
assert(iiName in mc.objects)
}
@Test
fun nestedInterfacesAndImplementation() {
class A(override val i: Int, override val iii: Int) : III
val testI = 7
val testIII = 11
val a = A(testI, testIII)
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a))
val amqpSchema = obj.envelope.schema.mangleNames(listOf(
classTestName("A"),
"${this.javaClass.`package`.name}.I",
"${this.javaClass.`package`.name}.III"))
val aName = mangleName(classTestName("A"))
val iName = mangleName("${this.javaClass.`package`.name}.I")
val iiiName = mangleName("${this.javaClass.`package`.name}.III")
val carpenterSchema = amqpSchema.carpenterSchema()
// Since A depends on III and III extends I we will have to construct them
// in that reverse order (I -> III -> A)
assertEquals(1, carpenterSchema.carpenterSchemas.size)
assertNotNull(carpenterSchema.carpenterSchemas.find { it.name == iName })
assertNull(carpenterSchema.carpenterSchemas.find { it.name == iiiName })
assertNull(carpenterSchema.carpenterSchemas.find { it.name == aName })
// I has III as a direct dependent and A as an indirect one
assert(iName in carpenterSchema.dependsOn)
assertEquals(2, carpenterSchema.dependsOn[iName]?.size)
assertNotNull(carpenterSchema.dependsOn[iName]?.find({ it == iiiName }))
assertNotNull(carpenterSchema.dependsOn[iName]?.find({ it == aName }))
// III has A as a dependent
assert(iiiName in carpenterSchema.dependsOn)
assertEquals(1, carpenterSchema.dependsOn[iiiName]?.size)
assertNotNull(carpenterSchema.dependsOn[iiiName]?.find { it == aName })
// conversly III depends on I
assert(iiiName in carpenterSchema.dependencies)
assertEquals(1, carpenterSchema.dependencies[iiiName]!!.second.size)
assertNotNull(carpenterSchema.dependencies[iiiName]!!.second.find { it == iName })
// and A depends on III and I
assert(aName in carpenterSchema.dependencies)
assertEquals(2, carpenterSchema.dependencies[aName]!!.second.size)
assertNotNull(carpenterSchema.dependencies[aName]!!.second.find { it == iiiName })
assertNotNull(carpenterSchema.dependencies[aName]!!.second.find { it == iName })
val mc = MetaCarpenter(carpenterSchema)
mc.build()
assertEquals(0, mc.schemas.carpenterSchemas.size)
assertEquals(0, mc.schemas.dependencies.size)
assertEquals(0, mc.schemas.dependsOn.size)
assertEquals(3, mc.objects.size)
assert(aName in mc.objects)
assert(iName in mc.objects)
assert(iiiName in mc.objects)
}
}

View File

@ -0,0 +1,96 @@
package net.corda.core.serialization.carpenter
import net.corda.core.serialization.carpenter.test.AmqpCarpenterBase
import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.amqp.*
import org.junit.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotEquals
class MultiMemberCompositeSchemaToClassCarpenterTests : AmqpCarpenterBase() {
@Test
fun twoInts() {
@CordaSerializable
data class A(val a: Int, val b: Int)
val testA = 10
val testB = 20
val a = A(testA, testB)
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a))
assert(obj.obj is A)
val amqpObj = obj.obj as A
assertEquals(testA, amqpObj.a)
assertEquals(testB, amqpObj.b)
assertEquals(1, obj.envelope.schema.types.size)
assert(obj.envelope.schema.types[0] is CompositeType)
val amqpSchema = obj.envelope.schema.types[0] as CompositeType
assertEquals(2, amqpSchema.fields.size)
assertEquals("a", amqpSchema.fields[0].name)
assertEquals("int", amqpSchema.fields[0].type)
assertEquals("b", amqpSchema.fields[1].name)
assertEquals("int", amqpSchema.fields[1].type)
val carpenterSchema = CarpenterSchemas.newInstance()
amqpSchema.carpenterSchema(carpenterSchemas = carpenterSchema, force = true)
assertEquals(1, carpenterSchema.size)
val aSchema = carpenterSchema.carpenterSchemas.find { it.name == classTestName("A") }
assertNotEquals(null, aSchema)
val pinochio = ClassCarpenter().build(aSchema!!)
val p = pinochio.constructors[0].newInstance(testA, testB)
assertEquals(pinochio.getMethod("getA").invoke(p), amqpObj.a)
assertEquals(pinochio.getMethod("getB").invoke(p), amqpObj.b)
}
@Test
fun intAndStr() {
@CordaSerializable
data class A(val a: Int, val b: String)
val testA = 10
val testB = "twenty"
val a = A(testA, testB)
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a))
assert(obj.obj is A)
val amqpObj = obj.obj as A
assertEquals(testA, amqpObj.a)
assertEquals(testB, amqpObj.b)
assertEquals(1, obj.envelope.schema.types.size)
assert(obj.envelope.schema.types[0] is CompositeType)
val amqpSchema = obj.envelope.schema.types[0] as CompositeType
assertEquals(2, amqpSchema.fields.size)
assertEquals("a", amqpSchema.fields[0].name)
assertEquals("int", amqpSchema.fields[0].type)
assertEquals("b", amqpSchema.fields[1].name)
assertEquals("string", amqpSchema.fields[1].type)
val carpenterSchema = CarpenterSchemas.newInstance()
amqpSchema.carpenterSchema(carpenterSchemas = carpenterSchema, force = true)
assertEquals(1, carpenterSchema.size)
val aSchema = carpenterSchema.carpenterSchemas.find { it.name == classTestName("A") }
assertNotEquals(null, aSchema)
val pinochio = ClassCarpenter().build(aSchema!!)
val p = pinochio.constructors[0].newInstance(testA, testB)
assertEquals(pinochio.getMethod("getA").invoke(p), amqpObj.a)
assertEquals(pinochio.getMethod("getB").invoke(p), amqpObj.b)
}
}

View File

@ -0,0 +1,198 @@
package net.corda.core.serialization.carpenter
import net.corda.core.serialization.carpenter.test.AmqpCarpenterBase
import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.amqp.*
import org.junit.Test
import kotlin.test.assertEquals
class SingleMemberCompositeSchemaToClassCarpenterTests : AmqpCarpenterBase() {
@Test
fun singleInteger() {
@CordaSerializable
data class A(val a: Int)
val test = 10
val a = A(test)
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a))
assert(obj.obj is A)
val amqpObj = obj.obj as A
assertEquals(test, amqpObj.a)
assertEquals(1, obj.envelope.schema.types.size)
assert(obj.envelope.schema.types[0] is CompositeType)
val amqpSchema = obj.envelope.schema.types[0] as CompositeType
assertEquals(1, amqpSchema.fields.size)
assertEquals("a", amqpSchema.fields[0].name)
assertEquals("int", amqpSchema.fields[0].type)
val carpenterSchema = CarpenterSchemas.newInstance()
amqpSchema.carpenterSchema(carpenterSchemas = carpenterSchema, force = true)
val aSchema = carpenterSchema.carpenterSchemas.find { it.name == classTestName("A") }!!
val aBuilder = ClassCarpenter().build(aSchema)
val p = aBuilder.constructors[0].newInstance(test)
assertEquals(aBuilder.getMethod("getA").invoke(p), amqpObj.a)
}
@Test
fun singleString() {
@CordaSerializable
data class A(val a: String)
val test = "ten"
val a = A(test)
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a))
assert(obj.obj is A)
val amqpObj = obj.obj as A
assertEquals(test, amqpObj.a)
assertEquals(1, obj.envelope.schema.types.size)
assert(obj.envelope.schema.types[0] is CompositeType)
val amqpSchema = obj.envelope.schema.types[0] as CompositeType
val carpenterSchema = CarpenterSchemas.newInstance()
amqpSchema.carpenterSchema(carpenterSchemas = carpenterSchema, force = true)
val aSchema = carpenterSchema.carpenterSchemas.find { it.name == classTestName("A") }!!
val aBuilder = ClassCarpenter().build(aSchema)
val p = aBuilder.constructors[0].newInstance(test)
assertEquals(aBuilder.getMethod("getA").invoke(p), amqpObj.a)
}
@Test
fun singleLong() {
@CordaSerializable
data class A(val a: Long)
val test = 10L
val a = A(test)
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a))
assert(obj.obj is A)
val amqpObj = obj.obj as A
assertEquals(test, amqpObj.a)
assertEquals(1, obj.envelope.schema.types.size)
assert(obj.envelope.schema.types[0] is CompositeType)
val amqpSchema = obj.envelope.schema.types[0] as CompositeType
assertEquals(1, amqpSchema.fields.size)
assertEquals("a", amqpSchema.fields[0].name)
assertEquals("long", amqpSchema.fields[0].type)
val carpenterSchema = CarpenterSchemas.newInstance()
amqpSchema.carpenterSchema(carpenterSchemas = carpenterSchema, force = true)
val aSchema = carpenterSchema.carpenterSchemas.find { it.name == classTestName("A") }!!
val aBuilder = ClassCarpenter().build(aSchema)
val p = aBuilder.constructors[0].newInstance(test)
assertEquals(aBuilder.getMethod("getA").invoke(p), amqpObj.a)
}
@Test
fun singleShort() {
@CordaSerializable
data class A(val a: Short)
val test = 10.toShort()
val a = A(test)
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a))
assert(obj.obj is A)
val amqpObj = obj.obj as A
assertEquals(test, amqpObj.a)
assertEquals(1, obj.envelope.schema.types.size)
assert(obj.envelope.schema.types[0] is CompositeType)
val amqpSchema = obj.envelope.schema.types[0] as CompositeType
assertEquals(1, amqpSchema.fields.size)
assertEquals("a", amqpSchema.fields[0].name)
assertEquals("short", amqpSchema.fields[0].type)
val carpenterSchema = CarpenterSchemas.newInstance()
amqpSchema.carpenterSchema(carpenterSchemas = carpenterSchema, force = true)
val aSchema = carpenterSchema.carpenterSchemas.find { it.name == classTestName("A") }!!
val aBuilder = ClassCarpenter().build(aSchema)
val p = aBuilder.constructors[0].newInstance(test)
assertEquals(aBuilder.getMethod("getA").invoke(p), amqpObj.a)
}
@Test
fun singleDouble() {
@CordaSerializable
data class A(val a: Double)
val test = 10.0
val a = A(test)
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a))
assert(obj.obj is A)
val amqpObj = obj.obj as A
assertEquals(test, amqpObj.a)
assertEquals(1, obj.envelope.schema.types.size)
assert(obj.envelope.schema.types[0] is CompositeType)
val amqpSchema = obj.envelope.schema.types[0] as CompositeType
assertEquals(1, amqpSchema.fields.size)
assertEquals("a", amqpSchema.fields[0].name)
assertEquals("double", amqpSchema.fields[0].type)
val carpenterSchema = CarpenterSchemas.newInstance()
amqpSchema.carpenterSchema(carpenterSchemas = carpenterSchema, force = true)
val aSchema = carpenterSchema.carpenterSchemas.find { it.name == classTestName("A") }!!
val aBuilder = ClassCarpenter().build(aSchema)
val p = aBuilder.constructors[0].newInstance(test)
assertEquals(aBuilder.getMethod("getA").invoke(p), amqpObj.a)
}
@Test
fun singleFloat() {
@CordaSerializable
data class A(val a: Float)
val test: Float = 10.0F
val a = A(test)
val obj = DeserializationInput(factory).deserializeAndReturnEnvelope(serialise(a))
assert(obj.obj is A)
val amqpObj = obj.obj as A
assertEquals(test, amqpObj.a)
assertEquals(1, obj.envelope.schema.types.size)
assert(obj.envelope.schema.types[0] is CompositeType)
val amqpSchema = obj.envelope.schema.types[0] as CompositeType
assertEquals(1, amqpSchema.fields.size)
assertEquals("a", amqpSchema.fields[0].name)
assertEquals("float", amqpSchema.fields[0].type)
val carpenterSchema = CarpenterSchemas.newInstance()
amqpSchema.carpenterSchema(carpenterSchemas = carpenterSchema, force = true)
val aSchema = carpenterSchema.carpenterSchemas.find { it.name == classTestName("A") }!!
val aBuilder = ClassCarpenter().build(aSchema)
val p = aBuilder.constructors[0].newInstance(test)
assertEquals(aBuilder.getMethod("getA").invoke(p), amqpObj.a)
}
}