├── .gitignore ├── LICENSE ├── README.md ├── pom.xml └── src ├── main └── kotlin │ └── kotlinx │ └── event │ ├── AbstractEvent.kt │ ├── Event.kt │ ├── MapEvent.kt │ ├── SetEvent.kt │ └── events.kt └── test └── kotlin ├── NamedEventTest.kt └── UnnamedEventTest.kt /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | target/ 3 | out/ 4 | *.iml 5 | *.ipr 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 stuhlmeier 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Kotlin events 2 | 3 | `kotlinx.events` is a simple event library for Kotlin which is designed to be intuitive and similar in design to C#'s 4 | event features. 5 | 6 | A typical C# event handling system looks like this: 7 | 8 | ```cs 9 | using System; 10 | 11 | namespace EventTest { 12 | public struct ServerEvent { ... } 13 | 14 | public delegate void EventHandler(ServerEvent data); 15 | 16 | internal class Program { 17 | private static event EventHandler Event; 18 | 19 | private static void Main() { 20 | var count = 0; 21 | Event += data => Console.WriteLine($"{data.Name} {(data.Joined ? "joined" : "left")}"); 22 | Event += data => count += data.Joined ? 1 : -1; 23 | 24 | Event(new ServerEvent(true, "Alice")); 25 | Event(new ServerEvent(true, "Bob")); 26 | Event(new ServerEvent(true, "Charles")); 27 | 28 | Console.WriteLine($"Users: {count}"); 29 | 30 | Event(new ServerEvent(false, "Bob")); 31 | 32 | Console.WriteLine($"Users: {count}"); 33 | } 34 | } 35 | } 36 | ``` 37 | 38 | The code outputs 39 | ``` 40 | Alice joined 41 | Bob joined 42 | Charles joined 43 | Users: 3 44 | Bob left 45 | Users: 2 46 | ``` 47 | which is what we expect. 48 | Can we do better? 49 | 50 | Of course we can, using our favorite language. Here's the Kotlin equivalent, using `events`: 51 | 52 | ```kotlin 53 | data class ServerEvent(val name: String, val joined: Boolean) 54 | 55 | val event = event() 56 | var count = 0 57 | 58 | fun main() { 59 | event += { (name, joined) -> 60 | count += if (joined) 1 else -1 61 | println("$name ${if (joined) "joined" else "left"} (total: $count)") 62 | } 63 | 64 | event(ServerEvent("Alice", true)) 65 | event(ServerEvent("Bob", true)) 66 | event(ServerEvent("Charles", true)) 67 | 68 | event(ServerEvent("Bob", false)) 69 | } 70 | ``` 71 | 72 | The syntax is similar, but now you can use it in your JVM projects with little modification. 73 | 74 | ## Usage 75 | 76 | The top-level functions `event` and `namedEvent` can be used to initialize standard set-backed events and 77 | `Map Unit>`-backed events respectively. 78 | 79 | The `MapEvent` type allows retrieving and adding events ("named events") by a string key. 80 | 81 | The base interface `Event` is a collection type, and can be used accordingly. 82 | 83 | ## Maven 84 | 85 | You can retrieve this artifact from [JitPack](https://jitpack.io/): 86 | 87 | ```xml 88 | 89 | jitpack.io 90 | https://jitpack.io 91 | 92 | 93 | ... 94 | 95 | 96 | com.github.stuhlmeier 97 | kotlin-events 98 | v2.0 99 | 100 | ``` 101 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | me.halcyxn 8 | kotlin-events 9 | 2.0.0 10 | 11 | 12 | 3.8.1 13 | 14 | 1.3.50 15 | 16 | 1.3.2 17 | 5.3.2 18 | 19 | 20 | 21 | 22 | org.jetbrains.kotlin 23 | kotlin-stdlib-jdk8 24 | ${kotlin.version} 25 | 26 | 27 | org.jetbrains.kotlin 28 | kotlin-test 29 | ${kotlin.version} 30 | test 31 | 32 | 33 | 34 | org.junit.jupiter 35 | junit-jupiter-api 36 | ${junit-jupiter.version} 37 | test 38 | 39 | 40 | org.junit.jupiter 41 | junit-jupiter-params 42 | ${junit-jupiter.version} 43 | test 44 | 45 | 46 | org.junit.jupiter 47 | junit-jupiter-engine 48 | ${junit-jupiter.version} 49 | test 50 | 51 | 52 | 53 | 54 | ${project.basedir}/src/main/kotlin 55 | ${project.basedir}/src/test/kotlin 56 | 57 | 58 | 59 | org.jetbrains.kotlin 60 | kotlin-maven-plugin 61 | ${kotlin.version} 62 | 63 | 64 | compile 65 | process-sources 66 | 67 | compile 68 | 69 | 70 | 71 | test-compile 72 | test-compile 73 | 74 | test-compile 75 | 76 | 77 | 78 | 79 | 80 | org.apache.maven.plugins 81 | maven-compiler-plugin 82 | ${maven-compiler-plugin.version} 83 | 84 | 1.8 85 | 1.8 86 | 87 | 88 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /src/main/kotlin/kotlinx/event/AbstractEvent.kt: -------------------------------------------------------------------------------- 1 | package kotlinx.event 2 | 3 | abstract class AbstractEvent : Event { 4 | 5 | @JvmSynthetic 6 | override operator fun plusAssign(handler: (T) -> Unit) { 7 | add(handler) 8 | } 9 | 10 | @JvmSynthetic 11 | override operator fun minusAssign(handler: (T) -> Unit) { 12 | remove(handler) 13 | } 14 | 15 | override fun invoke(data: T) = forEach { it(data) } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/main/kotlin/kotlinx/event/Event.kt: -------------------------------------------------------------------------------- 1 | package kotlinx.event 2 | 3 | interface Event : MutableCollection<(T) -> Unit> { 4 | 5 | @JvmSynthetic 6 | operator fun plusAssign(handler: (T) -> Unit) 7 | 8 | @JvmSynthetic 9 | operator fun minusAssign(handler: (T) -> Unit) 10 | 11 | operator fun invoke(data: T) 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/kotlin/kotlinx/event/MapEvent.kt: -------------------------------------------------------------------------------- 1 | package kotlinx.event 2 | 3 | import java.util.concurrent.atomic.AtomicInteger 4 | 5 | class MapEvent(private val backing: MutableMap Unit>) 6 | : AbstractEvent(), MutableMap Unit> by backing { 7 | 8 | constructor() : this(HashMap()) 9 | 10 | private val nextUnnamedIndex = AtomicInteger() 11 | 12 | override val size get() = backing.size 13 | override fun contains(element: (T) -> Unit) = backing.containsValue(element) 14 | override fun containsAll(elements: Collection<(T) -> Unit>) = backing.values.containsAll(elements) 15 | override fun isEmpty() = backing.isEmpty() 16 | override fun iterator() = backing.values.iterator() 17 | override fun remove(element: (T) -> Unit) = backing.values.remove(element) 18 | override fun removeAll(elements: Collection<(T) -> Unit>) = backing.values.removeAll(elements) 19 | override fun retainAll(elements: Collection<(T) -> Unit>) = backing.values.retainAll(elements) 20 | 21 | override fun add(element: (T) -> Unit): Boolean { 22 | backing[nextUnnamedIndex.getAndIncrement().toString()] = element 23 | return true 24 | } 25 | 26 | override fun addAll(elements: Collection<(T) -> Unit>): Boolean { 27 | elements.forEach { add(it) } 28 | return true 29 | } 30 | 31 | @JvmSynthetic 32 | operator fun set(name: String, handler: ((T) -> Unit)?) { 33 | if (handler == null) backing.remove(name) 34 | else backing[name] = handler 35 | } 36 | 37 | override fun clear() = backing.clear() 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main/kotlin/kotlinx/event/SetEvent.kt: -------------------------------------------------------------------------------- 1 | package kotlinx.event 2 | 3 | open class SetEvent private constructor(private val backing: MutableSet<(T) -> Unit>) 4 | : AbstractEvent(), MutableCollection<(T) -> Unit> by backing { 5 | 6 | constructor() : this(HashSet()) 7 | 8 | } 9 | -------------------------------------------------------------------------------- /src/main/kotlin/kotlinx/event/events.kt: -------------------------------------------------------------------------------- 1 | package kotlinx.event 2 | 3 | fun event() = SetEvent() 4 | fun namedEvent() = MapEvent() 5 | -------------------------------------------------------------------------------- /src/test/kotlin/NamedEventTest.kt: -------------------------------------------------------------------------------- 1 | import kotlinx.event.namedEvent 2 | import org.junit.jupiter.api.Test 3 | import kotlin.test.assertEquals 4 | 5 | class NamedEventTest { 6 | 7 | data class ServerEvent(val name: String, val joined: Boolean) 8 | 9 | @Test 10 | fun test() { 11 | val event = namedEvent() 12 | 13 | var count = 0 14 | 15 | val handler: (ServerEvent) -> Unit = { (name, joined) -> 16 | count += if (joined) 1 else -1 17 | println("$name ${if (joined) "joined" else "left"} (total: $count)") 18 | } 19 | 20 | event["handler"] = handler 21 | 22 | assertEquals(1, event.size) 23 | 24 | event(ServerEvent("Alice", true)) 25 | event(ServerEvent("Bob", true)) 26 | event(ServerEvent("Charles", true)) 27 | 28 | assertEquals(3, count) 29 | 30 | event(ServerEvent("Bob", false)) 31 | 32 | assertEquals(2, count) 33 | 34 | event.remove("handler") 35 | 36 | event(ServerEvent("Alice", false)) 37 | event(ServerEvent("David", true)) 38 | 39 | assertEquals(2, count) 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/test/kotlin/UnnamedEventTest.kt: -------------------------------------------------------------------------------- 1 | import kotlinx.event.event 2 | import org.junit.jupiter.api.Test 3 | import kotlin.test.assertEquals 4 | 5 | class UnnamedEventTest { 6 | 7 | data class ServerEvent(val name: String, val joined: Boolean) 8 | 9 | @Test 10 | fun test() { 11 | val event = event() 12 | 13 | var count = 0 14 | 15 | val handler: (ServerEvent) -> Unit = { (name, joined) -> 16 | count += if (joined) 1 else -1 17 | println("$name ${if (joined) "joined" else "left"} (total: $count)") 18 | } 19 | 20 | event += handler 21 | 22 | assertEquals(1, event.size) 23 | 24 | event(ServerEvent("Alice", true)) 25 | event(ServerEvent("Bob", true)) 26 | event(ServerEvent("Charles", true)) 27 | 28 | assertEquals(3, count) 29 | 30 | event(ServerEvent("Bob", false)) 31 | 32 | assertEquals(2, count) 33 | 34 | event -= handler 35 | 36 | event(ServerEvent("Alice", false)) 37 | event(ServerEvent("David", true)) 38 | 39 | assertEquals(2, count) 40 | } 41 | 42 | } 43 | --------------------------------------------------------------------------------