CORDA-943 - Cope with multiple generics at str->type conversion in AMQP

Also fixes an odd bug where the inferred type of a getter wasn't
matching the constructor parameter type because that was still unbounded
and seen as T, looking at the raw type allows us to inspect this
properly
This commit is contained in:
Katelyn Baker 2018-02-01 12:19:32 +00:00
parent 222c5b9db8
commit 00b90a98fb
18 changed files with 254 additions and 100 deletions

View File

@ -18,7 +18,7 @@ class AMQPPrimitiveSerializer(clazz: Class<*>) : AMQPSerializer<Any> {
override fun writeClassInfo(output: SerializationOutput) {
}
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, offset: Int) {
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, debugIndent: Int) {
if (obj is ByteArray) {
data.putObject(Binary(obj))
} else {

View File

@ -30,7 +30,7 @@ interface AMQPSerializer<out T> {
/**
* Write the given object, with declared type, to the output.
*/
fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, offset: Int = 0)
fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, debugIndent: Int = 0)
/**
* Read the given object from the input. The envelope is provided in case the schema is required.

View File

@ -45,12 +45,12 @@ open class ArraySerializer(override val type: Type, factory: SerializerFactory)
}
}
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, offset: Int) {
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, debugIndent: Int) {
// Write described
data.withDescribed(typeNotation.descriptor) {
withList {
for (entry in obj as Array<*>) {
output.writeObjectOrNull(entry, this, elementType, offset)
output.writeObjectOrNull(entry, this, elementType, debugIndent)
}
}
}
@ -109,15 +109,19 @@ abstract class PrimArraySerializer(type: Type, factory: SerializerFactory) : Arr
class PrimIntArraySerializer(factory: SerializerFactory) :
PrimArraySerializer(IntArray::class.java, factory) {
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, offset: Int) {
localWriteObject(data) { (obj as IntArray).forEach { output.writeObjectOrNull(it, data, elementType, offset+4) } }
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, debugIndent: Int) {
localWriteObject(data) {
(obj as IntArray).forEach { output.writeObjectOrNull(it, data, elementType, debugIndent+1) }
}
}
}
class PrimCharArraySerializer(factory: SerializerFactory) :
PrimArraySerializer(CharArray::class.java, factory) {
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, offset: Int) {
localWriteObject(data) { (obj as CharArray).forEach { output.writeObjectOrNull(it, data, elementType, offset+4) } }
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, debugIndent: Int) {
localWriteObject(data) { (obj as CharArray).forEach {
output.writeObjectOrNull(it, data, elementType, debugIndent+1) }
}
}
override fun <T> List<T>.toArrayOfType(type: Type): Any {
@ -132,35 +136,45 @@ class PrimCharArraySerializer(factory: SerializerFactory) :
class PrimBooleanArraySerializer(factory: SerializerFactory) :
PrimArraySerializer(BooleanArray::class.java, factory) {
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, offset: Int) {
localWriteObject(data) { (obj as BooleanArray).forEach { output.writeObjectOrNull(it, data, elementType, offset+4) } }
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, debugIndent: Int) {
localWriteObject(data) {
(obj as BooleanArray).forEach { output.writeObjectOrNull(it, data, elementType, debugIndent+1) }
}
}
}
class PrimDoubleArraySerializer(factory: SerializerFactory) :
PrimArraySerializer(DoubleArray::class.java, factory) {
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, offset: Int) {
localWriteObject(data) { (obj as DoubleArray).forEach { output.writeObjectOrNull(it, data, elementType, offset+4) } }
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, debugIndent: Int) {
localWriteObject(data) {
(obj as DoubleArray).forEach { output.writeObjectOrNull(it, data, elementType, debugIndent+1) }
}
}
}
class PrimFloatArraySerializer(factory: SerializerFactory) :
PrimArraySerializer(FloatArray::class.java, factory) {
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, offset: Int) {
localWriteObject(data) { (obj as FloatArray).forEach { output.writeObjectOrNull(it, data, elementType, offset+4) } }
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, debugIndent: Int) {
localWriteObject(data) {
(obj as FloatArray).forEach { output.writeObjectOrNull(it, data, elementType, debugIndent+1) }
}
}
}
class PrimShortArraySerializer(factory: SerializerFactory) :
PrimArraySerializer(ShortArray::class.java, factory) {
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, offset: Int) {
localWriteObject(data) { (obj as ShortArray).forEach { output.writeObjectOrNull(it, data, elementType, offset+4) } }
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, debugIndent: Int) {
localWriteObject(data) {
(obj as ShortArray).forEach { output.writeObjectOrNull(it, data, elementType, debugIndent+1) }
}
}
}
class PrimLongArraySerializer(factory: SerializerFactory) :
PrimArraySerializer(LongArray::class.java, factory) {
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, offset: Int) {
localWriteObject(data) { (obj as LongArray).forEach { output.writeObjectOrNull(it, data, elementType, offset+4) } }
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, debugIndent: Int) {
localWriteObject(data) {
(obj as LongArray).forEach { output.writeObjectOrNull(it, data, elementType, debugIndent+1) }
}
}
}

View File

@ -66,18 +66,26 @@ class CollectionSerializer(val declaredType: ParameterizedType, factory: Seriali
}
}
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, offset: Int) = ifThrowsAppend({ declaredType.typeName }) {
override fun writeObject(
obj: Any,
data: Data,
type: Type,
output: SerializationOutput,
debugIndent: Int) = ifThrowsAppend({ declaredType.typeName }) {
// Write described
data.withDescribed(typeNotation.descriptor) {
withList {
for (entry in obj as Collection<*>) {
output.writeObjectOrNull(entry, this, declaredType.actualTypeArguments[0], offset)
output.writeObjectOrNull(entry, this, declaredType.actualTypeArguments[0], debugIndent)
}
}
}
}
override fun readObject(obj: Any, schemas: SerializationSchemas, input: DeserializationInput): Any = ifThrowsAppend({ declaredType.typeName }) {
override fun readObject(
obj: Any,
schemas: SerializationSchemas,
input: DeserializationInput): Any = ifThrowsAppend({ declaredType.typeName }) {
// TODO: Can we verify the entries in the list?
concreteBuilder((obj as List<*>).map { input.readObjectOrNull(it, schemas, declaredType.actualTypeArguments[0]) })
}

View File

@ -64,7 +64,7 @@ class CorDappCustomSerializer(
override fun writeClassInfo(output: SerializationOutput) {}
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, offset: Int) {
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, debugIndent: Int) {
val proxy = uncheckedCast<SerializationCustomSerializer<*, *>,
SerializationCustomSerializer<Any?, Any?>>(serializer).toProxy(obj)

View File

@ -40,7 +40,7 @@ abstract class CustomSerializer<T : Any> : AMQPSerializer<T>, SerializerFor {
*/
override val revealSubclassesInSchema: Boolean get() = false
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, offset: Int) {
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, debugIndent: Int) {
data.withDescribed(descriptor) {
writeDescribedObject(uncheckedCast(obj), data, type, output)
}

View File

@ -1,6 +1,5 @@
package net.corda.nodeapi.internal.serialization.amqp
import com.google.common.primitives.Primitives
import net.corda.core.internal.getStackTraceAsString
import net.corda.core.serialization.SerializedBytes
import net.corda.core.utilities.ByteSequence
@ -56,7 +55,7 @@ class DeserializationInput(internal val serializerFactory: SerializerFactory) {
@Throws(NotSerializableException::class)
inline internal fun <reified T : Any> deserializeAndReturnEnvelope(bytes: SerializedBytes<T>): ObjectAndEnvelope<T> =
deserializeAndReturnEnvelope(bytes, T::class.java)
deserializeAndReturnEnvelope(bytes, T::class.java)
@Throws(NotSerializableException::class)
internal fun getEnvelope(bytes: ByteSequence): Envelope {
@ -110,43 +109,42 @@ class DeserializationInput(internal val serializerFactory: SerializerFactory) {
return if (obj == null) null else readObject(obj, schema, type, offset)
}
internal fun readObject(obj: Any, schemas: SerializationSchemas, type: Type, offset: Int = 0): Any {
return if (obj is DescribedType && ReferencedObject.DESCRIPTOR == obj.descriptor) {
// It must be a reference to an instance that has already been read, cheaply and quickly returning it by reference.
val objectIndex = (obj.described as UnsignedInteger).toInt()
if (objectIndex !in 0..objectHistory.size)
throw NotSerializableException("Retrieval of existing reference failed. Requested index $objectIndex " +
"is outside of the bounds for the list of size: ${objectHistory.size}")
internal fun readObject(obj: Any, schemas: SerializationSchemas, type: Type, debugIndent: Int = 0): Any =
if (obj is DescribedType && ReferencedObject.DESCRIPTOR == obj.descriptor) {
// It must be a reference to an instance that has already been read, cheaply and quickly returning it by reference.
val objectIndex = (obj.described as UnsignedInteger).toInt()
if (objectIndex !in 0..objectHistory.size)
throw NotSerializableException("Retrieval of existing reference failed. Requested index $objectIndex " +
"is outside of the bounds for the list of size: ${objectHistory.size}")
val objectRetrieved = objectHistory[objectIndex]
if (!objectRetrieved::class.java.isSubClassOf(type.asClass()!!)) {
throw NotSerializableException(
"Existing reference type mismatch. Expected: '$type', found: '${objectRetrieved::class.java}' " +
"@ ${objectIndex}")
}
objectRetrieved
} else {
val objectRead = when (obj) {
is DescribedType -> {
// Look up serializer in factory by descriptor
val serializer = serializerFactory.get(obj.descriptor, schemas)
if (SerializerFactory.AnyType != type && serializer.type != type && with(serializer.type) { !isSubClassOf(type) && !materiallyEquivalentTo(type) })
throw NotSerializableException("Described type with descriptor ${obj.descriptor} was " +
"expected to be of type $type but was ${serializer.type}")
serializer.readObject(obj.described, schemas, this)
val objectRetrieved = objectHistory[objectIndex]
if (!objectRetrieved::class.java.isSubClassOf(type.asClass()!!)) {
throw NotSerializableException(
"Existing reference type mismatch. Expected: '$type', found: '${objectRetrieved::class.java}' " +
"@ ${objectIndex}")
}
objectRetrieved
} else {
val objectRead = when (obj) {
is DescribedType -> {
// Look up serializer in factory by descriptor
val serializer = serializerFactory.get(obj.descriptor, schemas)
if (SerializerFactory.AnyType != type && serializer.type != type && with(serializer.type) { !isSubClassOf(type) && !materiallyEquivalentTo(type) })
throw NotSerializableException("Described type with descriptor ${obj.descriptor} was " +
"expected to be of type $type but was ${serializer.type}")
serializer.readObject(obj.described, schemas, this)
}
is Binary -> obj.array
else -> obj // this will be the case for primitive types like [boolean] et al.
}
is Binary -> obj.array
else -> obj // this will be the case for primitive types like [boolean] et al.
}
// Store the reference in case we need it later on.
// Skip for primitive types as they are too small and overhead of referencing them will be much higher than their content
if (suitableForObjectReference(objectRead.javaClass)) {
objectHistory.add(objectRead)
// Store the reference in case we need it later on.
// Skip for primitive types as they are too small and overhead of referencing them will be much higher than their content
if (suitableForObjectReference(objectRead.javaClass)) {
objectHistory.add(objectRead)
}
objectRead
}
objectRead
}
}
/**
* Currently performs checks aimed at:

View File

@ -130,7 +130,7 @@ class EnumEvolutionSerializer(
throw UnsupportedOperationException("It should be impossible to write an evolution serializer")
}
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, offset: Int) {
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, debugIndent: Int) {
throw UnsupportedOperationException("It should be impossible to write an evolution serializer")
}
}

View File

@ -39,7 +39,7 @@ class EnumSerializer(declaredType: Type, declaredClass: Class<*>, factory: Seria
return fromOrd
}
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, offset: Int) {
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, debugIndent: Int) {
if (obj !is Enum<*>) throw NotSerializableException("Serializing $obj as enum when it isn't")
data.withDescribed(typeNotation.descriptor) {

View File

@ -73,7 +73,12 @@ class MapSerializer(private val declaredType: ParameterizedType, factory: Serial
}
}
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, offset: Int) = ifThrowsAppend({ declaredType.typeName }) {
override fun writeObject(
obj: Any,
data: Data,
type: Type,
output: SerializationOutput,
debugIndent: Int) = ifThrowsAppend({ declaredType.typeName }) {
obj.javaClass.checkSupportedMapType()
// Write described
data.withDescribed(typeNotation.descriptor) {
@ -81,8 +86,8 @@ class MapSerializer(private val declaredType: ParameterizedType, factory: Serial
data.putMap()
data.enter()
for ((key, value) in obj as Map<*, *>) {
output.writeObjectOrNull(key, data, declaredType.actualTypeArguments[0], offset)
output.writeObjectOrNull(value, data, declaredType.actualTypeArguments[1], offset)
output.writeObjectOrNull(key, data, declaredType.actualTypeArguments[0], debugIndent)
output.writeObjectOrNull(value, data, declaredType.actualTypeArguments[1], debugIndent)
}
data.exit() // exit map
}

View File

@ -52,13 +52,18 @@ open class ObjectSerializer(val clazz: Type, factory: SerializerFactory) : AMQPS
}
}
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, offset: Int) = ifThrowsAppend({ clazz.typeName }) {
override fun writeObject(
obj: Any,
data: Data,
type: Type,
output: SerializationOutput,
debugIndent: Int) = ifThrowsAppend({ clazz.typeName }) {
// Write described
data.withDescribed(typeNotation.descriptor) {
// Write list
withList {
propertySerializers.serializationOrder.forEach { property ->
property.getter.writeProperty(obj, this, output, offset+4)
property.getter.writeProperty(obj, this, output, debugIndent+1)
}
}
}

View File

@ -9,7 +9,7 @@ import java.lang.reflect.Type
*/
sealed class PropertySerializer(val name: String, val propertyReader: PropertyReader, val resolvedType: Type) {
abstract fun writeClassInfo(output: SerializationOutput)
abstract fun writeProperty(obj: Any?, data: Data, output: SerializationOutput, offset: Int = 0)
abstract fun writeProperty(obj: Any?, data: Data, output: SerializationOutput, debugIndent: Int = 0)
abstract fun readProperty(obj: Any?, schemas: SerializationSchemas, input: DeserializationInput): Any?
val type: String = generateType()
@ -80,8 +80,8 @@ sealed class PropertySerializer(val name: String, val propertyReader: PropertyRe
input.readObjectOrNull(obj, schemas, resolvedType)
}
override fun writeProperty(obj: Any?, data: Data, output: SerializationOutput, offset: Int) = ifThrowsAppend({ nameForDebug }) {
output.writeObjectOrNull(propertyReader.read(obj), data, resolvedType, offset)
override fun writeProperty(obj: Any?, data: Data, output: SerializationOutput, debugIndent: Int) = ifThrowsAppend({ nameForDebug }) {
output.writeObjectOrNull(propertyReader.read(obj), data, resolvedType, debugIndent)
}
private val nameForDebug = "$name(${resolvedType.typeName})"
@ -100,7 +100,7 @@ sealed class PropertySerializer(val name: String, val propertyReader: PropertyRe
return if (obj is Binary) obj.array else obj
}
override fun writeProperty(obj: Any?, data: Data, output: SerializationOutput, offset: Int) {
override fun writeProperty(obj: Any?, data: Data, output: SerializationOutput, debugIndent: Int) {
val value = propertyReader.read(obj)
if (value is ByteArray) {
data.putObject(Binary(value))
@ -123,7 +123,7 @@ sealed class PropertySerializer(val name: String, val propertyReader: PropertyRe
return if (obj == null) null else (obj as Short).toChar()
}
override fun writeProperty(obj: Any?, data: Data, output: SerializationOutput, offset: Int) {
override fun writeProperty(obj: Any?, data: Data, output: SerializationOutput, debugIndent: Int) {
val input = propertyReader.read(obj)
if (input != null) data.putShort((input as Char).toShort()) else data.putNull()
}

View File

@ -349,7 +349,7 @@ private fun Hasher.fingerprintWithCustomSerializerOrElse(factory: SerializerFact
// This method concatenates various elements of the types recursively as unencoded strings into the hasher, effectively
// creating a unique string for a type which we then hash in the calling function above.
private fun fingerprintForType(type: Type, contextType: Type?, alreadySeen: MutableSet<Type>,
hasher: Hasher, factory: SerializerFactory, offset: Int = 4): Hasher {
hasher: Hasher, factory: SerializerFactory, debugIndent: Int = 1): Hasher {
// We don't include Example<?> and Example<T> where type is ? or T in this otherwise we
// generate different fingerprints for class Outer<T>(val a: Inner<T>) when serialising
// and deserializing (assuming deserialization is occurring in a factory that didn't
@ -372,23 +372,23 @@ private fun fingerprintForType(type: Type, contextType: Type?, alreadySeen: Muta
hasher.putUnencodedChars(clazz.name)
} else {
hasher.fingerprintWithCustomSerializerOrElse(factory, clazz, type) {
fingerprintForObject(type, type, alreadySeen, hasher, factory, offset+4)
fingerprintForObject(type, type, alreadySeen, hasher, factory, debugIndent+1)
}
}
// ... and concatenate the type data for each parameter type.
type.actualTypeArguments.fold(startingHash) { orig, paramType ->
fingerprintForType(paramType, type, alreadySeen, orig, factory, offset+4)
fingerprintForType(paramType, type, alreadySeen, orig, factory, debugIndent+1)
}
}
// Previously, we drew a distinction between TypeVariable, Wildcard, and AnyType, changing
// Previously, we drew a distinction between TypeVariable, WildcardType, and AnyType, changing
// the signature of the fingerprinted object. This, however, doesn't work as it breaks bi-
// directional fingerprints. That is, fingerprinting a concrete instance of a generic
// type (Example<Int>), creates a different fingerprint from the generic type itself (Example<T>)
//
// On serialization Example<Int> is treated as Example<T>, a TypeVariable
// On deserialisation it is seen as Example<?>, A wildcard *and* a TypeVariable
// Note: AnyType is a special case of WildcarcType used in other parts of the
// On deserialisation it is seen as Example<?>, A WildcardType *and* a TypeVariable
// Note: AnyType is a special case of WildcardType used in other parts of the
// serializer so both cases need to be dealt with here
//
// If we treat these types as fundamentally different and alter the fingerprint we will
@ -400,7 +400,7 @@ private fun fingerprintForType(type: Type, contextType: Type?, alreadySeen: Muta
}
is Class<*> -> {
if (type.isArray) {
fingerprintForType(type.componentType, contextType, alreadySeen, hasher, factory, offset+4)
fingerprintForType(type.componentType, contextType, alreadySeen, hasher, factory, debugIndent+1)
.putUnencodedChars(ARRAY_HASH)
} else if (SerializerFactory.isPrimitive(type)) {
hasher.putUnencodedChars(type.name)
@ -420,7 +420,7 @@ private fun fingerprintForType(type: Type, contextType: Type?, alreadySeen: Muta
// to the CorDapp but maybe reference to the JAR in the short term.
hasher.putUnencodedChars(type.name)
} else {
fingerprintForObject(type, type, alreadySeen, hasher, factory, offset+4)
fingerprintForObject(type, type, alreadySeen, hasher, factory, debugIndent+1)
}
}
}
@ -428,10 +428,7 @@ private fun fingerprintForType(type: Type, contextType: Type?, alreadySeen: Muta
// Hash the element type + some array hash
is GenericArrayType -> {
fingerprintForType(type.genericComponentType, contextType, alreadySeen,
hasher, factory, offset+4).putUnencodedChars(ARRAY_HASH)
}
is WildcardType -> {
hasher.putUnencodedChars(type.typeName).putUnencodedChars(WILDCARD_TYPE_HASH)
hasher, factory, debugIndent+1).putUnencodedChars(ARRAY_HASH)
}
else -> throw NotSerializableException("Don't know how to hash")
}
@ -452,17 +449,17 @@ private fun fingerprintForObject(
alreadySeen: MutableSet<Type>,
hasher: Hasher,
factory: SerializerFactory,
offset: Int = 0): Hasher {
debugIndent: Int = 0): Hasher {
// Hash the class + properties + interfaces
val name = type.asClass()?.name ?: throw NotSerializableException("Expected only Class or ParameterizedType but found $type")
propertiesForSerialization(constructorForDeserialization(type), contextType ?: type, factory)
.serializationOrder
.fold(hasher.putUnencodedChars(name)) { orig, prop ->
fingerprintForType(prop.getter.resolvedType, type, alreadySeen, orig, factory, offset+4)
fingerprintForType(prop.getter.resolvedType, type, alreadySeen, orig, factory, debugIndent+1)
.putUnencodedChars(prop.getter.name)
.putUnencodedChars(if (prop.getter.mandatory) NOT_NULLABLE_HASH else NULLABLE_HASH)
}
interfacesForSerialization(type, factory).map { fingerprintForType(it, type, alreadySeen, hasher, factory, offset+4) }
interfacesForSerialization(type, factory).map { fingerprintForType(it, type, alreadySeen, hasher, factory, debugIndent+4) }
return hasher
}

View File

@ -114,8 +114,8 @@ internal fun <T : Any> propertiesForSerializationFromConstructor(
val returnType = resolveTypeVariables(getter.genericReturnType, type)
if (!constructorParamTakesReturnTypeOfGetter(returnType, getter.genericReturnType, param.value)) {
throw NotSerializableException(
"Property type $returnType for $name of $clazz differs from constructor parameter "
+ "type ${param.value.type.javaType}")
"Property type '$returnType' for '$name' of '$clazz' differs from constructor parameter "
+ "type '${param.value.type.javaType}'")
}
Pair(PublicPropertyReader(getter), returnType)
@ -166,9 +166,20 @@ private fun propertiesForSerializationFromSetters(
}
}
private fun constructorParamTakesReturnTypeOfGetter(getterReturnType: Type, rawGetterReturnType: Type, param: KParameter): Boolean {
val typeToken = TypeToken.of(param.type.javaType)
return typeToken.isSupertypeOf(getterReturnType) || typeToken.isSupertypeOf(rawGetterReturnType)
private fun constructorParamTakesReturnTypeOfGetter(
getterReturnType: Type,
rawGetterReturnType: Type,
param: KParameter): Boolean {
val paramToken = TypeToken.of(param.type.javaType)
val rawParamType = TypeToken.of(paramToken.rawType)
return paramToken.isSupertypeOf(getterReturnType)
|| paramToken.isSupertypeOf(rawGetterReturnType)
// cope with the case where the constructor parameter is a generic type (T etc) but we
// can discover it's raw type. When bounded this wil be the bounding type, unbounded
// generics this will be object
|| rawParamType.isSupertypeOf(getterReturnType)
|| rawParamType.isSupertypeOf(rawGetterReturnType)
}
private fun propertiesForSerializationFromAbstract(

View File

@ -86,15 +86,15 @@ open class SerializationOutput(internal val serializerFactory: SerializerFactory
data.putObject(transformsSchema)
}
internal fun writeObjectOrNull(obj: Any?, data: Data, type: Type, offset: Int) {
internal fun writeObjectOrNull(obj: Any?, data: Data, type: Type, debugIndent: Int) {
if (obj == null) {
data.putNull()
} else {
writeObject(obj, data, if (type == SerializerFactory.AnyType) obj.javaClass else type, offset)
writeObject(obj, data, if (type == SerializerFactory.AnyType) obj.javaClass else type, debugIndent)
}
}
internal fun writeObject(obj: Any, data: Data, type: Type, offset: Int = 0) {
internal fun writeObject(obj: Any, data: Data, type: Type, debugIndent: Int = 0) {
val serializer = serializerFactory.get(obj.javaClass, type)
if (serializer !in serializerHistory) {
serializerHistory.add(serializer)
@ -103,7 +103,7 @@ open class SerializationOutput(internal val serializerFactory: SerializerFactory
val retrievedRefCount = objectHistory[obj]
if (retrievedRefCount == null) {
serializer.writeObject(obj, data, type, this, offset)
serializer.writeObject(obj, data, type, this, debugIndent)
// Important to do it after serialization such that dependent object will have preceding reference numbers
// assigned to them first as they will be first read from the stream on receiving end.
// Skip for primitive types as they are too small and overhead of referencing them will be much higher than their content

View File

@ -22,7 +22,7 @@ class SingletonSerializer(override val type: Class<*>, val singleton: Any, facto
output.writeTypeNotations(typeNotation)
}
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, offset: Int) {
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, debugIndent: Int) {
data.withDescribed(typeNotation.descriptor) {
data.putBoolean(false)
}

View File

@ -126,9 +126,9 @@ fun AMQPField.getTypeAsClass(classloader: ClassLoader) = typeStrToType[Pair(type
"*" -> if (requires.isEmpty()) Any::class.java else classloader.loadClass(requires[0])
else -> {
classloader.loadClass(
if (type.endsWith("<?>")) {
type.substring(0, type.length-3)
} else type)
if (type.endsWith("?>")) {
type.substring(0, type.indexOf('<'))
} else type)
}
}

View File

@ -27,7 +27,7 @@ class TestAttachmentConstraint : AttachmentConstraint {
class GenericsTests {
companion object {
val VERBOSE = false
val VERBOSE = true
@Suppress("UNUSED")
var localPath = projectRootDir.toUri().resolve(
@ -365,7 +365,7 @@ class GenericsTests {
}
@Test
fun anotherTry() {
fun nestedGenericsWithBound() {
open class BaseState(val a : Int)
class DState(a: Int) : BaseState(a)
data class LTransactionState<out T : BaseState> constructor(val data: T)
@ -385,4 +385,120 @@ class GenericsTests {
assertEquals(state.data.a, des1.obj.state.data.a)
}
@Test
fun nestedMultiGenericsWithBound() {
open class BaseState(val a : Int)
class DState(a: Int) : BaseState(a)
class EState(a: Int, val msg: String) : BaseState(a)
data class LTransactionState<out T1 : BaseState, out T2: BaseState> (val data: T1, val context: T2)
data class StateWrapper<out T1 : BaseState, out T2: BaseState>(val state: LTransactionState<T1, T2>)
val factory1 = testDefaultFactoryNoEvolution()
val state = LTransactionState(DState(1020304), EState(5060708, msg = "thigns"))
val stateAndString = StateWrapper(state)
val ser1 = TestSerializationOutput(VERBOSE, factory1).serializeAndReturnSchema(stateAndString)
//val factory2 = testDefaultFactoryNoEvolution()
val factory2 = testDefaultFactory()
val des1 = DeserializationInput(factory2).deserializeAndReturnEnvelope(ser1.obj)
assertEquals(state.data.a, des1.obj.state.data.a)
assertEquals(state.context.a, des1.obj.state.context.a)
}
@Test
fun nestedMultiGenericsNoBound() {
open class BaseState(val a : Int)
class DState(a: Int) : BaseState(a)
class EState(a: Int, val msg: String) : BaseState(a)
data class LTransactionState<out T1, out T2> (val data: T1, val context: T2)
data class StateWrapper<out T1, out T2>(val state: LTransactionState<T1, T2>)
val factory1 = testDefaultFactoryNoEvolution()
val state = LTransactionState(DState(1020304), EState(5060708, msg = "things"))
val stateAndString = StateWrapper(state)
val ser1 = TestSerializationOutput(VERBOSE, factory1).serializeAndReturnSchema(stateAndString)
//val factory2 = testDefaultFactoryNoEvolution()
val factory2 = testDefaultFactory()
val des1 = DeserializationInput(factory2).deserializeAndReturnEnvelope(ser1.obj)
assertEquals(state.data.a, des1.obj.state.data.a)
assertEquals(state.context.a, des1.obj.state.context.a)
assertEquals(state.context.msg, des1.obj.state.context.msg)
}
@Test
fun baseClassInheritedButNotOverriden() {
val factory1 = testDefaultFactoryNoEvolution()
val factory2 = testDefaultFactory()
open class BaseState<T1, T2>(open val a : T1, open val b: T2)
class DState<T1, T2>(a: T1, b: T2) : BaseState<T1, T2>(a, b)
val state = DState(100, "hello")
val ser1 = TestSerializationOutput(VERBOSE, factory1).serializeAndReturnSchema(state)
val des1 = DeserializationInput(factory2).deserializeAndReturnEnvelope(ser1.obj)
assertEquals(state.a, des1.obj.a)
assertEquals(state.b, des1.obj.b)
class DState2<T1, T2, T3>(a: T1, b: T2, val c: T3) : BaseState<T1, T2>(a, b)
val state2 = DState2(100, "hello", 100L)
val ser2 = TestSerializationOutput(VERBOSE, factory1).serializeAndReturnSchema(state2)
val des2 = DeserializationInput(factory2).deserializeAndReturnEnvelope(ser2.obj)
assertEquals(state2.a, des2.obj.a)
assertEquals(state2.b, des2.obj.b)
assertEquals(state2.c, des2.obj.c)
}
@Test
fun baseClassInheritedButNotOverridenBounded() {
val factory1 = testDefaultFactoryNoEvolution()
val factory2 = testDefaultFactory()
open class Bound(val a: Int)
open class BaseState<out T1 : Bound>(open val a: T1)
class DState<out T1: Bound>(a: T1) : BaseState<T1>(a)
val state = DState(Bound(100))
val ser1 = TestSerializationOutput(VERBOSE, factory1).serializeAndReturnSchema(state)
val des1 = DeserializationInput(factory2).deserializeAndReturnEnvelope(ser1.obj)
assertEquals(state.a.a, des1.obj.a.a)
}
@Test
fun nestedMultiGenericsAtBottomWithBound() {
open class BaseState<T1, T2>(val a : T1, val b: T2)
class DState<T1, T2>(a: T1, b: T2) : BaseState<T1, T2>(a, b)
class EState<T1, T2>(a: T1, b: T2, val c: Long) : BaseState<T1, T2>(a, b)
data class LTransactionState<T1, T2, T3 : BaseState<T1, T2>, out T4: BaseState<T1, T2>> (val data: T3, val context: T4)
data class StateWrapper<T1, T2, T3 : BaseState<T1, T2>, out T4: BaseState<T1, T2>>(val state: LTransactionState<T1, T2, T3, T4>)
val factory1 = testDefaultFactoryNoEvolution()
val state = LTransactionState(DState(1020304, "Hello"), EState(5060708, "thins", 100L))
val stateAndString = StateWrapper(state)
val ser1 = TestSerializationOutput(VERBOSE, factory1).serializeAndReturnSchema(stateAndString)
//val factory2 = testDefaultFactoryNoEvolution()
val factory2 = testDefaultFactory()
val des1 = DeserializationInput(factory2).deserializeAndReturnEnvelope(ser1.obj)
assertEquals(state.data.a, des1.obj.state.data.a)
assertEquals(state.context.a, des1.obj.state.context.a)
}
}