Skip to content

Commit 878526a

Browse files
committed
feat: Implement initial version of SuperTableClass annotation
1 parent f59e406 commit 878526a

File tree

7 files changed

+335
-10
lines changed

7 files changed

+335
-10
lines changed
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package org.ktorm.ksp.annotation
2+
3+
import org.ktorm.schema.BaseTable
4+
import kotlin.reflect.KClass
5+
6+
/**
7+
* Be used on Entity interface, to specify the super table class for the table class generated.
8+
* @property value the super table class.
9+
* if not specified, the super table class will be determined by the kind of the entity class.
10+
* `org.ktorm.schema.Table` for interface, `org.ktorm.schema.BaseTable` for class.
11+
*
12+
* if there are multiple `SuperTableClass` on the inheritance hierarchy of entity,
13+
* and they have an inheritance relationship, the super table class will be the last one of them.
14+
* and they don't have an inheritance relationship, an error will be reported.
15+
*/
16+
@Target(AnnotationTarget.CLASS)
17+
@Retention(AnnotationRetention.SOURCE)
18+
public annotation class SuperTableClass(
19+
public val value: KClass<out BaseTable<out Any>>
20+
)

ktorm-ksp-compiler/src/main/kotlin/org/ktorm/ksp/compiler/generator/TableClassGenerator.kt

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,11 +49,7 @@ internal object TableClassGenerator {
4949
}
5050

5151
private fun TypeSpec.Builder.configureSuperClass(table: TableMetadata): TypeSpec.Builder {
52-
if (table.entityClass.classKind == ClassKind.INTERFACE) {
53-
superclass(Table::class.asClassName().parameterizedBy(table.entityClass.toClassName()))
54-
} else {
55-
superclass(BaseTable::class.asClassName().parameterizedBy(table.entityClass.toClassName()))
56-
}
52+
superclass(table.superClass.parameterizedBy(table.entityClass.toClassName()))
5753

5854
addSuperclassConstructorParameter("%S", table.name)
5955
addSuperclassConstructorParameter("alias")

ktorm-ksp-compiler/src/main/kotlin/org/ktorm/ksp/compiler/parser/MetadataParser.kt

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ import org.ktorm.ksp.spi.ColumnMetadata
2929
import org.ktorm.ksp.spi.DatabaseNamingStrategy
3030
import org.ktorm.ksp.spi.TableMetadata
3131
import org.ktorm.schema.TypeReference
32+
import com.squareup.kotlinpoet.ClassName
33+
import com.squareup.kotlinpoet.asClassName
34+
import com.squareup.kotlinpoet.ksp.KotlinPoetKspPreview
35+
import com.squareup.kotlinpoet.ksp.toClassName
3236
import java.lang.reflect.InvocationTargetException
3337
import java.util.*
3438
import kotlin.reflect.jvm.jvmName
@@ -93,6 +97,9 @@ internal class MetadataParser(resolver: Resolver, environment: SymbolProcessorEn
9397

9498
_logger.info("[ktorm-ksp-compiler] parse table metadata from entity: $className")
9599
val table = cls.getAnnotationsByType(Table::class).first()
100+
val (superClass, superTableClasses) = parseSuperTableClass(cls)
101+
val allPropertyNamesOfSuperTables = superTableClasses.flatMap { it.getProperties(emptySet()) }.map { it.simpleName.asString() }
102+
96103
val tableMetadata = TableMetadata(
97104
entityClass = cls,
98105
name = table.name.ifEmpty { _databaseNamingStrategy.getTableName(cls) },
@@ -101,8 +108,9 @@ internal class MetadataParser(resolver: Resolver, environment: SymbolProcessorEn
101108
schema = table.schema.ifEmpty { _options["ktorm.schema"] }?.takeIf { it.isNotEmpty() },
102109
tableClassName = table.className.ifEmpty { _codingNamingStrategy.getTableClassName(cls) },
103110
entitySequenceName = table.entitySequenceName.ifEmpty { _codingNamingStrategy.getEntitySequenceName(cls) },
104-
ignoreProperties = table.ignoreProperties.toSet(),
105-
columns = ArrayList()
111+
ignoreProperties = table.ignoreProperties.toSet() + allPropertyNamesOfSuperTables, // ignore properties of super tables
112+
columns = ArrayList(),
113+
superClass = superClass
106114
)
107115

108116
val columns = tableMetadata.columns as MutableList
@@ -274,6 +282,51 @@ internal class MetadataParser(resolver: Resolver, environment: SymbolProcessorEn
274282
)
275283
}
276284

285+
/**
286+
* @return the super table class and all class be annotated with [SuperTableClass] in the inheritance hierarchy.
287+
*/
288+
@OptIn(KotlinPoetKspPreview::class)
289+
private fun parseSuperTableClass(cls: KSClassDeclaration): Pair<ClassName, Set<KSClassDeclaration>> {
290+
val superTableClassAnnPair = cls.findAllAnnotationsInInheritanceHierarchy(SuperTableClass::class.qualifiedName!!)
291+
292+
// if there is no SuperTableClass annotation, return the default super table class based on the class kind.
293+
if (superTableClassAnnPair.isEmpty()) {
294+
return if (cls.classKind == INTERFACE) {
295+
org.ktorm.schema.Table::class.asClassName() to emptySet()
296+
} else {
297+
org.ktorm.schema.BaseTable::class.asClassName() to emptySet()
298+
}
299+
}
300+
301+
// SuperTableClass annotation can only be used on interface
302+
if (superTableClassAnnPair.map { it.first }.any { it.classKind != INTERFACE }) {
303+
val msg = "SuperTableClass annotation can only be used on interface."
304+
throw IllegalArgumentException(msg)
305+
}
306+
307+
// find the last annotation in the inheritance hierarchy
308+
val superTableClasses = superTableClassAnnPair
309+
.map { it.second }
310+
.map { it.arguments.single { it.name?.asString() == SuperTableClass::value.name } }
311+
.map { it.value as KSType }
312+
.map { it.declaration as KSClassDeclaration }
313+
314+
var lowestSubClass = superTableClasses.first()
315+
for (i in 1 until superTableClasses.size) {
316+
val cur = superTableClasses[i]
317+
if (cur.isSubclassOf(lowestSubClass)) {
318+
lowestSubClass = cur
319+
} else if (!lowestSubClass.isSubclassOf(cur)) {
320+
val msg =
321+
"There are multiple SuperTableClass annotations in the inheritance hierarchy of class ${cls.qualifiedName?.asString()}," +
322+
"but the values of annotation are not in the same inheritance hierarchy."
323+
throw IllegalArgumentException(msg)
324+
}
325+
}
326+
//TODO: to check All constructor parameters owned by `BaseTable` should also be owned by lowestSubClass.
327+
return lowestSubClass.toClassName() to superTableClasses.toSet()
328+
}
329+
277330
private fun TableMetadata.checkCircularRef(ref: KSClassDeclaration, stack: LinkedList<String> = LinkedList()) {
278331
val className = this.entityClass.qualifiedName?.asString()
279332
val refClassName = ref.qualifiedName?.asString()

ktorm-ksp-compiler/src/main/kotlin/org/ktorm/ksp/compiler/util/KspExtensions.kt

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,13 @@ internal inline fun <reified T : Any> KSClassDeclaration.isSubclassOf(): Boolean
5656
return findSuperTypeReference(T::class.qualifiedName!!) != null
5757
}
5858

59+
/**
60+
* Check if this class is a subclass of declaration.
61+
*/
62+
internal fun KSClassDeclaration.isSubclassOf(declaration: KSClassDeclaration): Boolean {
63+
return findSuperTypeReference(declaration.qualifiedName!!.asString()) != null
64+
}
65+
5966
/**
6067
* Find the specific super type reference for this class.
6168
*/
@@ -76,6 +83,24 @@ internal fun KSClassDeclaration.findSuperTypeReference(name: String): KSTypeRefe
7683
return null
7784
}
7885

86+
/**
87+
* Find all annotations with the given name in the inheritance hierarchy of this class.
88+
*
89+
* @param name the qualified name of the annotation.
90+
*/
91+
internal fun KSClassDeclaration.findAllAnnotationsInInheritanceHierarchy(name: String): List<Pair<KSClassDeclaration, KSAnnotation>> {
92+
val rst = mutableListOf<Pair<KSClassDeclaration, KSAnnotation>>()
93+
94+
fun KSClassDeclaration.collectAnnotations() {
95+
rst += annotations.filter { it.annotationType.resolve().declaration.qualifiedName?.asString() == name }.map { this to it }
96+
superTypes.forEach { (it.resolve().declaration as KSClassDeclaration).collectAnnotations() }
97+
}
98+
99+
collectAnnotations()
100+
return rst
101+
}
102+
103+
79104
/**
80105
* Check if the given symbol is valid.
81106
*/

ktorm-ksp-compiler/src/test/kotlin/org/ktorm/ksp/compiler/BaseKspTest.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ abstract class BaseKspTest {
6161

6262
private fun compile(@Language("kotlin") code: String, options: Map<String, String>): KotlinCompilation.Result {
6363
@Language("kotlin")
64-
val header = """
64+
val source = """
6565
import java.math.*
6666
import java.sql.*
6767
import java.time.*
@@ -73,12 +73,13 @@ abstract class BaseKspTest {
7373
import org.ktorm.entity.*
7474
import org.ktorm.ksp.annotation.*
7575
76+
$code
77+
7678
lateinit var database: Database
7779
7880
7981
""".trimIndent()
8082

81-
val source = header + code
8283
printFile(source, "Source.kt")
8384

8485
val compilation = createCompilation(SourceFile.kotlin("Source.kt", source), options)

0 commit comments

Comments
 (0)