Search code examples
propertiesdelegateskotlinmemberdelegation

Kotlin: How can I get the delegation class of a member property?


How can I get the delegation class of a member property?

By this, I mean is it possible to complete such a function:

inline fun <reified T> delegationExample(t: T) {
    for (prop in T::class.declaredMemberProperties) {
        val delegatedClass = // what to do?!
    }
}

Where the delegation class may look like:

class DelegationExample {
    operator fun getValue(ref: Any, prop: KProperty<*>) = 0
}

And the declaration class might look like this:

object Example {
    val a by DelegationExample()
    val b by DelegationExample()
    val c by DelegationExample()
}

Solution

  • To find properties that delegate to a delegate class along with the instance of that class, here is a utility function:

    data class DelegatedProperty<T : Any, DELEGATE : Any>(val property: KProperty1<T, *>, val delegatingToInstance: DELEGATE)
    
    inline fun <reified T : Any, DELEGATE : Any> findDelegatingPropertyInstances(instance: T, delegatingTo: KClass<DELEGATE>): List<DelegatedProperty<T, DELEGATE>> {
        return T::class.declaredMemberProperties.map { prop ->
            val javaField = prop.javaField
            if (javaField != null && delegatingTo.java.isAssignableFrom(javaField.type)) {
                javaField.isAccessible = true // is private, have to open that up
                @Suppress("UNCHECKED_CAST")
                val delegateInstance = javaField.get(instance) as DELEGATE
                DelegatedProperty(prop, delegateInstance)
            } else {
                null
            }
        }.filterNotNull()
    }
    

    A few notes:

    • First correct your reified type T to T: Any or you cannot access all of the extensions in Kotlin reflection including declaredMemberProperties
    • It is easiest to get to the field from a property reference to be sure you are actually talking about something that is really a property, so for each of declaredMemberProperties use javaField to do so.
    • Since javaField is a custom getter and could be nullable, it is saved to a local variable so smart casting will work later.
    • Then if this field has the same type as the delegation class you are looking for, you can then access the field.
    • But first you have to force the field's accessibility because it is a private field.

    Running this in test program:

    class DelegationExample {
        operator fun getValue(ref: Any, prop: KProperty<*>) = 0
    }
    
    class Example {
        val a by DelegationExample()
        val b by DelegationExample()
        val c by DelegationExample()
    }
    
    fun main(args: Array<String>) {
        findDelegatingPropertyInstances(Example(), DelegationExample::class).forEach {
            println("property '${it.property.name}' delegates to instance of [${it.delegatingToInstance}]")
        }
    }
    

    The output is something like:

    property 'a' delegates to instance of [DelegationExample@2c1b194a]
    property 'b' delegates to instance of [DelegationExample@4dbb42b7]
    property 'c' delegates to instance of [DelegationExample@66f57048]