aboutsummaryrefslogtreecommitdiff
path: root/symbols/src/main/kotlin
diff options
context:
space:
mode:
Diffstat (limited to 'symbols/src/main/kotlin')
-rw-r--r--symbols/src/main/kotlin/Subscribe.kt12
-rw-r--r--symbols/src/main/kotlin/process/SubscribeAnnotationProcessor.kt116
2 files changed, 128 insertions, 0 deletions
diff --git a/symbols/src/main/kotlin/Subscribe.kt b/symbols/src/main/kotlin/Subscribe.kt
new file mode 100644
index 0000000..e7a7bf4
--- /dev/null
+++ b/symbols/src/main/kotlin/Subscribe.kt
@@ -0,0 +1,12 @@
+/*
+ * SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+package moe.nea.firmament.annotations
+
+@Target(AnnotationTarget.FUNCTION)
+@Retention(AnnotationRetention.SOURCE)
+annotation class Subscribe
+
diff --git a/symbols/src/main/kotlin/process/SubscribeAnnotationProcessor.kt b/symbols/src/main/kotlin/process/SubscribeAnnotationProcessor.kt
new file mode 100644
index 0000000..be817ce
--- /dev/null
+++ b/symbols/src/main/kotlin/process/SubscribeAnnotationProcessor.kt
@@ -0,0 +1,116 @@
+/*
+ * SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+package moe.nea.firmament.annotations.process
+
+import com.google.auto.service.AutoService
+import com.google.devtools.ksp.processing.CodeGenerator
+import com.google.devtools.ksp.processing.Dependencies
+import com.google.devtools.ksp.processing.KSPLogger
+import com.google.devtools.ksp.processing.Resolver
+import com.google.devtools.ksp.processing.SymbolProcessor
+import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
+import com.google.devtools.ksp.processing.SymbolProcessorProvider
+import com.google.devtools.ksp.symbol.ClassKind
+import com.google.devtools.ksp.symbol.KSAnnotated
+import com.google.devtools.ksp.symbol.KSClassDeclaration
+import com.google.devtools.ksp.symbol.KSFunctionDeclaration
+import com.google.devtools.ksp.symbol.KSType
+import com.google.devtools.ksp.symbol.Nullability
+import com.google.devtools.ksp.validate
+import java.text.SimpleDateFormat
+import java.util.Date
+import moe.nea.firmament.annotations.Subscribe
+
+class SubscribeAnnotationProcessor(
+ val logger: KSPLogger,
+ val codeGenerator: CodeGenerator,
+) : SymbolProcessor {
+ override fun finish() {
+ val dependencies = Dependencies(
+ aggregating = true,
+ *subscriptions.mapTo(mutableSetOf()) { it.parent.containingFile!! }.toTypedArray())
+ val subscriptionsFile =
+ codeGenerator
+ .createNewFile(dependencies, "moe.nea.firmament.annotations.generated", "AllSubscriptions")
+ .bufferedWriter()
+ subscriptionsFile.apply {
+ appendLine("// This file is @generated by SubscribeAnnotationProcessor at ${SimpleDateFormat("yyyy-MM-dd HH:mm:ss z").format(
+ Date())}")
+ appendLine("// Do not edit")
+ appendLine("package moe.nea.firmament.annotations.generated")
+ appendLine()
+ appendLine("import moe.nea.firmament.events.subscription.*")
+ appendLine()
+ appendLine("object AllSubscriptions {")
+ appendLine(" fun provideSubscriptions(addSubscription: (Subscription<*>) -> Unit) {")
+ for (subscription in subscriptions) {
+ val owner = subscription.parent.qualifiedName!!.asString()
+ val method = subscription.child.simpleName.asString()
+ val type = subscription.type.declaration.qualifiedName!!.asString()
+ appendLine(" addSubscription(Subscription<$type>(")
+ appendLine(" ${owner},")
+ appendLine(" ${owner}::${method},")
+ appendLine(" ${type}))")
+ }
+ appendLine(" }")
+ appendLine("}")
+ }
+ subscriptionsFile.close()
+ }
+
+ data class Subscription(
+ val parent: KSClassDeclaration,
+ val child: KSFunctionDeclaration,
+ val type: KSType,
+ )
+
+ val subscriptions = mutableListOf<Subscription>()
+
+ fun processCandidates(list: List<KSAnnotated>) {
+ for (element in list) {
+ if (element !is KSFunctionDeclaration) {
+ logger.error("@Subscribe annotation on a not-function", element)
+ continue
+ }
+ if (element.isAbstract) {
+ logger.error("@Subscribe annotation on an abstract function", element)
+ continue
+ }
+ val parent = element.parentDeclaration
+ if (parent !is KSClassDeclaration || parent.isCompanionObject || parent.classKind != ClassKind.OBJECT) {
+ logger.error("@Subscribe on a non-object", element)
+ continue
+ }
+ val param = element.parameters.singleOrNull()
+ if (param == null) {
+ logger.error("@Subscribe annotated functions need to take exactly one parameter", element)
+ continue
+ }
+ val type = param.type.resolve()
+ if (type.nullability != Nullability.NOT_NULL) {
+ logger.error("@Subscribe annotated functions cannot take a nullable event", element)
+ continue
+ }
+ subscriptions.add(Subscription(parent, element, type))
+ }
+ }
+
+ override fun process(resolver: Resolver): List<KSAnnotated> {
+ val candidates = resolver.getSymbolsWithAnnotation(Subscribe::class.qualifiedName!!).toList()
+ val valid = candidates.filter { it.validate() }
+ val invalid = candidates.filter { !it.validate() }
+ processCandidates(valid)
+ return invalid
+ }
+}
+
+@AutoService(SymbolProcessorProvider::class)
+class SubscribeAnnotationProcessorProvider : SymbolProcessorProvider {
+ override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor {
+ return SubscribeAnnotationProcessor(environment.logger, environment.codeGenerator)
+ }
+}