Unit Tests for the amqp -> carpenter schema

Squahed commit mesages:
	* Better tests
	* WIP
	* WIP
This commit is contained in:
Katelyn Baker
2017-06-16 09:05:47 +01:00
parent 4cbf31681b
commit ce172887d0
9 changed files with 560 additions and 348 deletions

View File

@ -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)

View File

@ -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.

View File

@ -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)

View File

@ -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)