mirror of
https://github.com/corda/corda.git
synced 2025-01-30 08:04:16 +00:00
Merge pull request #942 from corda/kat-carpentty
Add array support to the class carpenter
This commit is contained in:
commit
5b78863e57
@ -1,10 +1,13 @@
|
|||||||
package net.corda.carpenter
|
package net.corda.core.serialization.carpenter
|
||||||
|
|
||||||
import org.objectweb.asm.ClassWriter
|
import org.objectweb.asm.ClassWriter
|
||||||
import org.objectweb.asm.MethodVisitor
|
import org.objectweb.asm.MethodVisitor
|
||||||
import org.objectweb.asm.Opcodes.*
|
import org.objectweb.asm.Opcodes.*
|
||||||
import org.objectweb.asm.Type
|
import org.objectweb.asm.Type
|
||||||
import java.lang.Character.*
|
|
||||||
|
import java.lang.Character.isJavaIdentifierPart
|
||||||
|
import java.lang.Character.isJavaIdentifierStart
|
||||||
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -16,6 +19,7 @@ interface SimpleFieldAccess {
|
|||||||
operator fun get(name: String): Any?
|
operator fun get(name: String): Any?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A class carpenter generates JVM bytecodes for a class given a schema and then loads it into a sub-classloader.
|
* A class carpenter generates JVM bytecodes for a class given a schema and then loads it into a sub-classloader.
|
||||||
* The generated classes have getters, a toString method and implement a simple property access interface. The
|
* The generated classes have getters, a toString method and implement a simple property access interface. The
|
||||||
@ -61,22 +65,122 @@ interface SimpleFieldAccess {
|
|||||||
* Equals/hashCode methods are not yet supported.
|
* Equals/hashCode methods are not yet supported.
|
||||||
*/
|
*/
|
||||||
class ClassCarpenter {
|
class ClassCarpenter {
|
||||||
// TODO: Array types.
|
|
||||||
// TODO: Generics.
|
// TODO: Generics.
|
||||||
// TODO: Sandbox the generated code when a security manager is in use.
|
// TODO: Sandbox the generated code when a security manager is in use.
|
||||||
// TODO: Generate equals/hashCode.
|
// TODO: Generate equals/hashCode.
|
||||||
// TODO: Support annotations.
|
// TODO: Support annotations.
|
||||||
// TODO: isFoo getter patterns for booleans (this is what Kotlin generates)
|
// 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) {
|
||||||
|
println ("generateField $name $nullabilityAnnotation")
|
||||||
|
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.
|
* A Schema represents a desired class.
|
||||||
*/
|
*/
|
||||||
open class Schema(val name: String, fields: Map<String, Class<out Any?>>, val superclass: Schema? = null, val interfaces: List<Class<*>> = emptyList()) {
|
abstract class Schema(
|
||||||
val fields = LinkedHashMap(fields) // Fix the order up front if the user didn't.
|
val name: String,
|
||||||
val descriptors = fields.map { it.key to Type.getDescriptor(it.value) }.toMap()
|
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 })
|
||||||
|
|
||||||
fun fieldsIncludingSuperclasses(): Map<String, Class<out Any?>> = (superclass?.fieldsIncludingSuperclasses() ?: emptyMap()) + LinkedHashMap(fields)
|
/* Fix the order up front if the user didn't, inject the name into the field as it's
|
||||||
fun descriptorsIncludingSuperclasses(): Map<String, String> = (superclass?.descriptorsIncludingSuperclasses() ?: emptyMap()) + LinkedHashMap(descriptors)
|
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
|
val jvmName: String
|
||||||
get() = name.replace(".", "/")
|
get() = name.replace(".", "/")
|
||||||
@ -86,21 +190,18 @@ class ClassCarpenter {
|
|||||||
|
|
||||||
class ClassSchema(
|
class ClassSchema(
|
||||||
name: String,
|
name: String,
|
||||||
fields: Map<String, Class<out Any?>>,
|
fields: Map<String, Field>,
|
||||||
superclass: Schema? = null,
|
superclass: Schema? = null,
|
||||||
interfaces: List<Class<*>> = emptyList()
|
interfaces: List<Class<*>> = emptyList()
|
||||||
) : Schema(name, fields, superclass, interfaces)
|
) : Schema(name, fields, superclass, interfaces)
|
||||||
|
|
||||||
class InterfaceSchema(
|
class InterfaceSchema(
|
||||||
name: String,
|
name: String,
|
||||||
fields: Map<String, Class<out Any?>>,
|
fields: Map<String, Field>,
|
||||||
superclass: Schema? = null,
|
superclass: Schema? = null,
|
||||||
interfaces: List<Class<*>> = emptyList()
|
interfaces: List<Class<*>> = emptyList()
|
||||||
) : Schema(name, fields, superclass, interfaces)
|
) : Schema(name, fields, superclass, interfaces)
|
||||||
|
|
||||||
class DuplicateName : RuntimeException("An attempt was made to register two classes with the same name within the same ClassCarpenter namespace.")
|
|
||||||
class InterfaceMismatch(msg: String) : RuntimeException(msg)
|
|
||||||
|
|
||||||
private class CarpenterClassLoader : ClassLoader(Thread.currentThread().contextClassLoader) {
|
private class CarpenterClassLoader : ClassLoader(Thread.currentThread().contextClassLoader) {
|
||||||
fun load(name: String, bytes: ByteArray) = defineClass(name, bytes, 0, bytes.size)
|
fun load(name: String, bytes: ByteArray) = defineClass(name, bytes, 0, bytes.size)
|
||||||
}
|
}
|
||||||
@ -140,8 +241,8 @@ class ClassCarpenter {
|
|||||||
return _loaded[schema.name]!!
|
return _loaded[schema.name]!!
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun generateInterface(schema: Schema): Class<*> {
|
private fun generateInterface(interfaceSchema: Schema): Class<*> {
|
||||||
return generate(schema) { cw, schema ->
|
return generate(interfaceSchema) { cw, schema ->
|
||||||
val interfaces = schema.interfaces.map { it.name.jvm }.toTypedArray()
|
val interfaces = schema.interfaces.map { it.name.jvm }.toTypedArray()
|
||||||
|
|
||||||
with(cw) {
|
with(cw) {
|
||||||
@ -154,8 +255,8 @@ class ClassCarpenter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun generateClass(schema: Schema): Class<*> {
|
private fun generateClass(classSchema: Schema): Class<*> {
|
||||||
return generate(schema) { cw, schema ->
|
return generate(classSchema) { cw, schema ->
|
||||||
val superName = schema.superclass?.jvmName ?: "java/lang/Object"
|
val superName = schema.superclass?.jvmName ?: "java/lang/Object"
|
||||||
val interfaces = arrayOf(SimpleFieldAccess::class.java.name.jvm) + schema.interfaces.map { it.name.jvm }
|
val interfaces = arrayOf(SimpleFieldAccess::class.java.name.jvm) + schema.interfaces.map { it.name.jvm }
|
||||||
|
|
||||||
@ -186,9 +287,7 @@ class ClassCarpenter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun ClassWriter.generateFields(schema: Schema) {
|
private fun ClassWriter.generateFields(schema: Schema) {
|
||||||
for ((name, desc) in schema.descriptors) {
|
schema.fields.forEach { it.value.generateField(this) }
|
||||||
visitField(ACC_PROTECTED + ACC_FINAL, name, desc, null, null).visitEnd()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun ClassWriter.generateToString(schema: Schema) {
|
private fun ClassWriter.generateToString(schema: Schema) {
|
||||||
@ -199,12 +298,11 @@ class ClassCarpenter {
|
|||||||
visitLdcInsn(schema.name.split('.').last())
|
visitLdcInsn(schema.name.split('.').last())
|
||||||
visitMethodInsn(INVOKESTATIC, "com/google/common/base/MoreObjects", "toStringHelper", "(Ljava/lang/String;)L$toStringHelper;", false)
|
visitMethodInsn(INVOKESTATIC, "com/google/common/base/MoreObjects", "toStringHelper", "(Ljava/lang/String;)L$toStringHelper;", false)
|
||||||
// Call the add() methods.
|
// Call the add() methods.
|
||||||
for ((name, type) in schema.fieldsIncludingSuperclasses().entries) {
|
for ((name, field) in schema.fieldsIncludingSuperclasses().entries) {
|
||||||
visitLdcInsn(name)
|
visitLdcInsn(name)
|
||||||
visitVarInsn(ALOAD, 0) // this
|
visitVarInsn(ALOAD, 0) // this
|
||||||
visitFieldInsn(GETFIELD, schema.jvmName, name, schema.descriptorsIncludingSuperclasses()[name])
|
visitFieldInsn(GETFIELD, schema.jvmName, name, schema.descriptorsIncludingSuperclasses()[name])
|
||||||
val desc = if (type.isPrimitive) schema.descriptors[name] else "Ljava/lang/Object;"
|
visitMethodInsn(INVOKEVIRTUAL, toStringHelper, "add", "(Ljava/lang/String;${field.type})L$toStringHelper;", false)
|
||||||
visitMethodInsn(INVOKEVIRTUAL, toStringHelper, "add", "(Ljava/lang/String;$desc)L$toStringHelper;", false)
|
|
||||||
}
|
}
|
||||||
// call toString() on the builder and return.
|
// call toString() on the builder and return.
|
||||||
visitMethodInsn(INVOKEVIRTUAL, toStringHelper, "toString", "()Ljava/lang/String;", false)
|
visitMethodInsn(INVOKEVIRTUAL, toStringHelper, "toString", "()Ljava/lang/String;", false)
|
||||||
@ -232,13 +330,14 @@ class ClassCarpenter {
|
|||||||
|
|
||||||
private fun ClassWriter.generateGetters(schema: Schema) {
|
private fun ClassWriter.generateGetters(schema: Schema) {
|
||||||
for ((name, type) in schema.fields) {
|
for ((name, type) in schema.fields) {
|
||||||
val descriptor = schema.descriptors[name]
|
with(visitMethod(ACC_PUBLIC, "get" + name.capitalize(), "()" + type.descriptor, null, null)) {
|
||||||
with(visitMethod(ACC_PUBLIC, "get" + name.capitalize(), "()" + descriptor, null, null)) {
|
type.addNullabilityAnnotation(this)
|
||||||
visitCode()
|
visitCode()
|
||||||
visitVarInsn(ALOAD, 0) // Load 'this'
|
visitVarInsn(ALOAD, 0) // Load 'this'
|
||||||
visitFieldInsn(GETFIELD, schema.jvmName, name, descriptor)
|
visitFieldInsn(GETFIELD, schema.jvmName, name, type.descriptor)
|
||||||
when (type) {
|
when (type.field) {
|
||||||
java.lang.Boolean.TYPE, Integer.TYPE, java.lang.Short.TYPE, java.lang.Byte.TYPE, TYPE -> visitInsn(IRETURN)
|
java.lang.Boolean.TYPE, Integer.TYPE, java.lang.Short.TYPE, java.lang.Byte.TYPE,
|
||||||
|
java.lang.Character.TYPE -> visitInsn(IRETURN)
|
||||||
java.lang.Long.TYPE -> visitInsn(LRETURN)
|
java.lang.Long.TYPE -> visitInsn(LRETURN)
|
||||||
java.lang.Double.TYPE -> visitInsn(DRETURN)
|
java.lang.Double.TYPE -> visitInsn(DRETURN)
|
||||||
java.lang.Float.TYPE -> visitInsn(FRETURN)
|
java.lang.Float.TYPE -> visitInsn(FRETURN)
|
||||||
@ -251,10 +350,9 @@ class ClassCarpenter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun ClassWriter.generateAbstractGetters(schema: Schema) {
|
private fun ClassWriter.generateAbstractGetters(schema: Schema) {
|
||||||
for ((name, _) in schema.fields) {
|
for ((name, field) in schema.fields) {
|
||||||
val descriptor = schema.descriptors[name]
|
|
||||||
val opcodes = ACC_ABSTRACT + ACC_PUBLIC
|
val opcodes = ACC_ABSTRACT + ACC_PUBLIC
|
||||||
with(visitMethod(opcodes, "get" + name.capitalize(), "()" + descriptor, null, null)) {
|
with(visitMethod(opcodes, "get" + name.capitalize(), "()${field.descriptor}", null, null)) {
|
||||||
// abstract method doesn't have any implementation so just end
|
// abstract method doesn't have any implementation so just end
|
||||||
visitEnd()
|
visitEnd()
|
||||||
}
|
}
|
||||||
@ -262,8 +360,18 @@ class ClassCarpenter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun ClassWriter.generateConstructor(schema: Schema) {
|
private fun ClassWriter.generateConstructor(schema: Schema) {
|
||||||
with(visitMethod(ACC_PUBLIC, "<init>", "(" + schema.descriptorsIncludingSuperclasses().values.joinToString("") + ")V", null, null)) {
|
with(visitMethod(
|
||||||
|
ACC_PUBLIC,
|
||||||
|
"<init>",
|
||||||
|
"(" + schema.descriptorsIncludingSuperclasses().values.joinToString("") + ")V",
|
||||||
|
null,
|
||||||
|
null))
|
||||||
|
{
|
||||||
|
var idx = 0
|
||||||
|
schema.fields.values.forEach { it.visitParameter(this, idx++) }
|
||||||
|
|
||||||
visitCode()
|
visitCode()
|
||||||
|
|
||||||
// Calculate the super call.
|
// Calculate the super call.
|
||||||
val superclassFields = schema.superclass?.fieldsIncludingSuperclasses() ?: emptyMap()
|
val superclassFields = schema.superclass?.fieldsIncludingSuperclasses() ?: emptyMap()
|
||||||
visitVarInsn(ALOAD, 0)
|
visitVarInsn(ALOAD, 0)
|
||||||
@ -276,14 +384,15 @@ class ClassCarpenter {
|
|||||||
val superDesc = schema.superclass.descriptorsIncludingSuperclasses().values.joinToString("")
|
val superDesc = schema.superclass.descriptorsIncludingSuperclasses().values.joinToString("")
|
||||||
visitMethodInsn(INVOKESPECIAL, schema.superclass.name.jvm, "<init>", "($superDesc)V", false)
|
visitMethodInsn(INVOKESPECIAL, schema.superclass.name.jvm, "<init>", "($superDesc)V", false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Assign the fields from parameters.
|
// Assign the fields from parameters.
|
||||||
var slot = 1 + superclassFields.size
|
var slot = 1 + superclassFields.size
|
||||||
for ((name, type) in schema.fields.entries) {
|
for ((name, field) in schema.fields.entries) {
|
||||||
if (type.isArray)
|
field.nullTest(this, slot)
|
||||||
throw UnsupportedOperationException("Array types are not implemented yet")
|
|
||||||
visitVarInsn(ALOAD, 0) // Load 'this' onto the stack
|
visitVarInsn(ALOAD, 0) // Load 'this' onto the stack
|
||||||
slot += load(slot, type) // Load the contents of the parameter onto the stack.
|
slot += load(slot, field) // Load the contents of the parameter onto the stack.
|
||||||
visitFieldInsn(PUTFIELD, schema.jvmName, name, schema.descriptors[name])
|
visitFieldInsn(PUTFIELD, schema.jvmName, name, field.descriptor)
|
||||||
}
|
}
|
||||||
visitInsn(RETURN)
|
visitInsn(RETURN)
|
||||||
visitMaxs(0, 0)
|
visitMaxs(0, 0)
|
||||||
@ -291,23 +400,23 @@ class ClassCarpenter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns how many slots the given type takes up.
|
private fun MethodVisitor.load(slot: Int, type: Field): Int {
|
||||||
private fun MethodVisitor.load(slot: Int, type: Class<out Any?>): Int {
|
when (type.field) {
|
||||||
when (type) {
|
java.lang.Boolean.TYPE, Integer.TYPE, java.lang.Short.TYPE, java.lang.Byte.TYPE,
|
||||||
java.lang.Boolean.TYPE, Integer.TYPE, java.lang.Short.TYPE, java.lang.Byte.TYPE, TYPE -> visitVarInsn(ILOAD, slot)
|
java.lang.Character.TYPE -> visitVarInsn(ILOAD, slot)
|
||||||
java.lang.Long.TYPE -> visitVarInsn(LLOAD, slot)
|
java.lang.Long.TYPE -> visitVarInsn(LLOAD, slot)
|
||||||
java.lang.Double.TYPE -> visitVarInsn(DLOAD, slot)
|
java.lang.Double.TYPE -> visitVarInsn(DLOAD, slot)
|
||||||
java.lang.Float.TYPE -> visitVarInsn(FLOAD, slot)
|
java.lang.Float.TYPE -> visitVarInsn(FLOAD, slot)
|
||||||
else -> visitVarInsn(ALOAD, slot)
|
else -> visitVarInsn(ALOAD, slot)
|
||||||
}
|
}
|
||||||
return when (type) {
|
return when (type.field) {
|
||||||
java.lang.Long.TYPE, java.lang.Double.TYPE -> 2
|
java.lang.Long.TYPE, java.lang.Double.TYPE -> 2
|
||||||
else -> 1
|
else -> 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun validateSchema(schema: Schema) {
|
private fun validateSchema(schema: Schema) {
|
||||||
if (schema.name in _loaded) throw DuplicateName()
|
if (schema.name in _loaded) throw DuplicateNameException()
|
||||||
fun isJavaName(n: String) = n.isNotBlank() && isJavaIdentifierStart(n.first()) && n.all(::isJavaIdentifierPart)
|
fun isJavaName(n: String) = n.isNotBlank() && isJavaIdentifierStart(n.first()) && n.all(::isJavaIdentifierPart)
|
||||||
require(isJavaName(schema.name.split(".").last())) { "Not a valid Java name: ${schema.name}" }
|
require(isJavaName(schema.name.split(".").last())) { "Not a valid Java name: ${schema.name}" }
|
||||||
schema.fields.keys.forEach { require(isJavaName(it)) { "Not a valid Java name: $it" } }
|
schema.fields.keys.forEach { require(isJavaName(it)) { "Not a valid Java name: $it" } }
|
||||||
@ -319,11 +428,15 @@ class ClassCarpenter {
|
|||||||
itf.methods.forEach {
|
itf.methods.forEach {
|
||||||
val fieldNameFromItf = when {
|
val fieldNameFromItf = when {
|
||||||
it.name.startsWith("get") -> it.name.substring(3).decapitalize()
|
it.name.startsWith("get") -> it.name.substring(3).decapitalize()
|
||||||
else -> throw InterfaceMismatch("Requested interfaces must consist only of methods that start with 'get': ${itf.name}.${it.name}")
|
else -> throw InterfaceMismatchException(
|
||||||
|
"Requested interfaces must consist only of methods that start "
|
||||||
|
+ "with 'get': ${itf.name}.${it.name}")
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((schema is ClassSchema) and (fieldNameFromItf !in allFields))
|
if ((schema is ClassSchema) and (fieldNameFromItf !in allFields))
|
||||||
throw InterfaceMismatch("Interface ${itf.name} requires a field named $fieldNameFromItf but that isn't found in the schema or any superclass schemas")
|
throw InterfaceMismatchException(
|
||||||
|
"Interface ${itf.name} requires a field named $fieldNameFromItf but that "
|
||||||
|
+ "isn't found in the schema or any superclass schemas")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
package net.corda.carpenter
|
package net.corda.core.serialization.carpenter
|
||||||
|
|
||||||
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import java.lang.reflect.Field
|
import java.lang.reflect.Field
|
||||||
import java.lang.reflect.Method
|
import java.lang.reflect.Method
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
|
|
||||||
class ClassCarpenterTest {
|
class ClassCarpenterTest {
|
||||||
@ -30,16 +32,19 @@ class ClassCarpenterTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun prims() {
|
fun prims() {
|
||||||
val clazz = cc.build(ClassCarpenter.ClassSchema("gen.Prims", mapOf(
|
val clazz = cc.build(ClassCarpenter.ClassSchema(
|
||||||
"anIntField" to Int::class.javaPrimitiveType!!,
|
"gen.Prims",
|
||||||
"aLongField" to Long::class.javaPrimitiveType!!,
|
mapOf(
|
||||||
"someCharField" to Char::class.javaPrimitiveType!!,
|
"anIntField" to Int::class.javaPrimitiveType!!,
|
||||||
"aShortField" to Short::class.javaPrimitiveType!!,
|
"aLongField" to Long::class.javaPrimitiveType!!,
|
||||||
"doubleTrouble" to Double::class.javaPrimitiveType!!,
|
"someCharField" to Char::class.javaPrimitiveType!!,
|
||||||
"floatMyBoat" to Float::class.javaPrimitiveType!!,
|
"aShortField" to Short::class.javaPrimitiveType!!,
|
||||||
"byteMe" to Byte::class.javaPrimitiveType!!,
|
"doubleTrouble" to Double::class.javaPrimitiveType!!,
|
||||||
"booleanField" to Boolean::class.javaPrimitiveType!!
|
"floatMyBoat" to Float::class.javaPrimitiveType!!,
|
||||||
)))
|
"byteMe" to Byte::class.javaPrimitiveType!!,
|
||||||
|
"booleanField" to Boolean::class.javaPrimitiveType!!).mapValues {
|
||||||
|
ClassCarpenter.NonNullableField (it.value)
|
||||||
|
}))
|
||||||
assertEquals(8, clazz.nonSyntheticFields.size)
|
assertEquals(8, clazz.nonSyntheticFields.size)
|
||||||
assertEquals(10, clazz.nonSyntheticMethods.size)
|
assertEquals(10, clazz.nonSyntheticMethods.size)
|
||||||
assertEquals(8, clazz.declaredConstructors[0].parameterCount)
|
assertEquals(8, clazz.declaredConstructors[0].parameterCount)
|
||||||
@ -68,7 +73,7 @@ class ClassCarpenterTest {
|
|||||||
val clazz = cc.build(ClassCarpenter.ClassSchema("gen.Person", mapOf(
|
val clazz = cc.build(ClassCarpenter.ClassSchema("gen.Person", mapOf(
|
||||||
"age" to Int::class.javaPrimitiveType!!,
|
"age" to Int::class.javaPrimitiveType!!,
|
||||||
"name" to String::class.java
|
"name" to String::class.java
|
||||||
)))
|
).mapValues { ClassCarpenter.NonNullableField (it.value) } ))
|
||||||
val i = clazz.constructors[0].newInstance(32, "Mike")
|
val i = clazz.constructors[0].newInstance(32, "Mike")
|
||||||
return Pair(clazz, i)
|
return Pair(clazz, i)
|
||||||
}
|
}
|
||||||
@ -82,11 +87,11 @@ class ClassCarpenterTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `generated toString`() {
|
fun `generated toString`() {
|
||||||
val (clazz, i) = genPerson()
|
val (_, i) = genPerson()
|
||||||
assertEquals("Person{age=32, name=Mike}", i.toString())
|
assertEquals("Person{age=32, name=Mike}", i.toString())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = ClassCarpenter.DuplicateName::class)
|
@Test(expected = ClassCarpenter.DuplicateNameException::class)
|
||||||
fun duplicates() {
|
fun duplicates() {
|
||||||
cc.build(ClassCarpenter.ClassSchema("gen.EmptyClass", emptyMap(), null))
|
cc.build(ClassCarpenter.ClassSchema("gen.EmptyClass", emptyMap(), null))
|
||||||
cc.build(ClassCarpenter.ClassSchema("gen.EmptyClass", emptyMap(), null))
|
cc.build(ClassCarpenter.ClassSchema("gen.EmptyClass", emptyMap(), null))
|
||||||
@ -96,7 +101,7 @@ class ClassCarpenterTest {
|
|||||||
fun `can refer to each other`() {
|
fun `can refer to each other`() {
|
||||||
val (clazz1, i) = genPerson()
|
val (clazz1, i) = genPerson()
|
||||||
val clazz2 = cc.build(ClassCarpenter.ClassSchema("gen.Referee", mapOf(
|
val clazz2 = cc.build(ClassCarpenter.ClassSchema("gen.Referee", mapOf(
|
||||||
"ref" to clazz1
|
"ref" to ClassCarpenter.NonNullableField (clazz1)
|
||||||
)))
|
)))
|
||||||
val i2 = clazz2.constructors[0].newInstance(i)
|
val i2 = clazz2.constructors[0].newInstance(i)
|
||||||
assertEquals(i, (i2 as SimpleFieldAccess)["ref"])
|
assertEquals(i, (i2 as SimpleFieldAccess)["ref"])
|
||||||
@ -104,8 +109,15 @@ class ClassCarpenterTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun superclasses() {
|
fun superclasses() {
|
||||||
val schema1 = ClassCarpenter.ClassSchema("gen.A", mapOf("a" to String::class.java))
|
val schema1 = ClassCarpenter.ClassSchema(
|
||||||
val schema2 = ClassCarpenter.ClassSchema("gen.B", mapOf("b" to String::class.java), schema1)
|
"gen.A",
|
||||||
|
mapOf("a" to ClassCarpenter.NonNullableField (String::class.java)))
|
||||||
|
|
||||||
|
val schema2 = ClassCarpenter.ClassSchema(
|
||||||
|
"gen.B",
|
||||||
|
mapOf("b" to ClassCarpenter.NonNullableField (String::class.java)),
|
||||||
|
schema1)
|
||||||
|
|
||||||
val clazz = cc.build(schema2)
|
val clazz = cc.build(schema2)
|
||||||
val i = clazz.constructors[0].newInstance("xa", "xb") as SimpleFieldAccess
|
val i = clazz.constructors[0].newInstance("xa", "xb") as SimpleFieldAccess
|
||||||
assertEquals("xa", i["a"])
|
assertEquals("xa", i["a"])
|
||||||
@ -115,18 +127,32 @@ class ClassCarpenterTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun interfaces() {
|
fun interfaces() {
|
||||||
val schema1 = ClassCarpenter.ClassSchema("gen.A", mapOf("a" to String::class.java))
|
val schema1 = ClassCarpenter.ClassSchema(
|
||||||
val schema2 = ClassCarpenter.ClassSchema("gen.B", mapOf("b" to Int::class.java), schema1, interfaces = listOf(DummyInterface::class.java))
|
"gen.A",
|
||||||
|
mapOf("a" to ClassCarpenter.NonNullableField(String::class.java)))
|
||||||
|
|
||||||
|
val schema2 = ClassCarpenter.ClassSchema("gen.B",
|
||||||
|
mapOf("b" to ClassCarpenter.NonNullableField(Int::class.java)),
|
||||||
|
schema1,
|
||||||
|
interfaces = listOf(DummyInterface::class.java))
|
||||||
val clazz = cc.build(schema2)
|
val clazz = cc.build(schema2)
|
||||||
val i = clazz.constructors[0].newInstance("xa", 1) as DummyInterface
|
val i = clazz.constructors[0].newInstance("xa", 1) as DummyInterface
|
||||||
assertEquals("xa", i.a)
|
assertEquals("xa", i.a)
|
||||||
assertEquals(1, i.b)
|
assertEquals(1, i.b)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = ClassCarpenter.InterfaceMismatch::class)
|
@Test(expected = ClassCarpenter.InterfaceMismatchException::class)
|
||||||
fun `mismatched interface`() {
|
fun `mismatched interface`() {
|
||||||
val schema1 = ClassCarpenter.ClassSchema("gen.A", mapOf("a" to String::class.java))
|
val schema1 = ClassCarpenter.ClassSchema(
|
||||||
val schema2 = ClassCarpenter.ClassSchema("gen.B", mapOf("c" to Int::class.java), schema1, interfaces = listOf(DummyInterface::class.java))
|
"gen.A",
|
||||||
|
mapOf("a" to ClassCarpenter.NonNullableField(String::class.java)))
|
||||||
|
|
||||||
|
val schema2 = ClassCarpenter.ClassSchema(
|
||||||
|
"gen.B",
|
||||||
|
mapOf("c" to ClassCarpenter.NonNullableField(Int::class.java)),
|
||||||
|
schema1,
|
||||||
|
interfaces = listOf(DummyInterface::class.java))
|
||||||
|
|
||||||
val clazz = cc.build(schema2)
|
val clazz = cc.build(schema2)
|
||||||
val i = clazz.constructors[0].newInstance("xa", 1) as DummyInterface
|
val i = clazz.constructors[0].newInstance("xa", 1) as DummyInterface
|
||||||
assertEquals(1, i.b)
|
assertEquals(1, i.b)
|
||||||
@ -134,15 +160,22 @@ class ClassCarpenterTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `generate interface`() {
|
fun `generate interface`() {
|
||||||
val schema1 = ClassCarpenter.InterfaceSchema("gen.Interface", mapOf("a" to Int::class.java))
|
val schema1 = ClassCarpenter.InterfaceSchema(
|
||||||
|
"gen.Interface",
|
||||||
|
mapOf("a" to ClassCarpenter.NonNullableField (Int::class.java)))
|
||||||
|
|
||||||
val iface = cc.build(schema1)
|
val iface = cc.build(schema1)
|
||||||
|
|
||||||
assert(iface.isInterface())
|
assert(iface.isInterface)
|
||||||
assert(iface.constructors.isEmpty())
|
assert(iface.constructors.isEmpty())
|
||||||
assertEquals(iface.declaredMethods.size, 1)
|
assertEquals(iface.declaredMethods.size, 1)
|
||||||
assertEquals(iface.declaredMethods[0].name, "getA")
|
assertEquals(iface.declaredMethods[0].name, "getA")
|
||||||
|
|
||||||
val schema2 = ClassCarpenter.ClassSchema("gen.Derived", mapOf("a" to Int::class.java), interfaces = listOf(iface))
|
val schema2 = ClassCarpenter.ClassSchema(
|
||||||
|
"gen.Derived",
|
||||||
|
mapOf("a" to ClassCarpenter.NonNullableField (Int::class.java)),
|
||||||
|
interfaces = listOf(iface))
|
||||||
|
|
||||||
val clazz = cc.build(schema2)
|
val clazz = cc.build(schema2)
|
||||||
val testA = 42
|
val testA = 42
|
||||||
val i = clazz.constructors[0].newInstance(testA) as SimpleFieldAccess
|
val i = clazz.constructors[0].newInstance(testA) as SimpleFieldAccess
|
||||||
@ -152,16 +185,25 @@ class ClassCarpenterTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `generate multiple interfaces`() {
|
fun `generate multiple interfaces`() {
|
||||||
val iFace1 = ClassCarpenter.InterfaceSchema("gen.Interface1", mapOf("a" to Int::class.java, "b" to String::class.java))
|
val iFace1 = ClassCarpenter.InterfaceSchema(
|
||||||
val iFace2 = ClassCarpenter.InterfaceSchema("gen.Interface2", mapOf("c" to Int::class.java, "d" to String::class.java))
|
"gen.Interface1",
|
||||||
|
mapOf(
|
||||||
|
"a" to ClassCarpenter.NonNullableField(Int::class.java),
|
||||||
|
"b" to ClassCarpenter.NonNullableField(String::class.java)))
|
||||||
|
|
||||||
|
val iFace2 = ClassCarpenter.InterfaceSchema(
|
||||||
|
"gen.Interface2",
|
||||||
|
mapOf(
|
||||||
|
"c" to ClassCarpenter.NonNullableField(Int::class.java),
|
||||||
|
"d" to ClassCarpenter.NonNullableField(String::class.java)))
|
||||||
|
|
||||||
val class1 = ClassCarpenter.ClassSchema(
|
val class1 = ClassCarpenter.ClassSchema(
|
||||||
"gen.Derived",
|
"gen.Derived",
|
||||||
mapOf(
|
mapOf(
|
||||||
"a" to Int::class.java,
|
"a" to ClassCarpenter.NonNullableField(Int::class.java),
|
||||||
"b" to String::class.java,
|
"b" to ClassCarpenter.NonNullableField(String::class.java),
|
||||||
"c" to Int::class.java,
|
"c" to ClassCarpenter.NonNullableField(Int::class.java),
|
||||||
"d" to String::class.java),
|
"d" to ClassCarpenter.NonNullableField(String::class.java)),
|
||||||
interfaces = listOf(cc.build(iFace1), cc.build(iFace2)))
|
interfaces = listOf(cc.build(iFace1), cc.build(iFace2)))
|
||||||
|
|
||||||
val clazz = cc.build(class1)
|
val clazz = cc.build(class1)
|
||||||
@ -182,23 +224,23 @@ class ClassCarpenterTest {
|
|||||||
val iFace1 = ClassCarpenter.InterfaceSchema(
|
val iFace1 = ClassCarpenter.InterfaceSchema(
|
||||||
"gen.Interface1",
|
"gen.Interface1",
|
||||||
mapOf(
|
mapOf(
|
||||||
"a" to Int::class.java,
|
"a" to ClassCarpenter.NonNullableField (Int::class.java),
|
||||||
"b" to String::class.java))
|
"b" to ClassCarpenter.NonNullableField(String::class.java)))
|
||||||
|
|
||||||
val iFace2 = ClassCarpenter.InterfaceSchema(
|
val iFace2 = ClassCarpenter.InterfaceSchema(
|
||||||
"gen.Interface2",
|
"gen.Interface2",
|
||||||
mapOf(
|
mapOf(
|
||||||
"c" to Int::class.java,
|
"c" to ClassCarpenter.NonNullableField(Int::class.java),
|
||||||
"d" to String::class.java),
|
"d" to ClassCarpenter.NonNullableField(String::class.java)),
|
||||||
interfaces = listOf(cc.build(iFace1)))
|
interfaces = listOf(cc.build(iFace1)))
|
||||||
|
|
||||||
val class1 = ClassCarpenter.ClassSchema(
|
val class1 = ClassCarpenter.ClassSchema(
|
||||||
"gen.Derived",
|
"gen.Derived",
|
||||||
mapOf(
|
mapOf(
|
||||||
"a" to Int::class.java,
|
"a" to ClassCarpenter.NonNullableField(Int::class.java),
|
||||||
"b" to String::class.java,
|
"b" to ClassCarpenter.NonNullableField(String::class.java),
|
||||||
"c" to Int::class.java,
|
"c" to ClassCarpenter.NonNullableField(Int::class.java),
|
||||||
"d" to String::class.java),
|
"d" to ClassCarpenter.NonNullableField(String::class.java)),
|
||||||
interfaces = listOf(cc.build(iFace2)))
|
interfaces = listOf(cc.build(iFace2)))
|
||||||
|
|
||||||
val clazz = cc.build(class1)
|
val clazz = cc.build(class1)
|
||||||
@ -213,4 +255,240 @@ class ClassCarpenterTest {
|
|||||||
assertEquals(testC, i["c"])
|
assertEquals(testC, i["c"])
|
||||||
assertEquals(testD, i["d"])
|
assertEquals(testD, i["d"])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test(expected = java.lang.IllegalArgumentException::class)
|
||||||
|
fun `null parameter small int`() {
|
||||||
|
val className = "iEnjoySwede"
|
||||||
|
val schema = ClassCarpenter.ClassSchema(
|
||||||
|
"gen.$className",
|
||||||
|
mapOf("a" to ClassCarpenter.NonNullableField (Int::class.java)))
|
||||||
|
|
||||||
|
val clazz = cc.build(schema)
|
||||||
|
|
||||||
|
val a : Int? = null
|
||||||
|
clazz.constructors[0].newInstance(a)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = ClassCarpenter.NullablePrimitiveException::class)
|
||||||
|
fun `nullable parameter small int`() {
|
||||||
|
val className = "iEnjoySwede"
|
||||||
|
val schema = ClassCarpenter.ClassSchema(
|
||||||
|
"gen.$className",
|
||||||
|
mapOf("a" to ClassCarpenter.NullableField (Int::class.java)))
|
||||||
|
|
||||||
|
cc.build(schema)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `nullable parameter integer`() {
|
||||||
|
val className = "iEnjoyWibble"
|
||||||
|
val schema = ClassCarpenter.ClassSchema(
|
||||||
|
"gen.$className",
|
||||||
|
mapOf("a" to ClassCarpenter.NullableField (Integer::class.java)))
|
||||||
|
|
||||||
|
val clazz = cc.build(schema)
|
||||||
|
val a1 : Int? = null
|
||||||
|
clazz.constructors[0].newInstance(a1)
|
||||||
|
|
||||||
|
val a2 : Int? = 10
|
||||||
|
clazz.constructors[0].newInstance(a2)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `non nullable parameter integer with non null`() {
|
||||||
|
val className = "iEnjoyWibble"
|
||||||
|
val schema = ClassCarpenter.ClassSchema(
|
||||||
|
"gen.$className",
|
||||||
|
mapOf("a" to ClassCarpenter.NonNullableField (Integer::class.java)))
|
||||||
|
|
||||||
|
val clazz = cc.build(schema)
|
||||||
|
|
||||||
|
val a : Int? = 10
|
||||||
|
clazz.constructors[0].newInstance(a)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = java.lang.reflect.InvocationTargetException::class)
|
||||||
|
fun `non nullable parameter integer with null`() {
|
||||||
|
val className = "iEnjoyWibble"
|
||||||
|
val schema = ClassCarpenter.ClassSchema(
|
||||||
|
"gen.$className",
|
||||||
|
mapOf("a" to ClassCarpenter.NonNullableField (Integer::class.java)))
|
||||||
|
|
||||||
|
val clazz = cc.build(schema)
|
||||||
|
|
||||||
|
val a : Int? = null
|
||||||
|
clazz.constructors[0].newInstance(a)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
fun `int array`() {
|
||||||
|
val className = "iEnjoyPotato"
|
||||||
|
val schema = ClassCarpenter.ClassSchema(
|
||||||
|
"gen.$className",
|
||||||
|
mapOf("a" to ClassCarpenter.NonNullableField(IntArray::class.java)))
|
||||||
|
|
||||||
|
val clazz = cc.build(schema)
|
||||||
|
|
||||||
|
val i = clazz.constructors[0].newInstance(intArrayOf(1, 2, 3)) as SimpleFieldAccess
|
||||||
|
|
||||||
|
val arr = clazz.getMethod("getA").invoke(i)
|
||||||
|
|
||||||
|
assertEquals(1, (arr as IntArray)[0])
|
||||||
|
assertEquals(2, arr[1])
|
||||||
|
assertEquals(3, arr[2])
|
||||||
|
assertEquals("$className{a=[1, 2, 3]}", i.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = java.lang.reflect.InvocationTargetException::class)
|
||||||
|
fun `nullable int array throws`() {
|
||||||
|
val className = "iEnjoySwede"
|
||||||
|
val schema = ClassCarpenter.ClassSchema(
|
||||||
|
"gen.$className",
|
||||||
|
mapOf("a" to ClassCarpenter.NonNullableField(IntArray::class.java)))
|
||||||
|
|
||||||
|
val clazz = cc.build(schema)
|
||||||
|
|
||||||
|
val a : IntArray? = null
|
||||||
|
clazz.constructors[0].newInstance(a)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
fun `integer array`() {
|
||||||
|
val className = "iEnjoyFlan"
|
||||||
|
val schema = ClassCarpenter.ClassSchema(
|
||||||
|
"gen.$className",
|
||||||
|
mapOf("a" to ClassCarpenter.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])
|
||||||
|
assertEquals(2, arr[1])
|
||||||
|
assertEquals(3, arr[2])
|
||||||
|
assertEquals("$className{a=[1, 2, 3]}", i.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
fun `int array with ints`() {
|
||||||
|
val className = "iEnjoyCrumble"
|
||||||
|
val schema = ClassCarpenter.ClassSchema(
|
||||||
|
"gen.$className", mapOf(
|
||||||
|
"a" to Int::class.java,
|
||||||
|
"b" to IntArray::class.java,
|
||||||
|
"c" to Int::class.java).mapValues { ClassCarpenter.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())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
fun `multiple int arrays`() {
|
||||||
|
val className = "iEnjoyJam"
|
||||||
|
val schema = ClassCarpenter.ClassSchema(
|
||||||
|
"gen.$className", mapOf(
|
||||||
|
"a" to IntArray::class.java,
|
||||||
|
"b" to Int::class.java,
|
||||||
|
"c" to IntArray::class.java).mapValues { ClassCarpenter.NonNullableField(it.value) })
|
||||||
|
|
||||||
|
val clazz = cc.build(schema)
|
||||||
|
val i = clazz.constructors[0].newInstance(intArrayOf(1, 2), 3, intArrayOf(4, 5, 6))
|
||||||
|
|
||||||
|
assertEquals(1, (clazz.getMethod("getA").invoke(i) as IntArray)[0])
|
||||||
|
assertEquals(2, (clazz.getMethod("getA").invoke(i) as IntArray)[1])
|
||||||
|
assertEquals(3, clazz.getMethod("getB").invoke(i))
|
||||||
|
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())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
fun `string array`() {
|
||||||
|
val className = "iEnjoyToast"
|
||||||
|
val schema = ClassCarpenter.ClassSchema(
|
||||||
|
"gen.$className",
|
||||||
|
mapOf("a" to ClassCarpenter.NullableField(Array<String>::class.java)))
|
||||||
|
|
||||||
|
val clazz = cc.build(schema)
|
||||||
|
|
||||||
|
val i = clazz.constructors[0].newInstance(arrayOf("toast", "butter", "jam"))
|
||||||
|
val arr = clazz.getMethod("getA").invoke(i) as Array<String>
|
||||||
|
|
||||||
|
assertEquals("toast", arr[0])
|
||||||
|
assertEquals("butter", arr[1])
|
||||||
|
assertEquals("jam", arr[2])
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
fun `string arrays`() {
|
||||||
|
val className = "iEnjoyToast"
|
||||||
|
val schema = ClassCarpenter.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) })
|
||||||
|
|
||||||
|
val clazz = cc.build(schema)
|
||||||
|
|
||||||
|
val i = clazz.constructors[0].newInstance(
|
||||||
|
arrayOf("bread", "spread", "cheese"),
|
||||||
|
"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>
|
||||||
|
|
||||||
|
assertEquals("bread", arr1[0])
|
||||||
|
assertEquals("spread", arr1[1])
|
||||||
|
assertEquals("cheese", arr1[2])
|
||||||
|
assertEquals("and on the side", clazz.getMethod("getB").invoke(i))
|
||||||
|
assertEquals("some pickles", arr2[0])
|
||||||
|
assertEquals("some fries", arr2[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `nullable sets annotations`() {
|
||||||
|
val className = "iEnjoyJam"
|
||||||
|
val schema = ClassCarpenter.ClassSchema(
|
||||||
|
"gen.$className",
|
||||||
|
mapOf("a" to ClassCarpenter.NullableField(String::class.java),
|
||||||
|
"b" to ClassCarpenter.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)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user