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:
Dominic Fox 2018-08-30 10:18:02 +01:00 committed by GitHub
parent 4337537791
commit 0f36e22314
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 627 additions and 593 deletions

View File

@ -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

View File

@ -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)
}

View File

@ -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),

View File

@ -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}' " +

View File

@ -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) {

View File

@ -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(

View File

@ -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,

View File

@ -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

View File

@ -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 {

View File

@ -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() }
}

View File

@ -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)

View File

@ -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.")

View File

@ -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

View File

@ -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)
}

View File

@ -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

View File

@ -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))