CORDA-1134 - Don't use private serializers for all CAPS properties (#2708)

Whilst it does currently work it only does so by going down an incorrect
code path. A property THING that is public and has a getter should
be fetched using that getter, not trip into the private property
accessor.
This commit is contained in:
Katelyn Baker 2018-03-02 13:49:30 +00:00 committed by GitHub
parent ba925c0277
commit 9e9fab7e1a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 74 additions and 28 deletions

View File

@ -136,7 +136,16 @@ fun Class<out Any?>.propertyDescriptors(): Map<String, PropertyDescriptor> {
this.field = property 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 // 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 }
@ -148,14 +157,23 @@ 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
PropertyDescriptorsRegex.re.find(func.name)?.apply { 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 to a cosntructor
// 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 // take into account those constructor properties that don't directly map to a named
// property which are, by default, already added to the map // property which are, by default, already added to the map
classProperties.computeIfAbsent(groups[2]!!.value.decapitalize()) { PropertyDescriptor() }.apply { classProperties.computeIfAbsent(groups[2]!!.value) { PropertyDescriptor() }
properties.apply {
when (groups[1]!!.value) { when (groups[1]!!.value) {
"set" -> { "set" -> {
if (func.parameterCount == 1) { if (func.parameterCount == 1) {
@ -221,12 +239,16 @@ internal fun <T : Any> propertiesForSerializationFromConstructor(
// it so just ignore it as it'll be supplied at runtime anyway on invocation // it so just ignore it as it'll be supplied at runtime anyway on invocation
val name = param.value.name ?: return@forEach val name = param.value.name ?: return@forEach
val propertyReader = if (name in classProperties) { // We will already have disambiguated getA for property A or a but we still need to cope
if (classProperties[name]!!.getter != null) { // with the case we don't know the case of A when the parameter doesn't match a property
// it's a publicly accessible property // but has a getter
val matchingProperty = classProperties[name]!! val matchingProperty = classProperties[name] ?: classProperties[name.capitalize()] ?:
throw NotSerializableException(
"Constructor parameter - \"$name\" - doesn't refer to a property of \"$clazz\"")
// Check that the method has a getter in java. // 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 NotSerializableException( val getter = matchingProperty.getter ?: throw NotSerializableException(
"Property has no getter method for - \"$name\" - of \"$clazz\". If using Java and the parameter name" "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 " + "looks anonymous, check that you have the -parameters option specified in the "
@ -250,10 +272,6 @@ internal fun <T : Any> propertiesForSerializationFromConstructor(
Pair(PrivatePropertyReader(field, type), field.genericType) Pair(PrivatePropertyReader(field, type), field.genericType)
} }
} else {
throw NotSerializableException(
"Constructor parameter - \"$name\" - doesn't refer to a property of \"$clazz\"")
}
this += PropertyAccessorConstructor( this += PropertyAccessorConstructor(
param.index, param.index,

View File

@ -196,4 +196,32 @@ class PrivatePropertyTests {
assertEquals(c1, c2) assertEquals(c1, c2)
} }
//
// Reproduces CORDA-1134
//
@Suppress("UNCHECKED_CAST")
@Test
fun allCapsProprtyNotPrivate() {
data class C (val CCC: String)
val output = SerializationOutput(factory).serializeAndReturnSchema(C("this is nice"))
val serializersByDescriptor = fields["serializersByDesc"]!!.get(factory) as ConcurrentHashMap<Any, AMQPSerializer<Any>>
val schemaDescriptor = output.schema.types.first().descriptor.name
serializersByDescriptor.filterKeys { (it as Symbol) == schemaDescriptor }.values.apply {
assertEquals(1, size)
assertTrue(this.first() is ObjectSerializer)
val propertySerializers = (this.first() as ObjectSerializer).propertySerializers.serializationOrder.map { it.getter }
// CCC is the only property to be serialised
assertEquals(1, propertySerializers.size)
// and despite being all caps it should still be a public getter
assertTrue(propertySerializers[0].propertyReader is PublicPropertyReader)
}
}
} }