Skip to content

Commit

Permalink
Fix Declarative DSL runtime setting properties in Kotlin types
Browse files Browse the repository at this point in the history
If Kotlin reflection cannot find the property or the setter method,
try to look up the setter via Java reflection.

Also, refactor some code around `DeclarativeRuntimeProperty`.
  • Loading branch information
h0tk3y committed Mar 25, 2024
1 parent 8f91068 commit 28e7367
Show file tree
Hide file tree
Showing 5 changed files with 264 additions and 142 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -168,8 +168,8 @@ class DeclarativeReflectionToObjectConverter(
?: error("tried to access a property ${dataProperty.name} on a null receiver")
val receiverKClass = receiverInstance::class
return when (val property = propertyResolver.resolvePropertyRead(receiverKClass, dataProperty.name)) {
is RuntimePropertyResolver.Resolution.Resolved -> property.property.getValue(receiverInstance)
else -> error("cannot get property ${dataProperty.name} from the receiver class $receiverKClass")
is RuntimePropertyResolver.ReadResolution.ResolvedRead -> property.getter.getValue(receiverInstance)
RuntimePropertyResolver.ReadResolution.UnresolvedRead -> error("cannot get property ${dataProperty.name} from the receiver class $receiverKClass")
}
}

Expand All @@ -179,8 +179,8 @@ class DeclarativeReflectionToObjectConverter(
?: error("tried to access a property ${dataProperty.name} on a null receiver")
val receiverKClass = receiverInstance::class
when (val property = propertyResolver.resolvePropertyWrite(receiverKClass, dataProperty.name)) {
is RuntimePropertyResolver.Resolution.Resolved -> property.property.setValue(receiverInstance, value)
RuntimePropertyResolver.Resolution.Unresolved -> error("cannot set property ${dataProperty.name} in the receiver class $receiverKClass")
is RuntimePropertyResolver.WriteResolution.ResolvedWrite -> property.setter.setValue(receiverInstance, value)
RuntimePropertyResolver.WriteResolution.UnresolvedWrite -> error("cannot set property ${dataProperty.name} in the receiver class $receiverKClass")
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@
package org.gradle.internal.declarativedsl.mappingToJvm


interface DeclarativeRuntimeProperty {
fun interface DeclarativeRuntimePropertyGetter {
fun getValue(receiver: Any): Any?
}


fun interface DeclarativeRuntimePropertySetter {
fun setValue(receiver: Any, value: Any?)
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,55 +16,86 @@

package org.gradle.internal.declarativedsl.mappingToJvm

import org.gradle.internal.declarativedsl.mappingToJvm.RuntimePropertyResolver.Resolution.Resolved
import org.gradle.internal.declarativedsl.mappingToJvm.RuntimePropertyResolver.Resolution.Unresolved
import org.gradle.internal.declarativedsl.mappingToJvm.RuntimePropertyResolver.ReadResolution
import org.gradle.internal.declarativedsl.mappingToJvm.RuntimePropertyResolver.ReadResolution.ResolvedRead
import org.gradle.internal.declarativedsl.mappingToJvm.RuntimePropertyResolver.ReadResolution.UnresolvedRead
import org.gradle.internal.declarativedsl.mappingToJvm.RuntimePropertyResolver.WriteResolution
import org.gradle.internal.declarativedsl.mappingToJvm.RuntimePropertyResolver.WriteResolution.ResolvedWrite
import org.gradle.internal.declarativedsl.mappingToJvm.RuntimePropertyResolver.WriteResolution.UnresolvedWrite
import java.lang.reflect.Modifier
import kotlin.reflect.KClass
import kotlin.reflect.KMutableProperty
import kotlin.reflect.KProperty
import kotlin.reflect.KVisibility
import kotlin.reflect.full.memberFunctions
import kotlin.reflect.full.memberProperties


interface RuntimePropertyResolver {
fun resolvePropertyRead(receiverClass: KClass<*>, name: String): Resolution
fun resolvePropertyWrite(receiverClass: KClass<*>, name: String): Resolution
fun resolvePropertyRead(receiverClass: KClass<*>, name: String): ReadResolution
fun resolvePropertyWrite(receiverClass: KClass<*>, name: String): WriteResolution

sealed interface Resolution {
data class Resolved(val property: DeclarativeRuntimeProperty) : Resolution
data object Unresolved : Resolution
sealed interface ReadResolution {
data class ResolvedRead(val getter: DeclarativeRuntimePropertyGetter) : ReadResolution
data object UnresolvedRead : ReadResolution
}

sealed interface WriteResolution {
data class ResolvedWrite(val setter: DeclarativeRuntimePropertySetter) : WriteResolution
data object UnresolvedWrite : WriteResolution
}
}


object ReflectionRuntimePropertyResolver : RuntimePropertyResolver {
override fun resolvePropertyRead(receiverClass: KClass<*>, name: String): RuntimePropertyResolver.Resolution {
val callable = receiverClass.memberProperties.find { it.name == name && it.visibility == KVisibility.PUBLIC }
?: receiverClass.memberFunctions.find { it.name == getterName(name) && it.parameters.size == 1 && it.visibility == KVisibility.PUBLIC }

return when (callable) {
null -> Unresolved
else -> Resolved(object : DeclarativeRuntimeProperty {
override fun getValue(receiver: Any): Any? = callable.call(receiver)
override fun setValue(receiver: Any, value: Any?) = throw UnsupportedOperationException()
})
}
override fun resolvePropertyRead(receiverClass: KClass<*>, name: String): ReadResolution {
val getter = findKotlinProperty(receiverClass, name)?.let(::kotlinPropertyGetter)
?: findKotlinFunctionGetter(receiverClass, name)
?: findJavaGetter(receiverClass, name)

return getter?.let(::ResolvedRead) ?: UnresolvedRead
}

override fun resolvePropertyWrite(receiverClass: KClass<*>, name: String): RuntimePropertyResolver.Resolution {
val setter = (receiverClass.memberProperties.find { it.name == name && it.visibility == KVisibility.PUBLIC } as? KMutableProperty<*>)?.setter
?: receiverClass.memberFunctions.find { it.name == setterName(name) && it.visibility == KVisibility.PUBLIC }

return when (setter) {
null -> Unresolved
else -> Resolved(object : DeclarativeRuntimeProperty {
override fun getValue(receiver: Any): Any = throw UnsupportedOperationException()
override fun setValue(receiver: Any, value: Any?) {
setter.call(receiver, value)
}
})
}
override fun resolvePropertyWrite(receiverClass: KClass<*>, name: String): WriteResolution {
val setter = (findKotlinProperty(receiverClass, name) as? KMutableProperty<*>)?.let(::kotlinPropertySetter)
?: findKotlinFunctionSetter(receiverClass, name)
?: findJavaSetter(receiverClass, name)

return setter?.let(::ResolvedWrite) ?: UnresolvedWrite
}

private
fun findKotlinProperty(receiverClass: KClass<*>, name: String) =
receiverClass.memberProperties.find { it.name == name && it.visibility == KVisibility.PUBLIC }

private
fun kotlinPropertyGetter(property: KProperty<*>) =
DeclarativeRuntimePropertyGetter { property.call(it) }

private
fun kotlinPropertySetter(property: KMutableProperty<*>) =
DeclarativeRuntimePropertySetter { receiver, value -> property.setter.call(receiver, value) }

private
fun findKotlinFunctionGetter(receiverClass: KClass<*>, name: String) =
receiverClass.memberFunctions.find { it.name == getterName(name) && it.parameters.size == 1 && it.visibility == KVisibility.PUBLIC }
?.let { property -> DeclarativeRuntimePropertyGetter { property.call(it) } }

private
fun findKotlinFunctionSetter(receiverClass: KClass<*>, name: String) =
receiverClass.memberFunctions.find { it.name == setterName(name) && it.visibility == KVisibility.PUBLIC }
?.let { function -> DeclarativeRuntimePropertySetter { receiver: Any, value: Any? -> function.call(receiver, value) } }

private
fun findJavaGetter(receiverClass: KClass<*>, name: String) =
receiverClass.java.methods.find { it.name == getterName(name) && it.parameters.isEmpty() && it.modifiers.and(Modifier.PUBLIC) != 0 }
?.let { method -> DeclarativeRuntimePropertyGetter { method.invoke(it) } }

private
fun findJavaSetter(receiverClass: KClass<*>, name: String) =
receiverClass.java.methods.find { it.name == setterName(name) && it.parameters.size == 1 && it.modifiers.and(Modifier.PUBLIC) != 0 }
?.let { method -> DeclarativeRuntimePropertySetter { receiver: Any, value: Any? -> method.invoke(receiver, value) } }

private
fun getterName(propertyName: String) = "get" + capitalize(propertyName)

Expand All @@ -79,21 +110,21 @@ object ReflectionRuntimePropertyResolver : RuntimePropertyResolver {


class CompositePropertyResolver(private val resolvers: List<RuntimePropertyResolver>) : RuntimePropertyResolver {
override fun resolvePropertyRead(receiverClass: KClass<*>, name: String): RuntimePropertyResolver.Resolution {
override fun resolvePropertyRead(receiverClass: KClass<*>, name: String): ReadResolution {
resolvers.forEach {
val resolution = it.resolvePropertyRead(receiverClass, name)
if (resolution is Resolved)
if (resolution !is UnresolvedRead)
return resolution
}
return Unresolved
return UnresolvedRead
}

override fun resolvePropertyWrite(receiverClass: KClass<*>, name: String): RuntimePropertyResolver.Resolution {
override fun resolvePropertyWrite(receiverClass: KClass<*>, name: String): WriteResolution {
resolvers.forEach {
val resolution = it.resolvePropertyWrite(receiverClass, name)
if (resolution is Resolved)
if (resolution !is UnresolvedWrite)
return resolution
}
return Unresolved
return UnresolvedWrite
}
}

0 comments on commit 28e7367

Please sign in to comment.