11package com.github.eventhorizonlab.spi
22
33import com.google.auto.service.AutoService
4- import java.io.Writer
54import javax.annotation.processing.*
65import javax.lang.model.SourceVersion
76import javax.lang.model.element.ElementKind
@@ -11,6 +10,12 @@ import javax.lang.model.type.MirroredTypeException
1110import javax.tools.Diagnostic
1211import javax.tools.StandardLocation
1312
13+ private data class ProviderInfo (
14+ val contractCanonical : String ,
15+ val contractBinary : String ,
16+ val providerBinary : String
17+ )
18+
1419@SupportedOptions(" org.gradle.annotation.processing.aggregating" )
1520@AutoService(Processor ::class )
1621@SupportedSourceVersion(SourceVersion .RELEASE_24 )
@@ -21,36 +26,35 @@ class ServiceSchemeProcessor : AbstractProcessor() {
2126 ServiceContract ::class .java.canonicalName
2227 )
2328
24- private val contracts = mutableSetOf<String >()
25- private val providers = mutableMapOf< String , MutableList < String > >()
29+ private val contracts = mutableSetOf<String >() // canonical names
30+ private val providers = mutableListOf< ProviderInfo >()
2631
2732 override fun process (
2833 annotations : MutableSet <out TypeElement >,
2934 roundEnv : RoundEnvironment
3035 ): Boolean {
31- processingEnv.messager.printMessage(Diagnostic .Kind .NOTE , " ServiceSchemeProcessor running" )
32-
3336 // 1) Collect contracts defined in this compilation
3437 roundEnv.getElementsAnnotatedWith(ServiceContract ::class .java)
3538 .filter { it.kind == ElementKind .INTERFACE }
3639 .map { (it as TypeElement ).qualifiedName.toString() }
3740 .forEach { contracts + = it }
3841
39- // 2) Collect providers (KClass-safe)
40- roundEnv.getElementsAnnotatedWith(ServiceProvider ::class .java)
41- .forEach { element ->
42- val ann = element.getAnnotation(ServiceProvider ::class .java)
43- val contractName = try {
44- ann.value.qualifiedName ? : error(" No qualified name for ${ann.value} " )
45- } catch (mte: MirroredTypeException ) {
46- val typeMirror = mte.typeMirror
47- val typeElement = (typeMirror as DeclaredType ).asElement() as TypeElement
48- typeElement.qualifiedName.toString()
49- }
50- providers.computeIfAbsent(contractName) { mutableListOf () }
51- .add((element as TypeElement ).qualifiedName.toString())
42+ // 2) Collect providers
43+ roundEnv.getElementsAnnotatedWith(ServiceProvider ::class .java).forEach { element ->
44+ val contractElement = try {
45+ (element.getAnnotation(ServiceProvider ::class .java).value as ? TypeElement )
46+ ? : throw IllegalStateException ()
47+ } catch (mte: MirroredTypeException ) {
48+ (mte.typeMirror as DeclaredType ).asElement() as TypeElement
5249 }
5350
51+ val contractCanonical = contractElement.qualifiedName.toString()
52+ val contractBinary = processingEnv.elementUtils.getBinaryName(contractElement).toString()
53+ val providerBinary = processingEnv.elementUtils.getBinaryName(element as TypeElement ).toString()
54+
55+ providers + = ProviderInfo (contractCanonical, contractBinary, providerBinary)
56+ }
57+
5458 // 3) On final round, generate files + validate
5559 if (roundEnv.processingOver()) {
5660 generateServiceFiles()
@@ -61,26 +65,25 @@ class ServiceSchemeProcessor : AbstractProcessor() {
6165 }
6266
6367 private fun generateServiceFiles () {
64- // Write service files for all contracts that actually have providers (cross-module safe)
65- providers.forEach { (contract, impls) ->
66- if (impls.isNotEmpty()) {
67- writeServiceFile(contract, impls.distinct().sorted())
68+ providers.groupBy { it.contractBinary }
69+ .forEach { (contractBinary, infos) ->
70+ writeServiceFile(contractBinary, infos.map { it.providerBinary }.distinct().sorted())
6871 }
69- }
7072
71- // Also flag any contracts declared here that have no providers
72- contracts.filter { providers[it].isNullOrEmpty() }
73- .forEach { contract ->
74- processingEnv.messager.printMessage(
75- Diagnostic .Kind .ERROR ,
76- missingServiceProviderErrorMessage(contract)
77- )
78- }
73+ // Flag contracts declared here with no providers
74+ contracts.filter { canonical ->
75+ providers.none { it.contractCanonical == canonical }
76+ }.forEach { contract ->
77+ processingEnv.messager.printMessage(
78+ Diagnostic .Kind .ERROR ,
79+ missingServiceProviderErrorMessage(contract)
80+ )
81+ }
7982 }
8083
8184 private fun validateProviders () {
82- providers.keys. forEach { contractName ->
83- val contractElement = processingEnv.elementUtils.getTypeElement(contractName )
85+ providers.map { it.contractCanonical }.distinct(). forEach { canonicalName ->
86+ val contractElement = processingEnv.elementUtils.getTypeElement(canonicalName )
8487 val hasAnnotation = contractElement?.annotationMirrors?.any {
8588 val annType = (it.annotationType.asElement() as TypeElement )
8689 .qualifiedName.toString()
@@ -95,27 +98,23 @@ class ServiceSchemeProcessor : AbstractProcessor() {
9598 }
9699 processingEnv.messager.printMessage(
97100 Diagnostic .Kind .ERROR ,
98- " @ServiceProvider target $contractName is not annotated with @ServiceContract. $hint "
101+ " @ServiceProvider target $canonicalName is not annotated with @ServiceContract. $hint "
99102 )
100103 }
101104 }
102105 }
103106
104- private fun writeServiceFile (contract : String , impls : List <String >) {
105- try {
106- processingEnv.messager.printMessage(
107- Diagnostic .Kind .NOTE ,
108- " Writing META-INF/services/$contract with ${impls.size} implementation(s): ${impls.joinToString()} "
109- )
110- processingEnv.filer
111- .createResource(StandardLocation .CLASS_OUTPUT , " " , " META-INF/services/$contract " )
112- .openWriter()
113- .use { writer: Writer ->
114- impls.forEach { writer.write(" $it \n " ) }
115- }
116- } catch (e: Exception ) {
117- throw RuntimeException (e)
118- }
107+ private fun writeServiceFile (contractBinary : String , impls : List <String >) {
108+ processingEnv.messager.printMessage(
109+ Diagnostic .Kind .NOTE ,
110+ " Writing META-INF/services/$contractBinary with ${impls.size} implementation(s): ${impls.joinToString()} "
111+ )
112+ processingEnv.filer
113+ .createResource(StandardLocation .CLASS_OUTPUT , " " , " META-INF/services/$contractBinary " )
114+ .openWriter()
115+ .use { writer ->
116+ impls.forEach { writer.write(" $it \n " ) }
117+ }
119118 }
120119}
121120
0 commit comments