Arrays of unboxed primitives don't work

This commit is contained in:
Katelyn Baker 2017-07-24 15:19:12 +01:00
parent 407b467f67
commit f8116febad
5 changed files with 361 additions and 22 deletions

View File

@ -7,12 +7,21 @@ import java.lang.reflect.Type
/**
* Serialization / deserialization of arrays.
*/
class ArraySerializer(override val type: Type, factory: SerializerFactory) : AMQPSerializer<Any> {
open class ArraySerializer(override val type: Type, factory: SerializerFactory) : AMQPSerializer<Any> {
companion object {
fun make(type: Type, factory: SerializerFactory) = when (type) {
Array<Character>::class.java -> CharArraySerializer (factory)
else -> ArraySerializer(type, factory)
}
}
override val typeDescriptor = "$DESCRIPTOR_DOMAIN:${fingerprintForType(type, factory)}"
internal val elementType: Type = type.componentType()
internal open val typeName = type.typeName
private val typeNotation: TypeNotation = RestrictedType(type.typeName, null, emptyList(), "list", Descriptor(typeDescriptor, null), emptyList())
internal val typeNotation: TypeNotation by lazy {
RestrictedType(typeName, null, emptyList(), "list", Descriptor(typeDescriptor, null), emptyList())
}
override fun writeClassInfo(output: SerializationOutput) {
if (output.writeTypeNotations(typeNotation)) {
@ -37,14 +46,115 @@ class ArraySerializer(override val type: Type, factory: SerializerFactory) : AMQ
} else throw NotSerializableException("Expected a List but found $obj")
}
private fun <T> List<T>.toArrayOfType(type: Type): Any {
open fun <T> List<T>.toArrayOfType(type: Type): Any {
val elementType = type.asClass() ?: throw NotSerializableException("Unexpected array element type $type")
val list = this
println ("toArrayOfType - $elementType")
return java.lang.reflect.Array.newInstance(elementType, this.size).apply {
(0..lastIndex).forEach { java.lang.reflect.Array.set(this, it, list[it]) }
}
}
}
/**
* Boxed Character arrays required a specialisation to handle the type conversion properly when populating
* the array since Kotlin won't allow an implicit cast from Int (as they're stored as 16bit ints) to Char
*/
class CharArraySerializer(factory: SerializerFactory) : ArraySerializer(Array<Character>::class.java, factory) {
override fun <T> List<T>.toArrayOfType(type: Type): Any {
val elementType = type.asClass() ?: throw NotSerializableException("Unexpected array element type $type")
val list = this
return java.lang.reflect.Array.newInstance(elementType, this.size).apply {
(0..lastIndex).forEach { java.lang.reflect.Array.set(this, it, (list[it] as Int).toChar()) }
}
}
}
/**
* Specialisation of [ArraySerializer] that handles arrays of unboxed java primitive types
*/
abstract class PrimArraySerializer (type: Type, factory: SerializerFactory) : ArraySerializer(type, factory) {
companion object {
val primTypes: Map<Type, (SerializerFactory) -> PrimArraySerializer> = mapOf(
IntArray::class.java to { f -> PrimIntArraySerializer(f, "int[p]") },
CharArray::class.java to { f -> PrimCharArraySerializer(f, "char[p]") },
BooleanArray::class.java to { f -> PrimBooleanArraySerializer(f, "boolean[p]") },
FloatArray::class.java to { f -> PrimFloatArraySerializer(f, "float[p]") },
ShortArray::class.java to { f -> PrimShortArraySerializer(f, "short[p]") },
DoubleArray::class.java to { f -> PrimDoubleArraySerializer(f, "double[p]") },
ByteArray::class.java to { f -> PrimByteArraySerializer(f, "byte[p]") },
LongArray::class.java to { f -> PrimLongArraySerializer(f, "long[p]") }
)
fun make(type: Type, factory: SerializerFactory) = primTypes[type]!!(factory)
}
fun localWriteObject(data: Data, func : () -> Unit) {
data.withDescribed(typeNotation.descriptor) { withList { func() } }
}
}
class PrimIntArraySerializer(factory: SerializerFactory, override val typeName : String) :
PrimArraySerializer(IntArray::class.java, factory) {
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput) {
localWriteObject(data) { (obj as IntArray).forEach { output.writeObjectOrNull(it, data, elementType) }}
}
}
class PrimCharArraySerializer(factory: SerializerFactory, override val typeName : String) :
PrimArraySerializer(CharArray::class.java, factory) {
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput) {
localWriteObject(data) { (obj as CharArray).forEach { output.writeObjectOrNull(it, data, elementType) }}
}
override fun <T> List<T>.toArrayOfType(type: Type): Any {
val elementType = type.asClass() ?: throw NotSerializableException("Unexpected array element type $type")
val list = this
return java.lang.reflect.Array.newInstance(elementType, this.size).apply {
val array = this
for (i in 0..lastIndex) {
java.lang.reflect.Array.set(array, i, list[i])
}
(0..lastIndex).forEach { java.lang.reflect.Array.set(array, it, (list[it] as Int).toChar()) }
}
}
}
}
class PrimBooleanArraySerializer(factory: SerializerFactory, override val typeName : String) :
PrimArraySerializer(BooleanArray::class.java, factory) {
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput) {
localWriteObject(data) { (obj as BooleanArray).forEach { output.writeObjectOrNull(it, data, elementType) } }
}
}
class PrimDoubleArraySerializer(factory: SerializerFactory, override val typeName : String) :
PrimArraySerializer(DoubleArray::class.java, factory) {
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput) {
localWriteObject(data) { (obj as DoubleArray).forEach { output.writeObjectOrNull(it, data, elementType) } }
}
}
class PrimFloatArraySerializer(factory: SerializerFactory, override val typeName: String) :
PrimArraySerializer(FloatArray::class.java, factory) {
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput) {
localWriteObject(data) { (obj as FloatArray).forEach { output.writeObjectOrNull(it, data, elementType) } }
}
}
class PrimShortArraySerializer(factory: SerializerFactory, override val typeName: String) :
PrimArraySerializer(ShortArray::class.java, factory) {
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput) {
localWriteObject(data) { (obj as ShortArray).forEach { output.writeObjectOrNull(it, data, elementType) } }
}
}
class PrimByteArraySerializer(factory: SerializerFactory, override val typeName: String) :
PrimArraySerializer(ByteArray::class.java, factory) {
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput) {
localWriteObject(data) { (obj as ByteArray).forEach { output.writeObjectOrNull(it, data, elementType) } }
}
}
class PrimLongArraySerializer(factory: SerializerFactory, override val typeName: String) :
PrimArraySerializer(LongArray::class.java, factory) {
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput) {
localWriteObject(data) { (obj as FloatArray).forEach { output.writeObjectOrNull(it, data, elementType) } }
}
}

View File

@ -133,7 +133,8 @@ class DeserializationInput(internal val serializerFactory: SerializerFactory = S
// Look up serializer in factory by descriptor
val serializer = serializerFactory.get(obj.descriptor, schema)
if (serializer.type != type && !serializer.type.isSubClassOf(type))
throw NotSerializableException("Described type with descriptor ${obj.descriptor} was expected to be of type $type")
throw NotSerializableException("Described type with descriptor ${obj.descriptor} was " +
"expected to be of type $type but was ${serializer.type}")
return serializer.readObject(obj.described, schema, this)
} else if (obj is Binary) {
return obj.array

View File

@ -13,6 +13,6 @@ class DeserializedGenericArrayType(private val componentType: Type) : GenericArr
override fun toString(): String = typeName
override fun hashCode(): Int = Objects.hashCode(componentType)
override fun equals(other: Any?): Boolean {
return other is GenericArrayType && componentType.equals(other.genericComponentType)
return other is GenericArrayType && (componentType == other.genericComponentType)
}
}
}

View File

@ -203,8 +203,20 @@ class SerializerFactory(val whitelist: ClassWhitelist = AllWhitelist) {
} else {
findCustomSerializer(clazz, declaredType) ?: run {
if (type.isArray()) {
whitelisted(type.componentType())
ArraySerializer(type, this)
println ("Array: ${type.componentType()}, ${type.componentType()::class.java}")
println (clazz)
println (declaredType)
println ("${clazz.componentType.isPrimitive()}")
if (clazz.componentType.isPrimitive) {
println ("primitive array")
whitelisted(type.componentType())
PrimArraySerializer.make(type, this)
}
else {
println ("non primitive array")
whitelisted(type.componentType())
ArraySerializer.make (type, this)
}
} else if (clazz.kotlin.objectInstance != null) {
whitelisted(clazz)
SingletonSerializer(clazz, clazz.kotlin.objectInstance!!, this)
@ -292,14 +304,19 @@ class SerializerFactory(val whitelist: ClassWhitelist = AllWhitelist) {
private val namesOfPrimitiveTypes: Map<String, Class<*>> = primitiveTypeNames.map { it.value to it.key }.toMap()
fun nameForType(type: Type): String {
if (type is Class<*>) {
return primitiveTypeName(type) ?: if (type.isArray) "${nameForType(type.componentType)}[]" else type.name
} else if (type is ParameterizedType) {
return "${nameForType(type.rawType)}<${type.actualTypeArguments.joinToString { nameForType(it) }}>"
} else if (type is GenericArrayType) {
return "${nameForType(type.genericComponentType)}[]"
} else throw NotSerializableException("Unable to render type $type to a string.")
fun nameForType(type: Type) : String {
println ("nameForType $type - ${(type as Class<*>).isArray} - ${type?.componentType?.isPrimitive}")
val result = when (type) {
is Class<*> -> primitiveTypeName(type) ?: if (type.isArray) {
if (type.componentType.isPrimitive) "${nameForType(type.componentType)}[p]" else "${nameForType(type.componentType)}[]"
} else type.name
is ParameterizedType -> "${nameForType(type.rawType)}<${type.actualTypeArguments.joinToString { nameForType(it) }}>"
is GenericArrayType -> "${nameForType(type.genericComponentType)}[]"
else -> throw NotSerializableException("Unable to render type $type to a string.")
}
println ("nameForType = $result")
return result
}
private fun typeForName(name: String): Type {
@ -312,6 +329,18 @@ class SerializerFactory(val whitelist: ClassWhitelist = AllWhitelist) {
} else {
throw NotSerializableException("Not able to deserialize array type: $name")
}
} else if (name.endsWith("[p]")) {
when(name) {
"int[p]" -> IntArray::class.java
"char[p]" -> CharArray::class.java
"boolean[p]" -> BooleanArray::class.java
"float[p]" -> FloatArray::class.java
"double[p]" -> DoubleArray::class.java
"short[p]" -> ShortArray::class.java
"byte[p]" -> ByteArray::class.java
"long[p]" -> LongArray::class.java
else -> throw NotSerializableException("Not able to deserialize array type: $name")
}
} else {
DeserializedParameterizedType.make(name)
}

View File

@ -1,8 +1,8 @@
package net.corda.core.serialization.amqp
import org.junit.Test
import java.io.ByteArrayOutputStream
import kotlin.test.assertEquals
import org.apache.qpid.proton.codec.Data
/**
* Prior to certain fixes being made within the [PropertySerializaer] classes these simple
@ -11,6 +11,25 @@ import kotlin.test.assertEquals
* as such
*/
class DeserializeSimpleTypesTests {
class TestSerializationOutput(
private val verbose: Boolean,
serializerFactory: SerializerFactory = SerializerFactory()) : SerializationOutput(serializerFactory) {
override fun writeSchema(schema: Schema, data: Data) {
if(verbose) println(schema)
super.writeSchema(schema, data)
}
}
companion object {
/**
* If you want to see the schema encoded into the envelope after serialisation change this to true
*/
private const val VERBOSE = false
}
val sf = SerializerFactory()
@Test
fun testChar() {
data class C(val c: Char)
@ -30,5 +49,185 @@ class DeserializeSimpleTypesTests {
assertEquals(c.c, deserializedC.c)
}
@Test
fun testArrayOfInt() {
class IA(val ia: Array<Int>)
val ia = IA(arrayOf(1, 2, 3))
assertEquals("class [Ljava.lang.Integer;", ia.ia::class.java.toString())
assertEquals(SerializerFactory.nameForType(ia.ia::class.java), "int[]")
val serialisedIA = TestSerializationOutput(VERBOSE, sf).serialize(ia)
val deserializedIA = DeserializationInput(sf).deserialize(serialisedIA)
assertEquals(ia.ia.size, deserializedIA.ia.size)
assertEquals(ia.ia[0], deserializedIA.ia[0])
assertEquals(ia.ia[1], deserializedIA.ia[1])
assertEquals(ia.ia[2], deserializedIA.ia[2])
}
@Test
fun testArrayOfInteger() {
class IA (val ia: Array<Integer>)
val ia = IA(arrayOf(Integer(1), Integer(2), Integer(3)))
assertEquals("class [Ljava.lang.Integer;", ia.ia::class.java.toString())
assertEquals(SerializerFactory.nameForType(ia.ia::class.java), "int[]")
val serialisedIA = TestSerializationOutput(VERBOSE, sf).serialize(ia)
val deserializedIA = DeserializationInput(sf).deserialize(serialisedIA)
assertEquals(ia.ia.size, deserializedIA.ia.size)
assertEquals(ia.ia[0], deserializedIA.ia[0])
assertEquals(ia.ia[1], deserializedIA.ia[1])
assertEquals(ia.ia[2], deserializedIA.ia[2])
}
/**
* Test unboxed primitives
*/
@Test
fun testIntArray() {
class IA (val ia: IntArray)
val v = IntArray(3)
v[0] = 1; v[1] = 2; v[2] = 3
val ia = IA(v)
assertEquals("class [I", ia.ia::class.java.toString())
assertEquals(SerializerFactory.nameForType(ia.ia::class.java), "int[p]")
val serialisedIA = TestSerializationOutput(VERBOSE, sf).serialize(ia)
val deserializedIA = DeserializationInput(sf).deserialize(serialisedIA)
assertEquals(ia.ia.size, deserializedIA.ia.size)
assertEquals(ia.ia[0], deserializedIA.ia[0])
assertEquals(ia.ia[1], deserializedIA.ia[1])
assertEquals(ia.ia[2], deserializedIA.ia[2])
}
@Test
fun testArrayOfChars() {
class C (val c: Array<Char>)
val c = C(arrayOf ('a', 'b', 'c'))
assertEquals("class [Ljava.lang.Character;", c.c::class.java.toString())
assertEquals(SerializerFactory.nameForType(c.c::class.java), "char[]")
val serialisedC = TestSerializationOutput(VERBOSE, sf).serialize(c)
val deserializedC = DeserializationInput(sf).deserialize(serialisedC)
assertEquals(c.c.size, deserializedC.c.size)
assertEquals(c.c[0], deserializedC.c[0])
assertEquals(c.c[1], deserializedC.c[1])
assertEquals(c.c[2], deserializedC.c[2])
}
@Test
fun testCharArray() {
class C(val c: CharArray)
val v = CharArray(3)
v[0] = 'a'; v[1] = 'b'; v[2] = 'c'
val c = C(v)
assertEquals("class [C", c.c::class.java.toString())
assertEquals(SerializerFactory.nameForType(c.c::class.java), "char[p]")
val serialisedC = TestSerializationOutput(VERBOSE, sf).serialize(c)
val deserializedC = DeserializationInput(sf).deserialize(serialisedC)
assertEquals(c.c.size, deserializedC.c.size)
assertEquals(c.c[0], deserializedC.c[0])
assertEquals(c.c[1], deserializedC.c[1])
assertEquals(c.c[2], deserializedC.c[2])
}
@Test
fun testArrayOfBoolean() {
class C (val c: Array<Boolean>)
val c = C(arrayOf (true, false, false, true))
assertEquals("class [Ljava.lang.Boolean;", c.c::class.java.toString())
assertEquals(SerializerFactory.nameForType(c.c::class.java), "boolean[]")
val serialisedC = TestSerializationOutput(VERBOSE, sf).serialize(c)
val deserializedC = DeserializationInput(sf).deserialize(serialisedC)
assertEquals(c.c.size, deserializedC.c.size)
assertEquals(c.c[0], deserializedC.c[0])
assertEquals(c.c[1], deserializedC.c[1])
assertEquals(c.c[2], deserializedC.c[2])
assertEquals(c.c[3], deserializedC.c[3])
}
@Test
fun testBooleanArray() {
class C(val c: BooleanArray)
val c = C(BooleanArray(4))
c.c[0] = true; c.c[1] = false; c.c[2] = false; c.c[3] = true
assertEquals("class [Z", c.c::class.java.toString())
assertEquals(SerializerFactory.nameForType(c.c::class.java), "boolean[p]")
val serialisedC = TestSerializationOutput(VERBOSE, sf).serialize(c)
val deserializedC = DeserializationInput(sf).deserialize(serialisedC)
assertEquals(c.c.size, deserializedC.c.size)
assertEquals(c.c[0], deserializedC.c[0])
assertEquals(c.c[1], deserializedC.c[1])
assertEquals(c.c[2], deserializedC.c[2])
assertEquals(c.c[3], deserializedC.c[3])
}
@Test
fun testArrayOfByte() {
class C(val c: Array<Byte>)
}
@Test
fun testByteArray() {
class C(val c: ByteArray)
}
@Test
fun testArrayOfShort() {
class C(val c: Array<Short>)
}
@Test
fun testShortArray() {
class C(val c: ShortArray)
}
@Test
fun testArrayOfLong() {
class C(val c: Array<Long>)
}
@Test
fun testLongArray() {
class C(val c: LongArray)
}
@Test
fun testArrayOfFloat() {
class C(val c: Array<Float>)
}
@Test
fun testFloatArray() {
class C(val c: FloatArray)
}
@Test
fun testArrayOfDouble() {
class C(val c: Array<Double>)
}
@Test
fun testDoubleArray() {
class C(val c: DoubleArray)
}
}