diff --git a/build.gradle b/build.gradle index 23885a5..cacd004 100755 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. apply plugin: "org.jlleitschuh.gradle.ktlint" buildscript { - ext.kotlin_version = '1.6.0' + ext.kotlin_version = '1.7.0' repositories { google() jcenter() @@ -12,8 +12,9 @@ buildscript { } dependencies { classpath 'com.android.tools.build:gradle:7.1.2' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.10" + classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.0' classpath "org.jlleitschuh.gradle:ktlint-gradle:10.2.0" + classpath "com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin:1.7.0-1.0.6" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } diff --git a/demo/build.gradle b/demo/build.gradle index 0c2f925..8229f6a 100644 --- a/demo/build.gradle +++ b/demo/build.gradle @@ -1,8 +1,8 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' -apply plugin: 'kotlin-kapt' apply plugin: "org.jlleitschuh.gradle.ktlint" +apply plugin: "com.google.devtools.ksp" android { compileSdkVersion 31 @@ -40,9 +40,10 @@ android { } } - -kapt { - correctErrorTypes = true +kotlin { + sourceSets.all { + kotlin.srcDir("build/generated/ksp/$name/kotlin") + } } dependencies { @@ -57,6 +58,6 @@ dependencies { implementation project(path: ':demolibrary') implementation project(path: ':kokain-di') implementation project(path: ':kokain-core-api') - kapt project(path: ':kokain-processor') + ksp ksp(project(':kokain-ksp',)) implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" } diff --git a/demolibrary/build.gradle b/demolibrary/build.gradle index 3b4d132..605d363 100644 --- a/demolibrary/build.gradle +++ b/demolibrary/build.gradle @@ -1,8 +1,9 @@ apply plugin: 'com.android.library' apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' -apply plugin: 'kotlin-kapt' +//apply plugin: 'kotlin-kapt' apply plugin: "org.jlleitschuh.gradle.ktlint" +apply plugin: "com.google.devtools.ksp" android { compileSdkVersion 31 @@ -27,6 +28,12 @@ android { } +kotlin { + sourceSets.all { + kotlin.srcDir("build/generated/ksp/$name/kotlin") + } +} + dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" @@ -34,7 +41,7 @@ dependencies { implementation 'androidx.core:core-ktx:1.1.0' implementation project(path: ':kokain-di') implementation project(path: ':kokain-core-api') - kapt project(path: ':kokain-processor') + ksp ksp(project(':kokain-ksp',)) testImplementation 'junit:junit:4.12' androidTestImplementation 'androidx.test.ext:junit:1.1.1' diff --git a/kokain-generator-lib/.gitignore b/kokain-generator-lib/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/kokain-generator-lib/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/kokain-generator-lib/build.gradle b/kokain-generator-lib/build.gradle new file mode 100644 index 0000000..0f01c43 --- /dev/null +++ b/kokain-generator-lib/build.gradle @@ -0,0 +1,53 @@ +apply plugin: 'kotlin' +apply plugin: 'maven-publish' +apply plugin: 'jacoco' +apply plugin: 'pmd' +apply plugin: 'kotlin-kapt' +apply plugin: "org.jlleitschuh.gradle.ktlint" + +targetCompatibility = '1.8' +sourceCompatibility = '1.8' + +publishing { + publications { + mavenJava(MavenPublication) { + groupId = 'com.schwarz.kokain' + artifactId = 'kokain-generator-lib' + version = '0.1' + from components.java + } + } +} + + +jacoco { + toolVersion = "0.8.7" +} + +jacocoTestReport { + group 'verification' + dependsOn 'test' + + reports { + + xml.enabled = true + html.enabled = false + } +} + +compileKotlin { + kotlinOptions { + jvmTarget = "1.8" + } +} +compileTestKotlin { + kotlinOptions { + jvmTarget = "1.8" + } +} + +dependencies { + implementation 'com.squareup:kotlinpoet:1.12.0' + api project(path: ':kokain-core-api', configuration: 'default') + implementation 'org.apache.commons:commons-lang3:3.4' +} \ No newline at end of file diff --git a/kokain-processor/src/main/java/com/schwarz/kokain/processor/generation/FactoryGenerator.kt b/kokain-generator-lib/src/main/java/com/schwarz/kokain/kokaingeneratorlib/generation/FactoryGenerator.kt similarity index 82% rename from kokain-processor/src/main/java/com/schwarz/kokain/processor/generation/FactoryGenerator.kt rename to kokain-generator-lib/src/main/java/com/schwarz/kokain/kokaingeneratorlib/generation/FactoryGenerator.kt index a1dcfc1..93df7dd 100644 --- a/kokain-processor/src/main/java/com/schwarz/kokain/processor/generation/FactoryGenerator.kt +++ b/kokain-generator-lib/src/main/java/com/schwarz/kokain/kokaingeneratorlib/generation/FactoryGenerator.kt @@ -1,9 +1,9 @@ -package com.schwarz.kokain.processor.generation +package com.schwarz.kokain.kokaingeneratorlib.generation import com.schwarz.kokain.api.EBean -import com.schwarz.kokain.processor.model.EBeanModel -import com.schwarz.kokain.processor.model.EFactoryModel -import com.schwarz.kokain.processor.util.TypeUtil +import com.schwarz.kokain.kokaingeneratorlib.model.IEBeanModel +import com.schwarz.kokain.kokaingeneratorlib.model.IEFactoryModel +import com.schwarz.kokain.kokaingeneratorlib.util.TypeUtil import com.squareup.kotlinpoet.ClassName import com.squareup.kotlinpoet.FileSpec import com.squareup.kotlinpoet.FunSpec @@ -11,7 +11,6 @@ import com.squareup.kotlinpoet.KModifier import com.squareup.kotlinpoet.MemberName import com.squareup.kotlinpoet.PropertySpec import com.squareup.kotlinpoet.TypeSpec -import com.squareup.kotlinpoet.asTypeName import java.lang.StringBuilder import java.util.StringJoiner import kotlin.collections.ArrayList @@ -20,7 +19,7 @@ class FactoryGenerator { private final val ADDITIONAL_FACTORY_PROPERTY_NAME = "additonalFactories" - fun generateModel(factory: EFactoryModel, beans: List): FileSpec { + fun generateModel(factory: IEFactoryModel, beans: List): FileSpec { val typeBuilder = TypeSpec.classBuilder("GeneratedFactory").addModifiers(KModifier.PUBLIC).addSuperinterface(TypeUtil.kdiFactory()) .addProperty(propAdditionalFactories(factory)) .addFunction(create(beans)) @@ -28,16 +27,16 @@ class FactoryGenerator { return FileSpec.get(factory.`package`, typeBuilder.build()) } - private fun propAdditionalFactories(factory: EFactoryModel): PropertySpec { + private fun propAdditionalFactories(factory: IEFactoryModel): PropertySpec { val builder = StringBuilder("arrayOf<%T>") val types = ArrayList() val joiner = StringJoiner(",", "(", ")") types.add(TypeUtil.kdiFactory()) for (factory in factory.additionalFactories) { - if (factory.asTypeName() != TypeUtil.void()) { + if (factory != TypeUtil.void()) { joiner.add("%T()") - types.add(factory.asTypeName()) + types.add(factory) } } builder.append(joiner) @@ -45,7 +44,7 @@ class FactoryGenerator { return PropertySpec.builder(ADDITIONAL_FACTORY_PROPERTY_NAME, TypeUtil.arrayKdiFactories()).initializer(builder.toString(), *types.toTypedArray()).build() } - private fun create(beans: List): FunSpec { + private fun create(beans: List): FunSpec { var builder = FunSpec.builder("createInstance").addModifiers(KModifier.PUBLIC, KModifier.OVERRIDE).addParameter("clazz", TypeUtil.classStar()).returns(TypeUtil.any().copy(true)) diff --git a/kokain-processor/src/main/java/com/schwarz/kokain/processor/generation/ShadowBeanGenerator.kt b/kokain-generator-lib/src/main/java/com/schwarz/kokain/kokaingeneratorlib/generation/ShadowBeanGenerator.kt similarity index 82% rename from kokain-processor/src/main/java/com/schwarz/kokain/processor/generation/ShadowBeanGenerator.kt rename to kokain-generator-lib/src/main/java/com/schwarz/kokain/kokaingeneratorlib/generation/ShadowBeanGenerator.kt index 10c4842..b23d78b 100644 --- a/kokain-processor/src/main/java/com/schwarz/kokain/processor/generation/ShadowBeanGenerator.kt +++ b/kokain-generator-lib/src/main/java/com/schwarz/kokain/kokaingeneratorlib/generation/ShadowBeanGenerator.kt @@ -1,7 +1,7 @@ -package com.schwarz.kokain.processor.generation +package com.schwarz.kokain.kokaingeneratorlib.generation -import com.schwarz.kokain.processor.model.EBeanModel -import com.schwarz.kokain.processor.util.TypeUtil +import com.schwarz.kokain.kokaingeneratorlib.model.IEBeanModel +import com.schwarz.kokain.kokaingeneratorlib.util.TypeUtil import com.squareup.kotlinpoet.ClassName import com.squareup.kotlinpoet.FileSpec import com.squareup.kotlinpoet.FunSpec @@ -11,7 +11,7 @@ import com.squareup.kotlinpoet.TypeSpec class ShadowBeanGenerator { - fun generateModel(holder: EBeanModel): FileSpec { + fun generateModel(holder: IEBeanModel): FileSpec { val typeBuilder = TypeSpec.classBuilder(holder.generatedClazzSimpleName).addModifiers(holder.classVisibility).superclass( ClassName(holder.`package`, holder.sourceClazzSimpleName) ).addSuperinterface(TypeUtil.activityRefered()).addSuperinterface(TypeUtil.beanScope()) diff --git a/kokain-generator-lib/src/main/java/com/schwarz/kokain/kokaingeneratorlib/model/IEBeanModel.kt b/kokain-generator-lib/src/main/java/com/schwarz/kokain/kokaingeneratorlib/model/IEBeanModel.kt new file mode 100644 index 0000000..3e5b181 --- /dev/null +++ b/kokain-generator-lib/src/main/java/com/schwarz/kokain/kokaingeneratorlib/model/IEBeanModel.kt @@ -0,0 +1,23 @@ +package com.schwarz.kokain.kokaingeneratorlib.model + +import com.schwarz.kokain.api.EBean +import com.squareup.kotlinpoet.ClassName +import com.squareup.kotlinpoet.KModifier +import com.squareup.kotlinpoet.TypeName + +interface IEBeanModel { + + val scope: EBean.Scope + + val sourceClazzSimpleName: String + + val generatedClazzSimpleName: String + get() = sourceClazzSimpleName + "Shadow" + + val `package`: String + + val generatedClazzTypeName: TypeName + get() = ClassName(`package`, generatedClazzSimpleName) + + val classVisibility: KModifier +} diff --git a/kokain-generator-lib/src/main/java/com/schwarz/kokain/kokaingeneratorlib/model/IEFactoryModel.kt b/kokain-generator-lib/src/main/java/com/schwarz/kokain/kokaingeneratorlib/model/IEFactoryModel.kt new file mode 100644 index 0000000..1ced172 --- /dev/null +++ b/kokain-generator-lib/src/main/java/com/schwarz/kokain/kokaingeneratorlib/model/IEFactoryModel.kt @@ -0,0 +1,19 @@ +package com.schwarz.kokain.kokaingeneratorlib.model + +import com.squareup.kotlinpoet.ClassName +import com.squareup.kotlinpoet.TypeName + +interface IEFactoryModel { + + val additionalFactories: List + + val sourceClazzSimpleName: String + + val generatedClazzSimpleName: String + get() = sourceClazzSimpleName + "Shadow" + + val `package`: String + + val generatedClazzTypeName: TypeName + get() = ClassName(`package`, generatedClazzSimpleName) +} diff --git a/kokain-processor/src/main/java/com/schwarz/kokain/processor/util/TypeUtil.kt b/kokain-generator-lib/src/main/java/com/schwarz/kokain/kokaingeneratorlib/util/TypeUtil.kt similarity index 83% rename from kokain-processor/src/main/java/com/schwarz/kokain/processor/util/TypeUtil.kt rename to kokain-generator-lib/src/main/java/com/schwarz/kokain/kokaingeneratorlib/util/TypeUtil.kt index b59576c..46da71a 100644 --- a/kokain-processor/src/main/java/com/schwarz/kokain/processor/util/TypeUtil.kt +++ b/kokain-generator-lib/src/main/java/com/schwarz/kokain/kokaingeneratorlib/util/TypeUtil.kt @@ -1,11 +1,10 @@ -package com.schwarz.kokain.processor.util +package com.schwarz.kokain.kokaingeneratorlib.util import com.squareup.kotlinpoet.ClassName import com.squareup.kotlinpoet.ParameterizedTypeName import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy import com.squareup.kotlinpoet.TypeName import com.squareup.kotlinpoet.WildcardTypeName -import javax.lang.model.type.TypeMirror object TypeUtil { @@ -83,16 +82,6 @@ object TypeUtil { return ClassName("kotlin", "Array").parameterizedBy(kdiFactory()) } - fun getSimpleName(type: TypeMirror): String { - val parts = type.toString().split("\\.".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() - return if (parts.size > 1) parts[parts.size - 1] else parts[0] - } - - fun getPackage(type: TypeMirror): String { - val lastIndexOf = type.toString().lastIndexOf(".") - return if (lastIndexOf >= 0) type.toString().substring(0, lastIndexOf) else type.toString() - } - fun classStar(): ParameterizedTypeName { return ClassName("kotlin.reflect", "KClass").parameterizedBy(star()) } diff --git a/kokain-ksp/.gitignore b/kokain-ksp/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/kokain-ksp/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/kokain-ksp/build.gradle b/kokain-ksp/build.gradle new file mode 100644 index 0000000..09433b4 --- /dev/null +++ b/kokain-ksp/build.gradle @@ -0,0 +1,59 @@ +apply plugin: 'kotlin' +apply plugin: 'maven-publish' +apply plugin: 'jacoco' +apply plugin: 'pmd' +apply plugin: 'kotlin-kapt' +apply plugin: "org.jlleitschuh.gradle.ktlint" + +targetCompatibility = '1.8' +sourceCompatibility = '1.8' + +publishing { + publications { + mavenJava(MavenPublication) { + groupId = 'com.schwarz.kokain' + artifactId = 'kokain-ksp' + version = '0.1' + from components.java + } + } +} + + +jacoco { + toolVersion = "0.8.7" +} + +jacocoTestReport { + group 'verification' + dependsOn 'test' + + reports { + + xml.enabled = true + html.enabled = false + } +} + +compileKotlin { + kotlinOptions { + jvmTarget = "1.8" + } +} +compileTestKotlin { + kotlinOptions { + jvmTarget = "1.8" + } +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" + implementation 'com.squareup:kotlinpoet:1.12.0' + implementation("com.squareup:kotlinpoet-ksp:1.12.0") + implementation("com.google.devtools.ksp:symbol-processing-api:1.6.21-1.0.6") + testImplementation 'junit:junit:4.12' + testImplementation project(path: ':kokain-core-api', configuration: 'default') + testImplementation 'com.github.tschuchortdev:kotlin-compile-testing-ksp:1.4.9' + testImplementation 'org.mockito:mockito-core:1.10.19' + implementation project(path: ':kokain-generator-lib', configuration: 'default') +} \ No newline at end of file diff --git a/kokain-ksp/src/main/java/com/schwarz/kokain/ksp/KokainProcessor.kt b/kokain-ksp/src/main/java/com/schwarz/kokain/ksp/KokainProcessor.kt new file mode 100644 index 0000000..0320899 --- /dev/null +++ b/kokain-ksp/src/main/java/com/schwarz/kokain/ksp/KokainProcessor.kt @@ -0,0 +1,54 @@ +package com.schwarz.kokain.ksp + +import com.google.devtools.ksp.processing.CodeGenerator +import com.google.devtools.ksp.processing.KSPLogger +import com.google.devtools.ksp.processing.Resolver +import com.google.devtools.ksp.processing.SymbolProcessor +import com.google.devtools.ksp.processing.SymbolProcessorEnvironment +import com.google.devtools.ksp.processing.SymbolProcessorProvider +import com.google.devtools.ksp.symbol.KSAnnotated +import com.schwarz.kokain.api.EBean +import com.schwarz.kokain.api.EFactory +import com.schwarz.kokain.kokaingeneratorlib.generation.FactoryGenerator +import com.schwarz.kokain.kokaingeneratorlib.generation.ShadowBeanGenerator +import com.schwarz.kokain.ksp.factory.EBeanModelFactory +import com.schwarz.kokain.ksp.factory.EFactoryModelFactory +import com.schwarz.kokain.ksp.generation.KokainCodeGenerator + +class KokainProcessor(codeGenerator: CodeGenerator, val logger: KSPLogger) : SymbolProcessor { + + private val shadowBeanGenerator = ShadowBeanGenerator() + + private val factoryGenerator = FactoryGenerator() + + private val codeGenerator = KokainCodeGenerator(codeGenerator) + + override fun process(resolver: Resolver): List { + val eBeanModelFactory = EBeanModelFactory(logger, resolver) + val eFactoryModelFactory = EFactoryModelFactory(logger, resolver) + + var beanModel = resolver.getSymbolsWithAnnotation(EBean::class.qualifiedName!!) + .mapNotNull { eBeanModelFactory.create(it) }.toList() + + var factory = resolver.getSymbolsWithAnnotation(EFactory::class.qualifiedName!!).firstOrNull()?.let { + eFactoryModelFactory.create(it) + } + + beanModel.forEach { + codeGenerator.generate(shadowBeanGenerator.generateModel(it)) + } + + factory?.let { + codeGenerator.generate(factoryGenerator.generateModel(it, beanModel)) + } + return emptyList() + } +} + +class KokainProcessorProvider : SymbolProcessorProvider { + override fun create( + environment: SymbolProcessorEnvironment + ): SymbolProcessor { + return KokainProcessor(environment.codeGenerator, environment.logger) + } +} diff --git a/kokain-ksp/src/main/java/com/schwarz/kokain/ksp/factory/EBeanModelFactory.kt b/kokain-ksp/src/main/java/com/schwarz/kokain/ksp/factory/EBeanModelFactory.kt new file mode 100644 index 0000000..6b9fdc8 --- /dev/null +++ b/kokain-ksp/src/main/java/com/schwarz/kokain/ksp/factory/EBeanModelFactory.kt @@ -0,0 +1,36 @@ +package com.schwarz.kokain.ksp.factory + +import com.google.devtools.ksp.KspExperimental +import com.google.devtools.ksp.getAnnotationsByType +import com.google.devtools.ksp.getVisibility +import com.google.devtools.ksp.processing.KSPLogger +import com.google.devtools.ksp.processing.Resolver +import com.google.devtools.ksp.symbol.KSAnnotated +import com.google.devtools.ksp.symbol.KSClassDeclaration +import com.google.devtools.ksp.symbol.Visibility +import com.schwarz.kokain.api.EBean +import com.schwarz.kokain.ksp.model.EBeanModel +import com.schwarz.kokain.ksp.validation.PreValidator +import com.squareup.kotlinpoet.KModifier + +class EBeanModelFactory(val logger: KSPLogger, resolver: Resolver) { + + private val preValidator = PreValidator(logger, resolver) + + @OptIn(KspExperimental::class) + fun create(element: KSAnnotated): EBeanModel? { + (element as? KSClassDeclaration)?.let { + if (preValidator.validateEbean(it)) { + val scope = it.getAnnotationsByType(EBean::class).first().scope + val simpleName = it.simpleName.asString() + val sPackage = it.packageName.asString() + val visibility = when (it.getVisibility()) { + Visibility.INTERNAL -> KModifier.INTERNAL + else -> KModifier.PUBLIC + } + return EBeanModel(scope, simpleName, sPackage, visibility) + } + } ?: logger.error("failed to process EBean annotation not a class file", element) + return null + } +} diff --git a/kokain-ksp/src/main/java/com/schwarz/kokain/ksp/factory/EFactoryModelFactory.kt b/kokain-ksp/src/main/java/com/schwarz/kokain/ksp/factory/EFactoryModelFactory.kt new file mode 100644 index 0000000..fec8b13 --- /dev/null +++ b/kokain-ksp/src/main/java/com/schwarz/kokain/ksp/factory/EFactoryModelFactory.kt @@ -0,0 +1,35 @@ +package com.schwarz.kokain.ksp.factory + +import com.google.devtools.ksp.KspExperimental +import com.google.devtools.ksp.processing.KSPLogger +import com.google.devtools.ksp.processing.Resolver +import com.google.devtools.ksp.symbol.KSAnnotated +import com.google.devtools.ksp.symbol.KSClassDeclaration +import com.google.devtools.ksp.symbol.KSType +import com.schwarz.kokain.api.EFactory +import com.schwarz.kokain.ksp.model.EFactoryModel +import com.schwarz.kokain.ksp.util.getKSAnnotationsByType +import com.schwarz.kokain.ksp.util.getKSValueArgumentByName +import com.schwarz.kokain.ksp.validation.PreValidator +import com.squareup.kotlinpoet.ksp.toTypeName + +class EFactoryModelFactory(val logger: KSPLogger, resolver: Resolver) { + + private val preValidator = PreValidator(logger, resolver) + + @OptIn(KspExperimental::class) + fun create(element: KSAnnotated): EFactoryModel? { + (element as? KSClassDeclaration)?.let { + if (preValidator.validateFactory(it)) { + val additionalFactories = element.getKSAnnotationsByType(EFactory::class) + .first() + .getKSValueArgumentByName("additionalFactories")?.value as List + + val simpleName = element.simpleName.asString() + val sPackage = element.packageName.asString() + return EFactoryModel(additionalFactories.map { it.toTypeName() }, simpleName, sPackage) + } + } ?: logger.error("failed to process EFactory annotation not a class file", element) + return null + } +} diff --git a/kokain-ksp/src/main/java/com/schwarz/kokain/ksp/generation/KokainCodeGenerator.kt b/kokain-ksp/src/main/java/com/schwarz/kokain/ksp/generation/KokainCodeGenerator.kt new file mode 100644 index 0000000..3aca965 --- /dev/null +++ b/kokain-ksp/src/main/java/com/schwarz/kokain/ksp/generation/KokainCodeGenerator.kt @@ -0,0 +1,26 @@ +package com.schwarz.kokain.ksp.generation + +import com.google.devtools.ksp.processing.CodeGenerator +import com.squareup.kotlinpoet.FileSpec +import com.squareup.kotlinpoet.ksp.writeTo +import java.io.IOException + +class KokainCodeGenerator(private val generator: CodeGenerator) { + + @Throws(IOException::class) + fun generate(entityToGenerate: FileSpec) { + + val fileWithHeader = entityToGenerate.toBuilder().addFileComment(HEADER).build() + + fileWithHeader.writeTo(generator, false) + } + + companion object { + + private val HEADER = ( + "DO NOT EDIT THIS FILE.\n" + + "Generated using Kokain KSP \n\n" + + "Do not edit this class!!!!.\n" + ) + } +} diff --git a/kokain-ksp/src/main/java/com/schwarz/kokain/ksp/model/EBeanModel.kt b/kokain-ksp/src/main/java/com/schwarz/kokain/ksp/model/EBeanModel.kt new file mode 100644 index 0000000..65d7b5a --- /dev/null +++ b/kokain-ksp/src/main/java/com/schwarz/kokain/ksp/model/EBeanModel.kt @@ -0,0 +1,12 @@ +package com.schwarz.kokain.ksp.model + +import com.schwarz.kokain.api.EBean +import com.schwarz.kokain.kokaingeneratorlib.model.IEBeanModel +import com.squareup.kotlinpoet.KModifier + +data class EBeanModel( + override val scope: EBean.Scope, + override val sourceClazzSimpleName: String, + override val `package`: String, + override val classVisibility: KModifier +) : IEBeanModel diff --git a/kokain-ksp/src/main/java/com/schwarz/kokain/ksp/model/EFactoryModel.kt b/kokain-ksp/src/main/java/com/schwarz/kokain/ksp/model/EFactoryModel.kt new file mode 100644 index 0000000..f6182a2 --- /dev/null +++ b/kokain-ksp/src/main/java/com/schwarz/kokain/ksp/model/EFactoryModel.kt @@ -0,0 +1,10 @@ +package com.schwarz.kokain.ksp.model + +import com.schwarz.kokain.kokaingeneratorlib.model.IEFactoryModel +import com.squareup.kotlinpoet.TypeName + +data class EFactoryModel( + override val additionalFactories: List, + override val sourceClazzSimpleName: String, + override val `package`: String +) : IEFactoryModel diff --git a/kokain-ksp/src/main/java/com/schwarz/kokain/ksp/util/KspUtil.kt b/kokain-ksp/src/main/java/com/schwarz/kokain/ksp/util/KspUtil.kt new file mode 100644 index 0000000..6060ab0 --- /dev/null +++ b/kokain-ksp/src/main/java/com/schwarz/kokain/ksp/util/KspUtil.kt @@ -0,0 +1,17 @@ +package com.schwarz.kokain.ksp.util + +import com.google.devtools.ksp.symbol.KSAnnotated +import com.google.devtools.ksp.symbol.KSAnnotation +import com.google.devtools.ksp.symbol.KSValueArgument +import kotlin.reflect.KClass + +fun KSAnnotated.getKSAnnotationsByType(annotationKClass: KClass<*>): Sequence { + return this.annotations.filter { + it.shortName.getShortName() == annotationKClass.simpleName && it.annotationType.resolve().declaration + .qualifiedName?.asString() == annotationKClass.qualifiedName + } +} + +fun KSAnnotation.getKSValueArgumentByName(name: String): KSValueArgument? { + return this.arguments.firstOrNull { it.name?.getShortName() == name } +} diff --git a/kokain-ksp/src/main/java/com/schwarz/kokain/ksp/validation/PreValidator.kt b/kokain-ksp/src/main/java/com/schwarz/kokain/ksp/validation/PreValidator.kt new file mode 100644 index 0000000..ad0a557 --- /dev/null +++ b/kokain-ksp/src/main/java/com/schwarz/kokain/ksp/validation/PreValidator.kt @@ -0,0 +1,80 @@ +package com.schwarz.kokain.ksp.validation + +import com.google.devtools.ksp.KspExperimental +import com.google.devtools.ksp.getJavaClassByName +import com.google.devtools.ksp.getKotlinClassByName +import com.google.devtools.ksp.processing.KSPLogger +import com.google.devtools.ksp.processing.Resolver +import com.google.devtools.ksp.symbol.KSClassDeclaration +import com.google.devtools.ksp.symbol.KSType +import com.google.devtools.ksp.symbol.Modifier +import com.schwarz.kokain.api.EBean +import com.schwarz.kokain.api.EFactory +import com.schwarz.kokain.api.KDiFactory +import com.schwarz.kokain.ksp.util.getKSAnnotationsByType +import com.schwarz.kokain.ksp.util.getKSValueArgumentByName +import com.squareup.kotlinpoet.ksp.toTypeName + +class PreValidator(logger: KSPLogger, val resolver: Resolver) { + + private val logger = logger + + @Throws(ClassNotFoundException::class) + fun validateEbean(model: KSClassDeclaration): Boolean { + + val entityElement = model + var result = true + + if (entityElement.modifiers.contains(Modifier.PRIVATE)) { + logger.error(EBean::class.java.simpleName + " can not be private", entityElement) + result = false + } + if (entityElement.modifiers.contains(Modifier.PROTECTED)) { + logger.error(EBean::class.java.simpleName + " can not be protected", entityElement) + result = false + } + if (entityElement.modifiers.contains(Modifier.FINAL)) { + logger.error(EBean::class.java.simpleName + " can not be final", entityElement) + result = false + } + + entityElement.primaryConstructor?.let { + if (it.parameters.isNotEmpty()) { + logger.error(EBean::class.java.simpleName + " should not have a contructor", it) + result = false + } + } + return result + } + + @KspExperimental + @Throws(ClassNotFoundException::class) + fun validateFactory(model: KSClassDeclaration): Boolean { + + var result = true + val additionalFactories = model.getKSAnnotationsByType(EFactory::class) + .first() + .getKSValueArgumentByName("additionalFactories")?.value as List + + val kdiFactoryKsType = resolver.getKotlinClassByName(KDiFactory::class.qualifiedName!!)?.asType( + emptyList() + ) + val voidKsType = resolver.getJavaClassByName("java.lang.Void")?.asType(listOf()) + + additionalFactories.forEach { + + val containsKdiFactorySupertype: Boolean = (it.declaration as? KSClassDeclaration)?.let { + it.superTypes.any { it.toTypeName() == kdiFactoryKsType!!.toTypeName() } + } == true + + if (containsKdiFactorySupertype.not() && it != voidKsType!!) { + logger.error( + EFactory::class.java.simpleName + " additionalFactories have to implement KdiFactory", + model + ) + result = false + } + } + return result + } +} diff --git a/kokain-ksp/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider b/kokain-ksp/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider new file mode 100644 index 0000000..54fe981 --- /dev/null +++ b/kokain-ksp/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider @@ -0,0 +1 @@ +com.schwarz.kokain.ksp.KokainProcessorProvider \ No newline at end of file diff --git a/kokain-ksp/src/test/java/com/schwarz/kokain/ksp/KokainProcessorKotlinTest.kt b/kokain-ksp/src/test/java/com/schwarz/kokain/ksp/KokainProcessorKotlinTest.kt new file mode 100644 index 0000000..6fb779b --- /dev/null +++ b/kokain-ksp/src/test/java/com/schwarz/kokain/ksp/KokainProcessorKotlinTest.kt @@ -0,0 +1,62 @@ +package com.schwarz.kokain.ksp + +import com.tschuchort.compiletesting.KotlinCompilation +import com.tschuchort.compiletesting.SourceFile +import com.tschuchort.compiletesting.symbolProcessorProviders +import org.junit.Assert +import org.junit.Test + +class KokainProcessorKotlinTest { + + @Test + fun testKotlinBasicGeneration() { + + val subEntity = SourceFile.kotlin( + "FooBean.kt", + HEADER + + "@EBean\n" + + "@EFactory\n" + + "open class FooBean {\n" + + "}" + ) + + val compilation = compileKotlin(subEntity) + + Assert.assertEquals(compilation.exitCode, KotlinCompilation.ExitCode.OK) + } + + @Test + fun testKotlinInternalBasicGeneration() { + + val subEntity = SourceFile.kotlin( + "FooBean.kt", + HEADER + + "@EBean\n" + + "@EFactory\n" + + "open internal class FooBean {\n" + + "}" + ) + + val compilation = compileKotlin(subEntity) + + Assert.assertEquals(compilation.exitCode, KotlinCompilation.ExitCode.OK) + } + + private fun compileKotlin(vararg sourceFiles: SourceFile): KotlinCompilation.Result { + return KotlinCompilation().apply { + sources = sourceFiles.toMutableList() + symbolProcessorProviders = listOf(KokainProcessorProvider()) + + inheritClassPath = true + messageOutputStream = System.out // see diagnostics in real time + }.compile() + } + + companion object { + const val HEADER: String = + "package com.kaufland.test\n" + + "\n" + + "import com.schwarz.kokain.api.EBean\n" + + "import com.schwarz.kokain.api.EFactory\n" + } +} diff --git a/kokain-processor/build.gradle b/kokain-processor/build.gradle index 99e0d33..937ad34 100644 --- a/kokain-processor/build.gradle +++ b/kokain-processor/build.gradle @@ -7,9 +7,9 @@ apply plugin: "org.jlleitschuh.gradle.ktlint" dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation 'com.squareup:kotlinpoet:1.6.0' + implementation 'com.squareup:kotlinpoet:1.12.0' implementation "org.jetbrains.kotlinx:kotlinx-metadata-jvm:0.3.0" - implementation project(path: ':kokain-core-api', configuration: 'default') + implementation project(path: ':kokain-generator-lib', configuration: 'default') implementation 'org.apache.commons:commons-lang3:3.4' testImplementation 'junit:junit:4.12' testImplementation project(path: ':kokain-core-api', configuration: 'default') diff --git a/kokain-processor/src/main/java/com/schwarz/kokain/processor/KokainProcessor.kt b/kokain-processor/src/main/java/com/schwarz/kokain/processor/KokainProcessor.kt index 9bbedeb..953d9f8 100644 --- a/kokain-processor/src/main/java/com/schwarz/kokain/processor/KokainProcessor.kt +++ b/kokain-processor/src/main/java/com/schwarz/kokain/processor/KokainProcessor.kt @@ -3,9 +3,9 @@ package com.schwarz.kokain.processor import com.google.auto.service.AutoService import com.schwarz.kokain.api.EBean import com.schwarz.kokain.api.EFactory +import com.schwarz.kokain.kokaingeneratorlib.generation.FactoryGenerator +import com.schwarz.kokain.kokaingeneratorlib.generation.ShadowBeanGenerator import com.schwarz.kokain.processor.generation.CodeGenerator -import com.schwarz.kokain.processor.generation.FactoryGenerator -import com.schwarz.kokain.processor.generation.ShadowBeanGenerator import com.schwarz.kokain.processor.model.EBeanModel import com.schwarz.kokain.processor.model.EFactoryModel import com.schwarz.kokain.processor.validation.PreValidator diff --git a/kokain-processor/src/main/java/com/schwarz/kokain/processor/generation/CodeGenerator.kt b/kokain-processor/src/main/java/com/schwarz/kokain/processor/generation/CodeGenerator.kt index c336174..2e3a6a6 100644 --- a/kokain-processor/src/main/java/com/schwarz/kokain/processor/generation/CodeGenerator.kt +++ b/kokain-processor/src/main/java/com/schwarz/kokain/processor/generation/CodeGenerator.kt @@ -9,7 +9,7 @@ class CodeGenerator(private val filer: Filer) { @Throws(IOException::class) fun generate(entityToGenerate: FileSpec) { - val fileWithHeader = entityToGenerate.toBuilder().addComment(HEADER).build() + val fileWithHeader = entityToGenerate.toBuilder().addFileComment(HEADER).build() fileWithHeader.writeTo(filer) } diff --git a/kokain-processor/src/main/java/com/schwarz/kokain/processor/model/EBeanModel.kt b/kokain-processor/src/main/java/com/schwarz/kokain/processor/model/EBeanModel.kt index 1cbe629..8a05991 100644 --- a/kokain-processor/src/main/java/com/schwarz/kokain/processor/model/EBeanModel.kt +++ b/kokain-processor/src/main/java/com/schwarz/kokain/processor/model/EBeanModel.kt @@ -1,6 +1,7 @@ package com.schwarz.kokain.processor.model import com.schwarz.kokain.api.EBean +import com.schwarz.kokain.kokaingeneratorlib.model.IEBeanModel import com.squareup.kotlinpoet.ClassName import com.squareup.kotlinpoet.KModifier import com.squareup.kotlinpoet.TypeName @@ -11,7 +12,7 @@ import kotlinx.metadata.jvm.KotlinClassHeader import kotlinx.metadata.jvm.KotlinClassMetadata import javax.lang.model.element.Element -class EBeanModel(scope: EBean.Scope, sourceElement: Element) { +class EBeanModel(scope: EBean.Scope, sourceElement: Element) : IEBeanModel { val kotlinClassMetadata: KmClass? @@ -24,23 +25,20 @@ class EBeanModel(scope: EBean.Scope, sourceElement: Element) { } } - val scope: EBean.Scope = scope + override val scope: EBean.Scope = scope val sourceElement: Element = sourceElement - val sourceClazzSimpleName: String + override val sourceClazzSimpleName: String get() = (sourceElement as Symbol.ClassSymbol).simpleName.toString() - val generatedClazzSimpleName: String - get() = sourceClazzSimpleName + "Shadow" - - val `package`: String + override val `package`: String get() = (sourceElement as Symbol.ClassSymbol).packge().toString() - val generatedClazzTypeName: TypeName + override val generatedClazzTypeName: TypeName get() = ClassName(`package`, generatedClazzSimpleName) - val classVisibility: KModifier + override val classVisibility: KModifier get() { // we are not interested in other visibilities since they're not injectable kotlinClassMetadata?.let { diff --git a/kokain-processor/src/main/java/com/schwarz/kokain/processor/model/EFactoryModel.kt b/kokain-processor/src/main/java/com/schwarz/kokain/processor/model/EFactoryModel.kt index 36bb392..d6842ed 100644 --- a/kokain-processor/src/main/java/com/schwarz/kokain/processor/model/EFactoryModel.kt +++ b/kokain-processor/src/main/java/com/schwarz/kokain/processor/model/EFactoryModel.kt @@ -1,38 +1,32 @@ package com.schwarz.kokain.processor.model import com.schwarz.kokain.api.EFactory -import com.squareup.kotlinpoet.ClassName +import com.schwarz.kokain.kokaingeneratorlib.model.IEFactoryModel import com.squareup.kotlinpoet.TypeName +import com.squareup.kotlinpoet.asTypeName import com.sun.tools.javac.code.Symbol import javax.lang.model.element.Element import javax.lang.model.type.MirroredTypesException import javax.lang.model.type.TypeMirror import javax.lang.model.util.Elements -class EFactoryModel(sourceElement: Element, util: Elements) { +class EFactoryModel(sourceElement: Element, util: Elements) : IEFactoryModel { - var additionalFactories: Array + var additionalFactoriesTypeMirror: Array = sourceElement.getAnnotation(EFactory::class.java).let { + try { + it.additionalFactories.map { util.getTypeElement(it.qualifiedName).asType() } + .toTypedArray() // this should throw + } catch (mte: MirroredTypesException) { + mte.typeMirrors.toTypedArray() + } + } val sourceElement: Element? = sourceElement + override val additionalFactories: List = additionalFactoriesTypeMirror.map { it.asTypeName() }.toList() - val sourceClazzSimpleName: String + override val sourceClazzSimpleName: String get() = (sourceElement as Symbol.ClassSymbol).simpleName.toString() - val generatedClazzSimpleName: String - get() = sourceClazzSimpleName + "Shadow" - - val `package`: String + override val `package`: String get() = (sourceElement as Symbol.ClassSymbol).packge().toString() - - val generatedClazzTypeName: TypeName - get() = ClassName(`package`, generatedClazzSimpleName) - - init { - val annotation = sourceElement.getAnnotation(EFactory::class.java) - try { - additionalFactories = annotation.additionalFactories.map { util.getTypeElement(it.qualifiedName).asType() }.toTypedArray() // this should throw - } catch (mte: MirroredTypesException) { - additionalFactories = mte.typeMirrors.toTypedArray() - } - } } diff --git a/kokain-processor/src/main/java/com/schwarz/kokain/processor/validation/PreValidator.kt b/kokain-processor/src/main/java/com/schwarz/kokain/processor/validation/PreValidator.kt index 11c537e..b1726a3 100644 --- a/kokain-processor/src/main/java/com/schwarz/kokain/processor/validation/PreValidator.kt +++ b/kokain-processor/src/main/java/com/schwarz/kokain/processor/validation/PreValidator.kt @@ -2,10 +2,10 @@ package com.schwarz.kokain.processor.validation import com.schwarz.kokain.api.EBean import com.schwarz.kokain.api.EFactory +import com.schwarz.kokain.kokaingeneratorlib.util.TypeUtil import com.schwarz.kokain.processor.Logger import com.schwarz.kokain.processor.model.EBeanModel import com.schwarz.kokain.processor.model.EFactoryModel -import com.schwarz.kokain.processor.util.TypeUtil import com.sun.tools.javac.code.Symbol import javax.lang.model.element.ElementKind import javax.lang.model.element.Modifier @@ -51,7 +51,7 @@ class PreValidator(logger: Logger, types: Types, elements: Elements) { @Throws(ClassNotFoundException::class) fun validateFactory(model: EFactoryModel) { - for (factory in model.additionalFactories) { + for (factory in model.additionalFactoriesTypeMirror) { if (!types.isSameType(factory, elements.getTypeElement("java.lang.Void").asType()) && !types.isAssignable(factory, elements.getTypeElement("${TypeUtil.KOKAIN_API_BASE_PACKAGE}.KDiFactory").asType())) { logger.error(EFactory::class.java.simpleName + " additionalFactories have to implement KdiFactory", model.sourceElement) diff --git a/settings.gradle b/settings.gradle index 06489bd..05306df 100755 --- a/settings.gradle +++ b/settings.gradle @@ -1,3 +1,5 @@ include ':kokain-core-api', ':kokain-processor', ':demo', ':kokain-di', ':demolibrary' include ':kokain-core-lib' include ':kokain-di-jvm' +include ':kokain-generator-lib' +include ':kokain-ksp'