aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md7
-rw-r--r--build.gradle9
-rw-r--r--gradle.properties2
-rw-r--r--src/main/kotlin/thedarkcolour/kotlinforforge/AutoKotlinEventBusSubscriber.kt6
-rw-r--r--src/main/kotlin/thedarkcolour/kotlinforforge/KotlinForForge.kt17
-rw-r--r--src/main/kotlin/thedarkcolour/kotlinforforge/KotlinLanguageProvider.kt4
-rw-r--r--src/main/kotlin/thedarkcolour/kotlinforforge/KotlinModContainer.kt51
-rw-r--r--src/main/kotlin/thedarkcolour/kotlinforforge/KotlinModLoadingContext.kt17
-rw-r--r--src/main/kotlin/thedarkcolour/kotlinforforge/Logger.kt2
-rw-r--r--src/main/kotlin/thedarkcolour/kotlinforforge/eventbus/KotlinEventBus.kt373
-rw-r--r--src/main/kotlin/thedarkcolour/kotlinforforge/eventbus/KotlinEventBusWrapper.kt52
-rw-r--r--src/main/kotlin/thedarkcolour/kotlinforforge/forge/Forge.kt33
-rw-r--r--src/main/resources/META-INF/mods.toml2
-rw-r--r--thedarkcolour/kotlinforforge/1.2.0/kotlinforforge-1.2.0-sources.jarbin0 -> 13341 bytes
-rw-r--r--thedarkcolour/kotlinforforge/1.2.0/kotlinforforge-1.2.0-sources.jar.md51
-rw-r--r--thedarkcolour/kotlinforforge/1.2.0/kotlinforforge-1.2.0-sources.jar.sha11
-rw-r--r--thedarkcolour/kotlinforforge/1.2.0/kotlinforforge-1.2.0.jarbin0 -> 91792 bytes
-rw-r--r--thedarkcolour/kotlinforforge/1.2.0/kotlinforforge-1.2.0.jar.md51
-rw-r--r--thedarkcolour/kotlinforforge/1.2.0/kotlinforforge-1.2.0.jar.sha11
-rw-r--r--thedarkcolour/kotlinforforge/1.2.0/kotlinforforge-1.2.0.pom59
-rw-r--r--thedarkcolour/kotlinforforge/1.2.0/kotlinforforge-1.2.0.pom.md51
-rw-r--r--thedarkcolour/kotlinforforge/1.2.0/kotlinforforge-1.2.0.pom.sha11
-rw-r--r--thedarkcolour/kotlinforforge/1.2.0/web.html15
-rw-r--r--thedarkcolour/kotlinforforge/maven-metadata.xml3
-rw-r--r--thedarkcolour/kotlinforforge/maven-metadata.xml.md52
-rw-r--r--thedarkcolour/kotlinforforge/maven-metadata.xml.sha12
-rw-r--r--thedarkcolour/kotlinforforge/web.html1
27 files changed, 602 insertions, 61 deletions
diff --git a/README.md b/README.md
index fe29422..620aecc 100644
--- a/README.md
+++ b/README.md
@@ -8,9 +8,12 @@ Makes Kotlin forge-friendly by doing the following:
To implement in your project, paste the following into your build.gradle:
```groovy
buildscript {
+ repositories {
+ maven { url = "https://dl.bintray.com/kotlin/kotlin-eap" }
+ }
dependencies {
// Make sure to use the correct version
- classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.61"
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.70"
}
}
@@ -40,7 +43,7 @@ compileKotlin {
}
}
```
-Then, add the following to your mods.toml file:
+Then, change the following to your mods.toml file:
```toml
modLoader="kotlinforforge"
# Change this if you require a certain version of KotlinForForge
diff --git a/build.gradle b/build.gradle
index 20d3c4c..aa1db38 100644
--- a/build.gradle
+++ b/build.gradle
@@ -2,6 +2,7 @@ buildscript {
repositories {
maven { url = 'https://files.minecraftforge.net/maven' }
maven { url = "https://maven.tterrag.com/" }
+ maven { url = "https://dl.bintray.com/kotlin/kotlin-eap" }
jcenter()
mavenCentral()
}
@@ -11,14 +12,13 @@ buildscript {
}
}
plugins {
- id "org.jetbrains.kotlin.jvm" version "1.3.70"
id "com.github.johnrengelman.shadow" version "4.0.4"
}
apply plugin: 'net.minecraftforge.gradle'
apply plugin: 'kotlin'
-version = '1.1.0'
+version = '1.2.0'
group = 'thedarkcolour.kotlinforforge'
archivesBaseName = 'kotlinforforge'
@@ -60,10 +60,15 @@ minecraft {
}
repositories {
+ mavenCentral()
maven {
name = "Yarn Mappings"
url = "https://maven.tterrag.com/"
}
+ maven {
+ name = "Kotlin Early Access"
+ url = "https://dl.bintray.com/kotlin/kotlin-eap"
+ }
}
dependencies {
diff --git a/gradle.properties b/gradle.properties
index 7a70982..c4f4ba4 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -2,6 +2,6 @@
# This is required to provide enough memory for the Minecraft decompilation process.
org.gradle.jvmargs=-Xmx3G
org.gradle.daemon=false
-kotlin_version=1.3.70
+kotlin_version=1.4-M1
coroutines_version = 1.3.4
annotations_version = 19.0.0 \ No newline at end of file
diff --git a/src/main/kotlin/thedarkcolour/kotlinforforge/AutoKotlinEventBusSubscriber.kt b/src/main/kotlin/thedarkcolour/kotlinforforge/AutoKotlinEventBusSubscriber.kt
index fa43f57..547918f 100644
--- a/src/main/kotlin/thedarkcolour/kotlinforforge/AutoKotlinEventBusSubscriber.kt
+++ b/src/main/kotlin/thedarkcolour/kotlinforforge/AutoKotlinEventBusSubscriber.kt
@@ -35,7 +35,7 @@ public object AutoKotlinEventBusSubscriber {
* }
*/
public fun inject(mod: ModContainer, scanData: ModFileScanData, classLoader: ClassLoader) {
- logger.debug(Logging.LOADING, "Attempting to inject @EventBusSubscriber kotlin objects in to the event bus for ${mod.modId}")
+ LOGGER.debug(Logging.LOADING, "Attempting to inject @EventBusSubscriber kotlin objects in to the event bus for ${mod.modId}")
val data: ArrayList<ModFileScanData.AnnotationData> = scanData.annotations.stream()
.filter { annotationData ->
EVENT_BUS_SUBSCRIBER == annotationData.annotationType
@@ -51,7 +51,7 @@ public object AutoKotlinEventBusSubscriber {
val ktObject = Class.forName(annotationData.classType.className, true, classLoader).kotlin.objectInstance
if (ktObject != null && mod.modId == modid && sides.contains(FMLEnvironment.dist)) {
try {
- logger.debug(Logging.LOADING, "Auto-subscribing kotlin object ${annotationData.classType.className} to $busTarget")
+ LOGGER.debug(Logging.LOADING, "Auto-subscribing kotlin object ${annotationData.classType.className} to $busTarget")
if (busTarget == Mod.EventBusSubscriber.Bus.MOD) {
// Gets the correct mod loading context
MOD_BUS.register(ktObject)
@@ -59,7 +59,7 @@ public object AutoKotlinEventBusSubscriber {
FORGE_BUS.register(ktObject)
}
} catch (e: Throwable) {
- logger.fatal(Logging.LOADING, "Failed to load mod class ${annotationData.classType} for @EventBusSubscriber annotation", e)
+ LOGGER.fatal(Logging.LOADING, "Failed to load mod class ${annotationData.classType} for @EventBusSubscriber annotation", e)
throw RuntimeException(e)
}
}
diff --git a/src/main/kotlin/thedarkcolour/kotlinforforge/KotlinForForge.kt b/src/main/kotlin/thedarkcolour/kotlinforforge/KotlinForForge.kt
index e5ec289..51840d9 100644
--- a/src/main/kotlin/thedarkcolour/kotlinforforge/KotlinForForge.kt
+++ b/src/main/kotlin/thedarkcolour/kotlinforforge/KotlinForForge.kt
@@ -1,9 +1,24 @@
package thedarkcolour.kotlinforforge
+import net.minecraft.block.Block
+import net.minecraft.block.material.Material
+import net.minecraftforge.event.RegistryEvent
import net.minecraftforge.fml.common.Mod
+import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext
+import thedarkcolour.kotlinforforge.forge.MOD_BUS
/**
* Set 'modLoader' in mods.toml to "kotlinforforge" and loaderVersion to "[1,)".
+ *
+ * Make sure to use [KotlinModLoadingContext] instead of [FMLJavaModLoadingContext].
*/
@Mod("kotlinforforge")
-public object KotlinForForge \ No newline at end of file
+object KotlinForForge {
+ init {
+ MOD_BUS.addGenericListener(::registerBlocks)
+ }
+
+ private fun registerBlocks(event: RegistryEvent.Register<Block>) {
+ event.registry.register(Block(Block.Properties.create(Material.ROCK)).setRegistryName("bruh"))
+ }
+} \ No newline at end of file
diff --git a/src/main/kotlin/thedarkcolour/kotlinforforge/KotlinLanguageProvider.kt b/src/main/kotlin/thedarkcolour/kotlinforforge/KotlinLanguageProvider.kt
index 61705ef..d393cbd 100644
--- a/src/main/kotlin/thedarkcolour/kotlinforforge/KotlinLanguageProvider.kt
+++ b/src/main/kotlin/thedarkcolour/kotlinforforge/KotlinLanguageProvider.kt
@@ -15,7 +15,7 @@ public class KotlinLanguageProvider : FMLJavaModLanguageProvider() {
return Consumer { scanResult ->
val target = scanResult.annotations.stream()
.filter { data -> data.annotationType == MODANNOTATION }
- .peek { data -> logger.debug(Logging.SCAN, "Found @Mod class ${data.classType.className} with id ${data.annotationData["value"]}") }
+ .peek { data -> LOGGER.debug(Logging.SCAN, "Found @Mod class ${data.classType.className} with id ${data.annotationData["value"]}") }
.map { data -> KotlinModTarget(data.classType.className, data.annotationData["value"] as String) }
.collect(Collectors.toMap({ target: KotlinModTarget -> target.modId }, { it }, { a, _ -> a }))
scanResult.addLanguageLoader(target)
@@ -29,7 +29,7 @@ public class KotlinLanguageProvider : FMLJavaModLanguageProvider() {
public class KotlinModTarget constructor(private val className: String, val modId: String) : IModLanguageProvider.IModLanguageLoader {
override fun <T> loadMod(info: IModInfo, modClassLoader: ClassLoader, modFileScanResults: ModFileScanData): T {
val ktContainer = Class.forName("thedarkcolour.kotlinforforge.KotlinModContainer", true, Thread.currentThread().contextClassLoader)
- logger.debug(Logging.LOADING, "Loading KotlinModContainer from classloader ${Thread.currentThread().contextClassLoader} - got ${ktContainer.classLoader}}")
+ LOGGER.debug(Logging.LOADING, "Loading KotlinModContainer from classloader ${Thread.currentThread().contextClassLoader} - got ${ktContainer.classLoader}}")
val constructor = ktContainer.declaredConstructors[0]//(IModInfo::class.java, String::class.java, ClassLoader::class.java, ModFileScanData::class.java)!!
return constructor.newInstance(info, className, modClassLoader, modFileScanResults) as T
}
diff --git a/src/main/kotlin/thedarkcolour/kotlinforforge/KotlinModContainer.kt b/src/main/kotlin/thedarkcolour/kotlinforforge/KotlinModContainer.kt
index 0ea150f..9802d90 100644
--- a/src/main/kotlin/thedarkcolour/kotlinforforge/KotlinModContainer.kt
+++ b/src/main/kotlin/thedarkcolour/kotlinforforge/KotlinModContainer.kt
@@ -6,21 +6,22 @@ import net.minecraftforge.eventbus.api.Event
import net.minecraftforge.eventbus.api.IEventBus
import net.minecraftforge.eventbus.api.IEventListener
import net.minecraftforge.fml.*
+import net.minecraftforge.fml.config.ModConfig
import net.minecraftforge.forgespi.language.IModInfo
import net.minecraftforge.forgespi.language.ModFileScanData
-import java.util.*
+import thedarkcolour.kotlinforforge.eventbus.KotlinEventBus
import java.util.function.Consumer
import java.util.function.Supplier
/**
* Functions as [net.minecraftforge.fml.javafmlmod.FMLModContainer] for Kotlin
*/
-public class KotlinModContainer(private val info: IModInfo, private val className: String, private val classLoader: ClassLoader, private val scanData: ModFileScanData) : ModContainer(info) {
+class KotlinModContainer(private val info: IModInfo, private val className: String, private val classLoader: ClassLoader, private val scanData: ModFileScanData) : ModContainer(info) {
private lateinit var modInstance: Any
- public val eventBus: IEventBus
+ val eventBus: KotlinEventBus
init {
- logger.debug(Logging.LOADING, "Creating KotlinModContainer instance for {} with classLoader {} & {}", className, classLoader, javaClass.classLoader)
+ LOGGER.debug(Logging.LOADING, "Creating KotlinModContainer instance for {} with classLoader {} & {}", className, classLoader, javaClass.classLoader)
triggerMap[ModLoadingStage.CONSTRUCT] = dummy().andThen(::constructMod).andThen(::afterEvent)
triggerMap[ModLoadingStage.CREATE_REGISTRIES] = dummy().andThen(::fireEvent).andThen(::afterEvent)
triggerMap[ModLoadingStage.LOAD_REGISTRIES] = dummy().andThen(::fireEvent).andThen(::afterEvent)
@@ -30,8 +31,7 @@ public class KotlinModContainer(private val info: IModInfo, private val classNam
triggerMap[ModLoadingStage.PROCESS_IMC] = dummy().andThen(::fireEvent).andThen(::afterEvent)
triggerMap[ModLoadingStage.COMPLETE] = dummy().andThen(::fireEvent).andThen(::afterEvent)
triggerMap[ModLoadingStage.GATHERDATA] = dummy().andThen(::fireEvent).andThen(::afterEvent)
- eventBus = BusBuilder.builder().setExceptionHandler(::onEventFailed).setTrackPhases(false).build()
- configHandler = Optional.of(Consumer { event -> eventBus.post(event) })
+ eventBus = KotlinEventBus(BusBuilder.builder().setExceptionHandler(::onEventFailed).setTrackPhases(false))
val ctx = KotlinModLoadingContext(this)
contextExtension = Supplier { ctx }
}
@@ -39,25 +39,25 @@ public class KotlinModContainer(private val info: IModInfo, private val classNam
private fun dummy(): Consumer<LifecycleEventProvider.LifecycleEvent> = Consumer {}
private fun onEventFailed(iEventBus: IEventBus, event: Event, iEventListeners: Array<IEventListener>, i: Int, throwable: Throwable) {
- logger.error(EventBusErrorMessage(event, i, iEventListeners, throwable))
+ LOGGER.error(EventBusErrorMessage(event, i, iEventListeners, throwable))
}
private fun fireEvent(lifecycleEvent: LifecycleEventProvider.LifecycleEvent) {
val event = lifecycleEvent.getOrBuildEvent(this)
- logger.debug(Logging.LOADING, "Firing event for modid $modId : $event")
+ LOGGER.debug(Logging.LOADING, "Firing event for modid $modId : $event")
try {
eventBus.post(event)
- logger.debug(Logging.LOADING, "Fired event for modid $modId : $event")
+ LOGGER.debug(Logging.LOADING, "Fired event for modid $modId : $event")
} catch (throwable: Throwable) {
- logger.error(Logging.LOADING,"An error occurred while dispatching event ${lifecycleEvent.fromStage()} to $modId")
+ LOGGER.error(Logging.LOADING,"An error occurred while dispatching event ${lifecycleEvent.fromStage()} to $modId")
throw ModLoadingException(modInfo, lifecycleEvent.fromStage(), "fml.modloading.errorduringevent", throwable)
}
}
private fun afterEvent(lifecycleEvent: LifecycleEventProvider.LifecycleEvent) {
if (currentState == ModLoadingStage.ERROR) {
- logger.error(Logging.LOADING, "An error occurred while dispatching event ${lifecycleEvent.fromStage()} to $modId")
+ LOGGER.error(Logging.LOADING, "An error occurred while dispatching event ${lifecycleEvent.fromStage()} to $modId")
}
}
@@ -65,41 +65,40 @@ public class KotlinModContainer(private val info: IModInfo, private val classNam
val modClass: Class<*>
try {
modClass = Class.forName(className, true, classLoader)
- logger.debug(Logging.LOADING, "Loaded kotlin modclass ${modClass.name} with ${modClass.classLoader}")
+ LOGGER.debug(Logging.LOADING, "Loaded kotlin modclass ${modClass.name} with ${modClass.classLoader}")
} catch (throwable: Throwable) {
- logger.error(Logging.LOADING, "Failed to load kotlin class $className", throwable)
+ LOGGER.error(Logging.LOADING, "Failed to load kotlin class $className", throwable)
throw ModLoadingException(info, ModLoadingStage.CONSTRUCT, "fml.modloading.failedtoloadmodclass", throwable)
}
try {
- logger.debug(Logging.LOADING, "Loading mod instance ${getModId()} of type ${modClass.name}")
+ LOGGER.debug(Logging.LOADING, "Loading mod instance ${getModId()} of type ${modClass.name}")
modInstance = modClass.kotlin.objectInstance ?: modClass.newInstance()
- logger.debug(Logging.LOADING, "Loaded mod instance ${getModId()} of type ${modClass.name}")
+ LOGGER.debug(Logging.LOADING, "Loaded mod instance ${getModId()} of type ${modClass.name}")
} catch (throwable: Throwable) {
- logger.error(Logging.LOADING, "Failed to create mod instance. ModID: ${getModId()}, class ${modClass.name}", throwable)
+ LOGGER.error(Logging.LOADING, "Failed to create mod instance. ModID: ${getModId()}, class ${modClass.name}", throwable)
throw ModLoadingException(modInfo, lifecycleEvent.fromStage(), "fml.modloading.failedtoloadmod", throwable, modClass)
}
try {
- logger.debug(Logging.LOADING, "Injecting Automatic Kotlin event subscribers for ${getModId()}")
+ LOGGER.debug(Logging.LOADING, "Injecting Automatic Kotlin event subscribers for ${getModId()}")
// Inject into object EventBusSubscribers
AutoKotlinEventBusSubscriber.inject(this, scanData, modClass.classLoader)
- logger.debug(Logging.LOADING, "Completed Automatic Kotlin event subscribers for ${getModId()}")
+ LOGGER.debug(Logging.LOADING, "Completed Automatic Kotlin event subscribers for ${getModId()}")
} catch (throwable: Throwable) {
- logger.error(Logging.LOADING, "Failed to register Automatic Kotlin subscribers. ModID: ${getModId()}, class ${modClass.name}", throwable)
+ LOGGER.error(Logging.LOADING, "Failed to register Automatic Kotlin subscribers. ModID: ${getModId()}, class ${modClass.name}", throwable)
throw ModLoadingException(modInfo, lifecycleEvent.fromStage(), "fml.modloading.failedtoloadmod", throwable, modClass)
}
}
- override fun matches(mod: Any?): Boolean {
- return mod == modInstance
- }
-
- override fun getMod(): Any {
- return modInstance
- }
+ override fun matches(mod: Any?) = mod == modInstance
+ override fun getMod() = modInstance
override fun acceptEvent(e: Event) {
eventBus.post(e)
}
+
+ override fun dispatchConfigEvent(event: ModConfig.ModConfigEvent) {
+ eventBus.post(event)
+ }
} \ No newline at end of file
diff --git a/src/main/kotlin/thedarkcolour/kotlinforforge/KotlinModLoadingContext.kt b/src/main/kotlin/thedarkcolour/kotlinforforge/KotlinModLoadingContext.kt
index af42d16..44aac2d 100644
--- a/src/main/kotlin/thedarkcolour/kotlinforforge/KotlinModLoadingContext.kt
+++ b/src/main/kotlin/thedarkcolour/kotlinforforge/KotlinModLoadingContext.kt
@@ -1,19 +1,22 @@
package thedarkcolour.kotlinforforge
-import net.minecraftforge.eventbus.api.IEventBus
-import net.minecraftforge.fml.ModLoadingContext
+import thedarkcolour.kotlinforforge.eventbus.KotlinEventBus
+import thedarkcolour.kotlinforforge.forge.LOADING_CONTEXT
/**
* Functions as [net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext] for Kotlin
*/
-public class KotlinModLoadingContext constructor(private val container: KotlinModContainer) {
- public fun getEventBus(): IEventBus {
+class KotlinModLoadingContext constructor(private val container: KotlinModContainer) {
+ /**
+ * @see thedarkcolour.kotlinforforge.forge.MOD_BUS
+ */
+ fun getEventBus(): KotlinEventBus {
return container.eventBus
}
- public companion object {
- public fun get(): KotlinModLoadingContext {
- return ModLoadingContext.get().extension()
+ companion object {
+ fun get(): KotlinModLoadingContext {
+ return LOADING_CONTEXT.extension()
}
}
} \ No newline at end of file
diff --git a/src/main/kotlin/thedarkcolour/kotlinforforge/Logger.kt b/src/main/kotlin/thedarkcolour/kotlinforforge/Logger.kt
index 8276903..3724657 100644
--- a/src/main/kotlin/thedarkcolour/kotlinforforge/Logger.kt
+++ b/src/main/kotlin/thedarkcolour/kotlinforforge/Logger.kt
@@ -8,4 +8,4 @@ import org.apache.logging.log4j.LogManager
* Kept here instead of [KotlinForForge] because logger is used
* before [KotlinModContainer] should initialize.
*/
-internal val logger = LogManager.getLogger() \ No newline at end of file
+internal val LOGGER = LogManager.getLogger() \ No newline at end of file
diff --git a/src/main/kotlin/thedarkcolour/kotlinforforge/eventbus/KotlinEventBus.kt b/src/main/kotlin/thedarkcolour/kotlinforforge/eventbus/KotlinEventBus.kt
new file mode 100644
index 0000000..7323f23
--- /dev/null
+++ b/src/main/kotlin/thedarkcolour/kotlinforforge/eventbus/KotlinEventBus.kt
@@ -0,0 +1,373 @@
+package thedarkcolour.kotlinforforge.eventbus
+
+import net.jodah.typetools.TypeResolver
+import net.minecraftforge.eventbus.ASMEventHandler
+import net.minecraftforge.eventbus.EventBus
+import net.minecraftforge.eventbus.ListenerList
+import net.minecraftforge.eventbus.api.*
+import org.apache.logging.log4j.Level
+import org.apache.logging.log4j.LogManager
+import org.apache.logging.log4j.MarkerManager
+import java.lang.reflect.InvocationTargetException
+import java.lang.reflect.Method
+import java.lang.reflect.Modifier
+import java.util.*
+import java.util.concurrent.ConcurrentHashMap
+import java.util.concurrent.atomic.AtomicInteger
+import java.util.function.Consumer
+
+/** @since 1.2.0
+ * Fixes [IEventBus.addListener] for Kotlin SAM interfaces.
+ */
+open class KotlinEventBus(builder: BusBuilder, synthetic: Boolean = false) : IEventBus, IEventExceptionHandler {
+ @Suppress("LeakingThis")
+ private val exceptionHandler = builder.exceptionHandler ?: this
+ private val trackPhases = builder.trackPhases
+ @Volatile
+ private var shutdown = builder.isStartingShutdown
+ protected open val busID = MAX_ID.getAndIncrement()
+ protected open val listeners = ConcurrentHashMap<Any, MutableList<IEventListener>>()
+
+ init {
+ // see companion object
+ if (!synthetic) {
+ RESIZE_LISTENER_LIST(busID + 1)
+ }
+ }
+
+ override fun register(target: Any) {
+ if (!listeners.containsKey(target)) {
+ if (target.javaClass == Class::class.java) {
+ registerClass(target as Class<*>)
+ } else {
+ registerObject(target)
+ }
+ }
+ }
+
+ protected fun registerClass(clazz: Class<*>) {
+ for (method in clazz.methods) {
+ if (Modifier.isStatic(method.modifiers) && method.isAnnotationPresent(SubscribeEvent::class.java)) {
+ registerListener(clazz, method, method)
+ }
+ }
+ }
+
+ protected fun registerObject(target: Any) {
+ val classes = HashSet<Class<*>>()
+ typesFor(target.javaClass, classes)
+ Arrays.stream(target.javaClass.methods).filter { m ->
+ !Modifier.isStatic(m.modifiers)
+ }.forEach { m ->
+ classes.map { c ->
+ getDeclMethod(c, m)
+ }.firstOrNull { rm ->
+ rm?.isAnnotationPresent(SubscribeEvent::class.java) == true
+ }?.let { rm ->
+ registerListener(target, m, rm)
+ }
+ }
+ }
+
+ private fun typesFor(clz: Class<*>, visited: MutableSet<Class<*>>) {
+ if (clz.superclass == null) return
+ typesFor(clz.superclass, visited)
+ Arrays.stream(clz.interfaces).forEach { typesFor(it, visited) }
+ visited.add(clz)
+ }
+
+ private fun getDeclMethod(clz: Class<*>, m: Method): Method? {
+ return try {
+ clz.getDeclaredMethod(m.name, *m.parameterTypes)
+ } catch (nse: NoSuchMethodException) {
+ null
+ }
+ }
+
+ private fun registerListener(target: Any, f: Method, real: Method) {
+ val params: Array<Class<*>> = f.parameterTypes
+
+ if (params.size != 1) {
+ throw IllegalArgumentException("""
+ Function $f has @SubscribeEvent annotation.
+ It has ${params.size} value parameters,
+ but event handler functions require1 value parameter.
+ """.trimIndent()
+ )
+ }
+
+ val type = params[0]
+
+ if (!Event::class.java.isAssignableFrom(type)) {
+ throw IllegalArgumentException("""
+ Function $f has @SubscribeEvent annotation,
+ but takes an argument that is not an Event subtype : $type
+ """.trimIndent())
+ }
+
+ register(type, target, real)
+ }
+
+ private fun register(type: Class<*>, target: Any, f: Method) {
+ try {
+ val asm = ASMEventHandler(target, f, IGenericEvent::class.java.isAssignableFrom(type))
+
+ addToListeners(target, type, asm.priority, asm)
+ } catch (e: IllegalAccessException) {
+ LOGGER.error(EVENT_BUS, "Error registering event handler: $type $f", e)
+ } catch (e: InstantiationException) {
+ LOGGER.error(EVENT_BUS, "Error registering event handler: $type $f", e)
+ } catch (e: NoSuchMethodException) {
+ LOGGER.error(EVENT_BUS, "Error registering event handler: $type $f", e)
+ } catch (e: InvocationTargetException) {
+ LOGGER.error(EVENT_BUS, "Error registering event handler: $type $f", e)
+ }
+ }
+
+ protected open fun addToListeners(target: Any, eventType: Class<*>, priority: EventPriority, listener: IEventListener) {
+ val listenerList = EventListenerHelper.getListenerList(eventType)
+ listenerList.register(busID, priority, listener)
+ val others = listeners.computeIfAbsent(target) { Collections.synchronizedList(ArrayList()) }
+ others.add(listener)
+ }
+
+ /**
+ * Add a consumer listener with default [EventPriority.NORMAL] and not receiving cancelled events.
+ *
+ * @param consumer Callback to invoke when a matching event is received
+ * @param T The [Event] subclass to listen for
+ */
+ override fun <T : Event> addListener(consumer: Consumer<T>) {
+ addListener(EventPriority.NORMAL, consumer)
+ }
+
+ /**
+ * Add a consumer listener with the specified [EventPriority] and not receiving cancelled events.
+ *
+ * @param priority [EventPriority] for this listener
+ * @param consumer Callback to invoke when a matching event is received
+ * @param T The [Event] subclass to listen for
+ */
+ override fun <T : Event> addListener(priority: EventPriority, consumer: Consumer<T>) {
+ addListener(priority, false, consumer)
+ }
+
+ /**
+ * Add a consumer listener with the specified [EventPriority] and potentially cancelled events.
+ *
+ * @param priority [EventPriority] for this listener
+ * @param receiveCancelled Indicate if this listener should receive events that have been [Cancelable] cancelled
+ * @param consumer Callback to invoke when a matching event is received
+ * @param T The [Event] subclass to listen for
+ */
+ override fun <T : Event> addListener(priority: EventPriority, receiveCancelled: Boolean, consumer: Consumer<T>) {
+ addListener(priority, passCancelled(receiveCancelled), consumer)
+ }
+
+ /**
+ * Add a consumer listener with the specified [EventPriority] and potentially cancelled events.
+ *
+ * Use this method when one of the other methods fails to determine the concrete [Event] subclass that is
+ * intended to be subscribed to.
+ *
+ * @param priority [EventPriority] for this listener
+ * @param receiveCancelled Indicate if this listener should receive events that have been [Cancelable] cancelled
+ * @param eventType The concrete [Event] subclass to subscribe to
+ * @param consumer Callback to invoke when a matching event is received
+ * @param T The [Event] subclass to listen for
+ */
+ override fun <T : Event> addListener(priority: EventPriority, receiveCancelled: Boolean, eventType: Class<T>, consumer: Consumer<T>) {
+ addListener(priority, passCancelled(receiveCancelled), eventType, consumer)
+ }
+
+ private fun <T : Event> addListener(priority: EventPriority, filter: (T) -> Boolean, eventType: Class<T>, consumer: Consumer<T>) {
+ addToListeners(consumer, eventType, priority) { e ->
+ if (filter(e as T)) {
+ consumer.accept(e)
+ }
+ }
+ }
+
+ private fun passCancelled(receiveCancelled: Boolean): (Event) -> Boolean = { event ->
+ receiveCancelled || !event.isCancelable || !event.isCanceled
+ }
+
+ /**
+ * Add a consumer listener for a [GenericEvent] subclass with generic type [F].
+ *
+ * @param consumer Callback to invoke when a matching event is received
+ * @param T The [GenericEvent] subclass to listen for
+ * @param F The [Class] to filter the [GenericEvent] for
+ */
+ inline fun <T : GenericEvent<out F>, reified F> addGenericListener(consumer: Consumer<T>) {
+ addGenericListener(F::class.java, consumer)
+ }
+
+ /**
+ * Add a consumer listener for a [GenericEvent] subclass, filtered to only be called for the specified
+ * filter [Class].
+ *
+ * @param genericClassFilter A [Class] which the [GenericEvent] should be filtered for
+ * @param consumer Callback to invoke when a matching event is received
+ * @param T The [GenericEvent] subclass to listen for
+ * @param F The [Class] to filter the [GenericEvent] for
+ */
+ override fun <T : GenericEvent<out F>, F> addGenericListener(genericClassFilter: Class<F>, consumer: Consumer<T>) {
+ addGenericListener(genericClassFilter, EventPriority.NORMAL, consumer)
+ }
+
+ /**
+ * Add a consumer listener with the specified [EventPriority] and not receiving cancelled events,
+ * for a [GenericEvent] subclass, filtered to only be called for the specified
+ * filter [Class].
+ *
+ * @param genericClassFilter A [Class] which the [GenericEvent] should be filtered for
+ * @param priority [EventPriority] for this listener
+ * @param consumer Callback to invoke when a matching event is received
+ * @param T The [GenericEvent] subclass to listen for
+ * @param F The [Class] to filter the [GenericEvent] for
+ */
+ override fun <T : GenericEvent<out F>, F> addGenericListener(genericClassFilter: Class<F>, priority: EventPriority, consumer: Consumer<T>) {
+ addGenericListener(genericClassFilter, priority, false, consumer)
+ }
+
+ /**
+ * Add a consumer listener with the specified [EventPriority] and potentially cancelled events,
+ * for a [GenericEvent] subclass, filtered to only be called for the specified
+ * filter [Class].
+ *
+ * @param genericClassFilter A [Class] which the [GenericEvent] should be filtered for
+ * @param priority [EventPriority] for this listener
+ * @param receiveCancelled Indicate if this listener should receive events that have been [Cancelable] cancelled
+ * @param consumer Callback to invoke when a matching event is received
+ * @param T The [GenericEvent] subclass to listen for
+ * @param F The [Class] to filter the [GenericEvent] for
+ */
+ override fun <T : GenericEvent<out F>, F> addGenericListener(genericClassFilter: Class<F>, priority: EventPriority, receiveCancelled: Boolean, consumer: Consumer<T>) {
+ addListener(priority, passGenericCancelled(genericClassFilter, receiveCancelled), consumer)
+ }
+
+ private fun <T : Event> addListener(priority: EventPriority, filter: (T) -> Boolean, consumer: Consumer<T>) {
+ val eventType = reflectKotlinSAM(consumer) as Class<T>?
+
+ if (eventType == null) {
+ LOGGER.error(EVENT_BUS, "Failed to resolve handler for \"$consumer\"")
+ throw IllegalStateException("Failed to resolve KFunction event type: $consumer")
+ }
+ if (eventType == Event::class.java) {
+ LOGGER.warn(EVENT_BUS, """
+ Attempting to add a Lambda listener with computed generic type of Event.
+ Are you sure this is what you meant? NOTE : there are complex lambda forms where
+ the generic type information is erased and cannot be recovered at runtime.
+ """.trimIndent())
+ }
+
+ addListener(priority, filter, eventType, consumer)
+ }
+
+ /**
+ * Fixes issue that crashes when trying to register Kotlin SAM interface
+ * for a [Consumer] using the Java [IEventBus.addListener] method
+ */
+ private fun reflectKotlinSAM(consumer: Consumer<*>): Class<*>? {
+ val clazz = consumer.javaClass
+
+ if (clazz.simpleName.contains("$\$Lambda$")) {
+ return TypeResolver.resolveRawArgument(Consumer::class.java, consumer.javaClass)
+ } else if (clazz.simpleName.contains("\$sam$")) {
+ try {
+ val functionField = clazz.getDeclaredField("function")
+ functionField.isAccessible = true
+ val function = functionField[consumer]
+
+ // Function should have two type parameters (parameter type and return type)
+ return TypeResolver.resolveRawArguments(kotlin.jvm.functions.Function1::class.java, function.javaClass)[0]
+ } catch (e: NoSuchFieldException) {
+ // Kotlin SAM interfaces compile to classes with a "function" field
+ LOGGER.log(Level.FATAL, "Tried to register invalid Kotlin SAM interface: Missing 'function' field")
+ throw e
+ }
+ } else return null
+ }
+
+ private fun <T : GenericEvent<out F>, F> passGenericCancelled(genericClassFilter: Class<F>, receiveCancelled: Boolean): (T) -> Boolean = { event ->
+ event.genericType == genericClassFilter && (receiveCancelled || !event.isCancelable || !event