CORDA-979 - Make public java setter accessible from within Java

Backport from master
This commit is contained in:
Katelyn Baker 2018-02-05 17:03:48 +00:00
parent 98b091a0eb
commit ac416690e0
3 changed files with 59 additions and 50 deletions

View File

@ -123,12 +123,18 @@ abstract class PropertyAccessor(
class PropertyAccessorGetterSetter( class PropertyAccessorGetterSetter(
initialPosition: Int, initialPosition: Int,
getter: PropertySerializer, getter: PropertySerializer,
private val setter: Method?) : PropertyAccessor(initialPosition, getter) { private val setter: Method) : PropertyAccessor(initialPosition, getter) {
init {
/**
* Play nicely with Java interop, public methods aren't marked as accessible
*/
setter.isAccessible = true
}
/** /**
* Invokes the setter on the underlying object passing in the serialized value. * Invokes the setter on the underlying object passing in the serialized value.
*/ */
override fun set(instance: Any, obj: Any?) { override fun set(instance: Any, obj: Any?) {
setter?.invoke(instance, *listOf(obj).toTypedArray()) setter.invoke(instance, *listOf(obj).toTypedArray())
} }
} }

View File

@ -89,12 +89,18 @@ fun isConcrete(clazz: Class<*>): Boolean = !(clazz.isInterface || Modifier.isAbs
* @property getter the method of a class that returns a fields value. Determined by * @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. * locating a function named getXyz for the property named in field as xyz.
*/ */
data class PropertyDescriptor(var field: Field?, var setter: Method?, var getter: Method?) { data class PropertyDescriptor(var field: Field?, var setter: Method?, var getter: Method?, var iser: Method?) {
override fun toString() = StringBuilder("").apply { override fun toString() = StringBuilder("").apply {
appendln("Property - ${field?.name ?: "null field"}\n") appendln("Property - ${field?.name ?: "null field"}\n")
appendln(" getter - ${getter?.name ?: "no getter"}") appendln(" getter - ${getter?.name ?: "no getter"}")
append(" setter - ${setter?.name ?: "no setter"}") appendln(" setter - ${setter?.name ?: "no setter"}")
appendln(" iser - ${iser?.name ?: "no isXYZ defined"}")
}.toString() }.toString()
constructor() : this(null, null, null, null)
constructor(field: Field?) : this(field, null, null, null)
fun preferredGetter() : Method? = getter ?: iser
} }
object PropertyDescriptorsRegex { object PropertyDescriptorsRegex {
@ -122,12 +128,10 @@ 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
do { clazz!!.declaredFields.forEach { classProperties.put(it.name, PropertyDescriptor(it)) }
// get the properties declared on this instance of class
clazz!!.declaredFields.forEach { classProperties.put(it.name, PropertyDescriptor(it, null, null)) }
// then pair them up with the declared getter and setter do {
// Note: It is possible for a class to have multiple instancess 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 }
// class D(override val a: String) : I<String> // class D(override val a: String) : I<String>
@ -138,45 +142,45 @@ 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 (func.name == "getClass") return@map
PropertyDescriptorsRegex.re.find(func.name)?.apply { PropertyDescriptorsRegex.re.find(func.name)?.apply {
try { // take into account those constructor properties that don't directly map to a named
classProperties.getValue(groups[2]!!.value.decapitalize()).apply { // property which are, by default, already added to the map
when (groups[1]!!.value) { classProperties.computeIfAbsent(groups[2]!!.value.decapitalize()) { PropertyDescriptor() }.apply {
"set" -> { when (groups[1]!!.value) {
if (func.parameterCount == 1) { "set" -> {
if (setter == null) setter = func if (func.parameterCount == 1) {
else if (TypeToken.of(setter!!.genericReturnType).isSupertypeOf(func.genericReturnType)) { if (setter == null) setter = func
setter = func else if (TypeToken.of(setter!!.genericReturnType).isSupertypeOf(func.genericReturnType)) {
} setter = func
} }
} }
"get" -> { }
if (func.parameterCount == 0) { "get" -> {
if (getter == null) getter = func if (func.parameterCount == 0) {
else if (TypeToken.of(getter!!.genericReturnType).isSupertypeOf(func.genericReturnType)) { if (getter == null) getter = func
getter = func else if (TypeToken.of(getter!!.genericReturnType).isSupertypeOf(func.genericReturnType)) {
} getter = func
} }
} }
"is" -> { }
if (func.parameterCount == 0) { "is" -> {
val rtnType = TypeToken.of(func.genericReturnType) if (func.parameterCount == 0) {
if ((rtnType == TypeToken.of(Boolean::class.java)) val rtnType = TypeToken.of(func.genericReturnType)
|| (rtnType == TypeToken.of(Boolean::class.javaObjectType))) { if ((rtnType == TypeToken.of(Boolean::class.java))
if (getter == null) getter = func || (rtnType == TypeToken.of(Boolean::class.javaObjectType))) {
} if (iser == null) iser = func
} }
} }
} }
} }
} catch (e: NoSuchElementException) {
// handles the getClass case from java.lang.Object
return@apply
} }
} }
} }
clazz = clazz?.superclass clazz = clazz.superclass
} while (clazz != null) } while (clazz != null)
return classProperties return classProperties
@ -260,7 +264,7 @@ fun propertiesForSerializationFromSetters(
return mutableListOf<PropertyAccessorGetterSetter>().apply { return mutableListOf<PropertyAccessorGetterSetter>().apply {
var idx = 0 var idx = 0
properties.forEach { property -> properties.forEach { property ->
val getter: Method? = property.value.getter val getter: Method? = property.value.preferredGetter()
val setter: Method? = property.value.setter val setter: Method? = property.value.setter
if (getter == null || setter == null) return@forEach if (getter == null || setter == null) return@forEach
@ -270,13 +274,20 @@ fun propertiesForSerializationFromSetters(
"takes too many arguments") "takes too many arguments")
} }
val setterType = setter.parameterTypes.getOrNull(0)!! val setterType = setter.parameterTypes[0]!!
if (!(TypeToken.of(property.value.field?.genericType!!).isSupertypeOf(setterType))) { if (!(TypeToken.of(property.value.field?.genericType!!).isSupertypeOf(setterType))) {
throw NotSerializableException("Defined setter for parameter ${property.value.field?.name} " + throw NotSerializableException("Defined setter for parameter ${property.value.field?.name} " +
"takes parameter of type $setterType yet underlying type is " + "takes parameter of type $setterType yet underlying type is " +
"${property.value.field?.genericType!!}") "${property.value.field?.genericType!!}")
} }
// make sure the setter returns the same type (within inheritance bounds) the getter accepts
if (!(TypeToken.of (setterType).isSupertypeOf(getter.returnType))) {
throw NotSerializableException("Defined setter for parameter ${property.value.field?.name} " +
"takes parameter of type $setterType yet the defined getter returns a value of type " +
"${getter.returnType}")
}
this += PropertyAccessorGetterSetter( this += PropertyAccessorGetterSetter(
idx++, idx++,
PropertySerializer.make(property.value.field!!.name, PublicPropertyReader(getter), PropertySerializer.make(property.value.field!!.name, PublicPropertyReader(getter),

View File

@ -286,12 +286,8 @@ public class SetterConstructorTests {
tm.setA(10); tm.setA(10);
assertEquals("10", tm.getA()); assertEquals("10", tm.getA());
TypeMismatch post = new DeserializationInput(factory1).deserialize(new SerializationOutput(factory1).serialize(tm), Assertions.assertThatThrownBy(() -> new SerializationOutput(factory1).serialize(tm)).isInstanceOf (
TypeMismatch.class); NotSerializableException.class);
// because there is a type mismatch in the class, it won't return that info as a BEAN property and thus
// we won't serialise it and thus on deserialization it won't be initialized
Assertions.assertThatThrownBy(() -> post.getA()).isInstanceOf(NullPointerException.class);
} }
@Test @Test
@ -306,11 +302,7 @@ public class SetterConstructorTests {
tm.setA("10"); tm.setA("10");
assertEquals((Integer)10, tm.getA()); assertEquals((Integer)10, tm.getA());
TypeMismatch2 post = new DeserializationInput(factory1).deserialize(new SerializationOutput(factory1).serialize(tm), Assertions.assertThatThrownBy(() -> new SerializationOutput(factory1).serialize(tm)).isInstanceOf(
TypeMismatch2.class); NotSerializableException.class);
// because there is a type mismatch in the class, it won't return that info as a BEAN property and thus
// we won't serialise it and thus on deserialization it won't be initialized
assertEquals(null, post.getA());
} }
} }