Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -102,3 +102,4 @@ fabric.properties

# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
.idea/deploymentTargetDropDown.xml
1 change: 1 addition & 0 deletions compiler/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ plugins {
dependencies {
implementation(projects.core)
implementation(projects.thirdParty.retrofit)
implementation(projects.thirdParty.ktorfit)
implementation(projects.thirdParty.androidx.room)
implementation(projects.thirdParty.android.testing)
testFixturesApi(projects.compiler.common.testUtils)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,18 +58,34 @@ object Errors {
"Replacement target $replacedObject must be annotated with @AutoBind"
}

object Retrofit {
const val nonInterface = "Only interfaces can be annotated with @AutoProvideService."
const val privateType = "@AutoProvideService annotated types must not be private."
const val emptyService = "@AutoProvideService annotated types must have at least one method."
const val propertiesNotAllowed = "Retrofit services cannot contain properties."
const val invalidServiceMethod = "Methods in retrofit services must be annotated with a HTTP method annotation such as @GET."
const val scopeAndReusable = "You cannot mix a scope and @Reusable on the same type. Remove the scope or @Reusable."

fun invalidScope(scope: String, component: String, neededScope: String) =
interface ApiService {
val nonInterface: String
val privateType: String
val emptyService: String
val propertiesNotAllowed: String
val invalidServiceMethod: String
val scopeAndReusable: String
fun invalidScope(scope: String, component: String, neededScope: String): String

}

object Retrofit : ApiService{
override val nonInterface = "Only interfaces can be annotated with @AutoProvideService."
override val privateType = "@AutoProvideService annotated types must not be private."
override val emptyService = "@AutoProvideService annotated types must have at least one method."
override val propertiesNotAllowed = "Retrofit services cannot contain properties."
override val invalidServiceMethod = "Methods in retrofit services must be annotated with a HTTP method annotation such as @GET."
override val scopeAndReusable = "You cannot mix a scope and @Reusable on the same type. Remove the scope or @Reusable."

override fun invalidScope(scope: String, component: String, neededScope: String) =
"You cannot use @$scope when installing in $component, use @$neededScope instead."
}

object Ktorfit : ApiService by Retrofit {
override val propertiesNotAllowed = "Ktorfit services cannot contain properties."
override val invalidServiceMethod = "Methods in ktorfit services must be annotated with a HTTP method annotation such as @GET."
}

object AndroidX {
object Room {
const val notADatabase = "Types annotated @AutoProvideDao must be annotated with @Database and directly extend RoomDatabase."
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import se.ansman.dagger.auto.compiler.autoinitialize.AutoInitializeProcessor
import se.ansman.dagger.auto.compiler.autoinitialize.renderer.JavaAutoInitializeModuleRenderer
import se.ansman.dagger.auto.compiler.common.Options
import se.ansman.dagger.auto.compiler.common.kapt.processing.KaptEnvironment
import se.ansman.dagger.auto.compiler.ktorfit.KtorfitProcessor
import se.ansman.dagger.auto.compiler.ktorfit.renderer.JavaKtorfitModuleRenderer
import se.ansman.dagger.auto.compiler.replaces.ReplacesProcessor
import se.ansman.dagger.auto.compiler.retrofit.RetrofitProcessor
import se.ansman.dagger.auto.compiler.retrofit.renderer.JavaRetrofitModuleRenderer
Expand All @@ -30,6 +32,7 @@ class AutoDaggerAnnotationProcessor : BasicAnnotationProcessor() {
AutoDaggerProcessorStep(environment, AutoBindProcessor(environment, JavaAutoBindModuleModuleRenderer)),
AutoDaggerProcessorStep(environment, ReplacesProcessor(environment, JavaAutoBindModuleModuleRenderer)),
AutoDaggerProcessorStep(environment, RetrofitProcessor(environment, JavaRetrofitModuleRenderer)),
AutoDaggerProcessorStep(environment, KtorfitProcessor(environment, JavaKtorfitModuleRenderer)),
AutoDaggerProcessorStep(environment, AndroidXRoomProcessor(environment, JavaAndroidXRoomDatabaseModuleRenderer)),
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import se.ansman.dagger.auto.compiler.autoinitialize.renderer.KotlinAutoInitiali
import se.ansman.dagger.auto.compiler.common.ksp.KspProcessor
import se.ansman.dagger.auto.compiler.common.ksp.processing.KspEnvironment
import se.ansman.dagger.auto.compiler.common.ksp.processing.KspResolver
import se.ansman.dagger.auto.compiler.ktorfit.KtorfitProcessor
import se.ansman.dagger.auto.compiler.ktorfit.renderer.KotlinKtorfitObjectRenderer
import se.ansman.dagger.auto.compiler.replaces.ReplacesProcessor
import se.ansman.dagger.auto.compiler.retrofit.RetrofitProcessor
import se.ansman.dagger.auto.compiler.retrofit.renderer.KotlinRetrofitObjectRenderer
Expand All @@ -36,6 +38,10 @@ class AutoDaggerSymbolProcessor(environment: SymbolProcessorEnvironment) : Symbo
environment = this.environment,
renderer = KotlinRetrofitObjectRenderer
),
KtorfitProcessor(
environment = this.environment,
renderer = KotlinKtorfitObjectRenderer
),
AndroidXRoomProcessor(
environment = this.environment,
renderer = KotlinAndroidXRoomDatabaseModuleRenderer
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package se.ansman.dagger.auto.compiler.ktorfit

import se.ansman.dagger.auto.compiler.Errors
import se.ansman.dagger.auto.compiler.common.processing.AutoDaggerEnvironment
import se.ansman.dagger.auto.compiler.ktorfit.renderer.KtorfitModuleRenderer
import se.ansman.dagger.auto.compiler.retrofit.BaseApiServiceProcessor
import se.ansman.dagger.auto.ktorfit.AutoProvideService

class KtorfitProcessor<N, TypeName, ClassName : TypeName, AnnotationSpec, F>(
environment: AutoDaggerEnvironment<N, TypeName, ClassName, AnnotationSpec, F>,
renderer: KtorfitModuleRenderer<N, TypeName, ClassName, AnnotationSpec, *, *, F>,
) : BaseApiServiceProcessor<N, TypeName, ClassName, AnnotationSpec, F>(
environment = environment,
renderer = renderer,
annotation = AutoProvideService::class,
serviceAnnotations = setOf(
"de.jensklingenberg.ktorfit.http.DELETE",
"de.jensklingenberg.ktorfit.http.GET",
"de.jensklingenberg.ktorfit.http.HEAD",
"de.jensklingenberg.ktorfit.http.HTTP",
"de.jensklingenberg.ktorfit.http.OPTIONS",
"de.jensklingenberg.ktorfit.http.PATCH",
"de.jensklingenberg.ktorfit.http.POST",
"de.jensklingenberg.ktorfit.http.PUT",
),
modulePrefix = "AutoBindKtorfit",
logger = environment.logger.withTag("ktorfit"),
errors = Errors.Ktorfit,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package se.ansman.dagger.auto.compiler.ktorfit.renderer

import com.squareup.javapoet.AnnotationSpec
import com.squareup.javapoet.ClassName
import com.squareup.javapoet.CodeBlock
import com.squareup.javapoet.JavaFile
import com.squareup.javapoet.ParameterSpec
import com.squareup.javapoet.TypeName
import se.ansman.dagger.auto.compiler.common.kapt.JavaPoetRenderEngine
import se.ansman.dagger.auto.compiler.common.rendering.HiltJavaModuleBuilder
import javax.lang.model.element.Element

object JavaKtorfitModuleRenderer :
KtorfitModuleRenderer<Element, TypeName, ClassName, AnnotationSpec, ParameterSpec, CodeBlock, JavaFile>(
JavaPoetRenderEngine,
HiltJavaModuleBuilder.Factory
) {

override fun provideService(serviceClass: ClassName, serviceFactoryParameter: ParameterSpec): CodeBlock =
CodeBlock.of("return \$N.create(null)", serviceFactoryParameter)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package se.ansman.dagger.auto.compiler.ktorfit.renderer

import com.google.devtools.ksp.symbol.KSDeclaration
import com.squareup.kotlinpoet.AnnotationSpec
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.CodeBlock
import com.squareup.kotlinpoet.FileSpec
import com.squareup.kotlinpoet.ParameterSpec
import com.squareup.kotlinpoet.TypeName
import se.ansman.dagger.auto.compiler.common.ksp.KotlinPoetRenderEngine
import se.ansman.dagger.auto.compiler.common.rendering.HiltKotlinModuleBuilder

object KotlinKtorfitObjectRenderer :
KtorfitModuleRenderer<KSDeclaration, TypeName, ClassName, AnnotationSpec, ParameterSpec, CodeBlock, FileSpec>(
KotlinPoetRenderEngine,
HiltKotlinModuleBuilder.Factory
) {

override fun provideService(serviceClass: ClassName, serviceFactoryParameter: ParameterSpec): CodeBlock =
CodeBlock.of("return %N.create()", serviceFactoryParameter)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package se.ansman.dagger.auto.compiler.ktorfit.renderer

import se.ansman.dagger.auto.compiler.common.processing.RenderEngine
import se.ansman.dagger.auto.compiler.common.rendering.HiltModuleBuilder
import se.ansman.dagger.auto.compiler.retrofit.renderer.BaseApiServiceModuleRenderer

abstract class KtorfitModuleRenderer<Node, TypeName, ClassName : TypeName, AnnotationSpec, ParameterSpec, CodeBlock, SourceFile>(
renderEngine: RenderEngine<TypeName, ClassName, AnnotationSpec>,
builderFactory: HiltModuleBuilder.Factory<Node, TypeName, ClassName, AnnotationSpec, ParameterSpec, CodeBlock, SourceFile>,
) : BaseApiServiceModuleRenderer<Node, TypeName, ClassName, AnnotationSpec, ParameterSpec, CodeBlock, SourceFile>(
renderEngine,
builderFactory
) {
override val serviceFactory: ClassName
get() = renderEngine.className("de.jensklingenberg.ktorfit.Ktorfit")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
package se.ansman.dagger.auto.compiler.retrofit

import dagger.Reusable
import dagger.hilt.components.SingletonComponent
import se.ansman.dagger.auto.compiler.Errors
import se.ansman.dagger.auto.compiler.common.Processor
import se.ansman.dagger.auto.compiler.common.processing.AutoDaggerEnvironment
import se.ansman.dagger.auto.compiler.common.processing.AutoDaggerLogger
import se.ansman.dagger.auto.compiler.common.processing.AutoDaggerResolver
import se.ansman.dagger.auto.compiler.common.processing.ClassDeclaration
import se.ansman.dagger.auto.compiler.common.processing.ClassDeclaration.Kind
import se.ansman.dagger.auto.compiler.common.processing.Function
import se.ansman.dagger.auto.compiler.common.processing.Property
import se.ansman.dagger.auto.compiler.common.processing.getAnnotation
import se.ansman.dagger.auto.compiler.common.processing.getQualifiers
import se.ansman.dagger.auto.compiler.common.processing.getValue
import se.ansman.dagger.auto.compiler.common.processing.isAnnotatedWith
import se.ansman.dagger.auto.compiler.common.processing.isFullyPrivate
import se.ansman.dagger.auto.compiler.common.processing.isFullyPublic
import se.ansman.dagger.auto.compiler.common.processing.lookupType
import se.ansman.dagger.auto.compiler.common.processing.nodesAnnotatedWith
import se.ansman.dagger.auto.compiler.common.processing.rootPeerClass
import se.ansman.dagger.auto.compiler.common.rendering.HiltModuleBuilder
import se.ansman.dagger.auto.compiler.retrofit.models.ApiServiceModule
import se.ansman.dagger.auto.compiler.retrofit.renderer.BaseApiServiceModuleRenderer
import se.ansman.dagger.auto.compiler.utils.ComponentValidator.validateComponent
import javax.inject.Scope
import kotlin.reflect.KClass

abstract class BaseApiServiceProcessor<N, TypeName, ClassName : TypeName, AnnotationSpec, F>(
private val environment: AutoDaggerEnvironment<N, TypeName, ClassName, AnnotationSpec, F>,
private val renderer: BaseApiServiceModuleRenderer<N, TypeName, ClassName, AnnotationSpec, *, *, F>,
private val annotation: KClass<out Annotation>,
serviceAnnotations: Set<String>,
private val modulePrefix: String,
private val logger: AutoDaggerLogger<N>,
private val errors: Errors.ApiService,
) : Processor<N, TypeName, ClassName, AnnotationSpec> {
override val annotations: Set<String> = setOf(annotation.java.canonicalName)

private val serviceAnnotations by lazy {
serviceAnnotations.map { environment.className(it) }
}

override fun process(resolver: AutoDaggerResolver<N, TypeName, ClassName, AnnotationSpec>) {
logger.info("@AutoProvideService processing started")
resolver.nodesAnnotatedWith(annotation)
.map { it as ClassDeclaration<N, TypeName, ClassName, AnnotationSpec> }
.map { service ->
logger.info("Processing ${service.className}")
val targetComponent = service
.getAnnotation(annotation)!!
.getValue<ClassDeclaration<N, TypeName, ClassName, AnnotationSpec>>("inComponent")
?: resolver.lookupType(SingletonComponent::class)

service.validateService()
targetComponent.validateComponent(service, logger)

ApiServiceModule(
moduleName = environment.rootPeerClass(
service.className,
environment.simpleNames(service.className).joinToString(
prefix = modulePrefix,
separator = ""
)
),
installation = HiltModuleBuilder.Installation.InstallIn(targetComponent.className),
originatingTopLevelClassName = environment.topLevelClassName(service.className),
originatingElement = service.node,
serviceClass = service.className,
isPublic = service.isFullyPublic,
qualifiers = service.getQualifiers(),
scope = service.findScope(targetComponent)
)
}
.map(renderer::render)
.forEach(environment::write)
}

private fun ClassDeclaration<N, TypeName, ClassName, AnnotationSpec>.validateService() {
if (kind != Kind.Interface) logger.error(errors.nonInterface, node)
if (isGeneric) logger.error(Errors.genericType(annotation), node)
if (isFullyPrivate) logger.error(errors.privateType, node)
if (declaredNodes.isEmpty()) logger.error(errors.emptyService, node)
for (node in declaredNodes) {
logger.info("Validating enclosed element $node")
when (node) {
is Function<N, TypeName, ClassName, AnnotationSpec> -> if (serviceAnnotations.none(node::isAnnotatedWith)) {
logger.error(errors.invalidServiceMethod, node.node)
}

is Property<N, TypeName, ClassName, AnnotationSpec> -> logger.error(
errors.propertiesNotAllowed,
node.node
)

else -> error("Unexpected node: $node")
}
}
}

private fun ClassDeclaration<N, TypeName, ClassName, AnnotationSpec>.findScope(
targetComponent: ClassDeclaration<N, TypeName, ClassName, AnnotationSpec>
): AnnotationSpec? =
annotations
.filter { annotation ->
when {
annotation.isOfType(Reusable::class) -> true
annotation.isAnnotatedWith(Scope::class) -> {
// This will have logged and error about an invalid component
val neededScope = targetComponent.annotations
.find { it.isAnnotatedWith(Scope::class) }
?: return@filter false

if (neededScope.className != annotation.className) {
logger.error(
errors.invalidScope(
scope = annotation.simpleName,
component = environment.simpleName(targetComponent.className),
neededScope = neededScope.simpleName
), node
)
return@filter false
}
true
}

else -> false
}
}
.let {
when (it.size) {
0 -> null
1 -> it.single()
else -> {
logger.error(errors.scopeAndReusable, node)
null
}
}
}
?.toAnnotationSpec()
}
Loading