[SOLVED] Kotlin invoke companion function with reflection

Issue

This Content is from Stack Overflow. Question asked by delimiter

I’m trying to notify a central class on startup about the existence of some classes that will be used during runtime. My idea is to use reflection: scan for annotation (or interface implementation) and call a method from the companions object of those classes.

As suggested in [1] I am using classgraph but I’m totally open to alternatives.

package com.test

import io.github.classgraph.ClassGraph
import io.github.classgraph.ClassInfo
import io.github.classgraph.ScanResult
import kotlin.reflect.KFunction
import kotlin.reflect.jvm.kotlinFunction

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

// a class that will be instantiated a lot of times during runtime
data class SomeClass(val name: String) {

  companion object {

    @OnStartup
    fun onStartup() {
      // notify someone at startup about our existence
    }
  }
}


fun main() {
  val scanResult: ScanResult = ClassGraph().enableAllInfo().acceptPackages("com.test").scan()

  scanResult
    .getClassesWithMethodAnnotation(OnStartup::class.java.name)
    .filter { it.isStatic }
    .flatMap { findStartupMethods(it) }
    .forEach { it.call() }
}

private fun findStartupMethods(classInfo: ClassInfo): List<KFunction<*>> {
  return classInfo.methodInfo.filter { function ->
    function.hasAnnotation(OnStartup::class.java)
  }.mapNotNull { method ->
    method.loadClassAndGetMethod().kotlinFunction
  }
}

The problem is, that the code exits with
Exception in thread "main" java.lang.IllegalArgumentException: Callable expects 1 arguments, but 0 were provided.

From reading the Kotlin Docs and [2] my guess is that I should handover the companionObjectInstance as parameter. But I have absolutely no idea how to get it…

Any help is really appreciated.

[1] Getting a list of annotated functions in Kotlin using reflection
[2] Kotlin invoke companion function with reflection



Solution

This should work:

inline fun <reified T> test() {
    val companionObject = T::class.companionObject
    if (companionObject != null) {
        val body: Person.() -> Unit = { println("body called on $name!") }
        val companionInstance = T::class.companionObjectInstance
        val functionEx = companionObject.functions.first { it.name.equals("sleep") }
        functionEx.call(companionInstance, body)
    }
}

To call the function using call(...), you need to pass the companion object instance as the first argument (which stands for the sleep function’s receiver) and an instance of the function type Person.() -> Unit as the second one.

Note that companionObject returns not the companion object instance but its KClass. To get the instance, use companionObjectInstance instead.


This Question was asked in StackOverflow by BhaskerYadav and Answered by hotkey It is licensed under the terms of CC BY-SA 2.5. - CC BY-SA 3.0. - CC BY-SA 4.0.

people found this article helpful. What about you?