lundi 22 mars 2021

Kotlin Reflection : How to add a KFunction to a list via reflection?

I want to add to a MutableList<(some-type)->Uint> at run-time depending on the type. I mostly got it, but I can't figure out how to get the instance info into the KFunction I have.

This code is a bit contrived, but it's a stripped down example of what I'm trying to do. I'm trying to get rid of a bunch of boiler plate code by wiring up dispatch at runtime.

This code compiles, but blows up at runtime when using the lists with the error : java.lang.IllegalArgumentException: Callable expects 2 arguments, but 1 were provided. from the doSomeWork() when dispatching.

This error makes me think the instance isn't associated w/ the KFunction I'm adding to the list... but I don't know how to associate it. I have the instance handy, I just don't know how to attach it. Or maybe it's something else entirely.

The code :

package reflectwork

import kotlin.reflect.KFunction
import kotlin.reflect.full.functions
import kotlin.reflect.full.hasAnnotation
import kotlin.reflect.full.memberProperties

interface Marker

@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
annotation class WireMeUp

class Dispacher {
    val strList = mutableListOf<(String)->Unit>()
    val intList = mutableListOf<(Int)->Unit>()
    val floatList = mutableListOf<(Float)->Unit>()

    fun doSomeWork() {
        strList.forEach { it("Some work") }
        intList.forEach { it(57) }
        floatList.forEach { it(3.14f) }
    }
}


class Client : Marker {
    @WireMeUp fun doStringWork(str:String) { println(str) }
    @WireMeUp fun doIntWork(i:Int) { println(i) }
    @WireMeUp fun doFloatWork(f:Float) { println(f) }
}


fun wire(app:Marker, dis:Dispacher) {

    app::class.functions.forEach { func ->
        if( func.hasAnnotation<WireMeUp>() ) {
            require( func.parameters.size == 2 )
            val parmType = func.parameters[1].type

            dis::class.memberProperties.forEach{ prop ->
                if( prop.returnType.arguments.size == 1 ) {

                    val argType = prop.returnType.arguments[0].type
                    // I have no idea how to compare a [type] to a ([type]) -> Unit
                    // so just do it via strings. better way?
                    if( "($parmType)->kotlin.Unit" == argType.toString().replace(" ","")) {

                        println("found match, setting type = $parmType")
                        // get the list. No idea how cast to the correct type
                        // which in theory I know at this point.
                        val lst = prop.call(dis) as MutableList<KFunction<*>>

                        // this works, but blows up at runtime in doSomeWork()
                        // with the error :
                        // java.lang.IllegalArgumentException: Callable expects 2 arguments, but 1 were provided.
                        // I think because "func" has not instance? Not sure how to attach an instance... which I have
                        lst.add( func )

                    }
                }
            }

        }
    }
}


fun main() {

    val dispatch = Dispacher()
    val cli = Client()
    wire(cli,dispatch)
    dispatch.doSomeWork()
}

Any advice?

Thanks in advance.





Aucun commentaire:

Enregistrer un commentaire