package me.eater.threedom.kapt import org.yanex.takenoko.* import java.io.File import javax.annotation.processing.* import javax.lang.model.SourceVersion import javax.lang.model.element.Element import javax.lang.model.element.TypeElement import javax.tools.Diagnostic.Kind.ERROR @SupportedSourceVersion(SourceVersion.RELEASE_8) @SupportedAnnotationTypes("me.eater.threedom.kapt.EventName") @SupportedOptions(EventNameProcessor.KAPT_KOTLIN_GENERATED_OPTION_NAME, "org.gradle.annotation.processing.aggregating") class EventNameProcessor : AbstractProcessor() { companion object { const val KAPT_KOTLIN_GENERATED_OPTION_NAME = "kapt.kotlin.generated" } override fun process(annotations: MutableSet?, roundEnv: RoundEnvironment): Boolean { val annotatedElements = roundEnv.getElementsAnnotatedWith(EventName::class.java) if (annotatedElements.isEmpty()) return false val kaptKotlinGeneratedDir = processingEnv.options[KAPT_KOTLIN_GENERATED_OPTION_NAME] ?: run { processingEnv.messager.printMessage(ERROR, "Can't find the target directory for generated Kotlin files.") return false } val generatedKtFile = kotlinFile("me.eater.threedom.generated") { objectDeclaration("EventNames") { val eventNames = mutableMapOf() for (element in annotatedElements) { val typeElement = element.toTypeElementOrNull() ?: continue val eventName = typeElement.getAnnotation(EventName::class.java).eventName if (eventName in eventNames) { processingEnv.messager.printMessage( ERROR, "Class ${eventNames[eventName]} already uses the event name '${eventName}'", element ) continue } eventNames[typeElement.qualifiedName.toString()] = eventName property(eventName) { initializer(typeElement.qualifiedName.toString() + "::class") } } property("EVENT_MAPPING") { initializer( "mapOf(\n${eventNames.map { (k, v) -> " \"" + k.replace('"', '\"') + "\" to \"" + v.replace('"', '\"') + '"' } .joinToString(",\n")}\n)" ) } function("getEventName") { param("eventClass") returnType() body { append("return EVENT_MAPPING[eventClass] ?: eventClass") } } function( "getEventName", INLINE ) { typeParam("reified T") returnType() body { append("return getEventName(T::class.java.name)") } } } } File("$kaptKotlinGeneratedDir/me/eater/threedom/generated", "EventNames.kt").apply { parentFile.mkdirs() writeText(generatedKtFile.accept(PrettyPrinter(PrettyPrinterConfiguration()))) } return true } fun Element.toTypeElementOrNull(): TypeElement? { if (this !is TypeElement) { processingEnv.messager.printMessage(ERROR, "Invalid element type, class expected", this) return null } return this } }