aboutsummaryrefslogtreecommitdiff
path: root/src/main/kotlin/me/bush/eventbuskotlin/Util.kt
blob: f576f4cc3c1fa53013563b80700aad6958ade963 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
package me.bush.eventbuskotlin

import org.apache.logging.log4j.LogManager
import kotlin.reflect.KCallable
import kotlin.reflect.KClass
import kotlin.reflect.full.*
import kotlin.reflect.jvm.isAccessible
import kotlin.reflect.typeOf

// by bush, unchanged since 1.0.0

internal val LOGGER = LogManager.getLogger("EventBus")

/**
 * Using [KClass.members] only returns public members, and using [KClass.declaredMembers]
 * doesn't return inherited members. This returns all members, private and inherited.
 */
internal val <T : Any> KClass<T>.allMembers
    get() = (declaredMembers + allSuperclasses.flatMap { it.declaredMembers }).asSequence()

/**
 * Checks if a [KCallable] is static on the jvm, and handles invocation accordingly.
 *
 * I have tried to check if the callable needs a receiver, and have left my code
 * below, but for some reason a property (private, no getter) of a companion object
 * (which is static in bytecode) requires a receiver, while an identical property in a
 * non companion object does not, and will throw if one is passed.
 *
 * I am not aware of a way to check if a property belongs to a companion object, is static,
 * or requires certain arguments. (instanceParameter exists, but it will throw if it is an argument)
 * I thought maybe I was using the wrong methods, but apart from KProperty#get, (which is only for
 * properties, and only accepts arguments of type `Nothing` when `T` is star projected or covariant)
 * I could not find any other way to do this, not even on StackOverflow.
 *
 * Funny how this solution is 1/10th the lines and always works.
 */
internal fun <R> KCallable<R>.handleCall(receiver: Any? = null): R {
    isAccessible = true
    return runCatching { call(receiver) }.getOrElse { call() }
}

/*
private val KCallable<*>.isJvmStatic
    get() = when (this) {
        is KFunction -> Modifier.isStatic(javaMethod?.modifiers ?: 0)
        is KProperty -> this.javaGetter == null && Modifier.isStatic(javaField?.modifiers ?: 0)
        else -> false
    }
 */

/**
 * Finds all members of return type [Listener]. (properties and methods)
 */
@Suppress("UNCHECKED_CAST") // This cannot fail
private inline val KClass<*>.listeners
    get() = allMembers.filter {
        // Set nullability to false, so this will detect listeners in Java
        // with "!" nullability. Also make sure there are no parameters.
        it.returnType.withNullability(false) == typeOf<Listener>() && it.valueParameters.isEmpty()
    } as Sequence<KCallable<Listener>>

/**
 * Finds all listeners in [subscriber].
 *
 * @return A list of listeners belonging to [subscriber], or null if an exception is caught.
 */
internal fun getListeners(subscriber: Any, config: Config) = runCatching {
    subscriber::class.listeners.filter { !config.annotationRequired || it.hasAnnotation<EventListener>() }
        .map { member -> member.handleCall(subscriber).also { it.subscriber = subscriber } }.toList()
}.onFailure { config.logger.error("Unable to register listeners for subscriber $subscriber", it) }.getOrNull()

/**
 * An annotation that must be used to identify listeners if [Config.annotationRequired] is `true`.
 *
 * [Information and examples](https://github.com/therealbush/eventbus-kotlin#annotationRequired)
 */
annotation class EventListener