vendredi 4 septembre 2020

Kotlin - link two variables by Delegates, Annotation or?

I get interesting idea, but I am not sure how to do it (if it is even possible - have some tips).

Main use case: Variable of (data) class have reference to another variable. Be able at runtime get information about "linked" variable.

Why? I am sick of writing dummy converters like:

fun Foo.toMap() { return mapOf("var1" to this.var1") }

Where constant keys are known. It would be nice to have this declaration attached to specific data class and then just use generic function to get map of it.

Some approach:

fun main() {
    val a = A("propA")
    val b = B("propB")
    
    val nameOfTargetedProp = getProperty(b::bName)
}


data class A(
    // target variable
    val name: String
)

data class B(
    // attach link to A::name
    val bName: String
)

// Be able to get runtime variable and it's properties / values
fun getProperty(prop: KProperty0<Any?>): String {
    val propA = A::name // retrieve target property
    return propA.name
}

Two things comes in mind:

  • annotations
    • I can put target class as parameter
    • constant string as second parameter - name of the targeted variable
    • in processor, I can retrieve member property of target class
    • not interested in using name constants (will be pain when target classes will change)

Using classes from above:

@Target(AnnotationTarget.FIELD)
@Retention(AnnotationRetention.RUNTIME)
annotation class TargetProp(val targetClass: KClass<A>, targetProp: String)

data class B(
    @TargetProp(A::class, "name") val bName: String
)

fun main() {
    val objA = A("aName")
    val objB = B("bName")

    objB.javaClass.declaredFields.forEach {
        if (it.isAnnotationPresent(TargetProp::class.java)) {
            val ann = it.getAnnotation(TargetProp::class.java)
            val field = ann.parent.memberProperties.first { it.name == ann.name }
            
            // Retrieve object of variable A.name
            val cc = f.getter.call(objA)
        }
    }
}
  • delegates
    • I can create delegate, which will store through setValue(...) target property
    • store this links in map
    • retrieve map when needed
    • I can not use data classes and classes with main constructor anymore
    • some crazy "harakiri" with nullability and Jackson de/serializing (still need to investigate it)

Some example (my friend wrote that, I am still learning, how to use it):

// main delegate class
open class LinkedProperties {
    @JsonIgnore
    val propertiesMap: MutableMap<String, Any?> = mutableMapOf()

    inline fun <reified T> byName(name: String) = object : ReadWriteProperty<LinkedProperties, T> {
        override fun getValue(thisRef: LinkedProperties, property: KProperty<*>): T {
            return propertiesMap[name] as T
        }

        override fun setValue(thisRef: LinkedProperties, property: KProperty<*>, value: T) {
            propertiesMap[name] = value
        }
    }

    operator inline fun <reified T> String.invoke() = byName<T>(this)
}

val someTitle = "targetPropTitle"
val somDescription = "targetPropDesc"

class Foo : LinkedProperties() {
    var title: String? by someTitle()
    var description: String? by somDescription()
}

fun main() {
    val foo = Foo().apply {
        title = "title sample"
        description = null
    }

    val targetNames = foo.propertiesMap
}

I hope it is readable and understandable. Any ideas how to solve it? Thanks





Aucun commentaire:

Enregistrer un commentaire