Jire
Jire

Reputation: 10290

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()
}

Upvotes: 5

Views: 3156

Answers (3)

Roman Kotenko
Roman Kotenko

Reputation: 516

One way to avoid reflection is to first initialize your delegate object and store it as a member of its own, and then delegate your property by it.

object Example {
    val aDelegate = DelegationExample()
    val bDelegate = DelegationExample()
    val cDelegate = DelegationExample()

    val a by aDelegate
    val b by bDelegate
    val c by cDelegate
}

Upvotes: 2

Jayson Minard
Jayson Minard

Reputation: 85946

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]

Upvotes: 4

voddan
voddan

Reputation: 33769

On the byte code level delegated properties do not defer from regular ones (public getter/setter and a private field).

One way you could go is scanning the private fields of Example and filtering those which have operator getValue(thisRef: R, KProperty<*>). Technically a field may contain a delegate object val x = lazy {1}, but that is not very likely.

Upvotes: 1

Related Questions