mirror of
https://github.com/corda/corda.git
synced 2025-02-21 09:51:57 +00:00
Corda-1869 serialisation refactor (#3780)
* Pull out and tidy type parameter inference * Contain null proliferation * Extract fingerprinter state * SerializerFingerPrinter is always initialised with a SerializerFactory * Move non-recursive state transition functions into state * Move all state transition functions into state * Simplify and optimise with mutable state * Move TypeParameterUtils back into internal.amqp * Clarify behaviour of constructorForDeserialisation * constructorForDeserialization no longer returns null * Capture field properties * Narrow PropertyDescriptor * Use map rather than apply on a mutable list * Remove printStackTrace added for debugging * CORDA-1869 minor tweaks * Use groupingBy to avoid creating an intermediate map * Convert some functional origami to plain old for-loops * Eliminate nested lambda to unbreak pre-serialisation * Use EnumMap for map of Enums
This commit is contained in:
parent
4337537791
commit
0f36e22314
@ -49,7 +49,7 @@ open class ArraySerializer(override val type: Type, factory: SerializerFactory)
|
||||
|
||||
"$typeName[]"
|
||||
} else {
|
||||
val arrayType = if (type.asClass()!!.componentType.isPrimitive) "[p]" else "[]"
|
||||
val arrayType = if (type.asClass().componentType.isPrimitive) "[p]" else "[]"
|
||||
"${type.componentType().typeName}$arrayType"
|
||||
}
|
||||
}
|
||||
@ -93,7 +93,7 @@ open class ArraySerializer(override val type: Type, factory: SerializerFactory)
|
||||
}
|
||||
|
||||
open fun <T> List<T>.toArrayOfType(type: Type): Any {
|
||||
val elementType = type.asClass() ?: throw AMQPNotSerializableException(type, "Unexpected array element type $type")
|
||||
val elementType = type.asClass()
|
||||
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]) }
|
||||
@ -105,7 +105,7 @@ open class ArraySerializer(override val type: Type, factory: SerializerFactory)
|
||||
// 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<Char>::class.java, factory) {
|
||||
override fun <T> List<T>.toArrayOfType(type: Type): Any {
|
||||
val elementType = type.asClass() ?: throw AMQPNotSerializableException(type, "Unexpected array element type $type")
|
||||
val elementType = type.asClass()
|
||||
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()) }
|
||||
@ -159,11 +159,7 @@ class PrimCharArraySerializer(factory: SerializerFactory) : PrimArraySerializer(
|
||||
}
|
||||
|
||||
override fun <T> List<T>.toArrayOfType(type: Type): Any {
|
||||
val elementType = type.asClass() ?: throw AMQPNotSerializableException(
|
||||
type,
|
||||
"Unexpected array element type $type",
|
||||
"blob is corrupt")
|
||||
|
||||
val elementType = type.asClass()
|
||||
val list = this
|
||||
return java.lang.reflect.Array.newInstance(elementType, this.size).apply {
|
||||
val array = this
|
||||
|
@ -93,8 +93,8 @@ class CorDappCustomSerializer(
|
||||
* For 3rd party plugin serializers we are going to exist on exact type matching. i.e. we will
|
||||
* not support base class serializers for derivedtypes
|
||||
*/
|
||||
override fun isSerializerFor(clazz: Class<*>) : Boolean {
|
||||
return type.asClass()?.let { TypeToken.of(it) == TypeToken.of(clazz) } ?: false
|
||||
}
|
||||
override fun isSerializerFor(clazz: Class<*>) =
|
||||
TypeToken.of(type.asClass()) == TypeToken.of(clazz)
|
||||
|
||||
}
|
||||
|
||||
|
@ -67,7 +67,7 @@ abstract class CustomSerializer<T : Any> : AMQPSerializer<T>, SerializerFor {
|
||||
override fun isSerializerFor(clazz: Class<*>): Boolean = clazz == this.clazz
|
||||
override val type: Type get() = clazz
|
||||
override val typeDescriptor: Symbol by lazy {
|
||||
Symbol.valueOf("$DESCRIPTOR_DOMAIN:${SerializerFingerPrinter().fingerprintForDescriptors(superClassSerializer.typeDescriptor.toString(), nameForType(clazz))}")
|
||||
Symbol.valueOf("$DESCRIPTOR_DOMAIN:${fingerprintForDescriptors(superClassSerializer.typeDescriptor.toString(), nameForType(clazz))}")
|
||||
}
|
||||
private val typeNotation: TypeNotation = RestrictedType(
|
||||
SerializerFactory.nameForType(clazz),
|
||||
|
@ -156,7 +156,7 @@ class DeserializationInput constructor(
|
||||
"is outside of the bounds for the list of size: ${objectHistory.size}")
|
||||
|
||||
val objectRetrieved = objectHistory[objectIndex]
|
||||
if (!objectRetrieved::class.java.isSubClassOf(type.asClass()!!)) {
|
||||
if (!objectRetrieved::class.java.isSubClassOf(type.asClass())) {
|
||||
throw AMQPNotSerializableException(
|
||||
type,
|
||||
"Existing reference type mismatch. Expected: '$type', found: '${objectRetrieved::class.java}' " +
|
||||
|
@ -80,7 +80,7 @@ class EnumEvolutionSerializer(
|
||||
val renameRules: List<RenameSchemaTransform>? = uncheckedCast(transforms[TransformTypes.Rename])
|
||||
|
||||
// What values exist on the enum as it exists on the class path
|
||||
val localValues = new.type.asClass()!!.enumConstants.map { it.toString() }
|
||||
val localValues = new.type.asClass().enumConstants.map { it.toString() }
|
||||
|
||||
val conversions: MutableMap<String, String> = localValues
|
||||
.union(defaultRules?.map { it.new }?.toSet() ?: emptySet())
|
||||
@ -130,7 +130,7 @@ class EnumEvolutionSerializer(
|
||||
throw AMQPNotSerializableException(type, "No rule to evolve enum constant $type::$enumName")
|
||||
}
|
||||
|
||||
return type.asClass()!!.enumConstants[ordinals[conversions[enumName]]!!]
|
||||
return type.asClass().enumConstants[ordinals[conversions[enumName]]!!]
|
||||
}
|
||||
|
||||
override fun writeClassInfo(output: SerializationOutput) {
|
||||
|
@ -34,7 +34,7 @@ class EnumSerializer(declaredType: Type, declaredClass: Class<*>, factory: Seria
|
||||
): Any {
|
||||
val enumName = (obj as List<*>)[0] as String
|
||||
val enumOrd = obj[1] as Int
|
||||
val fromOrd = type.asClass()!!.enumConstants[enumOrd] as Enum<*>?
|
||||
val fromOrd = type.asClass().enumConstants[enumOrd] as Enum<*>?
|
||||
|
||||
if (enumName != fromOrd?.name) {
|
||||
throw AMQPNotSerializableException(
|
||||
|
@ -32,7 +32,7 @@ abstract class EvolutionSerializer(
|
||||
clazz: Type,
|
||||
factory: SerializerFactory,
|
||||
protected val oldReaders: Map<String, OldParam>,
|
||||
override val kotlinConstructor: KFunction<Any>?
|
||||
override val kotlinConstructor: KFunction<Any>
|
||||
) : ObjectSerializer(clazz, factory) {
|
||||
// explicitly set as empty to indicate it's unused by this type of serializer
|
||||
override val propertySerializers = PropertySerializersEvolution()
|
||||
@ -74,7 +74,7 @@ abstract class EvolutionSerializer(
|
||||
* TODO: rename annotation
|
||||
*/
|
||||
private fun getEvolverConstructor(type: Type, oldArgs: Map<String, OldParam>): KFunction<Any>? {
|
||||
val clazz: Class<*> = type.asClass()!!
|
||||
val clazz: Class<*> = type.asClass()
|
||||
|
||||
if (!clazz.isConcreteClass) return null
|
||||
|
||||
@ -189,7 +189,7 @@ abstract class EvolutionSerializer(
|
||||
// return the synthesised object which is, given the absence of a constructor, a no op
|
||||
val constructor = getEvolverConstructor(new.type, readersAsSerialized) ?: return new
|
||||
|
||||
val classProperties = new.type.asClass()?.propertyDescriptors() ?: emptyMap()
|
||||
val classProperties = new.type.asClass().propertyDescriptors()
|
||||
|
||||
return if (classProperties.isNotEmpty() && constructor.parameters.isEmpty()) {
|
||||
makeWithSetters(new, factory, constructor, readersAsSerialized, classProperties)
|
||||
@ -210,7 +210,7 @@ class EvolutionSerializerViaConstructor(
|
||||
clazz: Type,
|
||||
factory: SerializerFactory,
|
||||
oldReaders: Map<String, EvolutionSerializer.OldParam>,
|
||||
kotlinConstructor: KFunction<Any>?,
|
||||
kotlinConstructor: KFunction<Any>,
|
||||
private val constructorArgs: Array<Any?>) : EvolutionSerializer(clazz, factory, oldReaders, kotlinConstructor) {
|
||||
/**
|
||||
* Unlike a normal [readObject] call where we simply apply the parameter deserialisers
|
||||
@ -242,7 +242,7 @@ class EvolutionSerializerViaSetters(
|
||||
clazz: Type,
|
||||
factory: SerializerFactory,
|
||||
oldReaders: Map<String, EvolutionSerializer.OldParam>,
|
||||
kotlinConstructor: KFunction<Any>?,
|
||||
kotlinConstructor: KFunction<Any>,
|
||||
private val setters: Map<String, PropertyAccessor>) : EvolutionSerializer(clazz, factory, oldReaders, kotlinConstructor) {
|
||||
|
||||
override fun readObject(obj: Any, schemas: SerializationSchemas, input: DeserializationInput,
|
||||
|
@ -3,15 +3,15 @@ package net.corda.serialization.internal.amqp
|
||||
import com.google.common.hash.Hasher
|
||||
import com.google.common.hash.Hashing
|
||||
import net.corda.core.KeepForDJVM
|
||||
import net.corda.core.internal.isConcreteClass
|
||||
import net.corda.core.internal.kotlinObjectInstance
|
||||
import net.corda.core.utilities.loggerFor
|
||||
import net.corda.core.utilities.toBase64
|
||||
import java.io.NotSerializableException
|
||||
import net.corda.serialization.internal.amqp.SerializerFactory.Companion.isPrimitive
|
||||
import java.lang.reflect.*
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Should be implemented by classes which wish to provide plugable fingerprinting og types for a [SerializerFactory]
|
||||
* Should be implemented by classes which wish to provide pluggable fingerprinting on types for a [SerializerFactory]
|
||||
*/
|
||||
@KeepForDJVM
|
||||
interface FingerPrinter {
|
||||
@ -20,34 +20,13 @@ interface FingerPrinter {
|
||||
* of said type such that any modification to any sub element wll generate a different fingerprint
|
||||
*/
|
||||
fun fingerprint(type: Type): String
|
||||
|
||||
/**
|
||||
* If required, associate an instance of the fingerprinter with a specific serializer factory
|
||||
*/
|
||||
fun setOwner(factory: SerializerFactory)
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of the finger printing mechanism used by default
|
||||
*/
|
||||
@KeepForDJVM
|
||||
class SerializerFingerPrinter : FingerPrinter {
|
||||
private var factory: SerializerFactory? = null
|
||||
|
||||
private val ARRAY_HASH: String = "Array = true"
|
||||
private val ENUM_HASH: String = "Enum = true"
|
||||
private val ALREADY_SEEN_HASH: String = "Already seen = true"
|
||||
private val NULLABLE_HASH: String = "Nullable = true"
|
||||
private val NOT_NULLABLE_HASH: String = "Nullable = false"
|
||||
private val ANY_TYPE_HASH: String = "Any type = true"
|
||||
private val TYPE_VARIABLE_HASH: String = "Type variable = true"
|
||||
private val WILDCARD_TYPE_HASH: String = "Wild card = true"
|
||||
|
||||
private val logger by lazy { loggerFor<Schema>() }
|
||||
|
||||
override fun setOwner(factory: SerializerFactory) {
|
||||
this.factory = factory
|
||||
}
|
||||
class SerializerFingerPrinter(val factory: SerializerFactory) : FingerPrinter {
|
||||
|
||||
/**
|
||||
* The method generates a fingerprint for a given JVM [Type] that should be unique to the schema representation.
|
||||
@ -57,147 +36,167 @@ class SerializerFingerPrinter : FingerPrinter {
|
||||
* The idea being that even for two classes that share the same name but differ in a minor way, the fingerprint will be
|
||||
* different.
|
||||
*/
|
||||
override fun fingerprint(type: Type): String {
|
||||
return fingerprintForType(
|
||||
type, null, HashSet(), Hashing.murmur3_128().newHasher(), debugIndent = 1).hash().asBytes().toBase64()
|
||||
override fun fingerprint(type: Type): String = FingerPrintingState(factory).fingerprint(type)
|
||||
}
|
||||
|
||||
// Representation of the current state of fingerprinting
|
||||
internal class FingerPrintingState(private val factory: SerializerFactory) {
|
||||
|
||||
companion object {
|
||||
private const val ARRAY_HASH: String = "Array = true"
|
||||
private const val ENUM_HASH: String = "Enum = true"
|
||||
private const val ALREADY_SEEN_HASH: String = "Already seen = true"
|
||||
private const val NULLABLE_HASH: String = "Nullable = true"
|
||||
private const val NOT_NULLABLE_HASH: String = "Nullable = false"
|
||||
private const val ANY_TYPE_HASH: String = "Any type = true"
|
||||
}
|
||||
|
||||
private fun isCollectionOrMap(type: Class<*>) =
|
||||
(Collection::class.java.isAssignableFrom(type) || Map::class.java.isAssignableFrom(type))
|
||||
&& !EnumSet::class.java.isAssignableFrom(type)
|
||||
private val typesSeen: MutableSet<Type> = mutableSetOf()
|
||||
private var currentContext: Type? = null
|
||||
private var hasher: Hasher = newDefaultHasher()
|
||||
|
||||
internal fun fingerprintForDescriptors(vararg typeDescriptors: String): String {
|
||||
val hasher = Hashing.murmur3_128().newHasher()
|
||||
for (typeDescriptor in typeDescriptors) {
|
||||
hasher.putUnencodedChars(typeDescriptor)
|
||||
}
|
||||
return hasher.hash().asBytes().toBase64()
|
||||
}
|
||||
|
||||
private fun Hasher.fingerprintWithCustomSerializerOrElse(
|
||||
factory: SerializerFactory,
|
||||
clazz: Class<*>,
|
||||
declaredType: Type,
|
||||
block: () -> Hasher): Hasher {
|
||||
// Need to check if a custom serializer is applicable
|
||||
val customSerializer = factory.findCustomSerializer(clazz, declaredType)
|
||||
return if (customSerializer != null) {
|
||||
putUnencodedChars(customSerializer.typeDescriptor)
|
||||
} else {
|
||||
block()
|
||||
}
|
||||
}
|
||||
// Fingerprint the type recursively, and return the encoded fingerprint written into the hasher.
|
||||
fun fingerprint(type: Type) = fingerprintType(type).hasher.fingerprint
|
||||
|
||||
// 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, 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
|
||||
// serialise the object in the first place (and thus the cache lookup fails). This is also
|
||||
// true of Any, where we need Example<A, B> and Example<?, ?> to have the same fingerprint
|
||||
return if ((type in alreadySeen)
|
||||
&& (type !== SerializerFactory.AnyType)
|
||||
&& (type !is TypeVariable<*>)
|
||||
&& (type !is WildcardType)
|
||||
) {
|
||||
hasher.putUnencodedChars(ALREADY_SEEN_HASH)
|
||||
} else {
|
||||
alreadySeen += type
|
||||
ifThrowsAppend<Hasher>({ type.typeName }) {
|
||||
when (type) {
|
||||
is ParameterizedType -> {
|
||||
// Hash the rawType + params
|
||||
val clazz = type.rawType as Class<*>
|
||||
private fun fingerprintType(type: Type): FingerPrintingState = apply {
|
||||
// Don't go round in circles.
|
||||
if (hasSeen(type)) append(ALREADY_SEEN_HASH)
|
||||
else ifThrowsAppend(
|
||||
{ type.typeName },
|
||||
{
|
||||
typesSeen.add(type)
|
||||
currentContext = type
|
||||
fingerprintNewType(type)
|
||||
})
|
||||
}
|
||||
|
||||
val startingHash = if (isCollectionOrMap(clazz)) {
|
||||
hasher.putUnencodedChars(clazz.name)
|
||||
} else {
|
||||
hasher.fingerprintWithCustomSerializerOrElse(factory!!, clazz, type) {
|
||||
fingerprintForObject(type, type, alreadySeen, hasher, factory!!, debugIndent + 1)
|
||||
}
|
||||
}
|
||||
// For a type we haven't seen before, determine the correct path depending on the type of type it is.
|
||||
private fun fingerprintNewType(type: Type) = when (type) {
|
||||
is ParameterizedType -> fingerprintParameterizedType(type)
|
||||
// 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 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
|
||||
// end up breaking into the evolver when we shouldn't or, worse, evoking the carpenter.
|
||||
is SerializerFactory.AnyType,
|
||||
is WildcardType,
|
||||
is TypeVariable<*> -> append("?$ANY_TYPE_HASH")
|
||||
is Class<*> -> fingerprintClass(type)
|
||||
is GenericArrayType -> fingerprintType(type.genericComponentType).append(ARRAY_HASH)
|
||||
else -> throw AMQPNotSerializableException(type, "Don't know how to hash")
|
||||
}
|
||||
|
||||
// ... and concatenate the type data for each parameter type.
|
||||
type.actualTypeArguments.fold(startingHash) { orig, paramType ->
|
||||
fingerprintForType(paramType, type, alreadySeen, orig, debugIndent + 1)
|
||||
}
|
||||
}
|
||||
// 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 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
|
||||
// end up breaking into the evolver when we shouldn't or, worse, evoking the carpenter.
|
||||
is SerializerFactory.AnyType,
|
||||
is WildcardType,
|
||||
is TypeVariable<*> -> {
|
||||
hasher.putUnencodedChars("?").putUnencodedChars(ANY_TYPE_HASH)
|
||||
}
|
||||
is Class<*> -> {
|
||||
if (type.isArray) {
|
||||
fingerprintForType(type.componentType, contextType, alreadySeen, hasher, debugIndent + 1)
|
||||
.putUnencodedChars(ARRAY_HASH)
|
||||
} else if (SerializerFactory.isPrimitive(type)) {
|
||||
hasher.putUnencodedChars(type.name)
|
||||
} else if (isCollectionOrMap(type)) {
|
||||
hasher.putUnencodedChars(type.name)
|
||||
} else if (type.isEnum) {
|
||||
// ensures any change to the enum (adding constants) will trigger the need for evolution
|
||||
hasher.apply {
|
||||
type.enumConstants.forEach {
|
||||
putUnencodedChars(it.toString())
|
||||
}
|
||||
}.putUnencodedChars(type.name).putUnencodedChars(ENUM_HASH)
|
||||
} else {
|
||||
hasher.fingerprintWithCustomSerializerOrElse(factory!!, type, type) {
|
||||
if (type.kotlinObjectInstance != null) {
|
||||
// TODO: name collision is too likely for kotlin objects, we need to introduce some
|
||||
// reference to the CorDapp but maybe reference to the JAR in the short term.
|
||||
hasher.putUnencodedChars(type.name)
|
||||
} else {
|
||||
fingerprintForObject(type, type, alreadySeen, hasher, factory!!, debugIndent + 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Hash the element type + some array hash
|
||||
is GenericArrayType -> {
|
||||
fingerprintForType(type.genericComponentType, contextType, alreadySeen,
|
||||
hasher, debugIndent + 1).putUnencodedChars(ARRAY_HASH)
|
||||
}
|
||||
else -> throw AMQPNotSerializableException(type, "Don't know how to hash")
|
||||
}
|
||||
}
|
||||
private fun fingerprintClass(type: Class<*>) = when {
|
||||
type.isArray -> fingerprintType(type.componentType).append(ARRAY_HASH)
|
||||
type.isPrimitiveOrCollection -> append(type.name)
|
||||
type.isEnum -> fingerprintEnum(type)
|
||||
else -> fingerprintWithCustomSerializerOrElse(type, type) {
|
||||
if (type.kotlinObjectInstance != null) append(type.name)
|
||||
else fingerprintObject(type)
|
||||
}
|
||||
}
|
||||
|
||||
private fun fingerprintForObject(
|
||||
type: Type,
|
||||
contextType: Type?,
|
||||
alreadySeen: MutableSet<Type>,
|
||||
hasher: Hasher,
|
||||
factory: SerializerFactory,
|
||||
debugIndent: Int = 0): Hasher {
|
||||
// Hash the class + properties + interfaces
|
||||
val name = type.asClass()?.name
|
||||
?: throw AMQPNotSerializableException(type, "Expected only Class or ParameterizedType but found $type")
|
||||
private fun fingerprintParameterizedType(type: ParameterizedType) {
|
||||
// Hash the rawType + params
|
||||
type.asClass().let { clazz ->
|
||||
if (clazz.isCollectionOrMap) append(clazz.name)
|
||||
else fingerprintWithCustomSerializerOrElse(clazz, type) {
|
||||
fingerprintObject(type)
|
||||
}
|
||||
}
|
||||
|
||||
propertiesForSerialization(constructorForDeserialization(type), contextType ?: type, factory)
|
||||
.serializationOrder
|
||||
.fold(hasher.putUnencodedChars(name)) { orig, prop ->
|
||||
fingerprintForType(prop.serializer.resolvedType, type, alreadySeen, orig, debugIndent + 1)
|
||||
.putUnencodedChars(prop.serializer.name)
|
||||
.putUnencodedChars(if (prop.serializer.mandatory) NOT_NULLABLE_HASH else NULLABLE_HASH)
|
||||
}
|
||||
interfacesForSerialization(type, factory).map { fingerprintForType(it, type, alreadySeen, hasher, debugIndent + 1) }
|
||||
return hasher
|
||||
// ...and concatenate the type data for each parameter type.
|
||||
type.actualTypeArguments.forEach { paramType ->
|
||||
fingerprintType(paramType)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun fingerprintObject(type: Type) {
|
||||
// Hash the class + properties + interfaces
|
||||
append(type.asClass().name)
|
||||
|
||||
orderedPropertiesForSerialization(type).forEach { prop ->
|
||||
fingerprintType(prop.serializer.resolvedType)
|
||||
fingerprintPropSerialiser(prop)
|
||||
}
|
||||
|
||||
interfacesForSerialization(type, factory).forEach { iface ->
|
||||
fingerprintType(iface)
|
||||
}
|
||||
}
|
||||
|
||||
// ensures any change to the enum (adding constants) will trigger the need for evolution
|
||||
private fun fingerprintEnum(type: Class<*>) {
|
||||
append(type.enumConstants.joinToString())
|
||||
append(type.name)
|
||||
append(ENUM_HASH)
|
||||
}
|
||||
|
||||
private fun fingerprintPropSerialiser(prop: PropertyAccessor) {
|
||||
append(prop.serializer.name)
|
||||
append(if (prop.serializer.mandatory) NOT_NULLABLE_HASH
|
||||
else NULLABLE_HASH)
|
||||
}
|
||||
|
||||
// Write the given character sequence into the hasher.
|
||||
private fun append(chars: CharSequence) {
|
||||
hasher = hasher.putUnencodedChars(chars)
|
||||
}
|
||||
|
||||
// Give any custom serializers loaded into the factory the chance to supply their own type-descriptors
|
||||
private fun fingerprintWithCustomSerializerOrElse(
|
||||
clazz: Class<*>,
|
||||
declaredType: Type,
|
||||
defaultAction: () -> Unit)
|
||||
: Unit = factory.findCustomSerializer(clazz, declaredType)?.let {
|
||||
append(it.typeDescriptor)
|
||||
} ?: defaultAction()
|
||||
|
||||
// Test whether we are in a state in which we have already seen the given type.
|
||||
//
|
||||
// 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
|
||||
// serialise the object in the first place (and thus the cache lookup fails). This is also
|
||||
// true of Any, where we need Example<A, B> and Example<?, ?> to have the same fingerprint
|
||||
private fun hasSeen(type: Type) = (type in typesSeen)
|
||||
&& (type !== SerializerFactory.AnyType)
|
||||
&& (type !is TypeVariable<*>)
|
||||
&& (type !is WildcardType)
|
||||
|
||||
private fun orderedPropertiesForSerialization(type: Type): List<PropertyAccessor> {
|
||||
return propertiesForSerialization(
|
||||
if (type.asClass().isConcreteClass) constructorForDeserialization(type) else null,
|
||||
currentContext ?: type,
|
||||
factory).serializationOrder
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// region Utility functions
|
||||
|
||||
// Create a new instance of the [Hasher] used for fingerprinting by the default [SerializerFingerPrinter]
|
||||
private fun newDefaultHasher() = Hashing.murmur3_128().newHasher()
|
||||
|
||||
// We obtain a fingerprint from a [Hasher] by taking the Base 64 encoding of its hash bytes
|
||||
private val Hasher.fingerprint get() = hash().asBytes().toBase64()
|
||||
|
||||
internal fun fingerprintForDescriptors(vararg typeDescriptors: String): String =
|
||||
newDefaultHasher().putUnencodedChars(typeDescriptors.joinToString()).fingerprint
|
||||
|
||||
private val Class<*>.isCollectionOrMap get() =
|
||||
(Collection::class.java.isAssignableFrom(this) || Map::class.java.isAssignableFrom(this))
|
||||
&& !EnumSet::class.java.isAssignableFrom(this)
|
||||
|
||||
private val Class<*>.isPrimitiveOrCollection get() =
|
||||
isPrimitive(this) || isCollectionOrMap
|
||||
// endregion
|
||||
|
@ -1,5 +1,6 @@
|
||||
package net.corda.serialization.internal.amqp
|
||||
|
||||
import net.corda.core.internal.isConcreteClass
|
||||
import net.corda.core.serialization.SerializationContext
|
||||
import net.corda.core.utilities.contextLogger
|
||||
import net.corda.core.utilities.trace
|
||||
@ -18,7 +19,7 @@ import kotlin.reflect.jvm.javaConstructor
|
||||
*/
|
||||
open class ObjectSerializer(val clazz: Type, factory: SerializerFactory) : AMQPSerializer<Any> {
|
||||
override val type: Type get() = clazz
|
||||
open val kotlinConstructor = constructorForDeserialization(clazz)
|
||||
open val kotlinConstructor = if (clazz.asClass().isConcreteClass) constructorForDeserialization(clazz) else null
|
||||
val javaConstructor by lazy { kotlinConstructor?.javaConstructor }
|
||||
|
||||
companion object {
|
||||
|
@ -0,0 +1,205 @@
|
||||
package net.corda.serialization.internal.amqp
|
||||
|
||||
import com.google.common.reflect.TypeToken
|
||||
import net.corda.core.KeepForDJVM
|
||||
import net.corda.core.internal.isPublic
|
||||
import net.corda.serialization.internal.amqp.MethodClassifier.*
|
||||
import java.lang.reflect.Field
|
||||
import java.lang.reflect.Method
|
||||
import java.lang.reflect.Type
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Encapsulates the property of a class and its potential getter and setter methods.
|
||||
*
|
||||
* @property field a property of a class.
|
||||
* @property setter the method of a class that sets the field. Determined by locating
|
||||
* a function called setXyz on the class for the property named in field as xyz.
|
||||
* @property getter the method of a class that returns a fields value. Determined by
|
||||
* locating a function named getXyz for the property named in field as xyz.
|
||||
*/
|
||||
@KeepForDJVM
|
||||
data class PropertyDescriptor(val field: Field?, val setter: Method?, val getter: Method?) {
|
||||
override fun toString() = StringBuilder("").apply {
|
||||
appendln("Property - ${field?.name ?: "null field"}\n")
|
||||
appendln(" getter - ${getter?.name ?: "no getter"}")
|
||||
appendln(" setter - ${setter?.name ?: "no setter"}")
|
||||
}.toString()
|
||||
|
||||
/**
|
||||
* Check the types of the field, getter and setter methods against each other.
|
||||
*/
|
||||
fun validate() {
|
||||
getter?.apply {
|
||||
val getterType = genericReturnType
|
||||
field?.apply {
|
||||
if (!getterType.isSupertypeOf(genericReturnType))
|
||||
throw AMQPNotSerializableException(
|
||||
declaringClass,
|
||||
"Defined getter for parameter $name returns type $getterType " +
|
||||
"yet underlying type is $genericType")
|
||||
}
|
||||
}
|
||||
|
||||
setter?.apply {
|
||||
val setterType = genericParameterTypes[0]!!
|
||||
|
||||
field?.apply {
|
||||
if (!genericType.isSupertypeOf(setterType))
|
||||
throw AMQPNotSerializableException(
|
||||
declaringClass,
|
||||
"Defined setter for parameter $name takes parameter of type $setterType " +
|
||||
"yet underlying type is $genericType")
|
||||
}
|
||||
|
||||
getter?.apply {
|
||||
if (!genericReturnType.isSupertypeOf(setterType))
|
||||
throw AMQPNotSerializableException(
|
||||
declaringClass,
|
||||
"Defined setter for parameter $name takes parameter of type $setterType, " +
|
||||
"but getter returns $genericReturnType")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun Type.isSupertypeOf(that: Type) = TypeToken.of(this).isSupertypeOf(that)
|
||||
|
||||
// match an uppercase letter that also has a corresponding lower case equivalent
|
||||
private val propertyMethodRegex = Regex("(?<type>get|set|is)(?<var>\\p{Lu}.*)")
|
||||
|
||||
/**
|
||||
* Collate the properties of a class and match them with their getter and setter
|
||||
* methods as per a JavaBean.
|
||||
*
|
||||
* for a property
|
||||
* exampleProperty
|
||||
*
|
||||
* We look for methods
|
||||
* setExampleProperty
|
||||
* getExampleProperty
|
||||
* isExampleProperty
|
||||
*
|
||||
* Where getExampleProperty must return a type compatible with exampleProperty, setExampleProperty must
|
||||
* take a single parameter of a type compatible with exampleProperty and isExampleProperty must
|
||||
* return a boolean
|
||||
*/
|
||||
fun Class<out Any?>.propertyDescriptors(): Map<String, PropertyDescriptor> {
|
||||
val fieldProperties = superclassChain().declaredFields().byFieldName()
|
||||
|
||||
return superclassChain().declaredMethods()
|
||||
.thatArePublic()
|
||||
.thatArePropertyMethods()
|
||||
.withValidSignature()
|
||||
.byNameAndClassifier(fieldProperties.keys)
|
||||
.toClassProperties(fieldProperties)
|
||||
.validated()
|
||||
}
|
||||
|
||||
// Generate the sequence of classes starting with this class and ascending through it superclasses.
|
||||
private fun Class<*>.superclassChain() = generateSequence(this, Class<*>::getSuperclass)
|
||||
|
||||
// Obtain the fields declared by all classes in this sequence of classes.
|
||||
private fun Sequence<Class<*>>.declaredFields() = flatMap { it.declaredFields.asSequence() }
|
||||
|
||||
// Obtain the methods declared by all classes in this sequence of classes.
|
||||
private fun Sequence<Class<*>>.declaredMethods() = flatMap { it.declaredMethods.asSequence() }
|
||||
|
||||
// Map a sequence of fields by field name.
|
||||
private fun Sequence<Field>.byFieldName() = map { it.name to it }.toMap()
|
||||
|
||||
// Select only those methods that are public (and are not the "getClass" method)
|
||||
private fun Sequence<Method>.thatArePublic() = filter { it.isPublic && it.name != "getClass" }
|
||||
|
||||
// Select only those methods that are isX/getX/setX methods
|
||||
private fun Sequence<Method>.thatArePropertyMethods() = map { method ->
|
||||
propertyMethodRegex.find(method.name)?.let { result ->
|
||||
PropertyNamedMethod(
|
||||
result.groups[2]!!.value,
|
||||
MethodClassifier.valueOf(result.groups[1]!!.value.toUpperCase()),
|
||||
method)
|
||||
}
|
||||
}.filterNotNull()
|
||||
|
||||
// Pick only those methods whose signatures are valid, discarding the remainder without warning.
|
||||
private fun Sequence<PropertyNamedMethod>.withValidSignature() = filter { it.hasValidSignature() }
|
||||
|
||||
// Group methods by name and classifier, picking the method with the least generic signature if there is more than one
|
||||
// of a given name and type.
|
||||
private fun Sequence<PropertyNamedMethod>.byNameAndClassifier(fieldNames: Set<String>): Map<String, Map<MethodClassifier, Method>> {
|
||||
val result = mutableMapOf<String, EnumMap<MethodClassifier, Method>>()
|
||||
|
||||
forEach { (fieldName, classifier, method) ->
|
||||
result.compute(getPropertyName(fieldName, fieldNames)) { _, byClassifier ->
|
||||
(byClassifier ?: EnumMap(MethodClassifier::class.java)).merge(classifier, method)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Merge the given method into a map of methods by method classifier, picking the least generic method for each classifier.
|
||||
private fun EnumMap<MethodClassifier, Method>.merge(classifier: MethodClassifier, method: Method): EnumMap<MethodClassifier, Method> {
|
||||
compute(classifier) { _, existingMethod ->
|
||||
if (existingMethod == null) method
|
||||
else when (classifier) {
|
||||
IS -> existingMethod
|
||||
GET -> leastGenericBy({ genericReturnType }, existingMethod, method)
|
||||
SET -> leastGenericBy({ genericParameterTypes[0] }, existingMethod, method)
|
||||
}
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
// Make the property name conform to the underlying field name, if there is one.
|
||||
private fun getPropertyName(propertyName: String, fieldNames: Set<String>) =
|
||||
if (propertyName.decapitalize() in fieldNames) propertyName.decapitalize()
|
||||
else propertyName
|
||||
|
||||
|
||||
// Which of the three types of property method the method is.
|
||||
private enum class MethodClassifier { GET, SET, IS }
|
||||
|
||||
private data class PropertyNamedMethod(val fieldName: String, val classifier: MethodClassifier, val method: Method) {
|
||||
// Validate the method's signature against its classifier
|
||||
fun hasValidSignature(): Boolean = method.run {
|
||||
when (classifier) {
|
||||
GET -> parameterCount == 0 && returnType != Void.TYPE
|
||||
SET -> parameterCount == 1 && returnType == Void.TYPE
|
||||
IS -> parameterCount == 0 &&
|
||||
(returnType == Boolean::class.java ||
|
||||
returnType == Boolean::class.javaObjectType)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Construct a map of PropertyDescriptors by name, by merging the raw field map with the map of classified property methods
|
||||
private fun Map<String, Map<MethodClassifier, Method>>.toClassProperties(fieldMap: Map<String, Field>): Map<String, PropertyDescriptor> {
|
||||
val result = mutableMapOf<String, PropertyDescriptor>()
|
||||
|
||||
// Fields for which we have no property methods
|
||||
for ((name, field) in fieldMap) {
|
||||
if (name !in keys) {
|
||||
result[name] = PropertyDescriptor(field, null, null)
|
||||
}
|
||||
}
|
||||
|
||||
for ((name, methodMap) in this) {
|
||||
result[name] = PropertyDescriptor(
|
||||
fieldMap[name],
|
||||
methodMap[SET],
|
||||
methodMap[GET] ?: methodMap[IS]
|
||||
)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Select the least generic of two methods by a type associated with each.
|
||||
private fun leastGenericBy(feature: Method.() -> Type, first: Method, second: Method) =
|
||||
if (first.feature().isSupertypeOf(second.feature())) second else first
|
||||
|
||||
// Throw an exception if any property descriptor is inconsistent, e.g. the types don't match
|
||||
private fun Map<String, PropertyDescriptor>.validated() = apply {
|
||||
forEach { _, value -> value.validate() }
|
||||
}
|
@ -19,8 +19,8 @@ sealed class PropertySerializer(val name: String, val propertyReader: PropertyRe
|
||||
val default: String? = generateDefault()
|
||||
val mandatory: Boolean = generateMandatory()
|
||||
|
||||
private val isInterface: Boolean get() = resolvedType.asClass()?.isInterface == true
|
||||
private val isJVMPrimitive: Boolean get() = resolvedType.asClass()?.isPrimitive == true
|
||||
private val isInterface: Boolean get() = resolvedType.asClass().isInterface
|
||||
private val isJVMPrimitive: Boolean get() = resolvedType.asClass().isPrimitive
|
||||
|
||||
private fun generateType(): String {
|
||||
return if (isInterface || resolvedType == Any::class.java) "*" else SerializerFactory.nameForType(resolvedType)
|
||||
|
@ -2,15 +2,12 @@ package net.corda.serialization.internal.amqp
|
||||
|
||||
import com.google.common.primitives.Primitives
|
||||
import com.google.common.reflect.TypeToken
|
||||
import net.corda.core.KeepForDJVM
|
||||
import net.corda.core.internal.isConcreteClass
|
||||
import net.corda.core.internal.isPublic
|
||||
import net.corda.core.serialization.ClassWhitelist
|
||||
import net.corda.core.serialization.ConstructorForDeserialization
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
import net.corda.core.serialization.SerializationContext
|
||||
import org.apache.qpid.proton.codec.Data
|
||||
import java.io.NotSerializableException
|
||||
import java.lang.reflect.*
|
||||
import java.lang.reflect.Field
|
||||
import java.util.*
|
||||
@ -26,42 +23,37 @@ import kotlin.reflect.jvm.javaType
|
||||
/**
|
||||
* Code for finding the constructor we will use for deserialization.
|
||||
*
|
||||
* If there's only one constructor, it selects that. If there are two and one is the default, it selects the other.
|
||||
* Otherwise it starts with the primary constructor in kotlin, if there is one, and then will override this with any that is
|
||||
* annotated with [@ConstructorForDeserialization]. It will report an error if more than one constructor is annotated.
|
||||
* If any constructor is uniquely annotated with [@ConstructorForDeserialization], then that constructor is chosen.
|
||||
* An error is reported if more than one constructor is annotated.
|
||||
*
|
||||
* Otherwise, if there is a Kotlin primary constructor, it selects that, and if not it selects either the unique
|
||||
* constructor or, if there are two and one is the default no-argument constructor, the non-default constructor.
|
||||
*/
|
||||
fun constructorForDeserialization(type: Type): KFunction<Any>? {
|
||||
val clazz: Class<*> = type.asClass()!!
|
||||
if (clazz.isConcreteClass) {
|
||||
var preferredCandidate: KFunction<Any>? = clazz.kotlin.primaryConstructor
|
||||
var annotatedCount = 0
|
||||
val kotlinConstructors = clazz.kotlin.constructors
|
||||
val hasDefault = kotlinConstructors.any { it.parameters.isEmpty() }
|
||||
|
||||
for (kotlinConstructor in kotlinConstructors) {
|
||||
if (preferredCandidate == null && kotlinConstructors.size == 1) {
|
||||
preferredCandidate = kotlinConstructor
|
||||
} else if (preferredCandidate == null &&
|
||||
kotlinConstructors.size == 2 &&
|
||||
hasDefault &&
|
||||
kotlinConstructor.parameters.isNotEmpty()
|
||||
) {
|
||||
preferredCandidate = kotlinConstructor
|
||||
} else if (kotlinConstructor.findAnnotation<ConstructorForDeserialization>() != null) {
|
||||
if (annotatedCount++ > 0) {
|
||||
throw AMQPNotSerializableException(
|
||||
type,
|
||||
"More than one constructor for $clazz is annotated with @ConstructorForDeserialization.")
|
||||
}
|
||||
preferredCandidate = kotlinConstructor
|
||||
}
|
||||
}
|
||||
|
||||
return preferredCandidate?.apply { isAccessible = true }
|
||||
?: throw AMQPNotSerializableException(type, "No constructor for deserialization found for $clazz.")
|
||||
} else {
|
||||
return null
|
||||
fun constructorForDeserialization(type: Type): KFunction<Any> {
|
||||
val clazz = type.asClass().apply {
|
||||
if (!isConcreteClass) throw AMQPNotSerializableException(type,
|
||||
"Cannot find deserialisation constructor for non-concrete class $this")
|
||||
}
|
||||
|
||||
val kotlinCtors = clazz.kotlin.constructors
|
||||
|
||||
val annotatedCtors = kotlinCtors.filter { it.findAnnotation<ConstructorForDeserialization>() != null }
|
||||
if (annotatedCtors.size > 1) throw AMQPNotSerializableException(
|
||||
type,
|
||||
"More than one constructor for $clazz is annotated with @ConstructorForDeserialization.")
|
||||
|
||||
val defaultCtor = kotlinCtors.firstOrNull { it.parameters.isEmpty() }
|
||||
val nonDefaultCtors = kotlinCtors.filter { it != defaultCtor }
|
||||
|
||||
val preferredCandidate = annotatedCtors.firstOrNull() ?:
|
||||
clazz.kotlin.primaryConstructor ?:
|
||||
when(nonDefaultCtors.size) {
|
||||
1 -> nonDefaultCtors.first()
|
||||
0 -> defaultCtor ?: throw AMQPNotSerializableException(type, "No constructor found for $clazz.")
|
||||
else -> throw AMQPNotSerializableException(type, "No unique non-default constructor found for $clazz.")
|
||||
}
|
||||
|
||||
return preferredCandidate.apply { isAccessible = true }
|
||||
}
|
||||
|
||||
/**
|
||||
@ -75,145 +67,13 @@ fun constructorForDeserialization(type: Type): KFunction<Any>? {
|
||||
fun <T : Any> propertiesForSerialization(
|
||||
kotlinConstructor: KFunction<T>?,
|
||||
type: Type,
|
||||
factory: SerializerFactory): PropertySerializers {
|
||||
return PropertySerializers.make(
|
||||
factory: SerializerFactory): PropertySerializers = PropertySerializers.make(
|
||||
if (kotlinConstructor != null) {
|
||||
propertiesForSerializationFromConstructor(kotlinConstructor, type, factory)
|
||||
} else {
|
||||
propertiesForSerializationFromAbstract(type.asClass()!!, type, factory)
|
||||
propertiesForSerializationFromAbstract(type.asClass(), type, factory)
|
||||
}.sortedWith(PropertyAccessor)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Encapsulates the property of a class and its potential getter and setter methods.
|
||||
*
|
||||
* @property field a property of a class.
|
||||
* @property setter the method of a class that sets the field. Determined by locating
|
||||
* a function called setXyz on the class for the property named in field as xyz.
|
||||
* @property getter the method of a class that returns a fields value. Determined by
|
||||
* locating a function named getXyz for the property named in field as xyz.
|
||||
*/
|
||||
@KeepForDJVM
|
||||
data class PropertyDescriptor(var field: Field?, var setter: Method?, var getter: Method?, var iser: Method?) {
|
||||
override fun toString() = StringBuilder("").apply {
|
||||
appendln("Property - ${field?.name ?: "null field"}\n")
|
||||
appendln(" getter - ${getter?.name ?: "no getter"}")
|
||||
appendln(" setter - ${setter?.name ?: "no setter"}")
|
||||
appendln(" iser - ${iser?.name ?: "no isXYZ defined"}")
|
||||
}.toString()
|
||||
|
||||
constructor() : this(null, null, null, null)
|
||||
|
||||
fun preferredGetter(): Method? = getter ?: iser
|
||||
}
|
||||
|
||||
object PropertyDescriptorsRegex {
|
||||
// match an uppercase letter that also has a corresponding lower case equivalent
|
||||
val re = Regex("(?<type>get|set|is)(?<var>\\p{Lu}.*)")
|
||||
}
|
||||
|
||||
/**
|
||||
* Collate the properties of a class and match them with their getter and setter
|
||||
* methods as per a JavaBean.
|
||||
*
|
||||
* for a property
|
||||
* exampleProperty
|
||||
*
|
||||
* We look for methods
|
||||
* setExampleProperty
|
||||
* getExampleProperty
|
||||
* isExampleProperty
|
||||
*
|
||||
* Where setExampleProperty must return a type compatible with exampleProperty, getExampleProperty must
|
||||
* take a single parameter of a type compatible with exampleProperty and isExampleProperty must
|
||||
* return a boolean
|
||||
*/
|
||||
fun Class<out Any?>.propertyDescriptors(): Map<String, PropertyDescriptor> {
|
||||
val classProperties = mutableMapOf<String, PropertyDescriptor>()
|
||||
|
||||
var clazz: Class<out Any?>? = this
|
||||
|
||||
do {
|
||||
clazz!!.declaredFields.forEach { property ->
|
||||
classProperties.computeIfAbsent(property.name) {
|
||||
PropertyDescriptor()
|
||||
}.apply {
|
||||
this.field = property
|
||||
}
|
||||
}
|
||||
clazz = clazz.superclass
|
||||
} while (clazz != null)
|
||||
|
||||
//
|
||||
// Running as two loops rather than one as we need to ensure we have captured all of the properties
|
||||
// before looking for interacting methods and need to cope with the class hierarchy introducing
|
||||
// new properties / methods
|
||||
//
|
||||
clazz = this
|
||||
do {
|
||||
// Note: It is possible for a class to have multiple instances of a function where the types
|
||||
// differ. For example:
|
||||
// interface I<out T> { val a: T }
|
||||
// class D(override val a: String) : I<String>
|
||||
// instances of D will have both
|
||||
// getA - returning a String (java.lang.String) and
|
||||
// getA - returning an Object (java.lang.Object)
|
||||
// In this instance we take the most derived object
|
||||
//
|
||||
// In addition, only getters that take zero parameters and setters that take a single
|
||||
// parameter will be considered
|
||||
clazz!!.declaredMethods?.map { func ->
|
||||
if (!func.isPublic) return@map
|
||||
if (func.name == "getClass") return@map
|
||||
|
||||
PropertyDescriptorsRegex.re.find(func.name)?.apply {
|
||||
// matching means we have an func getX where the property could be x or X
|
||||
// so having pre-loaded all of the properties we try to match to either case. If that
|
||||
// fails the getter doesn't refer to a property directly, but may refer to a constructor
|
||||
// parameter that shadows a property
|
||||
val properties =
|
||||
classProperties[groups[2]!!.value] ?: classProperties[groups[2]!!.value.decapitalize()] ?:
|
||||
// take into account those constructor properties that don't directly map to a named
|
||||
// property which are, by default, already added to the map
|
||||
classProperties.computeIfAbsent(groups[2]!!.value) { PropertyDescriptor() }
|
||||
|
||||
properties.apply {
|
||||
when (groups[1]!!.value) {
|
||||
"set" -> {
|
||||
if (func.parameterCount == 1) {
|
||||
if (setter == null) setter = func
|
||||
else if (TypeToken.of(setter!!.genericReturnType).isSupertypeOf(func.genericReturnType)) {
|
||||
setter = func
|
||||
}
|
||||
}
|
||||
}
|
||||
"get" -> {
|
||||
if (func.parameterCount == 0) {
|
||||
if (getter == null) getter = func
|
||||
else if (TypeToken.of(getter!!.genericReturnType).isSupertypeOf(func.genericReturnType)) {
|
||||
getter = func
|
||||
}
|
||||
}
|
||||
}
|
||||
"is" -> {
|
||||
if (func.parameterCount == 0) {
|
||||
val rtnType = TypeToken.of(func.genericReturnType)
|
||||
if ((rtnType == TypeToken.of(Boolean::class.java))
|
||||
|| (rtnType == TypeToken.of(Boolean::class.javaObjectType))) {
|
||||
if (iser == null) iser = func
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
clazz = clazz.superclass
|
||||
} while (clazz != null)
|
||||
|
||||
return classProperties
|
||||
}
|
||||
|
||||
/**
|
||||
* From a constructor, determine which properties of a class are to be serialized.
|
||||
@ -235,66 +95,48 @@ internal fun <T : Any> propertiesForSerializationFromConstructor(
|
||||
// think you could inspect the parameter and check the isSynthetic flag but that is always
|
||||
// false so given the naming convention is specified by the standard we can just check for
|
||||
// this
|
||||
if (kotlinConstructor.javaConstructor?.parameterCount ?: 0 > 0 &&
|
||||
kotlinConstructor.javaConstructor?.parameters?.get(0)?.name == "this$0"
|
||||
) {
|
||||
throw SyntheticParameterException(type)
|
||||
kotlinConstructor.javaConstructor?.apply {
|
||||
if (parameterCount > 0 && parameters[0].name == "this$0") throw SyntheticParameterException(type)
|
||||
}
|
||||
|
||||
if (classProperties.isNotEmpty() && kotlinConstructor.parameters.isEmpty()) {
|
||||
return propertiesForSerializationFromSetters(classProperties, type, factory)
|
||||
}
|
||||
|
||||
return mutableListOf<PropertyAccessor>().apply {
|
||||
kotlinConstructor.parameters.withIndex().forEach { param ->
|
||||
// name cannot be null, if it is then this is a synthetic field and we will have bailed
|
||||
// out prior to this
|
||||
val name = param.value.name!!
|
||||
|
||||
// We will already have disambiguated getA for property A or a but we still need to cope
|
||||
// with the case we don't know the case of A when the parameter doesn't match a property
|
||||
// but has a getter
|
||||
val matchingProperty = classProperties[name] ?: classProperties[name.capitalize()]
|
||||
?: throw AMQPNotSerializableException(type,
|
||||
"Constructor parameter - \"$name\" - doesn't refer to a property of \"$clazz\"")
|
||||
|
||||
// If the property has a getter we'll use that to retrieve it's value from the instance, if it doesn't
|
||||
// *for *know* we switch to a reflection based method
|
||||
val propertyReader = if (matchingProperty.getter != null) {
|
||||
val getter = matchingProperty.getter ?: throw AMQPNotSerializableException(
|
||||
type,
|
||||
"Property has no getter method for - \"$name\" - of \"$clazz\". If using Java and the parameter name"
|
||||
+ "looks anonymous, check that you have the -parameters option specified in the "
|
||||
+ "Java compiler. Alternately, provide a proxy serializer "
|
||||
+ "(SerializationCustomSerializer) if recompiling isn't an option.")
|
||||
|
||||
val returnType = resolveTypeVariables(getter.genericReturnType, type)
|
||||
if (!constructorParamTakesReturnTypeOfGetter(returnType, getter.genericReturnType, param.value)) {
|
||||
throw AMQPNotSerializableException(
|
||||
type,
|
||||
"Property - \"$name\" - has type \"$returnType\" on \"$clazz\" but differs from constructor " +
|
||||
"parameter type \"${param.value.type.javaType}\"")
|
||||
}
|
||||
|
||||
Pair(PublicPropertyReader(getter), returnType)
|
||||
} else {
|
||||
val field = classProperties[name]!!.field
|
||||
?: throw AMQPNotSerializableException(type,
|
||||
"No property matching constructor parameter named - \"$name\" - " +
|
||||
"of \"$clazz\". If using Java, check that you have the -parameters option specified " +
|
||||
"in the Java compiler. Alternately, provide a proxy serializer " +
|
||||
"(SerializationCustomSerializer) if recompiling isn't an option")
|
||||
|
||||
Pair(PrivatePropertyReader(field, type), resolveTypeVariables(field.genericType, type))
|
||||
}
|
||||
|
||||
this += PropertyAccessorConstructor(
|
||||
param.index,
|
||||
PropertySerializer.make(name, propertyReader.first, propertyReader.second, factory))
|
||||
}
|
||||
return kotlinConstructor.parameters.withIndex().map { param ->
|
||||
toPropertyAccessorConstructor(param.index, param.value, classProperties, type, clazz, factory)
|
||||
}
|
||||
}
|
||||
|
||||
private fun toPropertyAccessorConstructor(index: Int, param: KParameter, classProperties: Map<String, PropertyDescriptor>, type: Type, clazz: Class<out Any>, factory: SerializerFactory): PropertyAccessorConstructor {
|
||||
// name cannot be null, if it is then this is a synthetic field and we will have bailed
|
||||
// out prior to this
|
||||
val name = param.name!!
|
||||
|
||||
// We will already have disambiguated getA for property A or a but we still need to cope
|
||||
// with the case we don't know the case of A when the parameter doesn't match a property
|
||||
// but has a getter
|
||||
val matchingProperty = classProperties[name] ?: classProperties[name.capitalize()]
|
||||
?: throw AMQPNotSerializableException(type,
|
||||
"Constructor parameter - \"$name\" - doesn't refer to a property of \"$clazz\"")
|
||||
|
||||
// If the property has a getter we'll use that to retrieve it's value from the instance, if it doesn't
|
||||
// *for *now* we switch to a reflection based method
|
||||
val propertyReader = matchingProperty.getter?.let { getter ->
|
||||
getPublicPropertyReader(getter, type, param, name, clazz)
|
||||
} ?: matchingProperty.field?.let { field ->
|
||||
getPrivatePropertyReader(field, type)
|
||||
} ?: throw AMQPNotSerializableException(type,
|
||||
"No property matching constructor parameter named - \"$name\" - " +
|
||||
"of \"${param}\". If using Java, check that you have the -parameters option specified " +
|
||||
"in the Java compiler. Alternately, provide a proxy serializer " +
|
||||
"(SerializationCustomSerializer) if recompiling isn't an option")
|
||||
|
||||
return PropertyAccessorConstructor(
|
||||
index,
|
||||
PropertySerializer.make(name, propertyReader.first, propertyReader.second, factory))
|
||||
}
|
||||
|
||||
/**
|
||||
* If we determine a class has a constructor that takes no parameters then check for pairs of getters / setters
|
||||
* and use those
|
||||
@ -302,107 +144,83 @@ internal fun <T : Any> propertiesForSerializationFromConstructor(
|
||||
fun propertiesForSerializationFromSetters(
|
||||
properties: Map<String, PropertyDescriptor>,
|
||||
type: Type,
|
||||
factory: SerializerFactory): List<PropertyAccessor> {
|
||||
return mutableListOf<PropertyAccessorGetterSetter>().apply {
|
||||
var idx = 0
|
||||
factory: SerializerFactory): List<PropertyAccessor> =
|
||||
properties.asSequence().withIndex().map { (index, entry) ->
|
||||
val (name, property) = entry
|
||||
|
||||
properties.forEach { property ->
|
||||
val getter: Method? = property.value.preferredGetter()
|
||||
val setter: Method? = property.value.setter
|
||||
val getter = property.getter
|
||||
val setter = property.setter
|
||||
|
||||
if (getter == null || setter == null) return@forEach
|
||||
if (getter == null || setter == null) return@map null
|
||||
|
||||
if (setter.parameterCount != 1) {
|
||||
throw AMQPNotSerializableException(
|
||||
type,
|
||||
"Defined setter for parameter ${property.value.field?.name} takes too many arguments")
|
||||
}
|
||||
|
||||
val setterType = setter.genericParameterTypes[0]!!
|
||||
|
||||
if ((property.value.field != null) &&
|
||||
(!(TypeToken.of(property.value.field?.genericType!!).isSupertypeOf(setterType)))
|
||||
) {
|
||||
throw AMQPNotSerializableException(
|
||||
type,
|
||||
"Defined setter for parameter ${property.value.field?.name} " +
|
||||
"takes parameter of type $setterType yet underlying type is " +
|
||||
"${property.value.field?.genericType!!}")
|
||||
}
|
||||
|
||||
// Make sure the getter returns the same type (within inheritance bounds) the setter accepts.
|
||||
if (!(TypeToken.of(getter.genericReturnType).isSupertypeOf(setterType))) {
|
||||
throw AMQPNotSerializableException(
|
||||
type,
|
||||
"Defined setter for parameter ${property.value.field?.name} " +
|
||||
"takes parameter of type $setterType yet the defined getter returns a value of type " +
|
||||
"${getter.returnType} [${getter.genericReturnType}]")
|
||||
}
|
||||
this += PropertyAccessorGetterSetter(
|
||||
idx++,
|
||||
PropertySerializer.make(property.key, PublicPropertyReader(getter),
|
||||
resolveTypeVariables(getter.genericReturnType, type), factory),
|
||||
PropertyAccessorGetterSetter(
|
||||
index,
|
||||
PropertySerializer.make(
|
||||
name,
|
||||
PublicPropertyReader(getter),
|
||||
resolveTypeVariables(getter.genericReturnType, type),
|
||||
factory),
|
||||
setter)
|
||||
}
|
||||
}
|
||||
}
|
||||
}.filterNotNull().toList()
|
||||
|
||||
private fun constructorParamTakesReturnTypeOfGetter(
|
||||
getterReturnType: Type,
|
||||
rawGetterReturnType: Type,
|
||||
param: KParameter): Boolean {
|
||||
private fun getPrivatePropertyReader(field: Field, type: Type) =
|
||||
PrivatePropertyReader(field, type) to resolveTypeVariables(field.genericType, type)
|
||||
|
||||
private fun getPublicPropertyReader(getter: Method, type: Type, param: KParameter, name: String, clazz: Class<out Any>): Pair<PublicPropertyReader, Type> {
|
||||
val returnType = resolveTypeVariables(getter.genericReturnType, type)
|
||||
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)
|
||||
if (!(paramToken.isSupertypeOf(returnType)
|
||||
|| paramToken.isSupertypeOf(getter.genericReturnType)
|
||||
// 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(returnType)
|
||||
|| rawParamType.isSupertypeOf(getter.genericReturnType))) {
|
||||
throw AMQPNotSerializableException(
|
||||
type,
|
||||
"Property - \"$name\" - has type \"$returnType\" on \"$clazz\" " +
|
||||
"but differs from constructor parameter type \"${param.type.javaType}\"")
|
||||
}
|
||||
|
||||
return PublicPropertyReader(getter) to returnType
|
||||
}
|
||||
|
||||
private fun propertiesForSerializationFromAbstract(
|
||||
clazz: Class<*>,
|
||||
type: Type,
|
||||
factory: SerializerFactory): List<PropertyAccessor> {
|
||||
val properties = clazz.propertyDescriptors()
|
||||
|
||||
return mutableListOf<PropertyAccessorConstructor>().apply {
|
||||
properties.toList().withIndex().forEach {
|
||||
val getter = it.value.second.getter ?: return@forEach
|
||||
if (it.value.second.field == null) return@forEach
|
||||
factory: SerializerFactory): List<PropertyAccessor> =
|
||||
clazz.propertyDescriptors().asSequence().withIndex().map { (index, entry) ->
|
||||
val (name, property) = entry
|
||||
if (property.getter == null || property.field == null) return@map null
|
||||
|
||||
val getter = property.getter
|
||||
val returnType = resolveTypeVariables(getter.genericReturnType, type)
|
||||
this += PropertyAccessorConstructor(
|
||||
it.index,
|
||||
PropertySerializer.make(it.value.first, PublicPropertyReader(getter), returnType, factory))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun interfacesForSerialization(type: Type, serializerFactory: SerializerFactory): List<Type> {
|
||||
val interfaces = LinkedHashSet<Type>()
|
||||
exploreType(type, interfaces, serializerFactory)
|
||||
return interfaces.toList()
|
||||
}
|
||||
PropertyAccessorConstructor(
|
||||
index,
|
||||
PropertySerializer.make(name, PublicPropertyReader(getter), returnType, factory))
|
||||
}.filterNotNull().toList()
|
||||
|
||||
private fun exploreType(type: Type?, interfaces: MutableSet<Type>, serializerFactory: SerializerFactory) {
|
||||
val clazz = type?.asClass()
|
||||
if (clazz != null) {
|
||||
if (clazz.isInterface) {
|
||||
if (serializerFactory.whitelist.isNotWhitelisted(clazz)) return // We stop exploring once we reach a branch that has no `CordaSerializable` annotation or whitelisting.
|
||||
else interfaces += type
|
||||
}
|
||||
for (newInterface in clazz.genericInterfaces) {
|
||||
if (newInterface !in interfaces) {
|
||||
exploreType(resolveTypeVariables(newInterface, type), interfaces, serializerFactory)
|
||||
}
|
||||
}
|
||||
val superClass = clazz.genericSuperclass ?: return
|
||||
exploreType(resolveTypeVariables(superClass, type), interfaces, serializerFactory)
|
||||
internal fun interfacesForSerialization(type: Type, serializerFactory: SerializerFactory): List<Type> =
|
||||
exploreType(type, serializerFactory).toList()
|
||||
|
||||
private fun exploreType(type: Type, serializerFactory: SerializerFactory, interfaces: MutableSet<Type> = LinkedHashSet()): MutableSet<Type> {
|
||||
val clazz = type.asClass()
|
||||
|
||||
if (clazz.isInterface) {
|
||||
// Ignore classes we've already seen, and stop exploring once we reach a branch that has no `CordaSerializable`
|
||||
// annotation or whitelisting.
|
||||
if (clazz in interfaces || serializerFactory.whitelist.isNotWhitelisted(clazz)) return interfaces
|
||||
else interfaces += type
|
||||
}
|
||||
|
||||
(clazz.genericInterfaces.asSequence() + clazz.genericSuperclass)
|
||||
.filterNotNull()
|
||||
.forEach { exploreType(resolveTypeVariables(it, type), serializerFactory, interfaces) }
|
||||
|
||||
return interfaces
|
||||
}
|
||||
|
||||
/**
|
||||
@ -459,21 +277,23 @@ fun resolveTypeVariables(actualType: Type, contextType: Type?): Type {
|
||||
}
|
||||
}
|
||||
|
||||
internal fun Type.asClass(): Class<*>? {
|
||||
return when {
|
||||
this is Class<*> -> this
|
||||
this is ParameterizedType -> this.rawType.asClass()
|
||||
this is GenericArrayType -> this.genericComponentType.asClass()?.arrayClass()
|
||||
this is TypeVariable<*> -> this.bounds.first().asClass()
|
||||
this is WildcardType -> this.upperBounds.first().asClass()
|
||||
else -> null
|
||||
internal fun Type.asClass(): Class<*> {
|
||||
return when(this) {
|
||||
is Class<*> -> this
|
||||
is ParameterizedType -> this.rawType.asClass()
|
||||
is GenericArrayType -> this.genericComponentType.asClass().arrayClass()
|
||||
is TypeVariable<*> -> this.bounds.first().asClass()
|
||||
is WildcardType -> this.upperBounds.first().asClass()
|
||||
// Per https://docs.oracle.com/javase/8/docs/api/java/lang/reflect/Type.html,
|
||||
// there is nothing else that it can be, so this can never happen.
|
||||
else -> throw UnsupportedOperationException("Cannot convert $this to class")
|
||||
}
|
||||
}
|
||||
|
||||
internal fun Type.asArray(): Type? {
|
||||
return when {
|
||||
this is Class<*> -> this.arrayClass()
|
||||
this is ParameterizedType -> DeserializedGenericArrayType(this)
|
||||
return when(this) {
|
||||
is Class<*> -> this.arrayClass()
|
||||
is ParameterizedType -> DeserializedGenericArrayType(this)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
@ -506,7 +326,7 @@ internal fun Type.isSubClassOf(type: Type): Boolean {
|
||||
// ByteArrays, primitives and boxed primitives are not stored in the object history
|
||||
internal fun suitableForObjectReference(type: Type): Boolean {
|
||||
val clazz = type.asClass()
|
||||
return type != ByteArray::class.java && (clazz != null && !clazz.isPrimitive && !Primitives.unwrap(clazz).isPrimitive)
|
||||
return type != ByteArray::class.java && (!clazz.isPrimitive && !Primitives.unwrap(clazz).isPrimitive)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -519,7 +339,7 @@ internal enum class CommonPropertyNames {
|
||||
|
||||
|
||||
fun ClassWhitelist.requireWhitelisted(type: Type) {
|
||||
if (!this.isWhitelisted(type.asClass()!!)) {
|
||||
if (!this.isWhitelisted(type.asClass())) {
|
||||
throw AMQPNotSerializableException(
|
||||
type,
|
||||
"Class \"$type\" is not on the whitelist or annotated with @CordaSerializable.")
|
||||
|
@ -1,7 +1,6 @@
|
||||
package net.corda.serialization.internal.amqp
|
||||
|
||||
import com.google.common.primitives.Primitives
|
||||
import com.google.common.reflect.TypeResolver
|
||||
import net.corda.core.DeleteForDJVM
|
||||
import net.corda.core.KeepForDJVM
|
||||
import net.corda.core.StubOutForDJVM
|
||||
@ -54,7 +53,7 @@ open class SerializerFactory(
|
||||
val whitelist: ClassWhitelist,
|
||||
val classCarpenter: ClassCarpenter,
|
||||
private val evolutionSerializerGetter: EvolutionSerializerGetterBase = EvolutionSerializerGetter(),
|
||||
val fingerPrinter: FingerPrinter = SerializerFingerPrinter(),
|
||||
val fingerPrinterConstructor: (SerializerFactory) -> FingerPrinter = ::SerializerFingerPrinter,
|
||||
private val serializersByType: MutableMap<Type, AMQPSerializer<Any>>,
|
||||
val serializersByDescriptor: MutableMap<Any, AMQPSerializer<Any>>,
|
||||
private val customSerializers: MutableList<SerializerFor>,
|
||||
@ -66,13 +65,13 @@ open class SerializerFactory(
|
||||
constructor(whitelist: ClassWhitelist,
|
||||
classCarpenter: ClassCarpenter,
|
||||
evolutionSerializerGetter: EvolutionSerializerGetterBase = EvolutionSerializerGetter(),
|
||||
fingerPrinter: FingerPrinter = SerializerFingerPrinter(),
|
||||
fingerPrinterConstructor: (SerializerFactory) -> FingerPrinter = ::SerializerFingerPrinter,
|
||||
onlyCustomSerializers: Boolean = false
|
||||
) : this(
|
||||
whitelist,
|
||||
classCarpenter,
|
||||
evolutionSerializerGetter,
|
||||
fingerPrinter,
|
||||
fingerPrinterConstructor,
|
||||
ConcurrentHashMap(),
|
||||
ConcurrentHashMap(),
|
||||
CopyOnWriteArrayList(),
|
||||
@ -86,18 +85,16 @@ open class SerializerFactory(
|
||||
carpenterClassLoader: ClassLoader,
|
||||
lenientCarpenter: Boolean = false,
|
||||
evolutionSerializerGetter: EvolutionSerializerGetterBase = EvolutionSerializerGetter(),
|
||||
fingerPrinter: FingerPrinter = SerializerFingerPrinter(),
|
||||
fingerPrinterConstructor: (SerializerFactory) -> FingerPrinter = ::SerializerFingerPrinter,
|
||||
onlyCustomSerializers: Boolean = false
|
||||
) : this(
|
||||
whitelist,
|
||||
ClassCarpenterImpl(whitelist, carpenterClassLoader, lenientCarpenter),
|
||||
evolutionSerializerGetter,
|
||||
fingerPrinter,
|
||||
fingerPrinterConstructor,
|
||||
onlyCustomSerializers)
|
||||
|
||||
init {
|
||||
fingerPrinter.setOwner(this)
|
||||
}
|
||||
val fingerPrinter by lazy { fingerPrinterConstructor(this) }
|
||||
|
||||
val classloader: ClassLoader get() = classCarpenter.classloader
|
||||
|
||||
@ -118,11 +115,9 @@ open class SerializerFactory(
|
||||
// can be useful to enable but will be *extremely* chatty if you do
|
||||
logger.trace { "Get Serializer for $actualClass ${declaredType.typeName}" }
|
||||
|
||||
val declaredClass = declaredType.asClass() ?: throw AMQPNotSerializableException(
|
||||
declaredType,
|
||||
"Declared types of $declaredType are not supported.")
|
||||
|
||||
val actualType: Type = inferTypeVariables(actualClass, declaredClass, declaredType) ?: declaredType
|
||||
val declaredClass = declaredType.asClass()
|
||||
val actualType: Type = if (actualClass == null) declaredType
|
||||
else inferTypeVariables(actualClass, declaredClass, declaredType) ?: declaredType
|
||||
|
||||
val serializer = when {
|
||||
// Declared class may not be set to Collection, but actual class could be a collection.
|
||||
@ -166,78 +161,6 @@ open class SerializerFactory(
|
||||
return serializer
|
||||
}
|
||||
|
||||
/**
|
||||
* Try and infer concrete types for any generics type variables for the actual class encountered,
|
||||
* based on the declared type.
|
||||
*/
|
||||
// TODO: test GenericArrayType
|
||||
private fun inferTypeVariables(actualClass: Class<*>?, declaredClass: Class<*>,
|
||||
declaredType: Type): Type? = when (declaredType) {
|
||||
is ParameterizedType -> inferTypeVariables(actualClass, declaredClass, declaredType)
|
||||
// Nothing to infer, otherwise we'd have ParameterizedType
|
||||
is Class<*> -> actualClass
|
||||
is GenericArrayType -> {
|
||||
val declaredComponent = declaredType.genericComponentType
|
||||
inferTypeVariables(actualClass?.componentType, declaredComponent.asClass()!!, declaredComponent)?.asArray()
|
||||
}
|
||||
is TypeVariable<*> -> actualClass
|
||||
is WildcardType -> actualClass
|
||||
else -> null
|
||||
}
|
||||
|
||||
/**
|
||||
* Try and infer concrete types for any generics type variables for the actual class encountered, based on the declared
|
||||
* type, which must be a [ParameterizedType].
|
||||
*/
|
||||
private fun inferTypeVariables(actualClass: Class<*>?, declaredClass: Class<*>, declaredType: ParameterizedType): Type? {
|
||||
if (actualClass == null || declaredClass == actualClass) {
|
||||
return null
|
||||
} else if (declaredClass.isAssignableFrom(actualClass)) {
|
||||
return if (actualClass.typeParameters.isNotEmpty()) {
|
||||
// The actual class can never have type variables resolved, due to the JVM's use of type erasure, so let's try and resolve them
|
||||
// Search for declared type in the inheritance hierarchy and then see if that fills in all the variables
|
||||
val implementationChain: List<Type>? = findPathToDeclared(actualClass, declaredType, mutableListOf())
|
||||
if (implementationChain != null) {
|
||||
val start = implementationChain.last()
|
||||
val rest = implementationChain.dropLast(1).drop(1)
|
||||
val resolver = rest.reversed().fold(TypeResolver().where(start, declaredType)) { resolved, chainEntry ->
|
||||
val newResolved = resolved.resolveType(chainEntry)
|
||||
TypeResolver().where(chainEntry, newResolved)
|
||||
}
|
||||
// The end type is a special case as it is a Class, so we need to fake up a ParameterizedType for it to get the TypeResolver to do anything.
|
||||
val endType = DeserializedParameterizedType(actualClass, actualClass.typeParameters)
|
||||
val resolvedType = resolver.resolveType(endType)
|
||||
resolvedType
|
||||
} else throw AMQPNotSerializableException(declaredType,
|
||||
"No inheritance path between actual $actualClass and declared $declaredType.")
|
||||
} else actualClass
|
||||
} else throw AMQPNotSerializableException(
|
||||
declaredType,
|
||||
"Found object of type $actualClass in a property expecting $declaredType")
|
||||
}
|
||||
|
||||
// Stop when reach declared type or return null if we don't find it.
|
||||
private fun findPathToDeclared(startingType: Type, declaredType: Type, chain: MutableList<Type>): List<Type>? {
|
||||
chain.add(startingType)
|
||||
val startingClass = startingType.asClass()
|
||||
if (startingClass == declaredType.asClass()) {
|
||||
// We're done...
|
||||
return chain
|
||||
}
|
||||
// Now explore potential options of superclass and all interfaces
|
||||
val superClass = startingClass?.genericSuperclass
|
||||
val superClassChain = if (superClass != null) {
|
||||
val resolved = TypeResolver().where(startingClass.asParameterizedType(), startingType.asParameterizedType()).resolveType(superClass)
|
||||
findPathToDeclared(resolved, declaredType, ArrayList(chain))
|
||||
} else null
|
||||
if (superClassChain != null) return superClassChain
|
||||
for (iface in startingClass?.genericInterfaces ?: emptyArray()) {
|
||||
val resolved = TypeResolver().where(startingClass!!.asParameterizedType(), startingType.asParameterizedType()).resolveType(iface)
|
||||
return findPathToDeclared(resolved, declaredType, ArrayList(chain)) ?: continue
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup and manufacture a serializer for the given AMQP type descriptor, assuming we also have the necessary types
|
||||
* contained in the [Schema].
|
||||
@ -349,7 +272,7 @@ open class SerializerFactory(
|
||||
// TODO: class loader logic, and compare the schema.
|
||||
val type = typeForName(typeNotation.name, classloader)
|
||||
return get(
|
||||
type.asClass() ?: throw AMQPNotSerializableException(type, "Unable to build composite type for $type"),
|
||||
type.asClass(),
|
||||
type)
|
||||
}
|
||||
|
||||
@ -402,7 +325,7 @@ open class SerializerFactory(
|
||||
// super type. Could be done, but do we need it?
|
||||
for (customSerializer in customSerializers) {
|
||||
if (customSerializer.isSerializerFor(clazz)) {
|
||||
val declaredSuperClass = declaredType.asClass()?.superclass
|
||||
val declaredSuperClass = declaredType.asClass().superclass
|
||||
|
||||
|
||||
return if (declaredSuperClass == null
|
||||
|
@ -0,0 +1,94 @@
|
||||
package net.corda.serialization.internal.amqp
|
||||
|
||||
import com.google.common.reflect.TypeResolver
|
||||
import java.lang.reflect.*
|
||||
|
||||
/**
|
||||
* Try and infer concrete types for any generics type variables for the actual class encountered,
|
||||
* based on the declared type.
|
||||
*/
|
||||
// TODO: test GenericArrayType
|
||||
fun inferTypeVariables(actualClass: Class<*>,
|
||||
declaredClass: Class<*>,
|
||||
declaredType: Type): Type? = when (declaredType) {
|
||||
is ParameterizedType -> inferTypeVariables(actualClass, declaredClass, declaredType)
|
||||
is GenericArrayType -> {
|
||||
val declaredComponent = declaredType.genericComponentType
|
||||
inferTypeVariables(actualClass.componentType, declaredComponent.asClass(), declaredComponent)?.asArray()
|
||||
}
|
||||
// Nothing to infer, otherwise we'd have ParameterizedType
|
||||
is Class<*> -> actualClass
|
||||
is TypeVariable<*> -> actualClass
|
||||
is WildcardType -> actualClass
|
||||
else -> throw UnsupportedOperationException("Cannot infer type variables for type $declaredType")
|
||||
}
|
||||
|
||||
/**
|
||||
* Try and infer concrete types for any generics type variables for the actual class encountered, based on the declared
|
||||
* type, which must be a [ParameterizedType].
|
||||
*/
|
||||
private fun inferTypeVariables(actualClass: Class<*>, declaredClass: Class<*>, declaredType: ParameterizedType): Type? {
|
||||
if (declaredClass == actualClass) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (!declaredClass.isAssignableFrom(actualClass)) {
|
||||
throw AMQPNotSerializableException(
|
||||
declaredType,
|
||||
"Found object of type $actualClass in a property expecting $declaredType")
|
||||
}
|
||||
|
||||
if (actualClass.typeParameters.isEmpty()) {
|
||||
return actualClass
|
||||
}
|
||||
// The actual class can never have type variables resolved, due to the JVM's use of type erasure, so let's try and resolve them
|
||||
// Search for declared type in the inheritance hierarchy and then see if that fills in all the variables
|
||||
val implementationChain: List<Type> = findPathToDeclared(actualClass, declaredType)?.toList()
|
||||
?: throw AMQPNotSerializableException(
|
||||
declaredType,
|
||||
"No inheritance path between actual $actualClass and declared $declaredType.")
|
||||
|
||||
val start = implementationChain.last()
|
||||
val rest = implementationChain.dropLast(1).drop(1)
|
||||
val resolver = rest.reversed().fold(TypeResolver().where(start, declaredType)) { resolved, chainEntry ->
|
||||
val newResolved = resolved.resolveType(chainEntry)
|
||||
TypeResolver().where(chainEntry, newResolved)
|
||||
}
|
||||
// The end type is a special case as it is a Class, so we need to fake up a ParameterizedType for it to get the TypeResolver to do anything.
|
||||
val endType = DeserializedParameterizedType(actualClass, actualClass.typeParameters)
|
||||
return resolver.resolveType(endType)
|
||||
}
|
||||
|
||||
// Stop when reach declared type or return null if we don't find it.
|
||||
private fun findPathToDeclared(startingType: Type, declaredType: Type, chain: Sequence<Type> = emptySequence()): Sequence<Type>? {
|
||||
val extendedChain = chain + startingType
|
||||
val startingClass = startingType.asClass()
|
||||
|
||||
if (startingClass == declaredType.asClass()) {
|
||||
// We're done...
|
||||
return extendedChain
|
||||
}
|
||||
|
||||
val resolver = { type: Type ->
|
||||
TypeResolver().where(
|
||||
startingClass.asParameterizedType(),
|
||||
startingType.asParameterizedType())
|
||||
.resolveType(type)
|
||||
}
|
||||
|
||||
// Now explore potential options of superclass and all interfaces
|
||||
return findPathViaGenericSuperclass(startingClass, resolver, declaredType, extendedChain)
|
||||
?: findPathViaInterfaces(startingClass, resolver, declaredType, extendedChain)
|
||||
}
|
||||
|
||||
private fun findPathViaInterfaces(startingClass: Class<*>, resolver: (Type) -> Type, declaredType: Type, extendedChain: Sequence<Type>): Sequence<Type>? =
|
||||
startingClass.genericInterfaces.asSequence().map {
|
||||
findPathToDeclared(resolver(it), declaredType, extendedChain)
|
||||
}.filterNotNull().firstOrNull()
|
||||
|
||||
|
||||
private fun findPathViaGenericSuperclass(startingClass: Class<*>, resolver: (Type) -> Type, declaredType: Type, extendedChain: Sequence<Type>): Sequence<Type>? {
|
||||
val superClass = startingClass.genericSuperclass ?: return null
|
||||
return findPathToDeclared(resolver(superClass), declaredType, extendedChain)
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ class ThrowableSerializer(factory: SerializerFactory) : CustomSerializer.Proxy<T
|
||||
// Try and find a constructor
|
||||
try {
|
||||
val constructor = constructorForDeserialization(obj.javaClass)
|
||||
propertiesForSerializationFromConstructor(constructor!!, obj.javaClass, factory).forEach { property ->
|
||||
propertiesForSerializationFromConstructor(constructor, obj.javaClass, factory).forEach { property ->
|
||||
extraProperties[property.serializer.name] = property.serializer.propertyReader.read(obj)
|
||||
}
|
||||
} catch (e: NotSerializableException) {
|
||||
@ -52,7 +52,7 @@ class ThrowableSerializer(factory: SerializerFactory) : CustomSerializer.Proxy<T
|
||||
// If it is CordaException or CordaRuntimeException, we can seek any constructor and then set the properties
|
||||
// Otherwise we just make a CordaRuntimeException
|
||||
if (CordaThrowable::class.java.isAssignableFrom(clazz) && Throwable::class.java.isAssignableFrom(clazz)) {
|
||||
val constructor = constructorForDeserialization(clazz)!!
|
||||
val constructor = constructorForDeserialization(clazz)
|
||||
val throwable = constructor.callBy(constructor.parameters.map { it to proxy.additionalProperties[it.name] }.toMap())
|
||||
(throwable as CordaThrowable).apply {
|
||||
if (this.javaClass.name != proxy.exceptionClass) this.originalExceptionClassName = proxy.exceptionClass
|
||||
|
@ -15,10 +15,6 @@ class FingerPrinterTesting : FingerPrinter {
|
||||
return cache.computeIfAbsent(type) { index++.toString() }
|
||||
}
|
||||
|
||||
override fun setOwner(factory: SerializerFactory) {
|
||||
return
|
||||
}
|
||||
|
||||
@Suppress("UNUSED")
|
||||
fun changeFingerprint(type: Type) {
|
||||
cache.computeIfAbsent(type) { "" }.apply { index++.toString() }
|
||||
@ -47,7 +43,7 @@ class FingerPrinterTestingTests {
|
||||
AllWhitelist,
|
||||
ClassLoader.getSystemClassLoader(),
|
||||
evolutionSerializerGetter = EvolutionSerializerGetterTesting(),
|
||||
fingerPrinter = FingerPrinterTesting())
|
||||
fingerPrinterConstructor = { _ -> FingerPrinterTesting() })
|
||||
|
||||
val blob = TestSerializationOutput(VERBOSE, factory).serializeAndReturnSchema(C(1, 2L))
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user