5

If I'm on the JVM I can do this:

object Playground {

    class DynamicInvocationHandler : InvocationHandler {

        @Throws(Throwable::class)
        override operator fun invoke(proxy: Any, method: Method, args: Array<Any>): Any {
            LOGGER.info("Invoked method: {}", method.name)

            return 42
        }

        companion object {

            private val LOGGER = LoggerFactory.getLogger(
                    DynamicInvocationHandler::class.java)
        }
    }

    @JvmStatic
    fun main(args: Array<String>) {
        val proxy = Proxy.newProxyInstance(
                Playground::class.java.classLoader,
                arrayOf<Class<*>>(MutableMap::class.java),
                DynamicInvocationHandler()) as MutableMap<String, String>

        proxy["foo"] = "bar"
    }
}

and running this will print Invoked method: put. How can I do something like this in a Kotlin common project?

Edit: I'm not trying to use anything from Java in my common module. I know how common projects work. What I'm interested in instead is whether there is a Kotlin-based solution for this or not.

Edit 2: I'm not trying to proxy the Map class. I'm looking for something like Proxy in the JDK which I can use to proxy any interface. Sorry for the confusion.

Adam Arold
  • 29,285
  • 22
  • 112
  • 207
  • You are not able to use Java Reflection in common module. Because this is a Java feature and available only in Java module. – Denis Shurygin Jan 23 '19 at 12:41
  • I edited my answer. I know that this is a Java feature, I understand what a common project is. I was not able to find a Kotlin-based solution though that's why I'm asking. – Adam Arold Jan 23 '19 at 12:44
  • Found https://discuss.kotlinlang.org/t/dynamic-proxy/17802 discussing this topic. – Michiel Leegwater May 02 '21 at 09:53

3 Answers3

1

Probably Expect/Actual Factory should solve the problem.

Common Code:

interface ProxyMethod {
    val name: String
    // other properties
}

interface ProxyHandler {
    fun invoke(proxy: Any, method: ProxyMethod, args: Array<Any>): Any
}

expect object Logger {
    fun info(message: String, vararg arguments: Any)
}

expect object ProxyFactory {
    fun mutableMapWithProxy(handler: ProxyHandler): MutableMap<String, String>
}

private class ProxyHandlerImpl: ProxyHandler {
    override fun invoke(proxy: Any, method: ProxyMethod, args: Array<Any>): Any {
        Logger.info("Invoked method: {}", method.name)
        return 0
    }
}

object Common {
    fun doSomething() {
        val myMap = ProxyFactory.mutableMapWithProxy(ProxyHandlerImpl())
        myMap["foo"] = "bar"
    }
}

Java Code:

actual object Logger {

    private val instance = LoggerFactory.getLogger(
            DynamicInvocationHandler::class.java)

    actual fun info(message: String, vararg arguments: Any) {
        instance.info(message, *arguments)
    }
}

actual object ProxyFactory  {
    actual fun mutableMapWithProxy(handler: ProxyHandler): MutableMap<String, String> {
        return Proxy.newProxyInstance(
                Playground::class.java.classLoader,
                arrayOf<Class<*>>(MutableMap::class.java),
                ProxyHandlerAdapter(handler)) as MutableMap<String, String>
    }
}

class ProxyHandlerAdapter(private val handler: ProxyHandler) : InvocationHandler {

    @Throws(Throwable::class)
    override operator fun invoke(proxy: Any, method: Method, args: Array<Any>): Any {
        return handler.invoke(proxy, methodToProxyMethod(method), args)
    }

    fun methodToProxyMethod(method: Method): ProxyMethod {
        // convert Method to ProxyMethod
    }
}

@JvmStatic
fun main(args: Array<String>) {
    Common.doSomething()
}

Unfortunately I don't know any library that simplify this work so you should do this by hand for each platform and interface.

  • 1
    I'm not trying to proxy a `Map`. I'm looking for a proxy solution with which I can proxy *any interface*. – Adam Arold Jan 23 '19 at 13:55
  • 1
    I understand what you want. But now this is not possible without excessive efforts. Maybe not, but it looks like. I Update my answer to show the way how to proxy any interface. – Denis Shurygin Jan 23 '19 at 14:46
  • Code generation will simplify the work. But some one should write a library that generate the code. – Denis Shurygin Jan 23 '19 at 14:52
  • Be careful with `java.lang.reflect.Proxy`, it does not let you throw an exception of any type (it will wrap them to follow JLS) https://jonnyzzz.com/blog/2018/11/22/proxy/ – Eugene Petrenko Jan 23 '19 at 16:15
1

I think the simple answer is that Kotlin Multi Platform reflection doesn't support proxies. You can use @KamiSempai's expect - actual solution when using the common module in a java application but you will need to find alternatives for JS and native targets.

Kirill Rakhman
  • 42,195
  • 18
  • 124
  • 148
1

There is no equivalent for this in current Kotlin Native versions. Looking at the other answers, they seem to have actual types in the expect/actuals, but the purpose of a live proxy is to supply a type at runtime and generate a compatible instance that can be delegated to.

This is how things like Retrofit work. Rather than source gen, the interface definitions are proxied internally.

For now, you'll need to do source-gen, as far as I know. That's for native. Not sure about JS.

Kevin Galligan
  • 16,159
  • 5
  • 42
  • 62