Skip to content
This repository has been archived by the owner on Sep 3, 2023. It is now read-only.

Commit

Permalink
IntoSet works, resolves #6
Browse files Browse the repository at this point in the history
  • Loading branch information
afollestad committed Aug 15, 2019
1 parent 5e439c0 commit ed5b4be
Show file tree
Hide file tree
Showing 10 changed files with 317 additions and 129 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,50 @@ interface BaseComponent : ScopeObserver {
wantedType = wantedType,
genericArgs = genericArgs,
qualifier = qualifier,
calledBy = calledBy
calledBy = calledBy ?: this
)
}

fun <T : Any> getSet(
setOfType: KClass<T>,
genericArgsOfType: Set<KClass<*>> = emptySet(),
qualifier: String? = null
): MutableSet<T> {
val newSet = mutableSetOf<T>()
populateSet(
set = newSet,
setOfType = setOfType,
genericArgsOfType = genericArgsOfType,
qualifier = qualifier
)
return newSet
}

fun <T : Any> populateSet(
set: MutableSet<T>,
setOfType: KClass<T>,
genericArgsOfType: Set<KClass<*>> = emptySet(),
qualifier: String? = null,
calledBy: BaseComponent? = null
) {
if (calledBy === this) return
modules.forEach { module ->
module.populateSet(
set = set,
setOfType = setOfType,
genericArgsOfType = genericArgsOfType,
qualifier = qualifier,
calledBy = calledBy ?: this
)
}

if (parent != null && calledBy === parent) return
parent?.populateSet(
set = set,
setOfType = setOfType,
genericArgsOfType = genericArgsOfType,
qualifier = qualifier,
calledBy = calledBy ?: this
)
}

Expand Down
10 changes: 10 additions & 0 deletions core/src/main/kotlin/com/afollestad/ulfberht/common/BaseModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
package com.afollestad.ulfberht.common

import com.afollestad.ulfberht.Provider
import com.afollestad.ulfberht.annotation.IntoSet
import kotlin.reflect.KClass

/**
Expand Down Expand Up @@ -51,6 +52,15 @@ interface BaseModule {
calledBy: BaseComponent? = null
): Provider<T>?

/** Used to recursively populate a Set for [IntoSet] dependencies. */
fun <T : Any> populateSet(
set: MutableSet<T>,
setOfType: KClass<T>,
genericArgsOfType: Set<KClass<*>> = emptySet(),
qualifier: String? = null,
calledBy: BaseComponent? = null
) = Unit

/** Destroys the module, releasing held singletons and other resources. */
fun destroy() {
cachedProviders.forEach { it.value.destroy() }
Expand Down
8 changes: 8 additions & 0 deletions core/src/test/kotlin/com/afollestad/ulfberht/TestModules.kt
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,14 @@ abstract class TestBaseModule : BaseModule {
calledBy: BaseComponent?
): Provider<T>? = null

override fun <T : Any> populateSet(
set: MutableSet<T>,
setOfType: KClass<T>,
genericArgsOfType: Set<KClass<*>>,
qualifier: String?,
calledBy: BaseComponent?
) = Unit

override fun destroy() {
isDestroyed = true
super.destroy()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,13 @@ import com.afollestad.ulfberht.util.Names.PARENT_TYPE_NAME
import com.afollestad.ulfberht.util.Names.RUNTIME_DEPS_NAME
import com.afollestad.ulfberht.util.Names.QUALIFIER
import com.afollestad.ulfberht.util.Names.VIEW_MODEL_FACTORY_CREATE
import com.afollestad.ulfberht.util.Annotations.SUPPRESS_UNCHECKED_CAST
import com.afollestad.ulfberht.util.ProcessorUtil.asFileName
import com.afollestad.ulfberht.util.ProcessorUtil.asTypeElement
import com.afollestad.ulfberht.util.ProcessorUtil.error
import com.afollestad.ulfberht.util.ProcessorUtil.filterMethods
import com.afollestad.ulfberht.util.ProcessorUtil.getAnnotationMirror
import com.afollestad.ulfberht.util.ProcessorUtil.applyIf
import com.afollestad.ulfberht.util.ProcessorUtil.asTypeAndArgs
import com.afollestad.ulfberht.util.ProcessorUtil.getFullClassName
import com.afollestad.ulfberht.util.ProcessorUtil.getModulesTypes
import com.afollestad.ulfberht.util.ProcessorUtil.getPackage
Expand All @@ -59,6 +59,7 @@ import com.afollestad.ulfberht.util.Types.ON_LIFECYCLE_EVENT
import com.afollestad.ulfberht.util.Types.VIEW_MODEL
import com.afollestad.ulfberht.util.Types.VIEW_MODEL_FACTORY
import com.afollestad.ulfberht.util.Types.VIEW_MODEL_PROVIDERS
import com.afollestad.ulfberht.util.asTypeAndArgs
import com.squareup.kotlinpoet.ANY
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.CodeBlock
Expand Down Expand Up @@ -289,7 +290,11 @@ internal class ComponentBuilder(
}
code.add(")$doubleBang")
code.applyIf(!fieldTypeAndArgs.isProvider && fieldTypeAndArgs.hasGenericArgs) {
add(" as %T", fieldTypeAndArgs.fullType)
if (fieldTypeAndArgs.isSet) {
add(" as %T<%T>", MUTABLE_SET, fieldTypeAndArgs.fullType)
} else {
add(" as %T", fieldTypeAndArgs.fullType)
}
}
}

Expand All @@ -301,6 +306,7 @@ internal class ComponentBuilder(
}

return FunSpec.builder(method.simpleName.toString())
.addAnnotation(SUPPRESS_UNCHECKED_CAST)
.addModifiers(OVERRIDE)
.addParameter(paramName, parameter.asType().asTypeName())
.addCode(code.build())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,24 @@ import com.afollestad.ulfberht.util.BindOrProvide.BIND
import com.afollestad.ulfberht.util.BindOrProvide.PROVIDE
import com.afollestad.ulfberht.util.BinderOrProvider
import com.afollestad.ulfberht.util.DependencyGraph
import com.afollestad.ulfberht.util.KeyedBinderOrProvider
import com.afollestad.ulfberht.util.Names.CACHED_PROVIDERS_NAME
import com.afollestad.ulfberht.util.Names.CALLED_BY
import com.afollestad.ulfberht.util.Names.CLASS_HEADER
import com.afollestad.ulfberht.util.Names.COMPONENT_PARAM_NAME
import com.afollestad.ulfberht.util.Names.FACTORY_EXTENSION_NAME
import com.afollestad.ulfberht.util.Names.GENERIC_ARGS
import com.afollestad.ulfberht.util.Names.GENERIC_ARGS_OF_TYPE
import com.afollestad.ulfberht.util.Names.GET_NAME
import com.afollestad.ulfberht.util.Names.GET_PROVIDER_NAME
import com.afollestad.ulfberht.util.Names.IS_SUBCLASS_EXTENSION_NAME
import com.afollestad.ulfberht.util.Names.LIBRARY_COMMON_PACKAGE
import com.afollestad.ulfberht.util.Names.LIBRARY_PACKAGE
import com.afollestad.ulfberht.util.Names.MODULE_NAME_SUFFIX
import com.afollestad.ulfberht.util.Names.POPULATE_SET_NAME
import com.afollestad.ulfberht.util.Names.QUALIFIER
import com.afollestad.ulfberht.util.Names.SET_NAME
import com.afollestad.ulfberht.util.Names.SET_OF_TYPE
import com.afollestad.ulfberht.util.Names.SINGLETON_PROVIDER_EXTENSION_NAME
import com.afollestad.ulfberht.util.Names.WANTED_TYPE
import com.afollestad.ulfberht.util.ProcessorUtil.applyIf
Expand All @@ -46,6 +52,7 @@ import com.afollestad.ulfberht.util.TypeAndArgs
import com.afollestad.ulfberht.util.Types.BASE_COMPONENT
import com.afollestad.ulfberht.util.Types.BASE_MODULE
import com.afollestad.ulfberht.util.Types.KCLASS_OF_ANY
import com.afollestad.ulfberht.util.Types.KCLASS_OF_T
import com.afollestad.ulfberht.util.Types.NULLABLE_BASE_COMPONENT
import com.afollestad.ulfberht.util.Types.NULLABLE_KOTLIN_STRING
import com.afollestad.ulfberht.util.Types.PROVIDER
Expand All @@ -61,6 +68,7 @@ import com.squareup.kotlinpoet.FunSpec
import com.squareup.kotlinpoet.KModifier.OVERRIDE
import com.squareup.kotlinpoet.KModifier.PRIVATE
import com.squareup.kotlinpoet.MUTABLE_MAP
import com.squareup.kotlinpoet.MUTABLE_SET
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
import com.squareup.kotlinpoet.PropertySpec
import com.squareup.kotlinpoet.SET
Expand Down Expand Up @@ -100,20 +108,32 @@ internal class ModuleBuilder(
.also { fullClassName = element.getFullClassName(environment, it) }
val fileName = fullClassName.asFileName(MODULE_NAME_SUFFIX)
val typeBuilder = moduleTypeBuilder(fileName, element.isAbstractClass(), fullClassName)
val providedTypeMethodNameMap = mutableMapOf<TypeAndArgs, BinderOrProvider>()
val bindersAndProvidersList = mutableListOf<KeyedBinderOrProvider>()

element.getBindsAndProvidesMethods(environment, dependencyGraph)
.map {
when (it.mode) {
BIND -> addBindsFunction(it, providedTypeMethodNameMap)
PROVIDE -> addProvidesFunction(it, providedTypeMethodNameMap)
.flatMap { method ->
bindersAndProvidersList.add(method)
if (!method.key.intoSet && method.value.size > 1) {
environment.error(
"$element: More than one binding found for ${method.key.providedType}"
)
return@flatMap emptySequence<FunSpec>()
}
val values = method.value
.asSequence()
when (method.key.mode) {
BIND -> values.map { addBindsFunction(it) }
PROVIDE -> values.map { addProvidesFunction(it) }
}.filterNotNull()
}
.filterNotNull()
.forEach { typeBuilder.addFunction(it) }

val typeSpec = typeBuilder
.addFunction(getProviderFunction(providedTypeMethodNameMap))
.addFunction(getProviderFunction(bindersAndProvidersList))
.apply {
populateSetFunction(bindersAndProvidersList)?.let { addFunction(it) }
}
.build()
val fileSpec = FileSpec.builder(pkg, fileName)
.addImport(LIBRARY_PACKAGE, IS_SUBCLASS_EXTENSION_NAME)
Expand Down Expand Up @@ -159,63 +179,118 @@ internal class ModuleBuilder(
.build()
}

private fun getProviderFunction(
providedTypeMethodNameMap: MutableMap<TypeAndArgs, BinderOrProvider>
): FunSpec {
private fun getProviderFunction(bindersAndProvidersList: List<KeyedBinderOrProvider>): FunSpec {
val code = CodeBlock.builder()
.add("return when {\n")
bindersAndProvidersList
.asSequence()
.filter { !it.key.intoSet }
.map { it.value.single() }
.forEach { binderOrProvider ->
val typeAndArgs = binderOrProvider.providedType
val getterName = binderOrProvider.getterName
check(getterName.isNotBlank())
val qualifier = binderOrProvider.qualifier

code.add(" $WANTED_TYPE.$IS_SUBCLASS_EXTENSION_NAME(%T::class)", typeAndArgs.erasedType)
code.applyIf(typeAndArgs.hasGenericArgs) {
add(" && $GENERIC_ARGS == setOf(")
for ((index, typeArg) in typeAndArgs.genericArgs.withIndex()) {
if (index > 0) add(", ")
add("%T::class", typeArg)
}
add(")")
}
code.applyIf(!typeAndArgs.hasGenericArgs) {
add(" && $GENERIC_ARGS.isEmpty()")
}
code.applyIf(qualifier != null) {
add(" && $QUALIFIER == %S", qualifier)
}
code.applyIf(qualifier == null) {
add(" && $QUALIFIER == null")
}
code.add(" -> %N() as %T\n", getterName, PROVIDER_OF_T)
}

code.apply {
addStatement(
" else -> %N.$GET_PROVIDER_NAME($WANTED_TYPE, $GENERIC_ARGS, $QUALIFIER, $CALLED_BY)",
COMPONENT_PARAM_NAME
)
add("}\n")
}

return FunSpec.builder(GET_PROVIDER_NAME)
.addAnnotation(SUPPRESS_UNCHECKED_CAST)
.addModifiers(OVERRIDE)
.addTypeVariable(TYPE_VARIABLE_T)
.addParameter(WANTED_TYPE, KCLASS_OF_ANY)
.addParameter(GENERIC_ARGS, SET.parameterizedBy(KCLASS_OF_ANY))
.addParameter(QUALIFIER, NULLABLE_KOTLIN_STRING)
.addParameter(CALLED_BY, NULLABLE_BASE_COMPONENT)
.returns(PROVIDER_OF_T_NULLABLE)
.addCode(code.build())
.build()
}

private fun populateSetFunction(bindersAndProvidersList: List<KeyedBinderOrProvider>): FunSpec? {
val setBindersOrProviders = bindersAndProvidersList
.filter { it.key.intoSet }
if (setBindersOrProviders.isEmpty()) {
return null
}

for ((typeAndArgs, binderOrProvider) in providedTypeMethodNameMap) {
val getterName = binderOrProvider.getterName
check(getterName.isNotBlank())
val qualifier = binderOrProvider.qualifier
val code = CodeBlock.builder()
.add("when {\n")
setBindersOrProviders.forEach { (key, binderOrProviders) ->
val typeAndArgs = key.providedType
val qualifier = key.qualifier

code.add(" $WANTED_TYPE.$IS_SUBCLASS_EXTENSION_NAME(%T::class)", typeAndArgs.erasedType)
code.add(" $SET_OF_TYPE.$IS_SUBCLASS_EXTENSION_NAME(%T::class)", typeAndArgs.erasedType)
code.applyIf(typeAndArgs.hasGenericArgs) {
add(" && $GENERIC_ARGS == setOf(")
add(" && $GENERIC_ARGS_OF_TYPE == setOf(")
for ((index, typeArg) in typeAndArgs.genericArgs.withIndex()) {
if (index > 0) add(", ")
add("%T::class", typeArg)
}
add(")")
}
code.applyIf(!typeAndArgs.hasGenericArgs) {
add(" && $GENERIC_ARGS.isEmpty()")
add(" && $GENERIC_ARGS_OF_TYPE.isEmpty()")
}
code.applyIf(qualifier != null) {
add(" && $QUALIFIER == %S", qualifier)
}
code.applyIf(qualifier == null) {
add(" && $QUALIFIER == null")
}
code.add(" -> %N() as %T\n", getterName, PROVIDER_OF_T)
}
code.apply {
addStatement(
" else -> %N.$GET_PROVIDER_NAME($WANTED_TYPE, $GENERIC_ARGS, $QUALIFIER, $CALLED_BY)",
COMPONENT_PARAM_NAME
)
add("}\n")

code.add(" -> $SET_NAME.addAll(setOf(")
binderOrProviders.forEachIndexed { index, binderOrProvider ->
if (index > 0) code.add(", ")
val getterName = binderOrProvider.getterName
check(getterName.isNotBlank())
code.add("%N().$GET_NAME() as %T", getterName, TYPE_VARIABLE_T)
}
code.add("))\n")
}

return FunSpec.builder(GET_PROVIDER_NAME)
code.add("}\n")
return FunSpec.builder(POPULATE_SET_NAME)
.addAnnotation(SUPPRESS_UNCHECKED_CAST)
.addModifiers(OVERRIDE)
.addTypeVariable(TYPE_VARIABLE_T)
.addParameter(WANTED_TYPE, KCLASS_OF_ANY)
.addParameter(GENERIC_ARGS, SET.parameterizedBy(KCLASS_OF_ANY))
.addParameter(SET_NAME, MUTABLE_SET.parameterizedBy(TYPE_VARIABLE_T))
.addParameter(SET_OF_TYPE, KCLASS_OF_T)
.addParameter(GENERIC_ARGS_OF_TYPE, SET.parameterizedBy(KCLASS_OF_ANY))
.addParameter(QUALIFIER, NULLABLE_KOTLIN_STRING)
.addParameter(CALLED_BY, NULLABLE_BASE_COMPONENT)
.returns(PROVIDER_OF_T_NULLABLE)
.addCode(code.build())
.build()
}

private fun addBindsFunction(
method: BinderOrProvider,
providedTypeMethodNameMap: MutableMap<TypeAndArgs, BinderOrProvider>
): FunSpec? {
providedTypeMethodNameMap[method.providedType] = method
private fun addBindsFunction(method: BinderOrProvider): FunSpec? {
val code = CodeBlock.builder()
.apply {
val fillArgumentTypes = method.fillArgumentTypes
Expand All @@ -242,11 +317,7 @@ internal class ModuleBuilder(
.build()
}

private fun addProvidesFunction(
method: BinderOrProvider,
providedTypeMethodNameMap: MutableMap<TypeAndArgs, BinderOrProvider>
): FunSpec? {
providedTypeMethodNameMap[method.providedType] = method
private fun addProvidesFunction(method: BinderOrProvider): FunSpec? {
val code = CodeBlock.builder()
.apply {
val fillArgumentTypes = method.fillArgumentTypes
Expand Down
Loading

0 comments on commit ed5b4be

Please sign in to comment.