mirror of
https://github.com/corda/corda.git
synced 2025-04-07 19:34:41 +00:00
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:
parent
222c5b9db8
commit
00b90a98fb
@ -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 {
|
||||
|
@ -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.
|
||||
|
@ -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) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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]) })
|
||||
}
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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:
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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(
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user