1

I'm trying to get the class that owns a companion object so I can create loggers with inline technique inside a companion object but referencing the main class that is being logged and not the companion object.

The problem is that I can't find a way to get the owner of the companion object, how can I do it?

fun Logger(c: Class<*>): Logger {
    var c2 = c
    val k = c.kotlin
    if (k.isCompanion) {
        c2 = k.<opposite of what companionObject does>.java
    }

    // Calls a factory, reuse the same instance if it already exists
    return RootLogger.getChild(c2)
}

@Suppress("NOTHING_TO_INLINE")
inline fun Logger(): Logger {
    return Logger(MethodHandles.lookup().lookupClass())
}

The intended use-cases:

A:

class SomeClass {
    companion object {
        // Logger is inside a companion object
        // But it must be the same as if it were created directly inside `SomeClass`
        private val log = Logger()
    }

    fun someFun() = log.info("Hello")
}

B:

// can be object or anything else
class SomeClass {
    // The same object is returned to all instances and would be the same object
    // as if it were inside a companion object
    private val log = Logger()

    fun someFun() = log.info("Hello")
}
Polyana Fontes
  • 3,156
  • 1
  • 27
  • 41
  • `MethodHandles.lookup().lookupClass()` - what is this sorcery? O_O I just learnt something new today. – broot Feb 17 '22 at 21:48

1 Answers1

0

In JVM environment that can be achieved with:

k.java.enclosingClass.kotlin

So the function would be:

fun Logger(c: Class<*>): Logger {
    var c2 = c
    val k = c.kotlin
    if (k.isCompanion) {
        c2 = k.java.enclosingClass
    }

    // Calls a factory, reuse the same instance if it already exists
    return RootLogger.getChild(c2)
}

That can be tested with:

internal class LoggerTest {
    @Test
    internal fun testUseCaseA() {
        val fqn = UseCaseA::class.qualifiedName
        assertEquals(fqn, UseCaseA.log.name)

        val log = Logger(UseCaseA::class.java)
        assertEquals(fqn, log.name)
        assertSame(UseCaseA.log, log)

        val log2 = Logger(UseCaseA.Companion::class.java)
        assertEquals(fqn, log2.name)
        assertSame(log, log2)
    }

    @Test
    internal fun testUseCaseB() {
        val fqn = UseCaseB::class.qualifiedName
        val b1 = UseCaseB()
        assertEquals(fqn, b1.log.name)

        val log = Logger(UseCaseB::class.java)
        assertEquals(fqn, log.name)
        assertSame(b1.log, log)

        val b2 = UseCaseB()
        assertEquals(fqn, b2.log.name)
        assertSame(b2.log, log)
    }

    private class UseCaseA {
        companion object {
            val log = Logger()
        }
    }

    private class UseCaseB {
        val log = Logger()
    }
}
Polyana Fontes
  • 3,156
  • 1
  • 27
  • 41