aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLinnea Gräf <nea@nea.moe>2024-10-18 14:19:55 +0200
committerLinnea Gräf <nea@nea.moe>2024-10-18 14:19:55 +0200
commit34a3477488e2e15a5a0e7cd2cd358faf8f926f71 (patch)
tree8a54c6f418448ce91696d03e1aeb4e94ffe8375d
parentf4fa81ebd56882bf25261427f3ca6672e2782e0b (diff)
downloadskyhanni-generic-permutable-resolution.tar.gz
skyhanni-generic-permutable-resolution.tar.bz2
skyhanni-generic-permutable-resolution.zip
Support permuting type arguments of GenericSkyhanniEventgeneric-permutable-resolution
-rw-r--r--src/main/java/at/hannibal2/skyhanni/api/event/EventListeners.kt22
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/combat/damageindicator/DamageIndicatorManager.kt2
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/ReflectionUtils.kt65
-rw-r--r--src/test/java/at/hannibal2/skyhanni/test/utils/ReflectUtilsTest.kt39
4 files changed, 120 insertions, 8 deletions
diff --git a/src/main/java/at/hannibal2/skyhanni/api/event/EventListeners.kt b/src/main/java/at/hannibal2/skyhanni/api/event/EventListeners.kt
index ce8194ebc..3aee266b2 100644
--- a/src/main/java/at/hannibal2/skyhanni/api/event/EventListeners.kt
+++ b/src/main/java/at/hannibal2/skyhanni/api/event/EventListeners.kt
@@ -1,11 +1,15 @@
package at.hannibal2.skyhanni.api.event
import at.hannibal2.skyhanni.data.IslandType
+import at.hannibal2.skyhanni.utils.ReflectionUtils
import java.lang.invoke.LambdaMetafactory
import java.lang.invoke.MethodHandles
import java.lang.invoke.MethodType
import java.lang.reflect.Method
import java.lang.reflect.ParameterizedType
+import java.lang.reflect.Type
+import java.lang.reflect.TypeVariable
+import java.lang.reflect.WildcardType
import java.util.function.Consumer
class EventListeners private constructor(val name: String, private val isGeneric: Boolean) {
@@ -14,15 +18,19 @@ class EventListeners private constructor(val name: String, private val isGeneric
constructor(event: Class<*>) : this(
(event.name.split(".").lastOrNull() ?: event.name).replace("$", "."),
- GenericSkyHanniEvent::class.java.isAssignableFrom(event)
+ GenericSkyHanniEvent::class.java.isAssignableFrom(event),
)
fun addListener(method: Method, instance: Any, options: HandleEvent) {
+ require(method.parameterCount == 1)
val generic: Class<*>? = if (isGeneric) {
- method.genericParameterTypes
- .firstNotNullOfOrNull { it as? ParameterizedType }
- ?.let { it.actualTypeArguments.firstOrNull() as? Class<*> }
- ?: throw IllegalArgumentException("Generic event handler must have a generic type")
+ ReflectionUtils.resolveUpperBoundSuperClassGenericParameter(
+ method.genericParameterTypes[0],
+ GenericSkyHanniEvent::class.java.typeParameters[0],
+ ) ?: error(
+ "Generic event handler type parameter is not present in " +
+ "event class hierarchy for type ${method.genericParameterTypes[0]}",
+ )
} else {
null
}
@@ -32,7 +40,7 @@ class EventListeners private constructor(val name: String, private val isGeneric
prefix = "(",
postfix = ")",
separator = ", ",
- transform = Class<*>::getTypeName
+ transform = Class<*>::getTypeName,
)
}"
listeners.add(Listener(name, createEventConsumer(name, instance, method), options, generic))
@@ -52,7 +60,7 @@ class EventListeners private constructor(val name: String, private val isGeneric
MethodType.methodType(Consumer::class.java, instance::class.java),
MethodType.methodType(Nothing::class.javaPrimitiveType, Object::class.java),
handle,
- MethodType.methodType(Nothing::class.javaPrimitiveType, method.parameterTypes[0])
+ MethodType.methodType(Nothing::class.javaPrimitiveType, method.parameterTypes[0]),
).target.bindTo(instance).invokeExact() as Consumer<Any>
} catch (e: Throwable) {
throw IllegalArgumentException("Method $name is not a valid consumer", e)
diff --git a/src/main/java/at/hannibal2/skyhanni/features/combat/damageindicator/DamageIndicatorManager.kt b/src/main/java/at/hannibal2/skyhanni/features/combat/damageindicator/DamageIndicatorManager.kt
index 7f5b42e3c..59726be51 100644
--- a/src/main/java/at/hannibal2/skyhanni/features/combat/damageindicator/DamageIndicatorManager.kt
+++ b/src/main/java/at/hannibal2/skyhanni/features/combat/damageindicator/DamageIndicatorManager.kt
@@ -856,7 +856,7 @@ object DamageIndicatorManager {
}
@HandleEvent
- fun onEntityJoin(event: EntityEnterWorldEvent<Entity>) {
+ fun onEntityJoin(event: EntityEnterWorldEvent<*>) {
mobFinder?.handleNewEntity(event.entity)
}
diff --git a/src/main/java/at/hannibal2/skyhanni/utils/ReflectionUtils.kt b/src/main/java/at/hannibal2/skyhanni/utils/ReflectionUtils.kt
index f714ebc97..cc6251a17 100644
--- a/src/main/java/at/hannibal2/skyhanni/utils/ReflectionUtils.kt
+++ b/src/main/java/at/hannibal2/skyhanni/utils/ReflectionUtils.kt
@@ -3,6 +3,10 @@ package at.hannibal2.skyhanni.utils
import java.lang.reflect.Constructor
import java.lang.reflect.Field
import java.lang.reflect.Modifier
+import java.lang.reflect.ParameterizedType
+import java.lang.reflect.Type
+import java.lang.reflect.TypeVariable
+import java.lang.reflect.WildcardType
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KMutableProperty1
import kotlin.reflect.KProperty
@@ -65,4 +69,65 @@ object ReflectionUtils {
}
fun Class<*>.getDeclaredFieldOrNull(name: String): Field? = declaredFields.firstOrNull { it.name == name }
+
+
+ /**
+ * Resolve all super class generic type parameters to their respective bound types in the class inheriting them.
+ * Note that this is only done once, so a class declaration like
+ * ```kotlin
+ * class Parent<ParentT>
+ * class Child<OtherT> : Parent<OtherT>
+ * class GrandChild : Child<String>
+ * ```
+ * would result in `mapOf(OtherT to String, OtherT to ParentT)`. Variables bound to variables need to be manually unraveled.
+ * Note also that wild cards like
+ * ```kotlin
+ * class WildChild : Parent<out String>
+ * ```
+ * are left untouched: `mapOf(ParentT to WildCardType(arrayOf(String), arrayOf()))`
+ */
+ fun findSuperClassTypeParameters(
+ type: Type?,
+ universe: MutableMap<TypeVariable<*>, Type>, // TODO: this could go with a (owner, name) tuple key instead
+ ) {
+ when (type) {
+ is ParameterizedType -> {
+ val rawType = type.rawType as Class<*> // TODO check
+ rawType.typeParameters.zip(type.actualTypeArguments).associateTo(universe) { it }
+ findSuperClassTypeParameters(rawType.genericSuperclass, universe)
+ }
+
+ is Class<*> -> {
+ findSuperClassTypeParameters(type.genericSuperclass, universe)
+ }
+
+ is TypeVariable<*> -> {
+ findSuperClassTypeParameters(universe[type] ?: return, universe)
+ }
+ }
+ }
+
+ /**
+ * Resolve the upper bound of a type variable from a child classes type parameters using [findSuperClassTypeParameters].
+ *
+ * This method performs the mentioned resolving of type parameters and wild card resolutions.
+ * Note that the returned class may not actually be allowed by all bounds along the chain, so might be a super class of
+ * what you would expect.
+ */
+ fun resolveUpperBoundSuperClassGenericParameter(type: Type, variable: TypeVariable<*>): Class<*>? {
+ val universe = mutableMapOf<TypeVariable<*>, Type>()
+ findSuperClassTypeParameters(type, universe)
+ var p: Type = variable
+ while (true) {
+ if (p is TypeVariable<*>) {
+ p = universe[p] ?: return null
+ } else if (p is WildcardType) {
+ p = p.upperBounds[0]
+ } else if (p is Class<*>) {
+ return p
+ } else {
+ return null
+ }
+ }
+ }
}
diff --git a/src/test/java/at/hannibal2/skyhanni/test/utils/ReflectUtilsTest.kt b/src/test/java/at/hannibal2/skyhanni/test/utils/ReflectUtilsTest.kt
new file mode 100644
index 000000000..106f3aeb0
--- /dev/null
+++ b/src/test/java/at/hannibal2/skyhanni/test/utils/ReflectUtilsTest.kt
@@ -0,0 +1,39 @@
+package at.hannibal2.skyhanni.test.utils
+
+import at.hannibal2.skyhanni.utils.ReflectionUtils
+import org.junit.jupiter.api.Assertions
+import org.junit.jupiter.api.Test
+import java.lang.reflect.ParameterizedType
+import java.lang.reflect.TypeVariable
+
+class ReflectUtilsTest {
+ class SomeClass
+ open class Parent<A, B>
+ open class Child<A, B> : Parent<B, A>()
+ open class GrandChild<T> : Child<String, T>()
+
+ abstract class Holder<X>
+
+ inline fun <reified T> resolve(typeParam: TypeVariable<*>): Class<*>? {
+ return ReflectionUtils.resolveUpperBoundSuperClassGenericParameter(
+ (object : Holder<T>() {}
+ .javaClass.genericSuperclass as ParameterizedType)
+ .actualTypeArguments[0],
+ typeParam,
+ )
+ }
+
+ @Test
+ fun testResolveUpperBoundSuperClassGenericParameter() {
+ val firstParent = Parent::class.java.typeParameters[0]
+ val secondParent = Parent::class.java.typeParameters[1]
+ Assertions.assertEquals(String::class.java, resolve<Parent<String, Int>>(firstParent))
+ Assertions.assertEquals(Integer::class.java, resolve<Parent<String, Int>>(secondParent))
+ Assertions.assertEquals(Integer::class.java, resolve<Child<String, Int>>(firstParent))
+ Assertions.assertEquals(String::class.java, resolve<Child<String, Int>>(secondParent))
+ Assertions.assertEquals(SomeClass::class.java, resolve<GrandChild<SomeClass>>(firstParent))
+ Assertions.assertEquals(String::class.java, resolve<GrandChild<SomeClass>>(secondParent))
+ Assertions.assertEquals(SomeClass::class.java, resolve<GrandChild<out SomeClass>>(firstParent))
+ Assertions.assertEquals(Any::class.java, resolve<GrandChild<*>>(firstParent))
+ }
+}