aboutsummaryrefslogtreecommitdiff
path: root/src/main/kotlin/me/bush/eventbuskotlin/CancelledState.kt
blob: a0e01c0bed6573831c78c87f95f0b8f36d05f06c (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
package me.bush.eventbuskotlin

import sun.misc.Unsafe
import java.lang.reflect.Modifier
import java.util.concurrent.ConcurrentHashMap
import kotlin.reflect.KClass
import kotlin.reflect.KMutableProperty
import kotlin.reflect.full.declaredMembers
import kotlin.reflect.full.isSubclassOf
import kotlin.reflect.full.withNullability
import kotlin.reflect.jvm.javaField
import kotlin.reflect.typeOf

/**
 * A simple SAM interface for determining if an event (or any class) is cancellable.
 *
 * @author bush
 * @since 1.0.0
 */
internal fun interface CancelledState {

    /**
     * [event] should only ever be of the type that was passed
     * to [CancelledState.invoke], **or this will throw.**
     *
     * @return `true` if [event] is cancelled, `false` otherwise.
     */
    fun isCancelled(event: Any): Boolean

    companion object {
        private val UNSAFE = runCatching {
            Unsafe::class.declaredMembers.single { it.name == "theUnsafe" }.handleCall() as Unsafe
        }.onFailure {
            LOGGER.error("Could not obtain Unsafe instance. Will not be able to determine external cancel state.")
        }.getOrNull() // soy jvm
        private val NAMES = arrayOf("canceled", "cancelled")
        private val NOT_CANCELLABLE = CancelledState { false }
        private val CACHE = ConcurrentHashMap<KClass<*>, CancelledState>()

        /**
         * Creates a [CancelledState] object for events of class [type].
         */
        operator fun invoke(type: KClass<*>, config: Config): CancelledState = CACHE.getOrPut(type) {
            // Default implementation for our event class.
            if (type.isSubclassOf(Event::class)) CancelledState { (it as Event).cancelled }
            // If compatibility is disabled.
            else if (!config.thirdPartyCompatibility) NOT_CANCELLABLE
            // Find a field named "cancelled" or "canceled" that is a boolean, and has a backing field.
            else type.allMembers.filter { it.name in NAMES && it.returnType.withNullability(false) == typeOf<Boolean>() }
                .filterIsInstance<KMutableProperty<*>>().filter { it.javaField != null }.toList().let {
                    if (it.isEmpty() || UNSAFE == null) NOT_CANCELLABLE else {
                        if (it.size != 1) config.logger.warn("Multiple possible cancel fields found for event $type")
                        val offset = it[0].javaField!!.let { field ->
                            if (Modifier.isStatic(field.modifiers))
                                UNSAFE.staticFieldOffset(field)
                            else UNSAFE.objectFieldOffset(field)
                        }
                        // This is the same speed as direct access, plus one JNI call.
                        CancelledState { event -> UNSAFE.getBoolean(event, offset) }
                    }
                }
        }
    }
}