@@ -15,41 +15,45 @@ import javax.tools.Diagnostic
1515import javax.tools.StandardLocation
1616
1717private data class ProviderInfo (
18- val contractCanonical : String , val contractBinary : String , val providerBinary : String
18+ val contractCanonical : String ,
19+ val contractBinary : String ,
20+ val providerBinary : String ,
1921)
2022
2123@SupportedOptions(" org.gradle.annotation.processing.aggregating" )
2224@AutoService(Processor ::class )
2325@SupportedSourceVersion(SourceVersion .RELEASE_24 )
2426class ServiceSchemeProcessor : AbstractProcessor () {
25-
26- override fun getSupportedAnnotationTypes () = setOf (
27- ServiceProvider ::class .java.canonicalName, ServiceContract ::class .java.canonicalName
28- )
27+ override fun getSupportedAnnotationTypes () =
28+ setOf (
29+ ServiceProvider ::class .java.canonicalName,
30+ ServiceContract ::class .java.canonicalName,
31+ )
2932
3033 private val contracts = mutableSetOf<String >() // canonical names
3134 private val providers = mutableListOf<ProviderInfo >()
3235
3336 override fun process (
34- annotations : MutableSet <out TypeElement >, roundEnv : RoundEnvironment
37+ annotations : MutableSet <out TypeElement >,
38+ roundEnv : RoundEnvironment ,
3539 ): Boolean {
36- // 1) Collect contracts defined in this compilation
37- roundEnv.getElementsAnnotatedWith(ServiceContract ::class .java).filter { it.kind == ElementKind .INTERFACE }
38- .map { (it as TypeElement ).qualifiedName.toString() }.forEach { contracts + = it }
40+ // 1) Collect contracts
41+ roundEnv
42+ .getElementsAnnotatedWith(ServiceContract ::class .java)
43+ .filter { it.kind == ElementKind .INTERFACE }
44+ .map { (it as TypeElement ).qualifiedName.toString() }
45+ .forEach { contracts + = it }
3946
4047 // 2) Collect providers
4148 roundEnv.getElementsAnnotatedWith(ServiceProvider ::class .java).forEach { element ->
42- // 1) Collect all ServiceProvider mirrors (direct + from container if repeatable)
4349 val spMirrors = collectServiceProviderMirrors(element, processingEnv)
44-
45- // 2) For each ServiceProvider mirror, read its "value" (array of class literals)
4650 spMirrors.forEach { spMirror ->
4751 val valuesWithDefaults = processingEnv.elementUtils.getElementValuesWithDefaults(spMirror)
4852 val valueAv =
49- valuesWithDefaults.entries.firstOrNull { it.key.simpleName.contentEquals( " value " ) }?.value ? : error(
50- " @ServiceProvider missing 'value' on ${element. simpleName} "
51- )
52-
53+ valuesWithDefaults.entries
54+ .firstOrNull { it.key. simpleName.contentEquals( " value " ) }
55+ ?.value
56+ ? : error( " @ServiceProvider missing 'value' on ${element.simpleName} " )
5357 val typeMirrors = classArrayAnnotationValues(valueAv, processingEnv)
5458 typeMirrors.forEach { tm ->
5559 val contractElement = (tm as DeclaredType ).asElement() as TypeElement
@@ -58,10 +62,13 @@ class ServiceSchemeProcessor : AbstractProcessor() {
5862 }
5963 }
6064
61- // 3) On final round, generate files + validate
65+ // 3) Validate implementations every round (so we catch missing @ServiceProvider)
66+ validateImplementations(roundEnv)
67+
68+ // 4) On final round, generate files + validate provider targets
6269 if (roundEnv.processingOver()) {
6370 generateServiceFiles()
64- validateProviders ()
71+ validateProviderTargets ()
6572 }
6673
6774 return true
@@ -73,120 +80,175 @@ class ServiceSchemeProcessor : AbstractProcessor() {
7380 }
7481
7582 // Flag contracts declared here with no providers
76- contracts.filter { canonical ->
77- providers.none { it.contractCanonical == canonical }
78- }.forEach { contract ->
79- processingEnv.messager.printMessage(
80- Diagnostic .Kind .ERROR , missingServiceProviderErrorMessage(contract)
81- )
82- }
83+ contracts
84+ .filter { canonical ->
85+ providers.none { it.contractCanonical == canonical }
86+ }.forEach { contract ->
87+ processingEnv.messager.printMessage(
88+ Diagnostic .Kind .WARNING ,
89+ " No @ServiceProvider found for contract $contract in this compilation unit. " +
90+ " This may be intentional if this module only defines contracts." ,
91+ )
92+ }
93+ }
94+
95+ private fun validateImplementations (roundEnv : RoundEnvironment ) {
96+ val contractTypes = contracts.mapNotNull { processingEnv.elementUtils.getTypeElement(it) }
97+ val contractTypeMirrors = contractTypes.map { it.asType() }.toSet()
98+
99+ roundEnv.rootElements
100+ .filterIsInstance<TypeElement >()
101+ .filter { it.kind == ElementKind .CLASS }
102+ .forEach { clazz ->
103+ val implementsContract =
104+ contractTypeMirrors.any { ct ->
105+ processingEnv.typeUtils.isAssignable(clazz.asType(), ct)
106+ }
107+ val hasServiceProvider =
108+ clazz.annotationMirrors.any {
109+ (it.annotationType.asElement() as TypeElement ).qualifiedName.toString() ==
110+ ServiceProvider ::class .java.canonicalName
111+ }
112+ if (implementsContract && ! hasServiceProvider) {
113+ val contractName =
114+ contractTypes
115+ .first {
116+ processingEnv.typeUtils.isAssignable(clazz.asType(), it.asType())
117+ }.qualifiedName
118+ .toString()
119+ processingEnv.messager.printMessage(
120+ Diagnostic .Kind .ERROR ,
121+ missingServiceProviderErrorMessage(contractName),
122+ )
123+ }
124+ }
83125 }
84126
85- private fun validateProviders () {
127+ private fun validateProviderTargets () {
86128 providers.map { it.contractCanonical }.distinct().forEach { canonicalName ->
87129 val contractElement = processingEnv.elementUtils.getTypeElement(canonicalName)
88- val hasAnnotation = contractElement?.annotationMirrors?.any {
89- val annType = (it.annotationType.asElement() as TypeElement ).qualifiedName.toString()
90- annType == ServiceContract :: class .java.canonicalName
91- } ? : false
92-
130+ val hasAnnotation =
131+ contractElement?.annotationMirrors?.any {
132+ val annType = (it.annotationType.asElement() as TypeElement ).qualifiedName.toString()
133+ annType == ServiceContract :: class .java.canonicalName
134+ } ? : false
93135 if (! hasAnnotation) {
94- val hint = if (contractElement == null ) {
95- " Contract type not found — is the API module on the processor's compile classpath?"
96- } else {
97- " Type is present but not annotated with @ServiceContract."
98- }
136+ val hint =
137+ if (contractElement == null ) {
138+ " Contract type not found— is the API module on the processor's compile classpath?"
139+ } else {
140+ " Type is present but not annotated with @ServiceContract."
141+ }
99142 processingEnv.messager.printMessage(
100143 Diagnostic .Kind .ERROR ,
101- " @ServiceProvider target $canonicalName is not annotated with @ServiceContract. $hint "
144+ " @ServiceProvider target $canonicalName is not annotated with @ServiceContract. $hint " ,
102145 )
103146 }
104147 }
105148 }
106149
107- private fun writeServiceFile (contractBinary : String , impls : List <String >) {
150+ private fun writeServiceFile (
151+ contractBinary : String ,
152+ impls : List <String >,
153+ ) {
108154 processingEnv.messager.printMessage(
109155 Diagnostic .Kind .NOTE ,
110- " Writing META-INF/services/$contractBinary with ${impls.size} implementation(s): ${impls.joinToString()} "
156+ " Writing META-INF/services/$contractBinary with ${impls.size} implementation(s): ${impls.joinToString()} " ,
111157 )
112- processingEnv.filer.createResource(StandardLocation .CLASS_OUTPUT , " " , " META-INF/services/$contractBinary " )
113- .openWriter().use { writer ->
158+ processingEnv.filer
159+ .createResource(StandardLocation .CLASS_OUTPUT , " " , " META-INF/services/$contractBinary " )
160+ .openWriter()
161+ .use { writer ->
114162 impls.forEach { writer.write(" $it \n " ) }
115163 }
116164 }
117165
118- private fun addProvider (providerElement : TypeElement , contractElement : TypeElement ) {
166+ private fun addProvider (
167+ providerElement : TypeElement ,
168+ contractElement : TypeElement ,
169+ ) {
119170 val contractCanonical = contractElement.qualifiedName.toString()
120171 val contractBinary = processingEnv.getStringifiedBinaryName(contractElement)
121172 val providerBinary = processingEnv.getStringifiedBinaryName(providerElement)
122173
123174 providers + = ProviderInfo (contractCanonical, contractBinary, providerBinary)
124175 }
125176
126-
127177 /* *
128178 * Returns all @ServiceProvider annotation mirrors on the element, expanding a repeatable
129179 * container if present, using only javax.lang.model APIs.
130180 */
131181 private fun collectServiceProviderMirrors (
132- element : javax.lang.model.element.Element , processingEnv : ProcessingEnvironment
182+ element : javax.lang.model.element.Element ,
183+ processingEnv : ProcessingEnvironment ,
133184 ): List <AnnotationMirror > {
134185 val spName = ServiceProvider ::class .java.canonicalName
135186 val spType = processingEnv.elementUtils.getTypeElement(spName) ? : return emptyList()
136187
137188 // Find container type from @Repeatable, if any
138- val containerName = spType.annotationMirrors.firstNotNullOfOrNull { am ->
139- val annType = (am.annotationType.asElement() as TypeElement ).qualifiedName.toString()
140- if (annType == Repeatable ::class .java.canonicalName) {
141- // @Repeatable(value = Container.class)
142- val values = processingEnv.elementUtils.getElementValuesWithDefaults(am)
143- val repeatableValueAv = values.entries.first { it.key.simpleName.contentEquals(" value" ) }.value
144- val containerTm = repeatableValueAv.value as TypeMirror
145- ((containerTm as DeclaredType ).asElement() as TypeElement ).qualifiedName.toString()
146- } else null
147- }
189+ val containerName =
190+ spType.annotationMirrors.firstNotNullOfOrNull { am ->
191+ val annType = (am.annotationType.asElement() as TypeElement ).qualifiedName.toString()
192+ if (annType == Repeatable ::class .java.canonicalName) {
193+ // @Repeatable(value = Container.class)
194+ val values = processingEnv.elementUtils.getElementValuesWithDefaults(am)
195+ val repeatableValueAv = values.entries.first { it.key.simpleName.contentEquals(" value" ) }.value
196+ val containerTm = repeatableValueAv.value as TypeMirror
197+ ((containerTm as DeclaredType ).asElement() as TypeElement ).qualifiedName.toString()
198+ } else {
199+ null
200+ }
201+ }
148202
149- val direct = element.annotationMirrors.filter { m ->
150- ((m.annotationType.asElement() as TypeElement ).qualifiedName.toString() == spName)
151- }
203+ val direct =
204+ element.annotationMirrors.filter { m ->
205+ ((m.annotationType.asElement() as TypeElement ).qualifiedName.toString() == spName)
206+ }
152207
153- val expandedFromContainer = if (containerName != null ) {
154- element.annotationMirrors.filter { (it.annotationType.asElement() as TypeElement ).qualifiedName.toString() == containerName }
155- .flatMap { containerMirror ->
156- // Container has a "value" which is an array of nested @ServiceProvider annotations
157- val values = processingEnv.elementUtils.getElementValuesWithDefaults(containerMirror)
158- val valueAv = values.entries.firstOrNull { it.key.simpleName.contentEquals(" value" ) }?.value
159- ? : return @flatMap emptyList()
160- val arr = valueAv.value as List <* >
161- arr.filterIsInstance<AnnotationValue >().mapNotNull { it.value as ? AnnotationMirror }
162- }
163- } else emptyList()
208+ val expandedFromContainer =
209+ if (containerName != null ) {
210+ element.annotationMirrors
211+ .filter { (it.annotationType.asElement() as TypeElement ).qualifiedName.toString() == containerName }
212+ .flatMap { containerMirror ->
213+ // Container has a "value" which is an array of nested @ServiceProvider annotations
214+ val values = processingEnv.elementUtils.getElementValuesWithDefaults(containerMirror)
215+ val valueAv =
216+ values.entries.firstOrNull { it.key.simpleName.contentEquals(" value" ) }?.value
217+ ? : return @flatMap emptyList()
218+ val arr = valueAv.value as List <* >
219+ arr.filterIsInstance<AnnotationValue >().mapNotNull { it.value as ? AnnotationMirror }
220+ }
221+ } else {
222+ emptyList()
223+ }
164224
165225 return direct + expandedFromContainer
166226 }
167227
168- private fun toTypeMirror (raw : Any? , processingEnv : ProcessingEnvironment ): TypeMirror ? = when (raw) {
169- null -> null
170- is TypeMirror -> raw
171- is AnnotationValue -> toTypeMirror(raw.value, processingEnv)
172- is String -> processingEnv.elementUtils.getTypeElement(raw)?.asType()
173- else -> null
174- }
228+ private fun toTypeMirror (
229+ raw : Any? ,
230+ processingEnv : ProcessingEnvironment ,
231+ ): TypeMirror ? =
232+ when (raw) {
233+ null -> null
234+ is TypeMirror -> raw
235+ is AnnotationValue -> toTypeMirror(raw.value, processingEnv)
236+ is String -> processingEnv.elementUtils.getTypeElement(raw)?.asType()
237+ else -> null
238+ }
175239
176240 /* *
177241 * Converts the "value" of a class[] annotation member into a List<TypeMirror>,
178242 * handling both array and single-class forms.
179243 */
180244 private fun classArrayAnnotationValues (
181245 valueAv : AnnotationValue ,
182- processingEnv : ProcessingEnvironment
183- ): List <TypeMirror > {
184- return when (val raw = valueAv.value) {
246+ processingEnv : ProcessingEnvironment ,
247+ ): List <TypeMirror > =
248+ when (val raw = valueAv.value) {
185249 is List <* > -> raw.mapNotNull { toTypeMirror(it, processingEnv) }
186250 else -> listOfNotNull(toTypeMirror(raw, processingEnv))
187251 }
188- }
189-
190252}
191253
192- internal fun missingServiceProviderErrorMessage (contract : String ) = " No @ServiceProvider found for contract $contract "
254+ internal fun missingServiceProviderErrorMessage (contract : String ) = " No @ServiceProvider found for contract $contract "
0 commit comments