├── .gitignore ├── README.md ├── build.gradle ├── gradle.properties ├── settings.gradle └── src └── main └── kotlin └── net.evilblock.pidgin ├── Pidgin.kt ├── PidginOptions.kt └── message ├── Message.kt ├── handler ├── IncomingMessageHandler.kt └── MessageExceptionHandler.kt └── listener ├── MessageListener.kt └── MessageListenerData.kt /.gitignore: -------------------------------------------------------------------------------- 1 | # IntelliJ 2 | .idea/ 3 | *.iml 4 | *.iws 5 | 6 | # Mac 7 | .DS_Store 8 | 9 | # Maven 10 | log/ 11 | target/ 12 | dependency-reduced-pom.xml 13 | 14 | # Java 15 | *.jar 16 | 17 | # JRebel 18 | rebel.xml 19 | 20 | # Keep libs 21 | !lib/*.jar 22 | 23 | gradle/ 24 | .gradle 25 | **/build/ 26 | !src/**/build/ 27 | 28 | # Ignore Gradle GUI config 29 | gradle-app.setting 30 | 31 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 32 | !gradle-wrapper.jar 33 | 34 | # Cache of project 35 | .gradletasknamecache 36 | 37 | # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 38 | # gradle/wrapper/gradle-wrapper.properties -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pidgin 2 | A Redis PubSub implementation that allows for bukkit-fashion handling of messages 3 | 4 | ## Creating a Pidgin instance 5 | ``` 6 | val jedisPool = JedisPool("127.0.0.1", 6379) 7 | val pidgin = Pidgin("Stark:PROXY", jedisPool, PidginOptions(async = true)) 8 | ``` 9 | 10 | ## Sending a Message 11 | ``` 12 | val data = mapOf( 13 | "server": { 14 | "id": "Test", 15 | "port": 25565 16 | }, 17 | "updates": { 18 | "slots": 500 19 | } 20 | ) 21 | 22 | val message = Message("SERVER_UPDATE", data)) 23 | 24 | pidgin.sendMessage(message) 25 | ``` 26 | 27 | ## Registering a MessageListener 28 | ``` 29 | object ProxyMessageListener : MessageListener { 30 | @IncomingMessageHandler("SERVER_UPDATE") 31 | fun onServerUpdate(data: JsonObject) { 32 | ... 33 | } 34 | } 35 | ``` 36 | 37 | ``` 38 | val pidgin = ... 39 | pidgin.registerListener(ProxyMessageListener) 40 | ``` 41 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'idea' 3 | id 'maven-publish' 4 | id 'org.jetbrains.kotlin.jvm' version '1.3.61' 5 | id 'com.github.johnrengelman.shadow' version '5.2.0' 6 | } 7 | 8 | group 'net.evilblock' 9 | version '1.2' 10 | 11 | repositories { 12 | mavenLocal() 13 | mavenCentral() 14 | } 15 | 16 | dependencies { 17 | compileOnly 'org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.61' 18 | compileOnly 'com.google.code.gson:gson:2.8.5' 19 | compileOnly 'redis.clients:jedis:3.5.1' 20 | compileOnly 'org.slf4j:slf4j-api:1.7.30' 21 | } 22 | 23 | compileKotlin { 24 | kotlinOptions.jvmTarget = "1.8" 25 | } 26 | 27 | sourceCompatibility = 1.8 28 | targetCompatibility = 1.8 29 | 30 | idea { 31 | module { 32 | downloadJavadoc = true 33 | downloadSources = true 34 | } 35 | } 36 | 37 | shadowJar { 38 | classifier = null 39 | exclude '**/*.kotlin_metadata' 40 | // exclude '**/*.kotlin_module' 41 | exclude '**/*.kotlin_builtins' 42 | } 43 | 44 | publishing { 45 | publications { 46 | shadow(MavenPublication) { publication -> 47 | project.shadow.component(publication) 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin.code.style=official -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'pidgin' 2 | 3 | -------------------------------------------------------------------------------- /src/main/kotlin/net.evilblock.pidgin/Pidgin.kt: -------------------------------------------------------------------------------- 1 | package net.evilblock.pidgin 2 | 3 | import net.evilblock.pidgin.message.Message 4 | import net.evilblock.pidgin.message.handler.IncomingMessageHandler 5 | import net.evilblock.pidgin.message.handler.MessageExceptionHandler 6 | import net.evilblock.pidgin.message.listener.MessageListener 7 | import net.evilblock.pidgin.message.listener.MessageListenerData 8 | import com.google.gson.* 9 | import java.util.concurrent.ForkJoinPool 10 | import redis.clients.jedis.JedisPool 11 | import redis.clients.jedis.JedisPubSub 12 | import java.lang.IllegalStateException 13 | import java.util.logging.Logger 14 | 15 | /** 16 | * A Jedis Pub/Sub implementation. 17 | */ 18 | class Pidgin( 19 | private val channel: String, 20 | private val jedisPool: JedisPool, 21 | private val gson: Gson, 22 | private val options: PidginOptions 23 | ) { 24 | 25 | private var jedisPubSub: JedisPubSub? = null 26 | private val listeners: MutableMap> = hashMapOf() 27 | 28 | var debug: Boolean = false 29 | 30 | init { 31 | setupPubSub() 32 | } 33 | 34 | fun close() { 35 | if (jedisPubSub != null && jedisPubSub!!.isSubscribed) { 36 | jedisPubSub!!.unsubscribe() 37 | } 38 | 39 | jedisPool.close() 40 | } 41 | 42 | @JvmOverloads 43 | fun sendMessage(message: Message, exceptionHandler: MessageExceptionHandler? = null) { 44 | try { 45 | jedisPool.resource.use { jedis -> jedis.publish(channel, message.id + ";" + gson.toJsonTree(message.data).toString()) } 46 | } catch (e: Exception) { 47 | exceptionHandler?.onException(e) 48 | } 49 | } 50 | 51 | fun registerListener(messageListener: MessageListener) { 52 | for (method in messageListener::class.java.declaredMethods) { 53 | if (method.getDeclaredAnnotation(IncomingMessageHandler::class.java) != null && method.parameters.isNotEmpty()) { 54 | if (!JsonObject::class.java.isAssignableFrom(method.parameters[0].type)) { 55 | throw IllegalStateException("First parameter should be of JsonObject type") 56 | } 57 | 58 | val messageId = method.getDeclaredAnnotation(IncomingMessageHandler::class.java).id 59 | listeners.putIfAbsent(messageId, arrayListOf()) 60 | listeners[messageId]!!.add(MessageListenerData(messageListener, method, messageId)) 61 | } 62 | } 63 | } 64 | 65 | private fun setupPubSub() { 66 | jedisPubSub = object : JedisPubSub() { 67 | override fun onMessage(channel: String, message: String) { 68 | if (channel.equals(this@Pidgin.channel, ignoreCase = true)) { 69 | try { 70 | val breakAt = message.indexOf(';') 71 | val messageId = message.substring(0, breakAt) 72 | 73 | if (listeners.containsKey(messageId)) { 74 | val messageData = gson.fromJson(message.substring(breakAt + 1, message.length), JsonObject::class.java) 75 | 76 | for (listener in listeners[messageId]!!) { 77 | listener.method.invoke(listener.instance, messageData) 78 | } 79 | } 80 | } catch (e: JsonParseException) { 81 | Logger.getGlobal().severe("[Pidgin] Failed to parse message into JSON") 82 | e.printStackTrace() 83 | } catch (e: Exception) { 84 | Logger.getGlobal().severe("[Pidgin] Failed to handle message") 85 | e.printStackTrace() 86 | } 87 | } 88 | } 89 | } 90 | 91 | if (options.async) { 92 | ForkJoinPool.commonPool().execute { 93 | jedisPool.resource.use { jedis -> jedis.subscribe(jedisPubSub!!, channel) } 94 | } 95 | } else { 96 | jedisPool.resource.use { jedis -> jedis.subscribe(jedisPubSub!!, channel) } 97 | } 98 | } 99 | 100 | } -------------------------------------------------------------------------------- /src/main/kotlin/net.evilblock.pidgin/PidginOptions.kt: -------------------------------------------------------------------------------- 1 | package net.evilblock.pidgin 2 | 3 | data class PidginOptions(val async: Boolean) -------------------------------------------------------------------------------- /src/main/kotlin/net.evilblock.pidgin/message/Message.kt: -------------------------------------------------------------------------------- 1 | package net.evilblock.pidgin.message 2 | 3 | class Message(var id: String, var data: Map) { 4 | 5 | constructor(id: String) : this(id, hashMapOf()) 6 | 7 | } 8 | -------------------------------------------------------------------------------- /src/main/kotlin/net.evilblock.pidgin/message/handler/IncomingMessageHandler.kt: -------------------------------------------------------------------------------- 1 | package net.evilblock.pidgin.message.handler 2 | 3 | @Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER) 4 | @Retention(AnnotationRetention.RUNTIME) 5 | annotation class IncomingMessageHandler(val id: String) 6 | -------------------------------------------------------------------------------- /src/main/kotlin/net.evilblock.pidgin/message/handler/MessageExceptionHandler.kt: -------------------------------------------------------------------------------- 1 | package net.evilblock.pidgin.message.handler 2 | 3 | class MessageExceptionHandler { 4 | 5 | fun onException(e: Exception) { 6 | println("Failed to send message") 7 | e.printStackTrace() 8 | } 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/main/kotlin/net.evilblock.pidgin/message/listener/MessageListener.kt: -------------------------------------------------------------------------------- 1 | package net.evilblock.pidgin.message.listener 2 | 3 | interface MessageListener 4 | -------------------------------------------------------------------------------- /src/main/kotlin/net.evilblock.pidgin/message/listener/MessageListenerData.kt: -------------------------------------------------------------------------------- 1 | package net.evilblock.pidgin.message.listener 2 | 3 | import java.lang.reflect.Method 4 | 5 | /** 6 | * A wrapper class that holds all the information needed to 7 | * identify and execute a message function. 8 | * 9 | */ 10 | data class MessageListenerData(val instance: Any, val method: Method, val id: String) 11 | --------------------------------------------------------------------------------