Annotation processor Implementation
This commit is contained in:
parent
c1b8d92461
commit
edf94325d8
19 changed files with 484 additions and 0 deletions
1
annotation/.gitignore
vendored
Normal file
1
annotation/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
/build
|
||||||
12
annotation/build.gradle
Normal file
12
annotation/build.gradle
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
plugins {
|
||||||
|
id 'java-library'
|
||||||
|
id 'kotlin'
|
||||||
|
}
|
||||||
|
|
||||||
|
java {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||||
|
targetCompatibility = JavaVersion.VERSION_1_8
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceCompatibility = "8"
|
||||||
|
targetCompatibility = "8"
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
package org.fnives.library.reloadable.module.annotation
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Annotate your custom Annotation with this to apply the annotation processor.
|
||||||
|
*
|
||||||
|
* The annotation processor will generate 2 classes for you.
|
||||||
|
*
|
||||||
|
* First will be an interface Reload<YourAnnotation>Module with only one method, reload.
|
||||||
|
* This Reload<YourAnnotation>Module can be injected anywhere where you want to reload the module.
|
||||||
|
*
|
||||||
|
* Second will be a class Reload<YourAnnotation>ModuleImpl which is the actual Module implementation for Hilt.
|
||||||
|
* This provides every class which constructor you annotate with YourAnnotation.
|
||||||
|
*
|
||||||
|
* Reload in this context means, every instance will be cleared and the next time Hilt accesses it, a new will be created.
|
||||||
|
* This newly created instance is reused until the next reload call.
|
||||||
|
*/
|
||||||
|
@Target(AnnotationTarget.ANNOTATION_CLASS)
|
||||||
|
@Retention(AnnotationRetention.SOURCE)
|
||||||
|
annotation class ReloadableModule
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||||
buildscript {
|
buildscript {
|
||||||
|
ext.kotlin_version = "1.5.21"
|
||||||
|
ext.hilt_version = "2.38.1"
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
|
|
|
||||||
1
processor/.gitignore
vendored
Normal file
1
processor/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
/build
|
||||||
32
processor/build.gradle
Normal file
32
processor/build.gradle
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
apply plugin: 'java-library'
|
||||||
|
apply plugin: 'kotlin'
|
||||||
|
|
||||||
|
java {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||||
|
targetCompatibility = JavaVersion.VERSION_1_8
|
||||||
|
}
|
||||||
|
|
||||||
|
test {
|
||||||
|
useJUnitPlatform()
|
||||||
|
testLogging {
|
||||||
|
events 'started', 'passed', 'skipped', 'failed'
|
||||||
|
exceptionFormat "full"
|
||||||
|
showStandardStreams true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||||
|
implementation "com.squareup:javapoet:1.13.0"
|
||||||
|
implementation project(":annotation")
|
||||||
|
implementation "com.google.dagger:hilt-core:$hilt_version"
|
||||||
|
|
||||||
|
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:5.7.0"
|
||||||
|
testImplementation "org.junit.jupiter:junit-jupiter-engine:5.7.0"
|
||||||
|
testImplementation "com.github.tschuchortdev:kotlin-compile-testing:1.4.2"
|
||||||
|
testRuntimeOnly("org.jetbrains.kotlin:kotlin-compiler-embeddable:$kotlin_version")
|
||||||
|
testRuntimeOnly("org.jetbrains.kotlin:kotlin-annotation-processing-embeddable:$kotlin_version")
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceCompatibility = "8"
|
||||||
|
targetCompatibility = "8"
|
||||||
|
|
@ -0,0 +1,201 @@
|
||||||
|
package org.fnives.library.reloadable.module.processor
|
||||||
|
|
||||||
|
import com.squareup.javapoet.AnnotationSpec
|
||||||
|
import com.squareup.javapoet.ClassName
|
||||||
|
import com.squareup.javapoet.CodeBlock
|
||||||
|
import com.squareup.javapoet.FieldSpec
|
||||||
|
import com.squareup.javapoet.JavaFile
|
||||||
|
import com.squareup.javapoet.MethodSpec
|
||||||
|
import com.squareup.javapoet.ParameterSpec
|
||||||
|
import com.squareup.javapoet.ParameterizedTypeName
|
||||||
|
import com.squareup.javapoet.TypeName
|
||||||
|
import com.squareup.javapoet.TypeSpec
|
||||||
|
import dagger.Module
|
||||||
|
import dagger.Provides
|
||||||
|
import dagger.hilt.InstallIn
|
||||||
|
import dagger.hilt.components.SingletonComponent
|
||||||
|
import org.fnives.library.reloadable.module.processor.GenerateReloadModuleUseCase.Companion.getModuleUseCaseName
|
||||||
|
import javax.annotation.processing.Filer
|
||||||
|
import javax.annotation.processing.RoundEnvironment
|
||||||
|
import javax.inject.Provider
|
||||||
|
import javax.lang.model.element.ExecutableElement
|
||||||
|
import javax.lang.model.element.Modifier
|
||||||
|
import javax.lang.model.element.TypeElement
|
||||||
|
import javax.lang.model.util.Elements
|
||||||
|
|
||||||
|
class GenerateReloadModule(private val elementUtils: Elements, private val filer: Filer) {
|
||||||
|
|
||||||
|
fun createAndWrite(
|
||||||
|
typeElement: TypeElement,
|
||||||
|
roundEnvironment: RoundEnvironment
|
||||||
|
) {
|
||||||
|
val annotatedElements = roundEnvironment.getElementsAnnotatedWith(typeElement)
|
||||||
|
.filterIsInstance<ExecutableElement>()
|
||||||
|
val annotatedTypes = annotatedElements.map { it.enclosingElement as TypeElement }
|
||||||
|
|
||||||
|
val reloadFunctionSpec = createReloadFunctionSpec(annotatedTypes)
|
||||||
|
val cachedProperties = annotatedTypes.map(::getPropertySpec)
|
||||||
|
val provideMethods = annotatedElements.mapIndexed { index, it ->
|
||||||
|
createProvideMethodForDependency(it, annotatedTypes[index])
|
||||||
|
}
|
||||||
|
val typeSpec = createTypeSpec(typeElement, reloadFunctionSpec, provideMethods, cachedProperties)
|
||||||
|
|
||||||
|
val packageName = elementUtils.getPackageOf(typeElement).toString()
|
||||||
|
writeToSourceFile(typeSpec, packageName, filer)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun writeToSourceFile(typeSpec: TypeSpec, packageName: String, filer: Filer) {
|
||||||
|
JavaFile.builder(packageName, typeSpec).build().writeTo(filer)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getPropertySpec(typeElement: TypeElement): FieldSpec =
|
||||||
|
FieldSpec.builder(TypeName.get(typeElement.asType()), typeElement.cachedPropertyName())
|
||||||
|
.initializer("null")
|
||||||
|
.addModifiers(Modifier.PRIVATE)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
private fun createProvideMethodForDependency(
|
||||||
|
constructorElement: ExecutableElement,
|
||||||
|
dependencyType: TypeElement
|
||||||
|
): MethodSpec {
|
||||||
|
val constructorParameters = constructorElement.parameters.map { "${it.simpleName}.get()" }.joinToString(", ")
|
||||||
|
val functionElement = MethodSpec.methodBuilder("provide${dependencyType.simpleName}")
|
||||||
|
.addAnnotation(Provides::class.java)
|
||||||
|
.returns(TypeName.get(dependencyType.asType()))
|
||||||
|
.addModifiers(Modifier.PUBLIC)
|
||||||
|
.addCode(
|
||||||
|
CodeBlock.of(
|
||||||
|
"if (${dependencyType.cachedPropertyName()} == null) {\n" +
|
||||||
|
" ${dependencyType.cachedPropertyName()} = new ${"$"}T ($constructorParameters);\n" +
|
||||||
|
"}\n" +
|
||||||
|
"return ${dependencyType.cachedPropertyName()};",
|
||||||
|
dependencyType
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return constructorElement.parameters.fold(functionElement) { methodSpecBuilder, variableElement ->
|
||||||
|
val className = TypeName.get(variableElement.asType())
|
||||||
|
val providerTypeSpec = ParameterizedTypeName.get(ClassName.get(Provider::class.java), className)
|
||||||
|
|
||||||
|
methodSpecBuilder.addParameter(
|
||||||
|
ParameterSpec.builder(providerTypeSpec, variableElement.simpleName.toString())
|
||||||
|
.addAnnotations(variableElement.annotationMirrors.map { AnnotationSpec.get(it) })
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
}.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun createProvideMethodForReloadModuleUseCase(typeElement: TypeElement): MethodSpec =
|
||||||
|
MethodSpec.methodBuilder("provide${getModuleUseCaseName(typeElement)}")
|
||||||
|
.addAnnotation(Provides::class.java)
|
||||||
|
.addModifiers(Modifier.PUBLIC)
|
||||||
|
.returns(GenerateReloadModuleUseCase.getTypeName(typeElement, elementUtils))
|
||||||
|
.addCode("return this;")
|
||||||
|
.build()
|
||||||
|
|
||||||
|
private fun createTypeSpec(typeElement: TypeElement, reloadFunctionSpec: MethodSpec, provideMethods: List<MethodSpec>, cacheProperties: List<FieldSpec>) =
|
||||||
|
TypeSpec.classBuilder(getModuleUseCaseName(typeElement) + "Impl")
|
||||||
|
.addModifiers(Modifier.PUBLIC)
|
||||||
|
.addSuperinterface(GenerateReloadModuleUseCase.getTypeName(typeElement, elementUtils))
|
||||||
|
.addAnnotation(Module::class.java)
|
||||||
|
.addAnnotation(
|
||||||
|
AnnotationSpec.builder(InstallIn::class.java)
|
||||||
|
.addMember("value", "\$T.class", SingletonComponent::class.java)
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
.addFields(cacheProperties)
|
||||||
|
.addMethod(createProvideMethodForReloadModuleUseCase(typeElement))
|
||||||
|
.addMethods(provideMethods)
|
||||||
|
.addMethod(reloadFunctionSpec)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
private fun createReloadFunctionSpec(annotatedElements: List<TypeElement>): MethodSpec {
|
||||||
|
val reloadFunctionSpec = MethodSpec.methodBuilder(GenerateReloadModuleUseCase.getReloadMethodName())
|
||||||
|
.addAnnotation(Override::class.java)
|
||||||
|
.addModifiers(Modifier.PUBLIC)
|
||||||
|
return annotatedElements.fold(reloadFunctionSpec) { methodSpecBuilder, typeElement ->
|
||||||
|
methodSpecBuilder.addCode("${typeElement.cachedPropertyName()} = null;")
|
||||||
|
}.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
// private fun createImplementation(interfaceTypeSpec: TypeSpec, typeElement: TypeElement, roundEnvironment: RoundEnvironment): TypeSpec {
|
||||||
|
// val annotatedElements = roundEnvironment.getElementsAnnotatedWith(typeElement)
|
||||||
|
// .filterIsInstance<ExecutableElement>()
|
||||||
|
//
|
||||||
|
// val reloadFunctionSpec = MethodSpec.methodBuilder("reload")
|
||||||
|
// .addAnnotation(Override::class.java)
|
||||||
|
// .addModifiers(Modifier.PUBLIC)
|
||||||
|
// val moduleImplName = "Reloadable${typeElement.simpleName}Impl"
|
||||||
|
// val typeSpecBuilder = TypeSpec.classBuilder(moduleImplName)
|
||||||
|
// .addModifiers(Modifier.PUBLIC)
|
||||||
|
// .addSuperinterface(interfaceTypeSpec.asTypeName(typeElement))
|
||||||
|
// .addAnnotation(Module::class.java)
|
||||||
|
// .addAnnotation(
|
||||||
|
// AnnotationSpec.builder(InstallIn::class.java)
|
||||||
|
// .addMember("value", "\$T.class", SingletonComponent::class.java)
|
||||||
|
// .build()
|
||||||
|
// )
|
||||||
|
// .addMethod(
|
||||||
|
// MethodSpec.methodBuilder("provideReloadable${typeElement.simpleName}")
|
||||||
|
// .addAnnotation(Provides::class.java)
|
||||||
|
// .addModifiers(Modifier.PUBLIC)
|
||||||
|
// .returns(interfaceTypeSpec.asTypeName(typeElement))
|
||||||
|
// .addCode("return this;")
|
||||||
|
// .build()
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
// annotatedElements.forEach { constructorElement ->
|
||||||
|
// val annotatedType = constructorElement.enclosingElement as TypeElement
|
||||||
|
// val annotatedTypeClassName = ClassName.bestGuess(annotatedType.qualifiedName.toString())
|
||||||
|
// val memberElement = FieldSpec.builder(
|
||||||
|
// annotatedTypeClassName,
|
||||||
|
// "cached${annotatedType.simpleName}"
|
||||||
|
// )
|
||||||
|
// .initializer("null")
|
||||||
|
// .addModifiers(Modifier.PRIVATE)
|
||||||
|
// .build()
|
||||||
|
// reloadFunctionSpec.addCode("cached${annotatedType.simpleName} = null;")
|
||||||
|
// val constructorParameters = constructorElement.parameters.map { "${it.simpleName}.get()" }.joinToString(", ")
|
||||||
|
// val functionElement = MethodSpec.methodBuilder("provide${annotatedType.simpleName}")
|
||||||
|
// .addAnnotation(Provides::class.java)
|
||||||
|
// .returns(annotatedTypeClassName)
|
||||||
|
// .addModifiers(Modifier.PUBLIC)
|
||||||
|
// .addCode(
|
||||||
|
// CodeBlock.of(
|
||||||
|
// """if (cached${annotatedType.simpleName} == null) {
|
||||||
|
// cached${annotatedType.simpleName} = new ${"$"}T ($constructorParameters);
|
||||||
|
// }
|
||||||
|
// return cached${annotatedType.simpleName};
|
||||||
|
// """.trim(),
|
||||||
|
// annotatedType
|
||||||
|
// )
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
// constructorElement.parameters.forEach { variableElement ->
|
||||||
|
// val className = TypeName.get(variableElement.asType())
|
||||||
|
// val providerTypeSpec = ParameterizedTypeName.get(ClassName.get(Provider::class.java), className)
|
||||||
|
// functionElement.addParameter(
|
||||||
|
// ParameterSpec.builder(providerTypeSpec, variableElement.simpleName.toString())
|
||||||
|
// .addAnnotations(variableElement.annotationMirrors.map { AnnotationSpec.get(it) })
|
||||||
|
// .build()
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// typeSpecBuilder.addField(memberElement)
|
||||||
|
// typeSpecBuilder.addMethod(functionElement.build())
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// typeSpecBuilder.addMethod(reloadFunctionSpec.build())
|
||||||
|
//
|
||||||
|
// return typeSpecBuilder.build()
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// private fun TypeSpec.asTypeName(typeElement: TypeElement) =
|
||||||
|
// ClassName.bestGuess(elementUtils.getPackageOf(typeElement).toString() + "." + name.orEmpty())
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private fun TypeElement.cachedPropertyName() = "cached${simpleName}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
package org.fnives.library.reloadable.module.processor
|
||||||
|
|
||||||
|
import com.squareup.javapoet.ClassName
|
||||||
|
import com.squareup.javapoet.JavaFile
|
||||||
|
import com.squareup.javapoet.MethodSpec
|
||||||
|
import com.squareup.javapoet.TypeSpec
|
||||||
|
import javax.annotation.processing.Filer
|
||||||
|
import javax.lang.model.element.Modifier
|
||||||
|
import javax.lang.model.element.TypeElement
|
||||||
|
import javax.lang.model.util.Elements
|
||||||
|
|
||||||
|
class GenerateReloadModuleUseCase(private val elementUtils: Elements, private val filer: Filer) {
|
||||||
|
|
||||||
|
fun createAndWrite(typeElement: TypeElement) {
|
||||||
|
val typeSpec = createTypeSpec(getModuleUseCaseName(typeElement))
|
||||||
|
val packageName = elementUtils.getPackageOf(typeElement).toString()
|
||||||
|
writeToSourceFile(typeSpec, packageName, filer)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createTypeSpec(moduleUseCaseName: String) =
|
||||||
|
TypeSpec.interfaceBuilder(moduleUseCaseName)
|
||||||
|
.addModifiers(Modifier.PUBLIC)
|
||||||
|
.addMethod(
|
||||||
|
MethodSpec.methodBuilder(getReloadMethodName())
|
||||||
|
.addModifiers(Modifier.ABSTRACT)
|
||||||
|
.addModifiers(Modifier.PUBLIC)
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
private fun writeToSourceFile(typeSpec: TypeSpec, packageName: String, filer: Filer) {
|
||||||
|
JavaFile.builder(packageName, typeSpec).build().writeTo(filer)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun getModuleUseCaseName(typeElement: TypeElement) = "Reload${typeElement.simpleName}Module"
|
||||||
|
|
||||||
|
fun getReloadMethodName() = "reload"
|
||||||
|
|
||||||
|
fun getTypeName(typeElement: TypeElement, elementUtils: Elements) =
|
||||||
|
ClassName.bestGuess("${elementUtils.getPackageOf(typeElement)}.${getModuleUseCaseName(typeElement)}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
package org.fnives.library.reloadable.module.processor
|
||||||
|
|
||||||
|
import org.fnives.library.reloadable.module.annotation.ReloadableModule
|
||||||
|
import javax.annotation.processing.AbstractProcessor
|
||||||
|
import javax.annotation.processing.Filer
|
||||||
|
import javax.annotation.processing.Messager
|
||||||
|
import javax.annotation.processing.ProcessingEnvironment
|
||||||
|
import javax.annotation.processing.RoundEnvironment
|
||||||
|
import javax.lang.model.SourceVersion
|
||||||
|
import javax.lang.model.element.TypeElement
|
||||||
|
import javax.lang.model.util.Elements
|
||||||
|
import javax.lang.model.util.Types
|
||||||
|
|
||||||
|
class ReloadableModuleProcessor : AbstractProcessor() {
|
||||||
|
|
||||||
|
private lateinit var filer: Filer
|
||||||
|
private lateinit var messager: Messager
|
||||||
|
private lateinit var elementUtils: Elements
|
||||||
|
private lateinit var typeUtils: Types
|
||||||
|
private lateinit var generateReloadModuleUseCase: GenerateReloadModuleUseCase
|
||||||
|
private lateinit var generateReloadModule: GenerateReloadModule
|
||||||
|
private val supportedAnnotations = setOf(ReloadableModule::class.java)
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
override fun init(processingEnvironment: ProcessingEnvironment) {
|
||||||
|
super.init(processingEnvironment)
|
||||||
|
filer = processingEnvironment.filer
|
||||||
|
messager = processingEnvironment.messager
|
||||||
|
elementUtils = processingEnvironment.elementUtils
|
||||||
|
typeUtils = processingEnvironment.typeUtils
|
||||||
|
generateReloadModuleUseCase = GenerateReloadModuleUseCase(elementUtils, filer)
|
||||||
|
generateReloadModule = GenerateReloadModule(elementUtils, filer)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getSupportedAnnotationTypes(): Set<String> =
|
||||||
|
supportedAnnotations.map(Class<*>::getCanonicalName).toSet()
|
||||||
|
|
||||||
|
override fun getSupportedSourceVersion(): SourceVersion = SourceVersion.latestSupported()
|
||||||
|
|
||||||
|
override fun process(set: Set<TypeElement>, roundEnvironment: RoundEnvironment): Boolean {
|
||||||
|
if (roundEnvironment.processingOver()) return false
|
||||||
|
val annotatedElements = supportedAnnotations.flatMap(roundEnvironment::getElementsAnnotatedWith).filterIsInstance<TypeElement>()
|
||||||
|
|
||||||
|
annotatedElements.forEach {
|
||||||
|
generateReloadModuleUseCase.createAndWrite(it)
|
||||||
|
generateReloadModule.createAndWrite(it, roundEnvironment)
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
org.fnives.library.reloadable.module.processor.ReloadableModuleProcessor,isolating
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
org.fnives.library.reloadable.module.processor.ReloadableModuleProcessor
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
package org.fnives.library.reloadable.module.processor
|
||||||
|
|
||||||
|
import java.io.File
|
||||||
|
import java.nio.file.Paths
|
||||||
|
|
||||||
|
/**
|
||||||
|
+ * Helper class which read the file in the resources folder with the given [fileName] into a string, each line separated with [lineDelimiter].
|
||||||
|
+ */
|
||||||
|
fun Any.readResourceFileToString(fileName: String): String {
|
||||||
|
val path = this::class.java.classLoader.getResource(fileName).toURI().path
|
||||||
|
return File(Paths.get(path).toUri()).readText()
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
package org.fnives.library.reloadable.module.processor
|
||||||
|
|
||||||
|
import com.tschuchort.compiletesting.KotlinCompilation
|
||||||
|
import com.tschuchort.compiletesting.SourceFile
|
||||||
|
import org.junit.jupiter.api.Assertions
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
|
class TestReloadableModuleProcessor {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testSimpleSetup() {
|
||||||
|
val userModuleInput = readResourceFileToString("simple/input/UserModuleInject.kt")
|
||||||
|
val providedByUserModule = readResourceFileToString("simple/input/ProvidedByUserModule.kt")
|
||||||
|
val fooDependency = readResourceFileToString("simple/input/FooDependency.kt")
|
||||||
|
|
||||||
|
val result = KotlinCompilation().apply {
|
||||||
|
sources = listOf(
|
||||||
|
SourceFile.kotlin("UserModule.kt", userModuleInput),
|
||||||
|
SourceFile.kotlin("ProvidedByUserModule.kt", providedByUserModule),
|
||||||
|
SourceFile.kotlin("FooDependency.kt", fooDependency)
|
||||||
|
)
|
||||||
|
|
||||||
|
annotationProcessors = listOf(ReloadableModuleProcessor())
|
||||||
|
|
||||||
|
inheritClassPath = true
|
||||||
|
messageOutputStream = System.out // see diagnostics in real time
|
||||||
|
}.compile()
|
||||||
|
|
||||||
|
val generatedFiles = result.generatedFiles.toList()
|
||||||
|
val reloadableUserModule = generatedFiles[0].readText()
|
||||||
|
val reloadableUserModuleImpl = generatedFiles[1].readText()
|
||||||
|
AssertionsAssertEqualsIgnoringMultipleSpaces(
|
||||||
|
readResourceFileToString("simple/expected/ReloadUserModuleInjectModule.java"),
|
||||||
|
reloadableUserModule
|
||||||
|
)
|
||||||
|
AssertionsAssertEqualsIgnoringMultipleSpaces(
|
||||||
|
readResourceFileToString("simple/expected/ReloadUserModuleInjectModuleImpl.java"),
|
||||||
|
reloadableUserModuleImpl
|
||||||
|
)
|
||||||
|
Assertions.assertEquals(KotlinCompilation.ExitCode.OK, result.exitCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private fun String.replaceSpacesWithOnlyOne(): String = replace(" *".toRegex(), " ")
|
||||||
|
|
||||||
|
@Suppress("TestFunctionName")
|
||||||
|
private fun AssertionsAssertEqualsIgnoringMultipleSpaces(expected: String, actual: String) =
|
||||||
|
Assertions.assertEquals(expected.replaceSpacesWithOnlyOne(), actual.replaceSpacesWithOnlyOne())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
package simple.input;
|
||||||
|
|
||||||
|
public interface ReloadUserModuleInjectModule {
|
||||||
|
void reload();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
package simple.input;
|
||||||
|
|
||||||
|
import dagger.Module;
|
||||||
|
import dagger.Provides;
|
||||||
|
import dagger.hilt.InstallIn;
|
||||||
|
import dagger.hilt.components.SingletonComponent;
|
||||||
|
import java.lang.Override;
|
||||||
|
import javax.inject.Provider;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
@Module
|
||||||
|
@InstallIn(SingletonComponent.class)
|
||||||
|
public class ReloadUserModuleInjectModuleImpl implements ReloadUserModuleInjectModule {
|
||||||
|
private ProvidedByUserModule cachedProvidedByUserModule = null;
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
public ReloadUserModuleInjectModule provideReloadUserModuleInjectModule() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
public ProvidedByUserModule provideProvidedByUserModule(
|
||||||
|
@NotNull Provider<FooDependency> fooDependency) {
|
||||||
|
if (cachedProvidedByUserModule == null) {
|
||||||
|
cachedProvidedByUserModule = new ProvidedByUserModule (fooDependency.get());
|
||||||
|
}
|
||||||
|
return cachedProvidedByUserModule;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reload() {
|
||||||
|
cachedProvidedByUserModule = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
package simple.input
|
||||||
|
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class FooDependency @Inject constructor()
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
package simple.input
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull
|
||||||
|
|
||||||
|
class ProvidedByUserModule @UserModuleInject constructor(@NotNull fooDependency: FooDependency)
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
package simple.input
|
||||||
|
|
||||||
|
import org.fnives.library.reloadable.module.annotation.ReloadableModule
|
||||||
|
|
||||||
|
@ReloadableModule
|
||||||
|
annotation class UserModuleInject
|
||||||
|
|
@ -8,3 +8,5 @@ dependencyResolutionManagement {
|
||||||
}
|
}
|
||||||
rootProject.name = "HiltReloadableModule"
|
rootProject.name = "HiltReloadableModule"
|
||||||
include ':app'
|
include ':app'
|
||||||
|
include ':processor'
|
||||||
|
include ':annotation'
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue