1+ package dev.slne.surf.surfapi.processor.autoservice
2+
3+ import com.google.auto.service.AutoService
4+ import com.google.devtools.ksp.closestClassDeclaration
5+ import com.google.devtools.ksp.getAllSuperTypes
6+ import com.google.devtools.ksp.isLocal
7+ import com.google.devtools.ksp.processing.Dependencies
8+ import com.google.devtools.ksp.processing.Resolver
9+ import com.google.devtools.ksp.processing.SymbolProcessor
10+ import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
11+ import com.google.devtools.ksp.symbol.KSAnnotated
12+ import com.google.devtools.ksp.symbol.KSClassDeclaration
13+ import com.google.devtools.ksp.symbol.KSFile
14+ import com.google.devtools.ksp.symbol.KSType
15+ import com.squareup.kotlinpoet.ClassName
16+ import java.io.IOException
17+
18+ class AutoServiceSymbolProcessor (environment : SymbolProcessorEnvironment ) : SymbolProcessor {
19+
20+ companion object {
21+ private val SERVICE_ANNOTATION = AutoService ::class .java.name
22+ }
23+
24+ private val logger = environment.logger
25+ private val codeGenerator = environment.codeGenerator
26+
27+ // Map: serviceFqName -> set of (impl binary name, source file)
28+ private val providers = mutableMapOf<String , MutableSet <Pair <String , KSFile ?>>>()
29+
30+ private val verify = environment.options[" autoserviceKsp.verify" ]?.toBoolean() == true
31+ private val verbose = environment.options[" autoserviceKsp.verbose" ]?.toBoolean() == true
32+
33+
34+ override fun process (resolver : Resolver ): List <KSAnnotated > {
35+ val deferred = mutableListOf<KSAnnotated >()
36+
37+ resolver.getSymbolsWithAnnotation(SERVICE_ANNOTATION )
38+ .filterIsInstance<KSClassDeclaration >()
39+ .forEach { implementer ->
40+ val annotation = implementer.annotations.firstOrNull {
41+ it.annotationType.resolve().declaration.qualifiedName?.asString() == SERVICE_ANNOTATION
42+ } ? : run {
43+ logger.error(" @AutoService annotation not found on element" , implementer)
44+ return @forEach
45+ }
46+
47+ val argValue =
48+ annotation.arguments.firstOrNull { it.name?.asString() == " value" }?.value
49+ val providerTypes: List <KSType > = when (argValue) {
50+ is List <* > -> argValue.filterIsInstance<KSType >()
51+ is KSType -> listOf (argValue)
52+ null -> emptyList()
53+ else -> emptyList()
54+ }
55+
56+ if (providerTypes.isEmpty()) {
57+ logger.error(
58+ " No service interfaces specified in @AutoService. " +
59+ " Use @AutoService(YourService::class)." ,
60+ annotation
61+ )
62+ }
63+
64+ for (providerType in providerTypes) {
65+ if (providerType.isError) {
66+ deferred + = implementer
67+ continue
68+ }
69+
70+ val providerDecl = providerType.declaration.closestClassDeclaration()
71+ if (providerDecl == null ) {
72+ deferred + = implementer
73+ continue
74+ }
75+
76+ when (checkImplementer(implementer, providerType)) {
77+ ValidationResult .VALID -> {
78+ val key = providerDecl.toBinaryName()
79+ val impl = implementer.toBinaryName()
80+ providers.getOrPut(key) { mutableSetOf () }
81+ .add(impl to implementer.containingFile)
82+ }
83+
84+ ValidationResult .INVALID -> {
85+ logger.error(
86+ " Service providers must implement their service interface. " +
87+ " ${implementer.qualifiedName?.asString()} does not implement " +
88+ providerDecl.qualifiedName?.asString(),
89+ implementer
90+ )
91+ }
92+
93+ ValidationResult .DEFERRED -> {
94+ deferred + = implementer
95+ }
96+ }
97+ }
98+ }
99+
100+ return deferred
101+ }
102+
103+ override fun finish () {
104+ generateAndClearConfigFiles()
105+ }
106+
107+ private fun generateAndClearConfigFiles () {
108+ for ((serviceFqName, impls) in providers) {
109+ val resourcePath = " META-INF/services/$serviceFqName "
110+ log(" Working on resource file: $resourcePath " )
111+
112+ try {
113+ val allServices = impls.asSequence().map { it.first }.toSortedSet()
114+ log(" New service file contents: $allServices " )
115+
116+ val ksFiles = impls.mapNotNull { it.second }
117+ log(" Originating files: ${ksFiles.map(KSFile ::fileName)} " )
118+
119+ val deps = if (ksFiles.isEmpty()) {
120+ Dependencies (aggregating = true )
121+ } else {
122+ Dependencies (aggregating = true , sources = ksFiles.toTypedArray())
123+ }
124+
125+ codeGenerator.createNewFileByPath(deps, resourcePath, " " ).bufferedWriter()
126+ .use { writer ->
127+ for (service in allServices) {
128+ writer.write(service)
129+ writer.newLine()
130+ }
131+ }
132+
133+ log(" Wrote to: $resourcePath " )
134+ } catch (e: IOException ) {
135+ logger.error(" Unable to create $resourcePath , $e " )
136+ }
137+ }
138+ }
139+
140+ private fun log (message : String ) {
141+ if (verbose) {
142+ logger.info(message)
143+ }
144+ }
145+
146+
147+ private fun KSClassDeclaration.toClassName (): ClassName {
148+ require(! isLocal()) { " Local/anonymous classes are not supported!" }
149+ val pkg = packageName.asString()
150+ val typesString = qualifiedName!! .asString().removePrefix(" $pkg ." )
151+ val simpleNames = typesString.split(" ." )
152+ return ClassName (pkg, simpleNames)
153+ }
154+
155+ private fun KSClassDeclaration.toBinaryName (): String = toClassName().reflectionName()
156+
157+ private fun checkImplementer (
158+ implementer : KSClassDeclaration ,
159+ providerType : KSType ,
160+ ): ValidationResult {
161+ if (! verify) return ValidationResult .VALID
162+
163+ for (superType in implementer.getAllSuperTypes()) {
164+ if (superType.isError) return ValidationResult .DEFERRED
165+ // Accept equal types or assignable in either direction
166+ if (superType == providerType ||
167+ superType.isAssignableFrom(providerType) ||
168+ providerType.isAssignableFrom(superType)
169+ ) {
170+ return ValidationResult .VALID
171+ }
172+ }
173+
174+ return ValidationResult .INVALID
175+ }
176+
177+ private enum class ValidationResult { VALID , INVALID , DEFERRED }
178+ }
0 commit comments