Skip to content

Commit e12ea89

Browse files
committed
feat: add conditional hook evaluation with custom conditions support
1 parent 1c47e83 commit e12ea89

9 files changed

Lines changed: 114 additions & 14 deletions

File tree

surf-api-core/surf-api-core-api/src/main/kotlin/dev/slne/surf/surfapi/core/api/hook/SurfHookApi.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,12 @@ interface SurfHookApi {
1111
suspend fun disable(owner: Any)
1212

1313
suspend fun <T : Any> hooksOfType(owner: Any, type: Class<T>): List<T>
14-
suspend fun <T: Any> hooksOfType(type: Class<T>): List<T>
14+
fun <T : Any> hooksOfTypeLoaded(owner: Any, type: Class<T>): List<T>
15+
suspend fun <T : Any> hooksOfType(type: Class<T>): List<T>
16+
fun <T : Any> hooksOfTypeLoaded(type: Class<T>): List<T>
17+
1518
suspend fun hooks(owner: Any): List<Hook>
19+
fun hooksLoaded(owner: Any): List<Hook>
1620

1721
companion object {
1822
val instance = requiredService<SurfHookApi>()

surf-api-core/surf-api-core-server/src/main/kotlin/dev/slne/surf/surfapi/core/server/hook/HookService.kt

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
package dev.slne.surf.surfapi.core.server.hook
22

33
import com.github.benmanes.caffeine.cache.Caffeine
4+
import com.sksamuel.aedile.core.asLoadingCache
45
import dev.slne.surf.surfapi.core.api.util.mutableObject2ObjectMapOf
56
import dev.slne.surf.surfapi.core.api.util.mutableObjectSetOf
67
import dev.slne.surf.surfapi.core.api.util.requiredService
78
import dev.slne.surf.surfapi.shared.api.hook.Hook
9+
import dev.slne.surf.surfapi.shared.api.hook.condition.HookCondition
10+
import dev.slne.surf.surfapi.shared.api.hook.condition.HookConditionContext
811
import dev.slne.surf.surfapi.shared.internal.hook.PluginHookMeta
912
import kotlinx.serialization.SerializationException
1013
import kotlinx.serialization.json.Json
@@ -19,7 +22,7 @@ abstract class HookService {
1922

2023
private val hooksCache = Caffeine.newBuilder()
2124
.weakKeys()
22-
.build<Any, List<Hook>> { owner -> loadHooks(owner) }
25+
.asLoadingCache { owner -> loadHooks(owner) }
2326

2427
private fun loadHooksMeta(owner: Any): PluginHookMeta {
2528
val rawStream = readHooksFileFromResources(owner, HOOKS_FILE_NAME) ?: return PluginHookMeta.empty()
@@ -32,7 +35,7 @@ abstract class HookService {
3235
}
3336
}
3437

35-
private fun loadHooks(owner: Any): List<Hook> {
38+
private suspend fun loadHooks(owner: Any): List<Hook> {
3639
val meta = hookMetaCache.get(owner)
3740
val classLoader = getClassloader(owner)
3841

@@ -48,7 +51,7 @@ abstract class HookService {
4851
return topologicalSort(hooksWithMeta, owner)
4952
}
5053

51-
private fun instantiateHookIfValid(
54+
private suspend fun instantiateHookIfValid(
5255
owner: Any,
5356
hookMeta: PluginHookMeta.Hook,
5457
classLoader: ClassLoader
@@ -80,6 +83,8 @@ abstract class HookService {
8083
return null
8184
}
8285

86+
if (!evaluateConditions(owner, hookMeta, classLoader)) return null
87+
8388
try {
8489
val hookClass = Class.forName(hookMeta.className, false, classLoader)
8590
val hookKClass = hookClass.kotlin
@@ -100,6 +105,36 @@ abstract class HookService {
100105
return null
101106
}
102107

108+
@Suppress("UNCHECKED_CAST")
109+
private suspend fun evaluateConditions(
110+
owner: Any,
111+
hookMeta: PluginHookMeta.Hook,
112+
classLoader: ClassLoader
113+
): Boolean {
114+
for (conditionClassName in hookMeta.customConditions) {
115+
try {
116+
val conditionClass = Class.forName(conditionClassName, false, classLoader)
117+
val condition = conditionClass.getConstructor().newInstance() as HookCondition
118+
val logger = getLogger(owner)
119+
120+
val context = HookConditionContext(
121+
owner = owner,
122+
logger = logger,
123+
hookClass = Class.forName(hookMeta.className, false, classLoader) as Class<out Hook>
124+
)
125+
126+
if (!condition.evaluate(context)) {
127+
logger.debug("Hook ${hookMeta.className} skipped due to condition $conditionClassName")
128+
return false
129+
}
130+
} catch (e: Exception) {
131+
getLogger(owner).error("Failed to evaluate condition $conditionClassName", e)
132+
return false
133+
}
134+
}
135+
return true
136+
}
137+
103138
private fun topologicalSort(
104139
hooksWithMeta: List<Pair<PluginHookMeta.Hook, Hook>>,
105140
owner: Any
@@ -210,14 +245,22 @@ abstract class HookService {
210245
)
211246
}
212247

213-
fun getHooks(owner: Any): List<Hook> {
248+
suspend fun getHooks(owner: Any): List<Hook> {
214249
return hooksCache.get(owner)
215250
}
216251

217-
fun getAllHooks(): List<Hook> {
252+
fun getHooksLoaded(owner: Any): List<Hook> {
253+
return hooksCache.underlying().asMap()[owner]?.getNow(emptyList()) ?: emptyList()
254+
}
255+
256+
suspend fun getAllHooks(): List<Hook> {
218257
return hooksCache.asMap().values.flatten().sorted()
219258
}
220259

260+
fun getAllHooksLoaded(): List<Hook> {
261+
return hooksCache.underlying().asMap().values.flatMap { it.getNow(emptyList()) }.sorted()
262+
}
263+
221264
abstract fun readHooksFileFromResources(owner: Any, fileName: String): InputStream?
222265
abstract fun getClassloader(owner: Any): ClassLoader
223266
abstract fun isPluginLoaded(pluginId: String): Boolean

surf-api-core/surf-api-core-server/src/main/kotlin/dev/slne/surf/surfapi/core/server/impl/hook/SurfHookApiImpl.kt

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
package dev.slne.surf.surfapi.core.server.impl.hook
22

33
import com.google.auto.service.AutoService
4-
import dev.slne.surf.surfapi.core.api.hook.AbstractHook
54
import dev.slne.surf.surfapi.core.api.hook.SurfHookApi
65
import dev.slne.surf.surfapi.core.server.hook.HookService
6+
import dev.slne.surf.surfapi.shared.api.hook.Hook
77

88
@AutoService(SurfHookApi::class)
99
class SurfHookApiImpl : SurfHookApi {
@@ -38,11 +38,26 @@ class SurfHookApiImpl : SurfHookApi {
3838
return hooks(owner).filterIsInstance(type)
3939
}
4040

41+
override fun <T : Any> hooksOfTypeLoaded(
42+
owner: Any,
43+
type: Class<T>
44+
): List<T> {
45+
return hooksLoaded(owner).filterIsInstance(type)
46+
}
47+
4148
override suspend fun <T : Any> hooksOfType(type: Class<T>): List<T> {
4249
return HookService.get().getAllHooks().filterIsInstance(type)
4350
}
4451

45-
override suspend fun hooks(owner: Any): List<AbstractHook> {
52+
override fun <T : Any> hooksOfTypeLoaded(type: Class<T>): List<T> {
53+
return HookService.get().getAllHooksLoaded().filterIsInstance(type)
54+
}
55+
56+
override suspend fun hooks(owner: Any): List<Hook> {
4657
return HookService.get().getHooks(owner)
4758
}
59+
60+
override fun hooksLoaded(owner: Any): List<Hook> {
61+
return HookService.get().getHooksLoaded(owner)
62+
}
4863
}

surf-api-gradle-plugin/surf-api-processor/src/main/kotlin/dev/slne/surf/surfapi/processor/hook/HookSymbolProcessor.kt

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ class HookSymbolProcessor(environment: SymbolProcessorEnvironment) : SymbolProce
2626
private val DEPENDS_ON_ONE_PLUGIN_ANNOTATION = nameOf<DependsOnOnePlugin>()
2727
private val DEPENDS_ON_PLUGIN_ANNOTATION = nameOf<DependsOnPlugin>()
2828
private val DEPENDS_ON_HOOK_ANNOTATION = nameOf<DependsOnHook>()
29+
private val CONDITIONAL_ON_CUSTOM_ANNOTATION = nameOf<ConditionalOnCustom>()
2930
}
3031

3132
private val logger = environment.logger
@@ -127,12 +128,20 @@ class HookSymbolProcessor(environment: SymbolProcessorEnvironment) : SymbolProce
127128
hookValue.declaration.closestClassDeclaration()?.toBinaryName()
128129
}
129130

131+
val customConditions = hookClass.annotations.findAnnotations(CONDITIONAL_ON_CUSTOM_ANNOTATION)
132+
.mapNotNull { annotation ->
133+
val conditionValue = annotation.arguments.find { it.name?.asString() == "condition" }?.value as? KSType
134+
conditionValue?.declaration?.closestClassDeclaration()?.toBinaryName()
135+
}
136+
130137
PluginHookMeta.Hook(
131138
priority = priority,
132139
className = hookClass.toBinaryName(),
133140
classDependencies = dependsOnClass.toList() + dependsOnClassName.toList(),
134141
pluginDependencies = dependsOnPlugin.toList(),
135-
pluginOneDependencies = dependsOnOnePlugin.toList()
142+
pluginOneDependencies = dependsOnOnePlugin.toList(),
143+
hookDependencies = dependsOnHook.toList(),
144+
customConditions = customConditions.toList()
136145
)
137146
}.toList()
138147

surf-api-shared/surf-api-shared-internal/src/main/kotlin/dev/slne/surf/surfapi/shared/internal/hook/HooksConfig.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,8 @@ import kotlinx.serialization.json.Json
44

55
object HooksConfig {
66
const val HOOKS_FILE_NAME = "surf-hooks.json"
7-
val json = Json { prettyPrint = true }
7+
val json = Json {
8+
prettyPrint = true
9+
encodeDefaults = false
10+
}
811
}

surf-api-shared/surf-api-shared-internal/src/main/kotlin/dev/slne/surf/surfapi/shared/internal/hook/PluginHookMeta.kt

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,11 @@ data class PluginHookMeta(val hooks: List<Hook>) {
99
data class Hook(
1010
val priority: Short,
1111
val className: String,
12-
val classDependencies: List<String>,
13-
val pluginDependencies: List<String>,
14-
val pluginOneDependencies: List<List<String>>,
15-
val hookDependencies: List<String>,
12+
val classDependencies: List<String> = emptyList(),
13+
val pluginDependencies: List<String> = emptyList(),
14+
val pluginOneDependencies: List<List<String>> = emptyList(),
15+
val hookDependencies: List<String> = emptyList(),
16+
val customConditions: List<String> = emptyList(),
1617
)
1718

1819
companion object {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package dev.slne.surf.surfapi.shared.api.hook.condition
2+
3+
interface HookCondition {
4+
suspend fun evaluate(context: HookConditionContext): Boolean
5+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package dev.slne.surf.surfapi.shared.api.hook.condition
2+
3+
import dev.slne.surf.surfapi.shared.api.hook.Hook
4+
import net.kyori.adventure.text.logger.slf4j.ComponentLogger
5+
6+
data class HookConditionContext(
7+
val owner: Any,
8+
val hookClass: Class<out Hook>,
9+
val logger: ComponentLogger,
10+
val environment: Map<String, Any> = emptyMap()
11+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package dev.slne.surf.surfapi.shared.api.hook.requirement
2+
3+
import dev.slne.surf.surfapi.shared.api.hook.condition.HookCondition
4+
import kotlin.reflect.KClass
5+
6+
@Target(AnnotationTarget.CLASS)
7+
@Retention(AnnotationRetention.RUNTIME)
8+
@Repeatable
9+
annotation class ConditionalOnCustom(val condition: KClass<out HookCondition>)

0 commit comments

Comments
 (0)