Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added Lazy and Provider inject support #9

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
34 changes: 17 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,22 +113,22 @@ See more in the [source code](https://github.com/daugeldauge/kinzhal/tree/master

## Dagger2 compatibility table

| Feature | Kinzhal support | Notes |
| ---------- | --------------- | -----------|
| `@Component` || |
| Constructor injection || |
| Field injection | 🚫 | [#1](https://github.com/daugeldauge/kinzhal/issues/1) |
| Component provision functions and properties || |
| Feature | Kinzhal support | Notes |
| ---------- | --------------- |---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `@Component` || |
| Constructor injection || |
| Field injection | 🚫 | [#1](https://github.com/daugeldauge/kinzhal/issues/1) |
| Component provision functions and properties || |
| `@Module` | ⚠️ | Kinzhal has modules but does not have `@Module` annotation. All classes in component module list are treated as modules. Only `object` modules with provides=functions and `interface` modules with binds-functions are allowed |
| `@Provides` | ⚠️ | Kinzhal does not have `@Provides` annotation. All non-abstract functions in a module are considered to be provides-functions |
| `@Binds` | ⚠️ | Kinzhal does not have `@Binds` annotation. All abstract functions in a module are considered to be binds-functions |
| `@Scope` || |
| `@Qualifier` || |
| Component dependencies || Dependency instances are passed to generated component's constructor instead of builder functions |
| `@Subcomponent` | 🚫 | [#3](https://github.com/daugeldauge/kinzhal/issues/3) <br> You can use component dependency to emulate behaviour of subcomponents |
| `@Reusable` | 🚫 | |
| `@BindsInstance` | 🚫 | You can use component dependency to bind instances |
| Lazy/provider injections | 🚫 | [#2](https://github.com/daugeldauge/kinzhal/issues/2) |
| `@Provides` | ⚠️ | Kinzhal does not have `@Provides` annotation. All non-abstract functions in a module are considered to be provides-functions |
| `@Binds` | ⚠️ | Kinzhal does not have `@Binds` annotation. All abstract functions in a module are considered to be binds-functions |
| `@Scope` || |
| `@Qualifier` || |
| Component dependencies || Dependency instances are passed to generated component's constructor instead of builder functions |
| `@Subcomponent` | 🚫 | [#3](https://github.com/daugeldauge/kinzhal/issues/3) <br> You can use component dependency to emulate behaviour of subcomponents |
| `@Reusable` | 🚫 | |
| `@BindsInstance` | 🚫 | You can use component dependency to bind instances |
| Lazy/provider injections | | You can use `kotlin.Lazy<T>` to inject a lazy instance, and `() -> T` to inject a provider |
| `@BindsOptionalOf` | 🚫 |
| Multibindings | 🚫 | |
| Assisted injection || |
| Multibindings | 🚫 | |
| Assisted injection || |
Original file line number Diff line number Diff line change
@@ -1,41 +1,24 @@
package com.daugeldauge.kinzhal.processor

import com.daugeldauge.kinzhal.annotations.Assisted
import com.daugeldauge.kinzhal.processor.generation.FactoryDependency
import com.daugeldauge.kinzhal.processor.generation.providerName
import com.daugeldauge.kinzhal.processor.model.AssistedFactoryType
import com.daugeldauge.kinzhal.processor.model.Key
import com.google.devtools.ksp.symbol.KSFunctionDeclaration
import com.google.devtools.ksp.symbol.KSValueParameter

internal class AssistedFactoryDependency(
val name: String,
val key: Key,
val isAssisted: Boolean,
val parameter: KSValueParameter,
)

internal object AssistedFactoryDependenciesResolver {
fun match(
sourceDeclaration: KSFunctionDeclaration,
assistedFactoryType: AssistedFactoryType,
): List<AssistedFactoryDependency> {
): List<FactoryDependency> {
val dependencies = sourceDeclaration.parameters.map {
val isAssisted = it.isAssisted
AssistedFactoryDependency(
name = if (isAssisted) {
it.name!!.asString()
} else {
"${it.name!!.asString()}Provider"
},
key = it.type.toKey(it.annotations),
isAssisted = isAssisted,
parameter = it
)
FactoryDependency.fromParameter(it)
}

val assistedSourceParameters = dependencies
.asSequence()
.filter(AssistedFactoryDependency::isAssisted)
.map { it.name to it.key.type }
.filter(FactoryDependency::isAssisted)
.map { it.providerName to it.key.type }
.toList()

val assistedFactoryParameters = assistedFactoryType.factoryMethod.parameters
Expand All @@ -52,7 +35,3 @@ internal object AssistedFactoryDependenciesResolver {
}
}

private val KSValueParameter.isAssisted: Boolean
get() = annotations.any {
it.annotationType.resolve().declaration.qualifiedName?.asString() == Assisted::class.requireQualifiedName()
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.daugeldauge.kinzhal.processor.generation

import com.daugeldauge.kinzhal.processor.AssistedFactoryDependency
import com.daugeldauge.kinzhal.processor.model.AssistedFactoryType
import com.daugeldauge.kinzhal.processor.model.FactoryBinding
import com.daugeldauge.kinzhal.processor.model.Key
Expand All @@ -17,13 +16,13 @@ internal fun generateAssistedFactory(
packageName: String,
factoryBaseName: String,
assistedFactoryType: AssistedFactoryType,
dependencies: List<AssistedFactoryDependency>,
dependencies: List<FactoryDependency>,
): FactoryBinding {
val providers: List<Pair<String, TypeName>> = dependencies
.asSequence()
.filterNot { it.isAssisted }
.map { dependency ->
dependency.name to LambdaTypeName.get(returnType = dependency.key.asTypeName())
dependency.providerName to dependency.providerType
}
.toList()

Expand Down Expand Up @@ -78,11 +77,8 @@ internal fun generateAssistedFactory(
} else {
add("(\n")
withIndent {
dependencies.forEach {
add("%N", it.name)
if (!it.isAssisted) {
add("()")
}
dependencies.forEach { dependency ->
addProviderCodeBlock(dependency, dependency.isAssisted)
add(",\n")
}
}
Expand All @@ -100,7 +96,7 @@ internal fun generateAssistedFactory(
injectableKey = Key(assistedFactoryType.type),
scoped = true,
sourceDeclaration = null,
parameters = dependencies.asSequence().filterNot(AssistedFactoryDependency::isAssisted).map(AssistedFactoryDependency::parameter).toList(),
dependencies = dependencies.asSequence().filterNot(FactoryDependency::isAssisted).toList(),
containingFile = containingFile,
addCreateInstanceCall = { add("%T", ClassName(packageName, implName)) },
providersAreTransitive = true,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
package com.daugeldauge.kinzhal.processor.generation

import com.daugeldauge.kinzhal.processor.*
import com.daugeldauge.kinzhal.processor.classDeclaration
import com.daugeldauge.kinzhal.processor.model.ComponentFunctionRequestedKey
import com.daugeldauge.kinzhal.processor.model.ComponentPropertyRequestedKey
import com.daugeldauge.kinzhal.processor.model.FactoryBinding
import com.daugeldauge.kinzhal.processor.model.ResolvedBindingGraph
import com.daugeldauge.kinzhal.processor.returnTypeKey
import com.daugeldauge.kinzhal.processor.typeKey
import com.google.devtools.ksp.processing.CodeGenerator
import com.google.devtools.ksp.symbol.KSClassDeclaration
import com.squareup.kotlinpoet.*

internal fun ResolvedBindingGraph.generateComponent(codeGenerator: CodeGenerator) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package com.daugeldauge.kinzhal.processor.generation

import com.daugeldauge.kinzhal.annotations.Assisted
import com.daugeldauge.kinzhal.processor.model.Key
import com.daugeldauge.kinzhal.processor.requireQualifiedName
import com.daugeldauge.kinzhal.processor.resolveToUnderlying
import com.daugeldauge.kinzhal.processor.toKey
import com.google.devtools.ksp.symbol.KSValueParameter
import com.squareup.kotlinpoet.CodeBlock
import com.squareup.kotlinpoet.LambdaTypeName

internal data class FactoryDependency(
val parameterName: String,
val key: Key,
val isAssisted: Boolean,
val laziness: Laziness,
) {
enum class Laziness {
None,
Lazy,
Provider,
}

companion object {
fun fromParameter(parameter: KSValueParameter): FactoryDependency {
val resolvedParameter = parameter.type.resolveToUnderlying()
val isAssisted = parameter.isAssisted

val (laziness, adjustedType) = when {
isAssisted -> Laziness.None to parameter.type

resolvedParameter.declaration.qualifiedName?.asString() == Lazy::class.java.name -> {
Laziness.Lazy to resolvedParameter.arguments.first().type!!
}

resolvedParameter.declaration.qualifiedName?.asString() == "kotlin.Function0" -> {
Laziness.Provider to resolvedParameter.arguments.first().type!!
}

else -> Laziness.None to parameter.type
}
return FactoryDependency(
parameterName = parameter.name!!.asString(),
key = adjustedType.toKey(parameter.annotations),
laziness = laziness,
isAssisted = isAssisted,
)
}
}
}

internal val FactoryDependency.providerName: String
get() = if (isAssisted) {
parameterName
} else {
"${parameterName}Provider"
}

internal val FactoryDependency.providerType: LambdaTypeName
get() = LambdaTypeName.get(returnType = key.asTypeName())

internal fun CodeBlock.Builder.addProviderCodeBlock(dependency: FactoryDependency, transitiveProvider: Boolean) {
if (transitiveProvider) {
add("%N", dependency.providerName)
} else {
when (dependency.laziness) {
FactoryDependency.Laziness.None -> add("%N()", dependency.providerName)
FactoryDependency.Laziness.Lazy -> add("lazy { %N() }", dependency.providerName)
FactoryDependency.Laziness.Provider -> add("%N", dependency.providerName)
}
}
}

private val KSValueParameter.isAssisted: Boolean
get() = annotations.any {
it.annotationType.resolve().declaration.qualifiedName?.asString() == Assisted::class.requireQualifiedName()
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,10 @@ import com.daugeldauge.kinzhal.processor.findAnnotation
import com.daugeldauge.kinzhal.processor.model.FactoryBinding
import com.daugeldauge.kinzhal.processor.model.Key
import com.daugeldauge.kinzhal.processor.resolveToUnderlying
import com.daugeldauge.kinzhal.processor.toKey
import com.google.devtools.ksp.processing.CodeGenerator
import com.google.devtools.ksp.symbol.KSAnnotation
import com.google.devtools.ksp.symbol.KSFile
import com.google.devtools.ksp.symbol.KSFunctionDeclaration
import com.google.devtools.ksp.symbol.KSValueParameter
import com.squareup.kotlinpoet.*


Expand All @@ -30,7 +28,9 @@ internal fun generateFactory(
it.annotationType.resolveToUnderlying().declaration.findAnnotation<Scope>()?.annotationType?.resolveToUnderlying()
}.toList().isNotEmpty(),
sourceDeclaration = sourceDeclaration,
parameters = sourceDeclaration.parameters,
dependencies = sourceDeclaration.parameters.map {
FactoryDependency.fromParameter(it)
},
containingFile = sourceDeclaration.containingFile!!,
addCreateInstanceCall = addCreateInstanceCall,
providersAreTransitive = false,
Expand All @@ -44,21 +44,13 @@ internal fun generateFactory(
injectableKey: Key,
scoped: Boolean,
sourceDeclaration: KSFunctionDeclaration?,
parameters: List<KSValueParameter>,
dependencies: List<FactoryDependency>,
containingFile: KSFile,
addCreateInstanceCall: CodeBlock.Builder.() -> Unit,
providersAreTransitive: Boolean,
packageName: String,
factoryBaseName: String,
): FactoryBinding {
val dependencies = parameters.map {
("${it.name!!.asString()}Provider") to it.type.toKey(it.annotations)
}

val providers: List<Pair<String, TypeName>> = dependencies.map { (providerName, key) ->
providerName to LambdaTypeName.get(returnType = key.asTypeName())
}

val factoryName = factoryBaseName + "_Factory"
codeGenerator.newFile(
dependenciesAggregating = false,
Expand All @@ -67,12 +59,12 @@ internal fun generateFactory(
fileName = factoryName,
) {

val properties = providers.map { (name, type) ->
val properties = dependencies.map { dependency ->
PropertySpec.builder(
name,
type,
dependency.providerName,
dependency.providerType,
KModifier.PRIVATE,
).initializer(name).build()
).initializer(dependency.providerName).build()
}

addType(
Expand All @@ -81,7 +73,7 @@ internal fun generateFactory(
.primaryConstructor(
FunSpec.constructorBuilder()
.addParameters(
providers.map { (name, type) -> ParameterSpec.builder(name, type).build() }
dependencies.map { dependency -> ParameterSpec.builder(dependency.providerName, dependency.providerType).build() }
)
.build()
)
Expand All @@ -95,16 +87,13 @@ internal fun generateFactory(
add("return ")
addCreateInstanceCall()

if (properties.isEmpty()) {
if (dependencies.isEmpty()) {
add("()")
} else {
add("(\n")
withIndent {
properties.forEach {
add("%N", it)
if (!providersAreTransitive) {
add("()")
}
dependencies.forEach { dependency ->
addProviderCodeBlock(dependency, providersAreTransitive)
add(",\n")
}
}
Expand All @@ -122,7 +111,7 @@ internal fun generateFactory(
declaration = sourceDeclaration,
containingFile = containingFile,
scoped = scoped,
dependencies = dependencies.map { it.second },
dependencies = dependencies.map { it.key },
factoryName = factoryName,
factoryPackage = packageName,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ class Router @Inject constructor(application: Application, versions: Versions, m
class ArtistsPresenter @Inject constructor(
private val database: Database,
private val artistImagesStorage: ArtistImagesStorage,
private val deezer: DeezerApi,
private val spotify: SpotifyApi,
private val deezer: () -> DeezerApi,
private val spotify: Lazy<SpotifyApi>,
private val discogs: DiscogsApi,
private val router: Router,
)
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import com.daugeldauge.kinzhal.annotations.AssistedFactory
import com.daugeldauge.kinzhal.annotations.AssistedInject
import com.daugeldauge.kinzhal.annotations.Inject
import com.daugeldauge.kinzhal.annotations.Scope
import com.daugeldauge.kinzhal.sample.graph.Application
import com.daugeldauge.kinzhal.sample.graph.Versions

@Scope
annotation class HttpClientScope
Expand All @@ -27,9 +29,22 @@ class DeezerKtorApi @Inject constructor(client: HttpClient) : DeezerApi

class SpotifyKtorApi @Inject constructor(client: HttpClient) : SpotifyApi

class DiscogsKtorApi @AssistedInject constructor(client: HttpClient, @Assisted apiKey: String, @Assisted userAgent: String) : DiscogsApi
class DiscogsKtorApi @AssistedInject constructor(
application: Application,
client: Lazy<HttpClient>,
versionsProvider: () -> Versions,
@Assisted apiKey: String,
@Assisted userAgent: String,
@Assisted providerApiKey: () -> String,
@Assisted lazyUserAgent: Lazy<String>,
) : DiscogsApi

@AssistedFactory
interface DiscogsKtorApiFactory {
fun create(apiKey: String, userAgent: String): DiscogsKtorApi
fun create(
apiKey: String,
userAgent: String,
providerApiKey: () -> String,
lazyUserAgent: Lazy<String>,
): DiscogsKtorApi
}
Loading