mirror of
https://github.com/corda/corda.git
synced 2025-06-16 22:28:15 +00:00
Unit Tests for the amqp -> carpenter schema
Squahed commit mesages: * Better tests * WIP * WIP
This commit is contained in:
@ -15,6 +15,8 @@ import java.lang.reflect.Type
|
||||
import java.lang.reflect.TypeVariable
|
||||
import java.util.*
|
||||
|
||||
import net.corda.core.serialization.ClassCarpenterSchema
|
||||
|
||||
// TODO: get an assigned number as per AMQP spec
|
||||
val DESCRIPTOR_TOP_32BITS: Long = 0xc0da0000
|
||||
|
||||
@ -87,9 +89,37 @@ data class Schema(val types: List<TypeNotation>) : DescribedType {
|
||||
override fun getDescribed(): Any = listOf(types)
|
||||
|
||||
override fun toString(): String = types.joinToString("\n")
|
||||
|
||||
fun carpenterSchema(loaders : List<ClassLoader> = listOf<ClassLoader>(ClassLoader.getSystemClassLoader()))
|
||||
: List<ClassCarpenterSchema>
|
||||
{
|
||||
var rtn = mutableListOf<ClassCarpenterSchema>()
|
||||
|
||||
for (type in types) {
|
||||
if (type is CompositeType) {
|
||||
var foundIt = false
|
||||
for (loader in loaders) {
|
||||
try {
|
||||
loader.loadClass(type.name)
|
||||
foundIt = true
|
||||
break
|
||||
} catch (e: ClassNotFoundException) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if (foundIt) continue
|
||||
else {
|
||||
rtn.add(type.carpenterSchema())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return rtn
|
||||
}
|
||||
}
|
||||
|
||||
data class Descriptor(val name: String?, val code: UnsignedLong? = null) : DescribedType {
|
||||
data class Descriptor(var name: String?, val code: UnsignedLong? = null) : DescribedType {
|
||||
companion object : DescribedTypeConstructor<Descriptor> {
|
||||
val DESCRIPTOR = UnsignedLong(3L or DESCRIPTOR_TOP_32BITS)
|
||||
|
||||
@ -127,7 +157,7 @@ data class Descriptor(val name: String?, val code: UnsignedLong? = null) : Descr
|
||||
}
|
||||
}
|
||||
|
||||
data class Field(val name: String, val type: String, val requires: List<String>, val default: String?, val label: String?, val mandatory: Boolean, val multiple: Boolean) : DescribedType {
|
||||
data class Field(var name: String, val type: String, val requires: List<String>, val default: String?, val label: String?, val mandatory: Boolean, val multiple: Boolean) : DescribedType {
|
||||
companion object : DescribedTypeConstructor<Field> {
|
||||
val DESCRIPTOR = UnsignedLong(4L or DESCRIPTOR_TOP_32BITS)
|
||||
|
||||
@ -169,6 +199,16 @@ data class Field(val name: String, val type: String, val requires: List<String>,
|
||||
return sb.toString()
|
||||
}
|
||||
|
||||
inline fun isKnownClass (type : String) : Class<*> {
|
||||
try {
|
||||
return ClassLoader.getSystemClassLoader().loadClass(type)
|
||||
}
|
||||
catch (e: ClassNotFoundException) {
|
||||
// call carpenter
|
||||
throw IllegalArgumentException ("${type} - ${name} - pants")
|
||||
}
|
||||
}
|
||||
|
||||
fun getPrimType() = when (type) {
|
||||
"int" -> Int::class.javaPrimitiveType!!
|
||||
"string" -> String::class.java
|
||||
@ -178,14 +218,8 @@ data class Field(val name: String, val type: String, val requires: List<String>,
|
||||
"boolean" -> Boolean::class.javaPrimitiveType!!
|
||||
"double" -> Double::class.javaPrimitiveType!!
|
||||
"float" -> Double::class.javaPrimitiveType!!
|
||||
else -> {
|
||||
try {
|
||||
ClassLoader.getSystemClassLoader().loadClass(type)
|
||||
}
|
||||
catch (e: ClassNotFoundException) {
|
||||
throw IllegalArgumentException ("pants")
|
||||
}
|
||||
}
|
||||
"*" -> isKnownClass(requires[0])
|
||||
else -> isKnownClass(type)
|
||||
}
|
||||
}
|
||||
|
||||
@ -203,13 +237,13 @@ sealed class TypeNotation : DescribedType {
|
||||
}
|
||||
}
|
||||
|
||||
abstract val name: String
|
||||
abstract var name: String
|
||||
abstract val label: String?
|
||||
abstract val provides: List<String>
|
||||
abstract val descriptor: Descriptor
|
||||
}
|
||||
|
||||
data class CompositeType(override val name: String, override val label: String?, override val provides: List<String>, override val descriptor: Descriptor, val fields: List<Field>) : TypeNotation() {
|
||||
data class CompositeType(override var name: String, override val label: String?, override val provides: List<String>, override val descriptor: Descriptor, val fields: List<Field>) : TypeNotation() {
|
||||
companion object : DescribedTypeConstructor<CompositeType> {
|
||||
val DESCRIPTOR = UnsignedLong(5L or DESCRIPTOR_TOP_32BITS)
|
||||
|
||||
@ -253,16 +287,36 @@ data class CompositeType(override val name: String, override val label: String?,
|
||||
return sb.toString()
|
||||
}
|
||||
|
||||
fun carpenterSchema() : Map<String, Class<out Any?>> {
|
||||
fun carpenterSchema(classLoaders: List<ClassLoader> = listOf<ClassLoader> (ClassLoader.getSystemClassLoader())) : ClassCarpenterSchema {
|
||||
var m : MutableMap<String, Class<out Any?>> = mutableMapOf()
|
||||
|
||||
fields.forEach { m[it.name] = it.getPrimType() }
|
||||
|
||||
return m
|
||||
var providesList = mutableListOf<Class<*>>()
|
||||
|
||||
for (iface in provides) {
|
||||
var found = false
|
||||
for (loader in classLoaders) {
|
||||
try {
|
||||
providesList.add (loader.loadClass(iface))
|
||||
found = true
|
||||
break
|
||||
}
|
||||
catch (e: ClassNotFoundException) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if (found == false) {
|
||||
println ("This needs to work but it wont - ${iface}")
|
||||
}
|
||||
else println ("found it ${iface}")
|
||||
}
|
||||
|
||||
return ClassCarpenterSchema (name, m, interfaces = providesList)
|
||||
}
|
||||
}
|
||||
|
||||
data class RestrictedType(override val name: String, override val label: String?, override val provides: List<String>, val source: String, override val descriptor: Descriptor, val choices: List<Choice>) : TypeNotation() {
|
||||
data class RestrictedType(override var name: String, override val label: String?, override val provides: List<String>, val source: String, override val descriptor: Descriptor, val choices: List<Choice>) : TypeNotation() {
|
||||
companion object : DescribedTypeConstructor<RestrictedType> {
|
||||
val DESCRIPTOR = UnsignedLong(6L or DESCRIPTOR_TOP_32BITS)
|
||||
|
||||
@ -305,7 +359,7 @@ data class RestrictedType(override val name: String, override val label: String?
|
||||
}
|
||||
}
|
||||
|
||||
data class Choice(val name: String, val value: String) : DescribedType {
|
||||
data class Choice(var name: String, val value: String) : DescribedType {
|
||||
companion object : DescribedTypeConstructor<Choice> {
|
||||
val DESCRIPTOR = UnsignedLong(7L or DESCRIPTOR_TOP_32BITS)
|
||||
|
||||
|
@ -159,58 +159,18 @@ class ClassCarpenter {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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()
|
||||
|
||||
fun classLoader() = classloader as ClassLoader
|
||||
|
||||
private val _loaded = HashMap<String, Class<*>>()
|
||||
|
||||
/** 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)
|
||||
fun loaded() : Map<String, Class<*>> = HashMap(_loaded)
|
||||
|
||||
/**
|
||||
* Generate bytecode for the given schema and load into the JVM. The returned class object can be used to
|
||||
@ -218,18 +178,17 @@ class ClassCarpenter {
|
||||
*
|
||||
* @throws DuplicateNameException if the schema's name is already taken in this namespace (you can create a new ClassCarpenter if you're OK with ambiguous names)
|
||||
*/
|
||||
fun build(schema: Schema): Class<*> {
|
||||
fun build(schema: ClassCarpenterSchema): Class<*> {
|
||||
validateSchema(schema)
|
||||
// Walk up the inheritance hierarchy and then start walking back down once we either hit the top, or
|
||||
// find a class we haven't generated yet.
|
||||
val hierarchy = ArrayList<Schema>()
|
||||
val hierarchy = ArrayList<ClassCarpenterSchema>()
|
||||
hierarchy += schema
|
||||
var cursor = schema.superclass
|
||||
while (cursor != null && cursor.name !in _loaded) {
|
||||
hierarchy += cursor
|
||||
cursor = cursor.superclass
|
||||
}
|
||||
|
||||
hierarchy.reversed().forEach {
|
||||
when (it) {
|
||||
is InterfaceSchema -> generateInterface(it)
|
||||
@ -346,6 +305,7 @@ class ClassCarpenter {
|
||||
visitEnd()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun ClassWriter.generateAbstractGetters(schema: Schema) {
|
||||
@ -374,14 +334,16 @@ class ClassCarpenter {
|
||||
// Calculate the super call.
|
||||
val superclassFields = schema.superclass?.fieldsIncludingSuperclasses() ?: emptyMap()
|
||||
visitVarInsn(ALOAD, 0)
|
||||
if (schema.superclass == null) {
|
||||
val sc = schema.superclass
|
||||
if (sc == null) {
|
||||
visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<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)
|
||||
//val superDesc = schema.superclass.descriptorsIncludingSuperclasses().values.joinToString("")
|
||||
val superDesc = sc.descriptorsIncludingSuperclasses().values.joinToString("")
|
||||
visitMethodInsn(INVOKESPECIAL, sc.name.jvm, "<init>", "($superDesc)V", false)
|
||||
}
|
||||
|
||||
// Assign the fields from parameters.
|
||||
|
@ -0,0 +1,44 @@
|
||||
package net.corda.core.serialization.carpenter
|
||||
|
||||
import org.objectweb.asm.Type
|
||||
import java.util.LinkedHashMap
|
||||
|
||||
/**
|
||||
* A Schema represents a desired class.
|
||||
*/
|
||||
abstract class Schema(
|
||||
val name: String,
|
||||
fields: Map<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(".", "/")
|
||||
}
|
||||
|
||||
class ClassSchema(
|
||||
name: String,
|
||||
fields: Map<String, Class<out Any?>>,
|
||||
superclass: Schema? = null,
|
||||
interfaces: List<Class<*>> = emptyList()
|
||||
) : ClassCarpenter.Schema (name, fields, superclass, interfaces)
|
||||
|
||||
class InterfaceSchema(
|
||||
name: String,
|
||||
fields: Map<String, Class<out Any?>>,
|
||||
superclass: Schema? = null,
|
||||
interfaces: List<Class<*>> = emptyList()
|
||||
) : ClassCarpenter.Schema (name, fields, superclass, interfaces)
|
@ -8,11 +8,31 @@ import java.beans.Introspector
|
||||
import kotlin.test.assertNotEquals
|
||||
|
||||
class ClassCarpenterTest {
|
||||
/*
|
||||
cw.visitInnerClass(
|
||||
"net/corda/carpenter/ClassCarpenterTest$DummyInterface",
|
||||
"net/corda/carpenter/ClassCarpenterTest",
|
||||
"DummyInterface",
|
||||
ACC_PUBLIC + ACC_STATIC + ACC_ABSTRACT + ACC_INTERFACE);
|
||||
*/
|
||||
interface DummyInterface {
|
||||
val a: String
|
||||
val b: Int
|
||||
}
|
||||
|
||||
/*
|
||||
cw.visitInnerClass(
|
||||
"net/corda/carpenter/ClassCarpenterTest$Dummy",
|
||||
"net/corda/carpenter/ClassCarpenterTest",
|
||||
"Dummy",
|
||||
ACC_PUBLIC + ACC_FINAL + ACC_STATIC);
|
||||
*/
|
||||
class Dummy (override val a: String, override val b: Int) : DummyInterface
|
||||
|
||||
val dummy = Dummy ("hi", 1)
|
||||
val dummy2 = Dummy2 ("hi", 1)
|
||||
|
||||
|
||||
val cc = ClassCarpenter()
|
||||
|
||||
// We have to ignore synthetic fields even though ClassCarpenter doesn't create any because the JaCoCo
|
||||
@ -22,7 +42,7 @@ class ClassCarpenterTest {
|
||||
|
||||
@Test
|
||||
fun empty() {
|
||||
val clazz = cc.build(ClassCarpenter.ClassSchema("gen.EmptyClass", emptyMap(), null))
|
||||
val clazz = cc.build(ClassSchema("gen.EmptyClass", emptyMap(), null))
|
||||
assertEquals(0, clazz.nonSyntheticFields.size)
|
||||
assertEquals(2, clazz.nonSyntheticMethods.size) // get, toString
|
||||
assertEquals(0, clazz.declaredConstructors[0].parameterCount)
|
||||
@ -69,7 +89,7 @@ class ClassCarpenterTest {
|
||||
}
|
||||
|
||||
private fun genPerson(): Pair<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) } ))
|
||||
@ -92,8 +112,8 @@ class ClassCarpenterTest {
|
||||
|
||||
@Test(expected = ClassCarpenter.DuplicateNameException::class)
|
||||
fun duplicates() {
|
||||
cc.build(ClassCarpenter.ClassSchema("gen.EmptyClass", emptyMap(), null))
|
||||
cc.build(ClassCarpenter.ClassSchema("gen.EmptyClass", emptyMap(), null))
|
||||
cc.build(ClassSchema("gen.EmptyClass", emptyMap(), null))
|
||||
cc.build(ClassSchema("gen.EmptyClass", emptyMap(), null))
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -134,6 +154,7 @@ class ClassCarpenterTest {
|
||||
mapOf("b" to ClassCarpenter.NonNullableField(Int::class.java)),
|
||||
schema1,
|
||||
interfaces = listOf(DummyInterface::class.java))
|
||||
|
||||
val clazz = cc.build(schema2)
|
||||
val i = clazz.constructors[0].newInstance("xa", 1) as DummyInterface
|
||||
assertEquals("xa", i.a)
|
||||
|
Reference in New Issue
Block a user