Hi, for my project’s DSL, I want to implement a method that accepts a KFunction<T>
, converts it to MethodHandle
and creates a dispatcher object, that supplies all parameters and invokes the MethodHandle
when needed.
Why a MethodHandle
? Because I want to invoke the method with dynamic arguments and this should be much more performant then KFunction<T>.call(...)
which (to my knowlege) utilizes reflection every time.
But when I create a method reference (KFunction<T>
), the instance receiver and it’s type are hidden from me. This is not conveniet for my purposes. And that’s what DSL is about - convenience.
I found two possible ways to retrieve the receiver instance or just it’s class, but both ways are clunky and probably not very safe
open class MyInterface {
fun exec() {
myMethod("param")
}
open fun myMethod(param: String) {}
}
class MyClass() : MyInterface() {
override fun myMethod(param: String) {
println(param)
}
}
fun <T> createDispatcher(function: KFunction<T>) {
// refers to MyInterface, but I need MyClass. This makes sense, of course - I'm just addressing this option as not viable
val receiverClass = function.javaMethod!!.declaringClass
// Option 1 - only getting the receiver class name
run {
// KFunction<T>.toString() this returns `fun org.budgetbakers.api.handlers.MyClass.exec(): kotlin.Unit`,
// which can be used to identify the receiver class at least. But having it as this solution is rather cumbersome.
// Matching a class based on string and relying on the `toString` method is just bad..
val methodfullName = function.toString()
val receiverClassName = methodfullName.removePrefix("fun ")
.split('(')
.first().split('.')
.dropLast(1)
.joinToString(".")
println("Receiver class name: $receiverClassName")
}
// Option 2 - obtaining the receiver via reflection and accessing internal class
run {
val getBoundReceiverInternalMethod =
Class.forName("kotlin.jvm.internal.CallableReference").declaredMethods.first { it.name == "getBoundReceiver" }
val receiver = getBoundReceiverInternalMethod.invoke(function)
println("Receiver class name: ${receiver.javaClass.name}")
}
}
So I’m proposing, that KFunction is extended with a member that lets us retrieve the receiver or just it’s class
EDIT: I just discovered a third and probably the safest solution (though far from ideal)
package kotlin.jvm.internal
import kotlin.reflect.KFunction
object KotlinJvmUtils {
fun getBoundReceiver(function: KFunction<*>) = run {
function as? CallableReference ?: throw InternalError("")
function.boundReceiver.takeIf { it != CallableReference.NO_RECEIVER }
}
}
EDIT II: the third option won’t compile (the error is a bit vague, but it is clear that the package name is not allowed) , so it is not an option at all