CORDA-904 - Fix evolver to work with setter instantiated classses (#2463)

- Cherry pick to backport from master

* CORDA-904 - Make evolver work with classes that use setters

* review comments

* review comments

* small fixs

* don't include systemTest in compiler.xml
This commit is contained in:
Katelyn Baker 2018-02-06 12:55:49 +00:00
parent f8359a74fd
commit f16e45abe9
3 changed files with 19 additions and 13 deletions

2
.idea/compiler.xml generated
View File

@ -159,4 +159,4 @@
<component name="JavacSettings"> <component name="JavacSettings">
<option name="ADDITIONAL_OPTIONS_STRING" value="-parameters" /> <option name="ADDITIONAL_OPTIONS_STRING" value="-parameters" />
</component> </component>
</project> </project>

View File

@ -60,6 +60,7 @@ abstract class EvolutionSerializer(
*/ */
private fun getEvolverConstructor(type: Type, oldArgs: Map<String, OldParam>): KFunction<Any>? { private fun getEvolverConstructor(type: Type, oldArgs: Map<String, OldParam>): KFunction<Any>? {
val clazz: Class<*> = type.asClass()!! val clazz: Class<*> = type.asClass()!!
if (!isConcrete(clazz)) return null if (!isConcrete(clazz)) return null
val oldArgumentSet = oldArgs.map { Pair(it.key as String?, it.value.property.resolvedType) } val oldArgumentSet = oldArgs.map { Pair(it.key as String?, it.value.property.resolvedType) }
@ -123,7 +124,6 @@ abstract class EvolutionSerializer(
*/ */
fun make(old: CompositeType, new: ObjectSerializer, fun make(old: CompositeType, new: ObjectSerializer,
factory: SerializerFactory): AMQPSerializer<Any> { factory: SerializerFactory): AMQPSerializer<Any> {
// The order in which the properties were serialised is important and must be preserved // The order in which the properties were serialised is important and must be preserved
val readersAsSerialized = LinkedHashMap<String, OldParam>() val readersAsSerialized = LinkedHashMap<String, OldParam>()
old.fields.forEach { old.fields.forEach {
@ -135,9 +135,9 @@ abstract class EvolutionSerializer(
} }
} }
val constructor = getEvolverConstructor(new.type, readersAsSerialized) ?: // cope with the situation where a generic interface was serialised as a type, in such cases
throw NotSerializableException( // return the synthesised object which is, given the absence of a constructor, a no op
"Attempt to deserialize an interface: ${new.type}. Serialized form is invalid.") val constructor = getEvolverConstructor(new.type, readersAsSerialized) ?: return new
val classProperties = new.type.asClass()?.propertyDescriptors() ?: emptyMap() val classProperties = new.type.asClass()?.propertyDescriptors() ?: emptyMap()

View File

@ -98,7 +98,6 @@ data class PropertyDescriptor(var field: Field?, var setter: Method?, var getter
}.toString() }.toString()
constructor() : this(null, null, null, null) constructor() : this(null, null, null, null)
constructor(field: Field?) : this(field, null, null, null)
fun preferredGetter() : Method? = getter ?: iser fun preferredGetter() : Method? = getter ?: iser
} }
@ -128,9 +127,16 @@ fun Class<out Any?>.propertyDescriptors(): Map<String, PropertyDescriptor> {
val classProperties = mutableMapOf<String, PropertyDescriptor>() val classProperties = mutableMapOf<String, PropertyDescriptor>()
var clazz: Class<out Any?>? = this var clazz: Class<out Any?>? = this
clazz!!.declaredFields.forEach { classProperties.put(it.name, PropertyDescriptor(it)) }
do { do {
clazz!!.declaredFields.forEach { property ->
classProperties.computeIfAbsent(property.name) {
PropertyDescriptor()
}.apply {
this.field = property
}
}
// Note: It is possible for a class to have multiple instances of a function where the types // Note: It is possible for a class to have multiple instances of a function where the types
// differ. For example: // differ. For example:
// interface I<out T> { val a: T } // interface I<out T> { val a: T }
@ -142,7 +148,7 @@ fun Class<out Any?>.propertyDescriptors(): Map<String, PropertyDescriptor> {
// //
// In addition, only getters that take zero parameters and setters that take a single // In addition, only getters that take zero parameters and setters that take a single
// parameter will be considered // parameter will be considered
clazz!!.declaredMethods?.map { func -> clazz.declaredMethods?.map { func ->
if (!Modifier.isPublic(func.modifiers)) return@map if (!Modifier.isPublic(func.modifiers)) return@map
if (func.name == "getClass") return@map if (func.name == "getClass") return@map
@ -231,15 +237,13 @@ internal fun <T : Any> propertiesForSerializationFromConstructor(
Pair(PublicPropertyReader(getter), returnType) Pair(PublicPropertyReader(getter), returnType)
} else { } else {
try { val field = classProperties[name]!!.field ?:
val field = clazz.getDeclaredField(param.value.name)
Pair(PrivatePropertyReader(field, type), field.genericType)
} catch (e: NoSuchFieldException) {
throw NotSerializableException("No property matching constructor parameter named '$name' " + throw NotSerializableException("No property matching constructor parameter named '$name' " +
"of '$clazz'. If using Java, check that you have the -parameters option specified " + "of '$clazz'. If using Java, check that you have the -parameters option specified " +
"in the Java compiler. Alternately, provide a proxy serializer " + "in the Java compiler. Alternately, provide a proxy serializer " +
"(SerializationCustomSerializer) if recompiling isn't an option") "(SerializationCustomSerializer) if recompiling isn't an option")
}
Pair(PrivatePropertyReader(field, type), field.genericType)
} }
} else { } else {
throw NotSerializableException( throw NotSerializableException(
@ -324,6 +328,8 @@ private fun propertiesForSerializationFromAbstract(
return mutableListOf<PropertyAccessorConstructor>().apply { return mutableListOf<PropertyAccessorConstructor>().apply {
properties.toList().withIndex().forEach { properties.toList().withIndex().forEach {
val getter = it.value.second.getter ?: return@forEach val getter = it.value.second.getter ?: return@forEach
if (it.value.second.field == null) return@forEach
val returnType = resolveTypeVariables(getter.genericReturnType, type) val returnType = resolveTypeVariables(getter.genericReturnType, type)
this += PropertyAccessorConstructor( this += PropertyAccessorConstructor(
it.index, it.index,