diff options
author | therealbush <therealbush@users.noreply.github.com> | 2022-04-14 00:31:16 -0700 |
---|---|---|
committer | therealbush <therealbush@users.noreply.github.com> | 2022-04-14 00:31:16 -0700 |
commit | b49065b50edd48d0f335e17bcf6868dc2f916ad5 (patch) | |
tree | a9ad233135919970f3a6f36a1369ef701a9ff677 /src | |
parent | b196c7c2fe06d84e862648a8bf25058f72c6c0b1 (diff) | |
download | eventbus-kotlin-b49065b50edd48d0f335e17bcf6868dc2f916ad5.tar.gz eventbus-kotlin-b49065b50edd48d0f335e17bcf6868dc2f916ad5.tar.bz2 eventbus-kotlin-b49065b50edd48d0f335e17bcf6868dc2f916ad5.zip |
soon (lol)
Diffstat (limited to 'src')
-rw-r--r-- | src/main/kotlin/me/bush/illnamethislater/Config.kt | 10 | ||||
-rw-r--r-- | src/main/kotlin/me/bush/illnamethislater/EventBus.kt | 47 | ||||
-rw-r--r-- | src/main/kotlin/me/bush/illnamethislater/EventListener.kt | 6 | ||||
-rw-r--r-- | src/main/kotlin/me/bush/illnamethislater/Listener.kt | 2 | ||||
-rw-r--r-- | src/main/kotlin/me/bush/illnamethislater/ListenerGroup.kt | 41 | ||||
-rw-r--r-- | src/main/kotlin/me/bush/illnamethislater/ReflectUtil.kt | 45 | ||||
-rw-r--r-- | src/test/java/JavaTest.java | 62 | ||||
-rw-r--r-- | src/test/java/TestJava.java | 80 | ||||
-rw-r--r-- | src/test/kotlin/KotlinTest.kt | 252 | ||||
-rw-r--r-- | src/test/kotlin/TestKotlin.kt | 215 |
10 files changed, 401 insertions, 359 deletions
diff --git a/src/main/kotlin/me/bush/illnamethislater/Config.kt b/src/main/kotlin/me/bush/illnamethislater/Config.kt index ef946c5..31c2b63 100644 --- a/src/main/kotlin/me/bush/illnamethislater/Config.kt +++ b/src/main/kotlin/me/bush/illnamethislater/Config.kt @@ -1,5 +1,6 @@ package me.bush.illnamethislater +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.Logger @@ -29,7 +30,7 @@ data class Config( * * [Information and examples](https://github.com/therealbush/eventbus-kotlin#tododothething) */ - val parallelContext: CoroutineContext = Dispatchers.Default, + val parallelScope: CoroutineScope = CoroutineScope(Dispatchers.Default), /** * Whether this [EventBus] should try to find a "cancelled" field in events being listened for that @@ -40,10 +41,7 @@ data class Config( val thirdPartyCompatibility: Boolean = true, /** - * Whether parallel listeners should be called before or after sequential listeners. Parallel listeners - * will always finish before sequential listeners are called, or before [EventBus.post] returns. - * - * [Information and examples](https://github.com/therealbush/eventbus-kotlin#tododothething) + * todo doc */ - val parallelFirst: Boolean = true + val annotationRequired: Boolean = false ) diff --git a/src/main/kotlin/me/bush/illnamethislater/EventBus.kt b/src/main/kotlin/me/bush/illnamethislater/EventBus.kt index 5b278e3..7affa4a 100644 --- a/src/main/kotlin/me/bush/illnamethislater/EventBus.kt +++ b/src/main/kotlin/me/bush/illnamethislater/EventBus.kt @@ -1,6 +1,10 @@ package me.bush.illnamethislater +import kotlinx.coroutines.delay import kotlin.reflect.KClass +import kotlin.reflect.full.companionObject +import kotlin.reflect.full.companionObjectInstance +import kotlin.reflect.full.hasAnnotation /** * [A simple event dispatcher.](https://github.com/therealbush/eventbus-kotlin#tododothething) @@ -13,22 +17,36 @@ class EventBus(private val config: Config = Config()) { private val subscribers = hashMapOf<Any, List<Listener>>() /** + * Returns the current count of active subscribers. + */ + val subscriberCount get() = subscribers.size + + /** + * Returns the current count of all listeners, regardless of type. + */ + val listenerCount get() = listeners.values.sumOf { it.parallel.size + it.sequential.size } + + /** * Searches [subscriber] for members that return [Listener] and registers them. * * This will not find top level listeners, use [register] instead. * - * Returns `false` if [subscriber] was already subscribed, `true` otherwise. + * Returns `true` if [subscriber] was successfully subscribed, + * `false` if it was already subscribed, or could not be. * * [Information and examples](https://github.com/therealbush/eventbus-kotlin#tododothething) */ fun subscribe(subscriber: Any): Boolean { return if (subscriber in subscribers) false else runCatching { - // Register every listener into a group, but also - // keep a separate list just for this subscriber. - subscribers[subscriber] = subscriber::class.listeners.map { member -> - register(member.handleCall(subscriber).also { it.subscriber = subscriber }) - }.toList() + // Keep a separate list just for this subscriber. + subscribers[subscriber] = subscriber::class.listeners + .filter { !config.annotationRequired || it.hasAnnotation<EventListener>() }.map { member -> + // Register listener to a group. + println("${member.name}, ${member.returnType}") + member.parameters.forEach { println(it) } + register(member.handleCall(subscriber).also { it.subscriber = subscriber }) + }.toList() true }.getOrElse { config.logger.error("Unable to register listeners for subscriber $subscriber", it) @@ -41,18 +59,13 @@ class EventBus(private val config: Config = Config()) { * * This will not remove top level listeners, use [unregister] instead. * - * Returns `true` if [subscriber] was subscribed, `false` otherwise. + * Returns `true` if [subscriber] was subscribed. * * [Information and examples](https://github.com/therealbush/eventbus-kotlin#tododothething) */ fun unsubscribe(subscriber: Any): Boolean { val contained = subscriber in subscribers - // Unregister every listener for this subscriber, - // and return null so the map entry is removed. - subscribers.computeIfPresent(subscriber) { _, listeners -> - listeners.forEach { unregister(it) } - null - } + subscribers.remove(subscriber)?.forEach { unregister(it) } return contained } @@ -69,7 +82,7 @@ class EventBus(private val config: Config = Config()) { } /** - * Unregisters a [Listener] from this [EventBus]. + * Unregisters a [Listener] from this [EventBus]. Returns `true` if [Listener] was registered. * * [Information and examples](https://github.com/therealbush/eventbus-kotlin#tododothething) */ @@ -115,11 +128,13 @@ class EventBus(private val config: Config = Config()) { * String: 3, 0 */ fun debugInfo() { - config.logger.info("Subscribers: ${subscribers.size}") + config.logger.info("Subscribers: ${subscribers.keys.size}") val sequential = listeners.values.sumOf { it.sequential.size } val parallel = listeners.values.sumOf { it.parallel.size } config.logger.info("Listeners: $sequential sequential, $parallel parallel") - listeners.values.sortedByDescending { it.sequential.size + it.parallel.size }.forEach { it.debugInfo() } + listeners.values.sortedByDescending { it.sequential.size + it.parallel.size }.forEach { + config.logger.info(it.toString()) + } } } diff --git a/src/main/kotlin/me/bush/illnamethislater/EventListener.kt b/src/main/kotlin/me/bush/illnamethislater/EventListener.kt new file mode 100644 index 0000000..e96b1db --- /dev/null +++ b/src/main/kotlin/me/bush/illnamethislater/EventListener.kt @@ -0,0 +1,6 @@ +package me.bush.illnamethislater + +/** + * todo docs + */ +annotation class EventListener diff --git a/src/main/kotlin/me/bush/illnamethislater/Listener.kt b/src/main/kotlin/me/bush/illnamethislater/Listener.kt index b2bb2ce..c9251d2 100644 --- a/src/main/kotlin/me/bush/illnamethislater/Listener.kt +++ b/src/main/kotlin/me/bush/illnamethislater/Listener.kt @@ -70,4 +70,4 @@ fun <T : Any> listener( // This might introduce some overhead, but its worth // not manually having to return "Kotlin.UNIT" from every Java listener. listener: Consumer<T> -) = Listener({ event: T -> listener.accept(event) }, type.kotlin, priority, parallel, receiveCancelled) +) = Listener(listener::accept, type.kotlin, priority, parallel, receiveCancelled) diff --git a/src/main/kotlin/me/bush/illnamethislater/ListenerGroup.kt b/src/main/kotlin/me/bush/illnamethislater/ListenerGroup.kt index fdcc7bc..b8116cb 100644 --- a/src/main/kotlin/me/bush/illnamethislater/ListenerGroup.kt +++ b/src/main/kotlin/me/bush/illnamethislater/ListenerGroup.kt @@ -1,7 +1,6 @@ package me.bush.illnamethislater -import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.* import java.util.concurrent.CopyOnWriteArrayList import kotlin.reflect.KClass @@ -23,9 +22,10 @@ internal class ListenerGroup( * Adds [listener] to this [ListenerGroup], and sorts its list. */ fun register(listener: Listener) { - with(if (listener.parallel) parallel else sequential) { - add(listener) - sortedByDescending { it.priority } + (if (listener.parallel) parallel else sequential).let { + if (it.addIfAbsent(listener)) { + it.sortedByDescending(Listener::priority) + } } } @@ -41,36 +41,25 @@ internal class ListenerGroup( * Posts an event to every listener. Returns true of the event was cancelled. */ fun post(event: Any): Boolean { - if (config.parallelFirst) postParallel(event) sequential.forEach { if (it.receiveCancelled || !cancelledState.isCancelled(event)) { it.listener(event) } } - if (!config.parallelFirst) postParallel(event) - return cancelledState.isCancelled(event) - } - - /** - * Posts an event to all parallel listeners. Cancel state of the event is checked once before - * posting the event as opposed to before calling each listener, to avoid inconsistencies. - */ - private fun postParallel(event: Any) { - if (parallel.isEmpty()) return - // We check this once, because listener order is not consistent - val cancelled = cancelledState.isCancelled(event) - // Credit to KB for the idea - runBlocking(config.parallelContext) { + if (parallel.isNotEmpty()) { + // We check this once, because listener order is not guaranteed. + val cancelled = cancelledState.isCancelled(event) + // Credit to KB for the idea parallel.forEach { - if (it.receiveCancelled || !cancelled) launch { - it.listener(event) + if (it.receiveCancelled || !cancelled) { + config.parallelScope.launch { + it.listener(event) + } } } } + return cancelledState.isCancelled(event) } - /** - * Logs information about this [ListenerGroup]. - */ - fun debugInfo() = config.logger.info("${type.simpleName}: ${sequential.size}, ${parallel.size}") + override fun toString() = "${type.simpleName}: ${sequential.size}, ${parallel.size}" } diff --git a/src/main/kotlin/me/bush/illnamethislater/ReflectUtil.kt b/src/main/kotlin/me/bush/illnamethislater/ReflectUtil.kt index ba42f76..2b16379 100644 --- a/src/main/kotlin/me/bush/illnamethislater/ReflectUtil.kt +++ b/src/main/kotlin/me/bush/illnamethislater/ReflectUtil.kt @@ -1,15 +1,15 @@ package me.bush.illnamethislater import java.lang.reflect.Modifier -import kotlin.reflect.KCallable -import kotlin.reflect.KClass -import kotlin.reflect.KProperty +import kotlin.reflect.* import kotlin.reflect.full.allSuperclasses import kotlin.reflect.full.declaredMembers +import kotlin.reflect.full.valueParameters +import kotlin.reflect.full.withNullability import kotlin.reflect.jvm.isAccessible import kotlin.reflect.jvm.javaField import kotlin.reflect.jvm.javaGetter -import kotlin.reflect.typeOf +import kotlin.reflect.jvm.javaMethod // by bush, unchanged since 1.0.0 @@ -22,26 +22,41 @@ internal val <T : Any> KClass<T>.allMembers /** * Checks if a [KCallable] is static on the jvm, and handles invocation accordingly. - * I am not aware of a better alternative that works with `object` classes. + * + * 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 if (static) call() else call(receiver) + return runCatching { call(receiver) }.getOrElse { call() } } -/** - * Checks if the calling [KCallable] is a static java field. Because Kotlin likes to be funny, properties - * belonging to `object` classes are static, but their getters are not. If there is a getter (the property - * is not private), we will be accessing that, otherwise we check if the field is static with Java reflection. - * This also lets us support static listeners in Java code. +/* +internal 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 + } */ -internal val KCallable<*>.static - get() = if (this !is KProperty<*> || javaGetter != null) false - else javaField?.let { Modifier.isStatic(it.modifiers) } ?: false /** * Finds all members of return type [Listener]. (properties and methods) */ @Suppress("UNCHECKED_CAST") // This cannot fail internal inline val KClass<*>.listeners - get() = allMembers.filter { it.returnType == typeOf<Listener>() } as Sequence<KCallable<Listener>> + // Force nullability to false, so this will detect listeners in Java + // with "!" nullability. Also make sure there are no parameters. + get() = allMembers.filter { + it.returnType.withNullability(false) == typeOf<Listener>() && it.valueParameters.isEmpty() + } as Sequence<KCallable<Listener>> diff --git a/src/test/java/JavaTest.java b/src/test/java/JavaTest.java new file mode 100644 index 0000000..0b942b6 --- /dev/null +++ b/src/test/java/JavaTest.java @@ -0,0 +1,62 @@ +import me.bush.illnamethislater.Listener; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.config.Configurator; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.TestInstance.Lifecycle; +import me.bush.illnamethislater.EventBus; +import org.apache.logging.log4j.LogManager; +import org.junit.jupiter.api.Test; + +import static me.bush.illnamethislater.ListenerKt.listener; + +/** + * I was getting NCDFE when trying to load this class + * from the other test and I don't care enough to fix it. + * + * @author bush + * @since 1.0.0 + */ +@TestInstance(Lifecycle.PER_CLASS) +public class JavaTest { + private EventBus eventBus; + private final Logger logger = LogManager.getLogger(); + + @BeforeAll + public void setup() { + Configurator.setRootLevel(Level.ALL); + logger.info("Running Java tests"); + eventBus = new EventBus(); + eventBus.subscribe(this); + } + + @Test + public void javaSubscriberTest() { + eventBus.subscribe(this); + SimpleEvent event = new SimpleEvent(); + eventBus.post(event); + Assertions.assertEquals(event.getCount(), 4); + } + + public Listener someInstanceListenerField = listener(SimpleEvent.class, event -> { + event.setCount(event.getCount() + 1); + }); + + public Listener someInstanceListenerMethod() { + return listener(SimpleEvent.class, event -> { + event.setCount(event.getCount() + 1); + }); + } + + public static Listener someStaticListenerMethod() { + return listener(SimpleEvent.class, event -> { + event.setCount(event.getCount() + 1); + }); + } + + public static Listener someStaticListenerField = listener(SimpleEvent.class, event -> { + event.setCount(event.getCount() + 1); + }); +} diff --git a/src/test/java/TestJava.java b/src/test/java/TestJava.java deleted file mode 100644 index c6c77a8..0000000 --- a/src/test/java/TestJava.java +++ /dev/null @@ -1,80 +0,0 @@ -import me.bush.illnamethislater.Listener; -import org.apache.logging.log4j.Level; -import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.core.config.Configurator; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.TestInstance; -import org.junit.jupiter.api.TestInstance.Lifecycle; -import me.bush.illnamethislater.EventBus; -import org.apache.logging.log4j.LogManager; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.Test; - -import static me.bush.illnamethislater.ListenerKt.listener; - -/** - * I was getting noclassdeffound when trying to load this Java - * class in the other test and I don't care enough to fix it. - * - * @author bush - * @since 1.0.0 - */ -@TestInstance(Lifecycle.PER_CLASS) -public class TestJava { - private static boolean thisShouldChange; - private boolean thisShouldChangeToo; - private EventBus eventBus; - private final Logger logger = LogManager.getLogger(); - - @BeforeAll - public void setup() { - Configurator.setRootLevel(Level.ALL); - - // Test that init works - logger.info("Initializing"); - eventBus = new EventBus(); - - // Ensure no npe - logger.info("Logging empty debug info"); - eventBus.debugInfo(); - - // Test that basic subscribing reflection works - logger.info("Subscribing"); - eventBus.subscribe(this); - - logger.info("Testing Events"); - } - - @AfterAll - public void unsubscribe() { - logger.info("Unsubscribing"); - eventBus.unsubscribe(this); - eventBus.debugInfo(); - } - - //////////////////////////////////////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////////////////////////////////////// - - @Test - public void javaSubscriberTest() { - eventBus.subscribe(this); - eventBus.post(new MyEvent()); - Assertions.assertTrue(TestJava.thisShouldChange); - Assertions.assertTrue(this.thisShouldChangeToo); - // TODO: 4/2/2022 fix calling from java - } - - public Listener listener = listener(MyEvent.class, 200, event -> { - Assertions.assertEquals(event.getSomeString(), "donda"); - event.setSomeString("donda 2"); - this.thisShouldChangeToo = true; - }); - - public static Listener someStaticListener() { - return listener(MyEvent.class, 100, event -> { - Assertions.assertEquals(event.getSomeString(), "donda 2"); - thisShouldChange = true; - }); - } -} diff --git a/src/test/kotlin/KotlinTest.kt b/src/test/kotlin/KotlinTest.kt new file mode 100644 index 0000000..6ad3273 --- /dev/null +++ b/src/test/kotlin/KotlinTest.kt @@ -0,0 +1,252 @@ +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.runBlocking +import me.bush.illnamethislater.* +import org.apache.logging.log4j.Level +import org.apache.logging.log4j.LogManager +import org.apache.logging.log4j.core.config.Configurator +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.TestInstance +import org.junit.jupiter.api.Test +import org.opentest4j.AssertionFailedError +import sun.misc.Unsafe +import kotlin.jvm.internal.PropertyReference0Impl +import kotlin.random.Random +import kotlin.reflect.KCallable +import kotlin.reflect.KProperty +import kotlin.reflect.full.* +import kotlin.reflect.javaType +import kotlin.reflect.jvm.isAccessible +import kotlin.reflect.jvm.javaField +import kotlin.reflect.jvm.javaGetter + +/** + * I don't know how to do these.... + * + * @author bush + * @since 1.0.0 + */ +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class KotlinTest { + private lateinit var eventBus: EventBus + private val logger = LogManager.getLogger("Kotlin Test") + + @BeforeAll + fun `setup logger and initialize eventbus` () { + // Log level defaults to only error + Configurator.setRootLevel(Level.ALL) + eventBus = EventBus( + // Defaults + Config( + logger = LogManager.getLogger("Eventbus"), + parallelScope = CoroutineScope(Dispatchers.Default), + thirdPartyCompatibility = true, + annotationRequired = false + ) + ) + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + @Test + fun `test listener priority and ability to cancel events or receive cancelled events` () { + eventBus.subscribe(this) + val event = SimpleEvent() + eventBus.post(event) + Assertions.assertEquals(3, event.count) + eventBus.unsubscribe(this) + } + + // Last to be called; does not have receiveCancelled, but the last listener un-cancelled the event. + fun listener4() = listener<SimpleEvent>(priority = Int.MIN_VALUE) { + Assertions.assertEquals(2, it.count) + it.count++ + } + + // Will not be called; second-highest priority, no receiveCancelled. + val listener2 + get() = listener<SimpleEvent>(priority = 0) { + Assertions.fail("This should not be called") + } + + // First to be called; highest priority. + private val listener1 = listener<SimpleEvent>(priority = 10) { + Assertions.assertEquals(0, it.count) + it.count++ + // Cancel, so next listener shouldn't receive it. + it.cancel() + } + + // Second to be called; has receiveCancelled and can un-cancel the event. + fun listener3() = listener<SimpleEvent>(priority = Int.MIN_VALUE + 100, receiveCancelled = true) { + Assertions.assertEquals(1, it.count) + it.count++ + it.cancelled = false + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + @Test + fun `test subscribing on a kotlin singleton object` () { + eventBus.subscribe(ObjectTest) + val event = SimpleEvent() + eventBus.post(event) + Assertions.assertEquals(3, event.count) + eventBus.unsubscribe(ObjectTest) + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + @Test + fun `test primitive types and listeners which don't belong to a class` () { + val random = Random.nextInt() + var changed = 0 + val listener = listener<Int> { + changed = it + } + eventBus.register(listener) + eventBus.post(random) + Assertions.assertEquals(random, changed) + eventBus.unregister(listener) + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + @Test + fun `test that we can detect if an external event is cancelled` () { + eventBus.subscribe(this) + val event = ExternalEvent() + eventBus.post(event) + eventBus.unsubscribe(this) + } + + @EventListener + fun externalListener1() = listener<ExternalEvent>(priority = 1) { + it.canceled = true + } + + // Should not be called + fun externalListener2() = listener<ExternalEvent>(priority = -1) { + Assertions.fail("This should not be called") + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + @Test + fun `test parallel event posting` () { + runBlocking { + sus() + } + } + + suspend fun sus() { + println() + } + + fun sussy() { + println() + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + @Test + fun `call every method on multiple threads concurrently to ensure no CME is thrown` () { + + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + @Test + fun `test that inheritance doesn't affect events or listeners` () { + val superTest = listener<SimpleEvent> { + Assertions.fail("This should not be called") + } + eventBus.register(superTest) + // No listeners should be called when a superclass is posted. + eventBus.post(Any()) + eventBus.unregister(superTest) + val subTest = listener<Any> { + Assertions.fail("This should not be called") + } + eventBus.register(subTest) + // No listeners should be called when a subclass is posted. + eventBus.post(SimpleEvent()) + eventBus.unregister(subTest) + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + @Test + fun `test that require annotation mode works` () { + val eventBus = EventBus(Config(annotationRequired = true)) + eventBus.subscribe(this) + eventBus.post(Unit) + Assertions.assertTrue(called) + } + + var called = false + + @EventListener + val annotation = listener<Unit> { + called = true + } + + val noAnnotation = listener<Unit> { + Assertions.fail("This should not be called") + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + @Test + fun `test subscribing on a companion object` () { + eventBus.subscribe(KotlinTest) + val string = "i love bush's eventbus <3" + eventBus.post(string) + Assertions.assertEquals(string, value) + eventBus.unsubscribe(KotlinTest) + } + + companion object { + var value = "" + + @EventListener + private val listener = listener<String> { + value = it + } + } +} + +object ObjectTest { + private val listener1 = listener<SimpleEvent> { + it.count++ + } + + @EventListener + private val listener2 get() = listener<SimpleEvent> { + it.count++ + } + + private fun listener3() = listener<SimpleEvent> { + it.count++ + } +} + +class SimpleEvent : Event() { + override val cancellable = true + + var count = 0 +} + +class ExternalEvent { + var canceled = false +} diff --git a/src/test/kotlin/TestKotlin.kt b/src/test/kotlin/TestKotlin.kt deleted file mode 100644 index 2ae6313..0000000 --- a/src/test/kotlin/TestKotlin.kt +++ /dev/null @@ -1,215 +0,0 @@ -import me.bush.illnamethislater.Event -import me.bush.illnamethislater.EventBus -import me.bush.illnamethislater.listener -import org.apache.logging.log4j.Level -import org.apache.logging.log4j.LogManager -import org.apache.logging.log4j.core.config.Configurator -import org.junit.jupiter.api.AfterAll -import org.junit.jupiter.api.Assertions -import org.junit.jupiter.api.BeforeAll -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.TestInstance -import org.junit.jupiter.api.TestInstance.Lifecycle -import kotlin.random.Random - -/** - * I don't know how to do these.... - * - * @author bush - * @since 1.0.0 - */ -@TestInstance(Lifecycle.PER_CLASS) -class Test { - private lateinit var eventBus: EventBus - private val logger = LogManager.getLogger() - - //////////////////////////////////////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////////////////////////////////////// - - @BeforeAll - fun setup() { - Configurator.setRootLevel(Level.ALL) - - // Test that init works - logger.info("Initializing") - eventBus = EventBus() - - // Ensure no npe - logger.info("Logging empty debug info") - eventBus.debugInfo() - - // Test that basic subscribing reflection works - logger.info("Subscribing") - eventBus.subscribe(this) - - logger.info("Testing Events") - } - - @AfterAll - fun unsubscribe() { - logger.info("Unsubscribing") - eventBus.unsubscribe(this) - eventBus.debugInfo() - } - - //////////////////////////////////////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////////////////////////////////////// - - // Tests debug info format - @Test - fun debugInfoTest() { - eventBus.debugInfo() - } - - //////////////////////////////////////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////////////////////////////////////// - - // Tests autoboxing and internal data structure against primitives. - @Test - fun primitiveListenerTest() { - val random = Random.nextInt() - eventBus.post(random) - Assertions.assertEquals(random, primitiveTestValue) - } - - private var primitiveTestValue = 0 - - val primitiveListener = listener<Int> { - primitiveTestValue = it - } - - //////////////////////////////////////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////////////////////////////////////// - - // Tests unsubscribing of listeners which don't belong to a subscriber. - @Test - fun freeListenerTest() { - // Register listener and keep the value - val listener = eventBus.register(listener<String> { - freeListenerTestValue = it - }) - val valueOne = "i love bush's eventbus <3" - val valueTwo = "sdklasdjsakdsadlksadlksdl" - // Will change the value - eventBus.post(valueOne) - Assertions.assertEquals(valueOne, freeListenerTestValue) - // Remove the listener - eventBus.unregister(listener) - // No effect - eventBus.post(valueTwo) - // Value will not change - Assertions.assertEquals(valueOne, freeListenerTestValue) - } - - private var freeListenerTestValue: String? = null - - //////////////////////////////////////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////////////////////////////////////// - - // Tests priority and receiveCancelled functionality. - @Test - fun myEventListenerTest() { - val event = MyEvent() - eventBus.post(event) - Assertions.assertEquals(event.lastListener, "myEventListener3") - } - - // First to be called; highest priority. - val myEventListener0 = listener<MyEvent>(priority = 10) { - Assertions.assertEquals(it.lastListener, "") - it.lastListener = "myEventListener0" - it.cancel() - } - - // Will not be called; second-highest priority, no receiveCancelled. - val myEventListener1 - get() = listener<MyEvent>(priority = 0) { - Assertions.assertTrue(false) - } - - // Second to be called; has receiveCancelled and can un-cancel the event. - fun myEventListener2() = listener<MyEvent>(priority = Int.MIN_VALUE + 100, receiveCancelled = true) { - Assertions.assertEquals(it.lastListener, "myEventListener0") - it.lastListener = "myEventListener2" - it.cancelled = false - } - - // Last to be called; does not have receiveCancelled, but the last listener un-cancelled the event. - fun myEventListener3() = listener<MyEvent>(priority = Int.MIN_VALUE) { - Assertions.assertEquals(it.lastListener, "myEventListener2") - it.lastListener = "myEventListener3" - } - - //////////////////////////////////////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////////////////////////////////////// - - // Tests external event cancel state functionality. - @Test - fun externalEventListenerTest() { - var unchanged = "this will not change" - // Cancels the event - eventBus.register(listener<ExternalEvent>(200) { - it.canceled = true - }) - // This shouldn't be called - eventBus.register(listener<ExternalEvent>(100) { - unchanged = "changed" - }) - eventBus.post(ExternalEvent()) - Assertions.assertEquals(unchanged, "this will not change") - // Tests that duplicates are detected, and that both - // "canceled" and "cancelled" are detected as valid fields - eventBus.register(listener<ExternalDuplicates> {}) - } - - //////////////////////////////////////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////////////////////////////////////// - - // Tests parallel invocation functionality. - @Test - fun parallelListenerTest() { - - } - - //////////////////////////////////////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////////////////////////////////////// - - // Tests reflection against singleton object classes. - @Test - fun objectSubscriberTest() { - eventBus.subscribe(ObjectSubscriber) - eventBus.post(Unit) - Assertions.assertTrue(ObjectSubscriber.willChange) - } - - //////////////////////////////////////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////////////////////////////////////// - - // todo test thread safety - // todo ensure boolean functions return proper value (subscribe, unsubscribe, etc) -} - -object ObjectSubscriber { - var willChange = false - - fun listener() = listener<Unit> { - willChange = true - } -} - -class MyEvent : Event() { - override val cancellable = true - - var lastListener = "" - var someString = "donda" -} - -class ExternalEvent { - var canceled = false -} - -// Should give us a warning about duplicates -class ExternalDuplicates { - var canceled = false - var cancelled = false -} |