aboutsummaryrefslogtreecommitdiff
path: root/src/main/kotlin/me/bush/eventbuskotlin/CancelledState.kt
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/kotlin/me/bush/eventbuskotlin/CancelledState.kt')
-rw-r--r--src/main/kotlin/me/bush/eventbuskotlin/CancelledState.kt70
1 files changed, 70 insertions, 0 deletions
diff --git a/src/main/kotlin/me/bush/eventbuskotlin/CancelledState.kt b/src/main/kotlin/me/bush/eventbuskotlin/CancelledState.kt
new file mode 100644
index 0000000..861b9fd
--- /dev/null
+++ b/src/main/kotlin/me/bush/eventbuskotlin/CancelledState.kt
@@ -0,0 +1,70 @@
+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.
+ *
+ * [Information and examples](https://github.com/therealbush/eventbus-kotlin#tododothething)
+ *
+ * @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.**
+ *
+ * [Information and examples](https://github.com/therealbush/eventbus-kotlin#tododothething)
+ *
+ * @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].
+ *
+ * [Information and examples](https://github.com/therealbush/eventbus-kotlin#tododothething)
+ */
+ 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) }
+ }
+ }
+ }
+ }
+}