From 878526a10dc908f7b1dd9c6951eecf28c703eca4 Mon Sep 17 00:00:00 2001 From: qumn Date: Sun, 16 Jun 2024 23:05:42 +0800 Subject: [PATCH 1/7] feat: Implement initial version of SuperTableClass annotation --- .../ktorm/ksp/annotation/SuperTableClass.kt | 20 ++ .../compiler/generator/TableClassGenerator.kt | 6 +- .../ksp/compiler/parser/MetadataParser.kt | 57 ++++- .../ktorm/ksp/compiler/util/KspExtensions.kt | 25 ++ .../org/ktorm/ksp/compiler/BaseKspTest.kt | 5 +- .../compiler/generator/SuperTableClassTest.kt | 224 ++++++++++++++++++ .../kotlin/org/ktorm/ksp/spi/TableMetadata.kt | 8 +- 7 files changed, 335 insertions(+), 10 deletions(-) create mode 100644 ktorm-ksp-annotations/src/main/kotlin/org/ktorm/ksp/annotation/SuperTableClass.kt create mode 100644 ktorm-ksp-compiler/src/test/kotlin/org/ktorm/ksp/compiler/generator/SuperTableClassTest.kt diff --git a/ktorm-ksp-annotations/src/main/kotlin/org/ktorm/ksp/annotation/SuperTableClass.kt b/ktorm-ksp-annotations/src/main/kotlin/org/ktorm/ksp/annotation/SuperTableClass.kt new file mode 100644 index 00000000..eb956d66 --- /dev/null +++ b/ktorm-ksp-annotations/src/main/kotlin/org/ktorm/ksp/annotation/SuperTableClass.kt @@ -0,0 +1,20 @@ +package org.ktorm.ksp.annotation + +import org.ktorm.schema.BaseTable +import kotlin.reflect.KClass + +/** + * Be used on Entity interface, to specify the super table class for the table class generated. + * @property value the super table class. + * if not specified, the super table class will be determined by the kind of the entity class. + * `org.ktorm.schema.Table` for interface, `org.ktorm.schema.BaseTable` for class. + * + * if there are multiple `SuperTableClass` on the inheritance hierarchy of entity, + * and they have an inheritance relationship, the super table class will be the last one of them. + * and they don't have an inheritance relationship, an error will be reported. + */ +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.SOURCE) +public annotation class SuperTableClass( + public val value: KClass> +) diff --git a/ktorm-ksp-compiler/src/main/kotlin/org/ktorm/ksp/compiler/generator/TableClassGenerator.kt b/ktorm-ksp-compiler/src/main/kotlin/org/ktorm/ksp/compiler/generator/TableClassGenerator.kt index 98609c1e..a2eb8b1b 100644 --- a/ktorm-ksp-compiler/src/main/kotlin/org/ktorm/ksp/compiler/generator/TableClassGenerator.kt +++ b/ktorm-ksp-compiler/src/main/kotlin/org/ktorm/ksp/compiler/generator/TableClassGenerator.kt @@ -49,11 +49,7 @@ internal object TableClassGenerator { } private fun TypeSpec.Builder.configureSuperClass(table: TableMetadata): TypeSpec.Builder { - if (table.entityClass.classKind == ClassKind.INTERFACE) { - superclass(Table::class.asClassName().parameterizedBy(table.entityClass.toClassName())) - } else { - superclass(BaseTable::class.asClassName().parameterizedBy(table.entityClass.toClassName())) - } + superclass(table.superClass.parameterizedBy(table.entityClass.toClassName())) addSuperclassConstructorParameter("%S", table.name) addSuperclassConstructorParameter("alias") diff --git a/ktorm-ksp-compiler/src/main/kotlin/org/ktorm/ksp/compiler/parser/MetadataParser.kt b/ktorm-ksp-compiler/src/main/kotlin/org/ktorm/ksp/compiler/parser/MetadataParser.kt index 55b94489..20f96e17 100644 --- a/ktorm-ksp-compiler/src/main/kotlin/org/ktorm/ksp/compiler/parser/MetadataParser.kt +++ b/ktorm-ksp-compiler/src/main/kotlin/org/ktorm/ksp/compiler/parser/MetadataParser.kt @@ -29,6 +29,10 @@ import org.ktorm.ksp.spi.ColumnMetadata import org.ktorm.ksp.spi.DatabaseNamingStrategy import org.ktorm.ksp.spi.TableMetadata import org.ktorm.schema.TypeReference +import com.squareup.kotlinpoet.ClassName +import com.squareup.kotlinpoet.asClassName +import com.squareup.kotlinpoet.ksp.KotlinPoetKspPreview +import com.squareup.kotlinpoet.ksp.toClassName import java.lang.reflect.InvocationTargetException import java.util.* import kotlin.reflect.jvm.jvmName @@ -93,6 +97,9 @@ internal class MetadataParser(resolver: Resolver, environment: SymbolProcessorEn _logger.info("[ktorm-ksp-compiler] parse table metadata from entity: $className") val table = cls.getAnnotationsByType(Table::class).first() + val (superClass, superTableClasses) = parseSuperTableClass(cls) + val allPropertyNamesOfSuperTables = superTableClasses.flatMap { it.getProperties(emptySet()) }.map { it.simpleName.asString() } + val tableMetadata = TableMetadata( entityClass = cls, name = table.name.ifEmpty { _databaseNamingStrategy.getTableName(cls) }, @@ -101,8 +108,9 @@ internal class MetadataParser(resolver: Resolver, environment: SymbolProcessorEn schema = table.schema.ifEmpty { _options["ktorm.schema"] }?.takeIf { it.isNotEmpty() }, tableClassName = table.className.ifEmpty { _codingNamingStrategy.getTableClassName(cls) }, entitySequenceName = table.entitySequenceName.ifEmpty { _codingNamingStrategy.getEntitySequenceName(cls) }, - ignoreProperties = table.ignoreProperties.toSet(), - columns = ArrayList() + ignoreProperties = table.ignoreProperties.toSet() + allPropertyNamesOfSuperTables, // ignore properties of super tables + columns = ArrayList(), + superClass = superClass ) val columns = tableMetadata.columns as MutableList @@ -274,6 +282,51 @@ internal class MetadataParser(resolver: Resolver, environment: SymbolProcessorEn ) } + /** + * @return the super table class and all class be annotated with [SuperTableClass] in the inheritance hierarchy. + */ + @OptIn(KotlinPoetKspPreview::class) + private fun parseSuperTableClass(cls: KSClassDeclaration): Pair> { + val superTableClassAnnPair = cls.findAllAnnotationsInInheritanceHierarchy(SuperTableClass::class.qualifiedName!!) + + // if there is no SuperTableClass annotation, return the default super table class based on the class kind. + if (superTableClassAnnPair.isEmpty()) { + return if (cls.classKind == INTERFACE) { + org.ktorm.schema.Table::class.asClassName() to emptySet() + } else { + org.ktorm.schema.BaseTable::class.asClassName() to emptySet() + } + } + + // SuperTableClass annotation can only be used on interface + if (superTableClassAnnPair.map { it.first }.any { it.classKind != INTERFACE }) { + val msg = "SuperTableClass annotation can only be used on interface." + throw IllegalArgumentException(msg) + } + + // find the last annotation in the inheritance hierarchy + val superTableClasses = superTableClassAnnPair + .map { it.second } + .map { it.arguments.single { it.name?.asString() == SuperTableClass::value.name } } + .map { it.value as KSType } + .map { it.declaration as KSClassDeclaration } + + var lowestSubClass = superTableClasses.first() + for (i in 1 until superTableClasses.size) { + val cur = superTableClasses[i] + if (cur.isSubclassOf(lowestSubClass)) { + lowestSubClass = cur + } else if (!lowestSubClass.isSubclassOf(cur)) { + val msg = + "There are multiple SuperTableClass annotations in the inheritance hierarchy of class ${cls.qualifiedName?.asString()}," + + "but the values of annotation are not in the same inheritance hierarchy." + throw IllegalArgumentException(msg) + } + } + //TODO: to check All constructor parameters owned by `BaseTable` should also be owned by lowestSubClass. + return lowestSubClass.toClassName() to superTableClasses.toSet() + } + private fun TableMetadata.checkCircularRef(ref: KSClassDeclaration, stack: LinkedList = LinkedList()) { val className = this.entityClass.qualifiedName?.asString() val refClassName = ref.qualifiedName?.asString() diff --git a/ktorm-ksp-compiler/src/main/kotlin/org/ktorm/ksp/compiler/util/KspExtensions.kt b/ktorm-ksp-compiler/src/main/kotlin/org/ktorm/ksp/compiler/util/KspExtensions.kt index fc96866a..5581624c 100644 --- a/ktorm-ksp-compiler/src/main/kotlin/org/ktorm/ksp/compiler/util/KspExtensions.kt +++ b/ktorm-ksp-compiler/src/main/kotlin/org/ktorm/ksp/compiler/util/KspExtensions.kt @@ -56,6 +56,13 @@ internal inline fun KSClassDeclaration.isSubclassOf(): Boolean return findSuperTypeReference(T::class.qualifiedName!!) != null } +/** + * Check if this class is a subclass of declaration. + */ +internal fun KSClassDeclaration.isSubclassOf(declaration: KSClassDeclaration): Boolean { + return findSuperTypeReference(declaration.qualifiedName!!.asString()) != null +} + /** * Find the specific super type reference for this class. */ @@ -76,6 +83,24 @@ internal fun KSClassDeclaration.findSuperTypeReference(name: String): KSTypeRefe return null } +/** + * Find all annotations with the given name in the inheritance hierarchy of this class. + * + * @param name the qualified name of the annotation. + */ +internal fun KSClassDeclaration.findAllAnnotationsInInheritanceHierarchy(name: String): List> { + val rst = mutableListOf>() + + fun KSClassDeclaration.collectAnnotations() { + rst += annotations.filter { it.annotationType.resolve().declaration.qualifiedName?.asString() == name }.map { this to it } + superTypes.forEach { (it.resolve().declaration as KSClassDeclaration).collectAnnotations() } + } + + collectAnnotations() + return rst +} + + /** * Check if the given symbol is valid. */ diff --git a/ktorm-ksp-compiler/src/test/kotlin/org/ktorm/ksp/compiler/BaseKspTest.kt b/ktorm-ksp-compiler/src/test/kotlin/org/ktorm/ksp/compiler/BaseKspTest.kt index 5b882e88..c47dc6eb 100644 --- a/ktorm-ksp-compiler/src/test/kotlin/org/ktorm/ksp/compiler/BaseKspTest.kt +++ b/ktorm-ksp-compiler/src/test/kotlin/org/ktorm/ksp/compiler/BaseKspTest.kt @@ -61,7 +61,7 @@ abstract class BaseKspTest { private fun compile(@Language("kotlin") code: String, options: Map): KotlinCompilation.Result { @Language("kotlin") - val header = """ + val source = """ import java.math.* import java.sql.* import java.time.* @@ -73,12 +73,13 @@ abstract class BaseKspTest { import org.ktorm.entity.* import org.ktorm.ksp.annotation.* + $code + lateinit var database: Database """.trimIndent() - val source = header + code printFile(source, "Source.kt") val compilation = createCompilation(SourceFile.kotlin("Source.kt", source), options) diff --git a/ktorm-ksp-compiler/src/test/kotlin/org/ktorm/ksp/compiler/generator/SuperTableClassTest.kt b/ktorm-ksp-compiler/src/test/kotlin/org/ktorm/ksp/compiler/generator/SuperTableClassTest.kt new file mode 100644 index 00000000..e4463d1c --- /dev/null +++ b/ktorm-ksp-compiler/src/test/kotlin/org/ktorm/ksp/compiler/generator/SuperTableClassTest.kt @@ -0,0 +1,224 @@ +package org.ktorm.ksp.compiler.generator; + +import org.junit.Test +import org.ktorm.ksp.compiler.BaseKspTest + + +class SuperTableClassTest : BaseKspTest() { + + @Test + fun defaultSuperTableClass() = runKotlin( + """ + import kotlin.reflect.full.isSubclassOf + + @Table + interface User : Entity { + var id: Int + var name: String + } + + @Table + data class Department( + val id: Int, + val name: String + ) + + fun run() { + assert(Users::class.isSubclassOf(org.ktorm.schema.Table::class)) + assert(Departments::class.isSubclassOf( org.ktorm.schema.BaseTable::class)) + assert(!Departments::class.isSubclassOf(org.ktorm.schema.Table::class)) + } + """.trimIndent() + ) + + @Test + fun superTableClassOnClass() = kspFailing( + "SuperTableClass annotation can only be used on interface.", """ + abstract class UserBaseTable>( + tableName: String, + alias: String? = null, + catalog: String? = null, + schema: String? = null, + entityClass: KClass? = null, + ) : org.ktorm.schema.Table( + tableName, alias, catalog, schema, entityClass + ) + + @Table + @SuperTableClass(UserBaseTable::class) + data class Department( + val id: Int, + val name: String + ) + """.trimIndent() + ) + + @Test + fun directUseSuperTableClass() = runKotlin( + """ + import kotlin.reflect.full.isSubclassOf + + abstract class UserBaseTable>( + tableName: String, + alias: String? = null, + catalog: String? = null, + schema: String? = null, + entityClass: KClass? = null, + ) : org.ktorm.schema.Table( + tableName, alias, catalog, schema, entityClass + ){ + fun foo() = 0 + fun bar() = 1 + } + + @Table + @SuperTableClass(UserBaseTable::class) + interface User : Entity { + var id: Int + var name: String + } + + fun run() { + assert(Users::class.isSubclassOf(UserBaseTable::class)) + } + """.trimIndent() + ) + + @Test + fun singleSuperTableClass() = runKotlin( + """ + import kotlin.reflect.full.isSubclassOf + import org.ktorm.schema.long + + @JvmInline + value class UID(val value: Long) + + @SuperTableClass(UserBaseTable::class) + interface UserBaseEntity> : Entity { + var uid: UID + } + + abstract class UserBaseTable>( + tableName: String, + alias: String? = null, + catalog: String? = null, + schema: String? = null, + entityClass: KClass? = null, + ) : org.ktorm.schema.Table( + tableName, alias, catalog, schema, entityClass + ) { + val uid = long("uid").transform({ UID(it) }, { it.value }).primaryKey().bindTo { it.uid } + } + + @Table + interface User : UserBaseEntity { + var name: String + } + + fun run() { + assert(Users::class.isSubclassOf(UserBaseTable::class)) + } + """.trimIndent() + ) + + @Test + fun multipleSuperClassTable() = runKotlin( + """ + import org.ktorm.schema.int + import kotlin.reflect.full.isSubclassOf + + @SuperTableClass(ATable::class) + interface AEntity> : Entity { + var a: Int + } + abstract class ATable>( + tableName: String, + alias: String? = null, + catalog: String? = null, + schema: String? = null, + entityClass: KClass? = null, + ) : org.ktorm.schema.Table( + tableName, alias, catalog, schema, entityClass + ) { + val a = int("a").bindTo { it.a } + } + + @SuperTableClass(BTable::class) + interface BEntity> : AEntity { + var b: Int + } + abstract class BTable>( + tableName: String, + alias: String? = null, + catalog: String? = null, + schema: String? = null, + entityClass: KClass? = null, + ) : ATable( + tableName, alias, catalog, schema, entityClass + ) { + val b = int("b").bindTo { it.b } + } + + @Table(className = "CTable") + interface CEntity : BEntity { + var c: Int + } + + fun run() { + assert(CTable::class.isSubclassOf(ATable::class)) + assert(CTable::class.isSubclassOf(BTable::class)) + } + """.trimIndent() + ) + + @Test + fun multipleSuperClassButHaveNotSameInheritanceHierarchy() = kspFailing( + "the values of annotation are not in the same inheritance hierarchy.", """ + import org.ktorm.schema.int + import kotlin.reflect.full.isSubclassOf + + @SuperTableClass(ATable::class) + interface AEntity> : Entity { + var a: Int + } + abstract class ATable>( + tableName: String, + alias: String? = null, + catalog: String? = null, + schema: String? = null, + entityClass: KClass? = null, + ) : org.ktorm.schema.Table( + tableName, alias, catalog, schema, entityClass + ) { + val a = int("a").bindTo { it.a } + } + + @SuperTableClass(BTable::class) + interface BEntity> : Entity { + var b: Int + } + abstract class BTable>( + tableName: String, + alias: String? = null, + catalog: String? = null, + schema: String? = null, + entityClass: KClass? = null, + ) : org.ktorm.schema.Table( + tableName, alias, catalog, schema, entityClass + ) { + val b = int("b").bindTo { it.b } + } + + @Table(className = "CTable") + interface CEntity : AEntity, BEntity { + var c: Int + } + + fun run() { + assert(CTable::class.isSubclassOf(ATable::class)) + } + """.trimIndent() + ) + +} + diff --git a/ktorm-ksp-spi/src/main/kotlin/org/ktorm/ksp/spi/TableMetadata.kt b/ktorm-ksp-spi/src/main/kotlin/org/ktorm/ksp/spi/TableMetadata.kt index 25ed0b15..f43744a5 100644 --- a/ktorm-ksp-spi/src/main/kotlin/org/ktorm/ksp/spi/TableMetadata.kt +++ b/ktorm-ksp-spi/src/main/kotlin/org/ktorm/ksp/spi/TableMetadata.kt @@ -17,6 +17,7 @@ package org.ktorm.ksp.spi import com.google.devtools.ksp.symbol.KSClassDeclaration +import com.squareup.kotlinpoet.ClassName /** * Table definition metadata. @@ -66,5 +67,10 @@ public data class TableMetadata( /** * Columns in the table. */ - val columns: List + val columns: List, + + /** + * the super table class of generated table class. + */ + val superClass: ClassName ) From 1be3b091bdc8b437a521eed27912a39c5fc9f3d4 Mon Sep 17 00:00:00 2001 From: qumn Date: Mon, 17 Jun 2024 08:52:03 +0800 Subject: [PATCH 2/7] refactor(SuperTableClass): rename --- .../ksp/compiler/parser/MetadataParser.kt | 19 ++++++++++--------- .../ktorm/ksp/compiler/util/KspExtensions.kt | 9 +++++---- .../compiler/generator/SuperTableClassTest.kt | 4 ---- 3 files changed, 15 insertions(+), 17 deletions(-) diff --git a/ktorm-ksp-compiler/src/main/kotlin/org/ktorm/ksp/compiler/parser/MetadataParser.kt b/ktorm-ksp-compiler/src/main/kotlin/org/ktorm/ksp/compiler/parser/MetadataParser.kt index 20f96e17..40c320b3 100644 --- a/ktorm-ksp-compiler/src/main/kotlin/org/ktorm/ksp/compiler/parser/MetadataParser.kt +++ b/ktorm-ksp-compiler/src/main/kotlin/org/ktorm/ksp/compiler/parser/MetadataParser.kt @@ -97,8 +97,9 @@ internal class MetadataParser(resolver: Resolver, environment: SymbolProcessorEn _logger.info("[ktorm-ksp-compiler] parse table metadata from entity: $className") val table = cls.getAnnotationsByType(Table::class).first() - val (superClass, superTableClasses) = parseSuperTableClass(cls) - val allPropertyNamesOfSuperTables = superTableClasses.flatMap { it.getProperties(emptySet()) }.map { it.simpleName.asString() } + + val (finalSuperClass, allSuperTableClasses) = parseSuperTableClass(cls) + val shouldIgnorePropertyNames = allSuperTableClasses.flatMap { it.getProperties(emptySet()) }.map { it.simpleName.asString() } val tableMetadata = TableMetadata( entityClass = cls, @@ -108,9 +109,9 @@ internal class MetadataParser(resolver: Resolver, environment: SymbolProcessorEn schema = table.schema.ifEmpty { _options["ktorm.schema"] }?.takeIf { it.isNotEmpty() }, tableClassName = table.className.ifEmpty { _codingNamingStrategy.getTableClassName(cls) }, entitySequenceName = table.entitySequenceName.ifEmpty { _codingNamingStrategy.getEntitySequenceName(cls) }, - ignoreProperties = table.ignoreProperties.toSet() + allPropertyNamesOfSuperTables, // ignore properties of super tables + ignoreProperties = table.ignoreProperties.toSet() + shouldIgnorePropertyNames, columns = ArrayList(), - superClass = superClass + superClass = finalSuperClass ) val columns = tableMetadata.columns as MutableList @@ -283,14 +284,14 @@ internal class MetadataParser(resolver: Resolver, environment: SymbolProcessorEn } /** - * @return the super table class and all class be annotated with [SuperTableClass] in the inheritance hierarchy. + * @return the final super table class and all super table classes in the inheritance hierarchy. */ @OptIn(KotlinPoetKspPreview::class) private fun parseSuperTableClass(cls: KSClassDeclaration): Pair> { - val superTableClassAnnPair = cls.findAllAnnotationsInInheritanceHierarchy(SuperTableClass::class.qualifiedName!!) + val entityAnnPairs = cls.findAnnotationsInHierarchy(SuperTableClass::class.qualifiedName!!) // if there is no SuperTableClass annotation, return the default super table class based on the class kind. - if (superTableClassAnnPair.isEmpty()) { + if (entityAnnPairs.isEmpty()) { return if (cls.classKind == INTERFACE) { org.ktorm.schema.Table::class.asClassName() to emptySet() } else { @@ -299,13 +300,13 @@ internal class MetadataParser(resolver: Resolver, environment: SymbolProcessorEn } // SuperTableClass annotation can only be used on interface - if (superTableClassAnnPair.map { it.first }.any { it.classKind != INTERFACE }) { + if (entityAnnPairs.map { it.first }.any { it.classKind != INTERFACE }) { val msg = "SuperTableClass annotation can only be used on interface." throw IllegalArgumentException(msg) } // find the last annotation in the inheritance hierarchy - val superTableClasses = superTableClassAnnPair + val superTableClasses = entityAnnPairs .map { it.second } .map { it.arguments.single { it.name?.asString() == SuperTableClass::value.name } } .map { it.value as KSType } diff --git a/ktorm-ksp-compiler/src/main/kotlin/org/ktorm/ksp/compiler/util/KspExtensions.kt b/ktorm-ksp-compiler/src/main/kotlin/org/ktorm/ksp/compiler/util/KspExtensions.kt index 5581624c..c5ad8187 100644 --- a/ktorm-ksp-compiler/src/main/kotlin/org/ktorm/ksp/compiler/util/KspExtensions.kt +++ b/ktorm-ksp-compiler/src/main/kotlin/org/ktorm/ksp/compiler/util/KspExtensions.kt @@ -87,17 +87,18 @@ internal fun KSClassDeclaration.findSuperTypeReference(name: String): KSTypeRefe * Find all annotations with the given name in the inheritance hierarchy of this class. * * @param name the qualified name of the annotation. + * @return a list of pairs, each pair contains the class declaration and the annotation. */ -internal fun KSClassDeclaration.findAllAnnotationsInInheritanceHierarchy(name: String): List> { - val rst = mutableListOf>() +internal fun KSClassDeclaration.findAnnotationsInHierarchy(name: String): List> { + val pairs = mutableListOf>() fun KSClassDeclaration.collectAnnotations() { - rst += annotations.filter { it.annotationType.resolve().declaration.qualifiedName?.asString() == name }.map { this to it } + pairs += annotations.filter { it.annotationType.resolve().declaration.qualifiedName?.asString() == name }.map { this to it } superTypes.forEach { (it.resolve().declaration as KSClassDeclaration).collectAnnotations() } } collectAnnotations() - return rst + return pairs } diff --git a/ktorm-ksp-compiler/src/test/kotlin/org/ktorm/ksp/compiler/generator/SuperTableClassTest.kt b/ktorm-ksp-compiler/src/test/kotlin/org/ktorm/ksp/compiler/generator/SuperTableClassTest.kt index e4463d1c..767f6fbd 100644 --- a/ktorm-ksp-compiler/src/test/kotlin/org/ktorm/ksp/compiler/generator/SuperTableClassTest.kt +++ b/ktorm-ksp-compiler/src/test/kotlin/org/ktorm/ksp/compiler/generator/SuperTableClassTest.kt @@ -213,10 +213,6 @@ class SuperTableClassTest : BaseKspTest() { interface CEntity : AEntity, BEntity { var c: Int } - - fun run() { - assert(CTable::class.isSubclassOf(ATable::class)) - } """.trimIndent() ) From 5f63c74db81eb3b4003c1d6fe910401f44b154fd Mon Sep 17 00:00:00 2001 From: qumn Date: Mon, 17 Jun 2024 10:44:34 +0800 Subject: [PATCH 3/7] test(SuperTableClass): add test case --- .../ktorm/ksp/annotation/SuperTableClass.kt | 6 +- .../ksp/compiler/parser/MetadataParser.kt | 2 +- .../org/ktorm/ksp/compiler/BaseKspTest.kt | 6 ++ .../compiler/generator/SuperTableClassTest.kt | 55 ++++++++++++++++++- 4 files changed, 65 insertions(+), 4 deletions(-) diff --git a/ktorm-ksp-annotations/src/main/kotlin/org/ktorm/ksp/annotation/SuperTableClass.kt b/ktorm-ksp-annotations/src/main/kotlin/org/ktorm/ksp/annotation/SuperTableClass.kt index eb956d66..c19718f3 100644 --- a/ktorm-ksp-annotations/src/main/kotlin/org/ktorm/ksp/annotation/SuperTableClass.kt +++ b/ktorm-ksp-annotations/src/main/kotlin/org/ktorm/ksp/annotation/SuperTableClass.kt @@ -5,11 +5,13 @@ import kotlin.reflect.KClass /** * Be used on Entity interface, to specify the super table class for the table class generated. - * @property value the super table class. + * @property value the super table class, which should be a subclass of `org.ktorm.schema.BaseTable` + * the `tableName` and `alias` parameters be required on primary constructor, other parameters are optional. + * * if not specified, the super table class will be determined by the kind of the entity class. * `org.ktorm.schema.Table` for interface, `org.ktorm.schema.BaseTable` for class. * - * if there are multiple `SuperTableClass` on the inheritance hierarchy of entity, + * If there are multiple `SuperTableClass` on the inheritance hierarchy of entity, * and they have an inheritance relationship, the super table class will be the last one of them. * and they don't have an inheritance relationship, an error will be reported. */ diff --git a/ktorm-ksp-compiler/src/main/kotlin/org/ktorm/ksp/compiler/parser/MetadataParser.kt b/ktorm-ksp-compiler/src/main/kotlin/org/ktorm/ksp/compiler/parser/MetadataParser.kt index 40c320b3..c5baee10 100644 --- a/ktorm-ksp-compiler/src/main/kotlin/org/ktorm/ksp/compiler/parser/MetadataParser.kt +++ b/ktorm-ksp-compiler/src/main/kotlin/org/ktorm/ksp/compiler/parser/MetadataParser.kt @@ -324,7 +324,7 @@ internal class MetadataParser(resolver: Resolver, environment: SymbolProcessorEn throw IllegalArgumentException(msg) } } - //TODO: to check All constructor parameters owned by `BaseTable` should also be owned by lowestSubClass. + return lowestSubClass.toClassName() to superTableClasses.toSet() } diff --git a/ktorm-ksp-compiler/src/test/kotlin/org/ktorm/ksp/compiler/BaseKspTest.kt b/ktorm-ksp-compiler/src/test/kotlin/org/ktorm/ksp/compiler/BaseKspTest.kt index c47dc6eb..d7b062a1 100644 --- a/ktorm-ksp-compiler/src/test/kotlin/org/ktorm/ksp/compiler/BaseKspTest.kt +++ b/ktorm-ksp-compiler/src/test/kotlin/org/ktorm/ksp/compiler/BaseKspTest.kt @@ -39,6 +39,12 @@ abstract class BaseKspTest { } } + protected fun compileFailing(message: String, @Language("kotlin") code: String, vararg options: Pair) { + val result = compile(code, mapOf(*options)) + assert(result.exitCode == KotlinCompilation.ExitCode.COMPILATION_ERROR) + assert(result.messages.contains(message)) + } + protected fun kspFailing(message: String, @Language("kotlin") code: String, vararg options: Pair) { val result = compile(code, mapOf(*options)) assert(result.exitCode == KotlinCompilation.ExitCode.COMPILATION_ERROR) diff --git a/ktorm-ksp-compiler/src/test/kotlin/org/ktorm/ksp/compiler/generator/SuperTableClassTest.kt b/ktorm-ksp-compiler/src/test/kotlin/org/ktorm/ksp/compiler/generator/SuperTableClassTest.kt index 767f6fbd..38de6c7f 100644 --- a/ktorm-ksp-compiler/src/test/kotlin/org/ktorm/ksp/compiler/generator/SuperTableClassTest.kt +++ b/ktorm-ksp-compiler/src/test/kotlin/org/ktorm/ksp/compiler/generator/SuperTableClassTest.kt @@ -46,7 +46,7 @@ class SuperTableClassTest : BaseKspTest() { @Table @SuperTableClass(UserBaseTable::class) - data class Department( + data class User( val id: Int, val name: String ) @@ -216,5 +216,58 @@ class SuperTableClassTest : BaseKspTest() { """.trimIndent() ) + @Test + fun minimumSuperTableClassParameter() = runKotlin( + """ + import kotlin.reflect.full.isSubclassOf + + abstract class UserBaseTable>( + tableName: String, + alias: String? = null, + ) : org.ktorm.schema.Table( + tableName, alias + ) + + @Table + @SuperTableClass(UserBaseTable::class) + interface User : Entity { + var id: Int + var name: String + } + + fun run() { + assert(Users::class.isSubclassOf(UserBaseTable::class)) + } + """.trimIndent() + ) + + @Test + fun lackAliasParameter() = compileFailing( + "Too many arguments for public constructor", """ + abstract class UserBaseTable>(tableName: String) : org.ktorm.schema.Table(tableName) + + @Table + @SuperTableClass(UserBaseTable::class) + interface User : Entity { + var id: Int + var name: String + } + """.trimIndent() + ) + + @Test + fun lackTableNameParameter() = compileFailing( + "Too many arguments for public constructor", """ + abstract class UserBaseTable>(alias: String?) : org.ktorm.schema.Table(alias = alias) + + @Table + @SuperTableClass(UserBaseTable::class) + interface User : Entity { + var id: Int + var name: String + } + """.trimIndent() + ) + } From 264ea1cb9970806aa925a5697344326640aa0505 Mon Sep 17 00:00:00 2001 From: qumn Date: Mon, 17 Jun 2024 11:10:59 +0800 Subject: [PATCH 4/7] chore: validate the constructor of super table class --- .../ksp/compiler/parser/MetadataParser.kt | 45 +++++++++++++++---- .../org/ktorm/ksp/compiler/BaseKspTest.kt | 6 --- .../compiler/generator/SuperTableClassTest.kt | 22 +++++++-- 3 files changed, 54 insertions(+), 19 deletions(-) diff --git a/ktorm-ksp-compiler/src/main/kotlin/org/ktorm/ksp/compiler/parser/MetadataParser.kt b/ktorm-ksp-compiler/src/main/kotlin/org/ktorm/ksp/compiler/parser/MetadataParser.kt index c5baee10..5481ccb6 100644 --- a/ktorm-ksp-compiler/src/main/kotlin/org/ktorm/ksp/compiler/parser/MetadataParser.kt +++ b/ktorm-ksp-compiler/src/main/kotlin/org/ktorm/ksp/compiler/parser/MetadataParser.kt @@ -21,6 +21,10 @@ import com.google.devtools.ksp.processing.Resolver import com.google.devtools.ksp.processing.SymbolProcessorEnvironment import com.google.devtools.ksp.symbol.* import com.google.devtools.ksp.symbol.ClassKind.* +import com.squareup.kotlinpoet.ClassName +import com.squareup.kotlinpoet.asClassName +import com.squareup.kotlinpoet.ksp.KotlinPoetKspPreview +import com.squareup.kotlinpoet.ksp.toClassName import org.ktorm.entity.Entity import org.ktorm.ksp.annotation.* import org.ktorm.ksp.compiler.util.* @@ -29,10 +33,6 @@ import org.ktorm.ksp.spi.ColumnMetadata import org.ktorm.ksp.spi.DatabaseNamingStrategy import org.ktorm.ksp.spi.TableMetadata import org.ktorm.schema.TypeReference -import com.squareup.kotlinpoet.ClassName -import com.squareup.kotlinpoet.asClassName -import com.squareup.kotlinpoet.ksp.KotlinPoetKspPreview -import com.squareup.kotlinpoet.ksp.toClassName import java.lang.reflect.InvocationTargetException import java.util.* import kotlin.reflect.jvm.jvmName @@ -99,7 +99,8 @@ internal class MetadataParser(resolver: Resolver, environment: SymbolProcessorEn val table = cls.getAnnotationsByType(Table::class).first() val (finalSuperClass, allSuperTableClasses) = parseSuperTableClass(cls) - val shouldIgnorePropertyNames = allSuperTableClasses.flatMap { it.getProperties(emptySet()) }.map { it.simpleName.asString() } + val shouldIgnorePropertyNames = + allSuperTableClasses.flatMap { it.getProperties(emptySet()) }.map { it.simpleName.asString() } val tableMetadata = TableMetadata( entityClass = cls, @@ -205,8 +206,8 @@ internal class MetadataParser(resolver: Resolver, environment: SymbolProcessorEn if (!hasConstructor) { val msg = "" + - "Parse sqlType error for property $propName: the sqlType should be a Kotlin singleton object or " + - "a normal class with a constructor that accepts a single org.ktorm.schema.TypeReference argument." + "Parse sqlType error for property $propName: the sqlType should be a Kotlin singleton object or " + + "a normal class with a constructor that accepts a single org.ktorm.schema.TypeReference argument." throw IllegalArgumentException(msg) } } @@ -305,13 +306,25 @@ internal class MetadataParser(resolver: Resolver, environment: SymbolProcessorEn throw IllegalArgumentException(msg) } - // find the last annotation in the inheritance hierarchy val superTableClasses = entityAnnPairs .map { it.second } .map { it.arguments.single { it.name?.asString() == SuperTableClass::value.name } } .map { it.value as KSType } .map { it.declaration as KSClassDeclaration } + val lowestSubClass = findLowestSubClass(superTableClasses, cls) + + validateSuperTableClassConstructor(lowestSubClass) + + return lowestSubClass.toClassName() to superTableClasses.toSet() + } + + + // find the last annotation in the inheritance hierarchy + private fun findLowestSubClass( + superTableClasses: List, + cls: KSClassDeclaration, + ): KSClassDeclaration { var lowestSubClass = superTableClasses.first() for (i in 1 until superTableClasses.size) { val cur = superTableClasses[i] @@ -324,8 +337,22 @@ internal class MetadataParser(resolver: Resolver, environment: SymbolProcessorEn throw IllegalArgumentException(msg) } } + return lowestSubClass + } - return lowestSubClass.toClassName() to superTableClasses.toSet() + private fun validateSuperTableClassConstructor(lowestSubClass: KSClassDeclaration) { + // validate the primary constructor of the super table class + if (lowestSubClass.primaryConstructor == null) { + val msg = + "The super table class ${lowestSubClass.qualifiedName?.asString()} should have a primary constructor." + throw IllegalArgumentException(msg) + } + val parameters = lowestSubClass.primaryConstructor!!.parameters + if (parameters.size < 2 || parameters[0].name!!.asString() != "tableName" || parameters[1].name!!.asString() != "alias") { + val msg = + "The super table class ${lowestSubClass.qualifiedName?.asString()} should have a primary constructor with parameters tableName and alias." + throw IllegalArgumentException(msg) + } } private fun TableMetadata.checkCircularRef(ref: KSClassDeclaration, stack: LinkedList = LinkedList()) { diff --git a/ktorm-ksp-compiler/src/test/kotlin/org/ktorm/ksp/compiler/BaseKspTest.kt b/ktorm-ksp-compiler/src/test/kotlin/org/ktorm/ksp/compiler/BaseKspTest.kt index d7b062a1..c47dc6eb 100644 --- a/ktorm-ksp-compiler/src/test/kotlin/org/ktorm/ksp/compiler/BaseKspTest.kt +++ b/ktorm-ksp-compiler/src/test/kotlin/org/ktorm/ksp/compiler/BaseKspTest.kt @@ -39,12 +39,6 @@ abstract class BaseKspTest { } } - protected fun compileFailing(message: String, @Language("kotlin") code: String, vararg options: Pair) { - val result = compile(code, mapOf(*options)) - assert(result.exitCode == KotlinCompilation.ExitCode.COMPILATION_ERROR) - assert(result.messages.contains(message)) - } - protected fun kspFailing(message: String, @Language("kotlin") code: String, vararg options: Pair) { val result = compile(code, mapOf(*options)) assert(result.exitCode == KotlinCompilation.ExitCode.COMPILATION_ERROR) diff --git a/ktorm-ksp-compiler/src/test/kotlin/org/ktorm/ksp/compiler/generator/SuperTableClassTest.kt b/ktorm-ksp-compiler/src/test/kotlin/org/ktorm/ksp/compiler/generator/SuperTableClassTest.kt index 38de6c7f..964f26bf 100644 --- a/ktorm-ksp-compiler/src/test/kotlin/org/ktorm/ksp/compiler/generator/SuperTableClassTest.kt +++ b/ktorm-ksp-compiler/src/test/kotlin/org/ktorm/ksp/compiler/generator/SuperTableClassTest.kt @@ -242,8 +242,22 @@ class SuperTableClassTest : BaseKspTest() { ) @Test - fun lackAliasParameter() = compileFailing( - "Too many arguments for public constructor", """ + fun lackPrimaryConstructor() = kspFailing( + "should have a primary constructor with parameters tableName and alias.", """ + abstract class UserBaseTable> : org.ktorm.schema.Table("t_user") + + @Table + @SuperTableClass(UserBaseTable::class) + interface User : Entity { + var id: Int + var name: String + } + """.trimIndent() + ) + + @Test + fun lackAliasParameter() = kspFailing( + "should have a primary constructor with parameters tableName and alias.", """ abstract class UserBaseTable>(tableName: String) : org.ktorm.schema.Table(tableName) @Table @@ -256,8 +270,8 @@ class SuperTableClassTest : BaseKspTest() { ) @Test - fun lackTableNameParameter() = compileFailing( - "Too many arguments for public constructor", """ + fun lackTableNameParameter() = kspFailing( + "should have a primary constructor with parameters tableName and alias.", """ abstract class UserBaseTable>(alias: String?) : org.ktorm.schema.Table(alias = alias) @Table From 677fa241ca5c3a1c344919803a6bb4569fdd5197 Mon Sep 17 00:00:00 2001 From: qumn Date: Mon, 17 Jun 2024 13:31:03 +0800 Subject: [PATCH 5/7] chore: add copyright of SuperTableClass --- .../org/ktorm/ksp/annotation/SuperTableClass.kt | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/ktorm-ksp-annotations/src/main/kotlin/org/ktorm/ksp/annotation/SuperTableClass.kt b/ktorm-ksp-annotations/src/main/kotlin/org/ktorm/ksp/annotation/SuperTableClass.kt index c19718f3..27d08798 100644 --- a/ktorm-ksp-annotations/src/main/kotlin/org/ktorm/ksp/annotation/SuperTableClass.kt +++ b/ktorm-ksp-annotations/src/main/kotlin/org/ktorm/ksp/annotation/SuperTableClass.kt @@ -1,3 +1,19 @@ +/* + * Copyright 2018-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package org.ktorm.ksp.annotation import org.ktorm.schema.BaseTable From b0b3a38ff16429442df8ecaaf50d6c06bfc29758 Mon Sep 17 00:00:00 2001 From: qumn Date: Mon, 17 Jun 2024 13:31:38 +0800 Subject: [PATCH 6/7] fix: detekt --- .../compiler/generator/TableClassGenerator.kt | 2 -- .../ksp/compiler/parser/MetadataParser.kt | 33 +++++++++++-------- .../ktorm/ksp/compiler/util/KspExtensions.kt | 5 +-- 3 files changed, 23 insertions(+), 17 deletions(-) diff --git a/ktorm-ksp-compiler/src/main/kotlin/org/ktorm/ksp/compiler/generator/TableClassGenerator.kt b/ktorm-ksp-compiler/src/main/kotlin/org/ktorm/ksp/compiler/generator/TableClassGenerator.kt index a2eb8b1b..5a08c119 100644 --- a/ktorm-ksp-compiler/src/main/kotlin/org/ktorm/ksp/compiler/generator/TableClassGenerator.kt +++ b/ktorm-ksp-compiler/src/main/kotlin/org/ktorm/ksp/compiler/generator/TableClassGenerator.kt @@ -28,9 +28,7 @@ import org.ktorm.ksp.compiler.util._type import org.ktorm.ksp.compiler.util.getKotlinType import org.ktorm.ksp.compiler.util.getRegisteringCodeBlock import org.ktorm.ksp.spi.TableMetadata -import org.ktorm.schema.BaseTable import org.ktorm.schema.Column -import org.ktorm.schema.Table @OptIn(KotlinPoetKspPreview::class) internal object TableClassGenerator { diff --git a/ktorm-ksp-compiler/src/main/kotlin/org/ktorm/ksp/compiler/parser/MetadataParser.kt b/ktorm-ksp-compiler/src/main/kotlin/org/ktorm/ksp/compiler/parser/MetadataParser.kt index 5481ccb6..5dce0c5b 100644 --- a/ktorm-ksp-compiler/src/main/kotlin/org/ktorm/ksp/compiler/parser/MetadataParser.kt +++ b/ktorm-ksp-compiler/src/main/kotlin/org/ktorm/ksp/compiler/parser/MetadataParser.kt @@ -289,7 +289,8 @@ internal class MetadataParser(resolver: Resolver, environment: SymbolProcessorEn */ @OptIn(KotlinPoetKspPreview::class) private fun parseSuperTableClass(cls: KSClassDeclaration): Pair> { - val entityAnnPairs = cls.findAnnotationsInHierarchy(SuperTableClass::class.qualifiedName!!) + val entityAnnPairs = + cls.findAnnotationsInHierarchy(SuperTableClass::class.qualifiedName!!) // if there is no SuperTableClass annotation, return the default super table class based on the class kind. if (entityAnnPairs.isEmpty()) { @@ -319,7 +320,6 @@ internal class MetadataParser(resolver: Resolver, environment: SymbolProcessorEn return lowestSubClass.toClassName() to superTableClasses.toSet() } - // find the last annotation in the inheritance hierarchy private fun findLowestSubClass( superTableClasses: List, @@ -331,26 +331,33 @@ internal class MetadataParser(resolver: Resolver, environment: SymbolProcessorEn if (cur.isSubclassOf(lowestSubClass)) { lowestSubClass = cur } else if (!lowestSubClass.isSubclassOf(cur)) { - val msg = - "There are multiple SuperTableClass annotations in the inheritance hierarchy of class ${cls.qualifiedName?.asString()}," + - "but the values of annotation are not in the same inheritance hierarchy." + val msg = """ + There are multiple SuperTableClass annotations in the inheritance hierarchy + of class ${cls.qualifiedName?.asString()}. + but the values of annotation are not in the same inheritance hierarchy. + """.trimIndent() throw IllegalArgumentException(msg) } } return lowestSubClass } - private fun validateSuperTableClassConstructor(lowestSubClass: KSClassDeclaration) { - // validate the primary constructor of the super table class - if (lowestSubClass.primaryConstructor == null) { + // validate the primary constructor of the super table class + private fun validateSuperTableClassConstructor(superTableClass: KSClassDeclaration) { + if (superTableClass.primaryConstructor == null) { val msg = - "The super table class ${lowestSubClass.qualifiedName?.asString()} should have a primary constructor." + "The super table class ${superTableClass.qualifiedName?.asString()} should have a primary constructor." throw IllegalArgumentException(msg) } - val parameters = lowestSubClass.primaryConstructor!!.parameters - if (parameters.size < 2 || parameters[0].name!!.asString() != "tableName" || parameters[1].name!!.asString() != "alias") { - val msg = - "The super table class ${lowestSubClass.qualifiedName?.asString()} should have a primary constructor with parameters tableName and alias." + + val parameters = superTableClass.primaryConstructor!!.parameters + if (parameters.size < 2 || + parameters[0].name!!.asString() != "tableName" || + parameters[1].name!!.asString() != "alias" + ) { + val msg = "" + + "The super table class ${superTableClass.qualifiedName?.asString()} should have " + + "a primary constructor with parameters tableName and alias." throw IllegalArgumentException(msg) } } diff --git a/ktorm-ksp-compiler/src/main/kotlin/org/ktorm/ksp/compiler/util/KspExtensions.kt b/ktorm-ksp-compiler/src/main/kotlin/org/ktorm/ksp/compiler/util/KspExtensions.kt index c5ad8187..3c161fed 100644 --- a/ktorm-ksp-compiler/src/main/kotlin/org/ktorm/ksp/compiler/util/KspExtensions.kt +++ b/ktorm-ksp-compiler/src/main/kotlin/org/ktorm/ksp/compiler/util/KspExtensions.kt @@ -93,7 +93,9 @@ internal fun KSClassDeclaration.findAnnotationsInHierarchy(name: String): List

>() fun KSClassDeclaration.collectAnnotations() { - pairs += annotations.filter { it.annotationType.resolve().declaration.qualifiedName?.asString() == name }.map { this to it } + pairs += annotations + .filter { it.annotationType.resolve().declaration.qualifiedName?.asString() == name } + .map { this to it } superTypes.forEach { (it.resolve().declaration as KSClassDeclaration).collectAnnotations() } } @@ -101,7 +103,6 @@ internal fun KSClassDeclaration.findAnnotationsInHierarchy(name: String): List

Date: Mon, 17 Jun 2024 15:26:11 +0800 Subject: [PATCH 7/7] chore: update developer info --- buildSrc/src/main/kotlin/ktorm.publish.gradle.kts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/buildSrc/src/main/kotlin/ktorm.publish.gradle.kts b/buildSrc/src/main/kotlin/ktorm.publish.gradle.kts index bb46335d..62c25363 100644 --- a/buildSrc/src/main/kotlin/ktorm.publish.gradle.kts +++ b/buildSrc/src/main/kotlin/ktorm.publish.gradle.kts @@ -155,6 +155,11 @@ publishing { id.set("brohacz") name.set("Michal Brosig") } + developer { + id.set("qumn") + name.set("qumn") + email.set("2476573497@qq.com") + } } } }