diff --git a/.github/.cspell/dev.dictionary.txt b/.github/.cspell/dev.dictionary.txt index d3d75ea..7bb4b4c 100644 --- a/.github/.cspell/dev.dictionary.txt +++ b/.github/.cspell/dev.dictionary.txt @@ -9,5 +9,6 @@ lateinit # keyword in Kotlin metaspace # a memory space in the JVM mkdirs # gradle API to create directories sarif # a standard for test results (https://gcc.gnu.org/wiki/SARIF) +typealiases # the plural form of typealias usertype # in Hibernate, a type with a custom adapter defined by the user Xjsr # a jvm flag to enable JSR 305 annotations diff --git a/yawn-integration-test/src/test/kotlin/com/faire/yawn/YawnEntityProcessorTypeAliasesTest.kt b/yawn-integration-test/src/test/kotlin/com/faire/yawn/YawnEntityProcessorTypeAliasesTest.kt new file mode 100644 index 0000000..4922ca5 --- /dev/null +++ b/yawn-integration-test/src/test/kotlin/com/faire/yawn/YawnEntityProcessorTypeAliasesTest.kt @@ -0,0 +1,72 @@ +package com.faire.yawn + +import com.faire.yawn.YawnTestUtils.assertGeneratedFile +import com.faire.yawn.criteria.query.JoinTypeSafeCriteriaQuery +import com.faire.yawn.criteria.query.ProjectedTypeSafeCriteriaQuery +import com.faire.yawn.criteria.query.TypeSafeCriteriaQuery +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test +import kotlin.reflect.KVisibility +import kotlin.reflect.typeOf + +internal class YawnEntityProcessorTypeAliasesTest { + @Test + fun `generates type aliases for TableDef`() { + assertThat(typeOf()).isEqualTo( + typeOf>(), + ) + } + + @Test + fun `generates type aliases for TypeSafeCriteriaQuery`() { + assertThat(typeOf()).isEqualTo( + typeOf< + TypeSafeCriteriaQuery< + EntityWithElementCollection, + EntityWithElementCollectionTableDef, + >, + >(), + ) + } + + @Test + fun `generates type aliases for JoinTypeSafeCriteriaQuery`() { + assertThat(typeOf()).isEqualTo( + typeOf< + JoinTypeSafeCriteriaQuery< + EntityWithElementCollection, + EntityWithElementCollection, + EntityWithElementCollectionTableDef, + >, + >(), + ) + } + + @Test + fun `generates type aliases for ProjectedTypeSafeCriteriaQuery`() { + assertThat(typeOf>()).isEqualTo( + typeOf>(), + ) + + assertThat(typeOf>()).isEqualTo( + typeOf>(), + ) + } + + @Test + fun `visibility of type aliases is correct`() { + assertGeneratedFile { + containsTypeAlias("InternalEmptyEntityTableDefType", KVisibility.INTERNAL) + containsTypeAlias("InternalEmptyEntityCriteriaQuery", KVisibility.INTERNAL) + containsTypeAlias("InternalEmptyEntityJoinCriteriaQuery", KVisibility.INTERNAL) + containsTypeAlias("InternalEmptyEntityProjectedCriteriaQuery", KVisibility.INTERNAL) + } + + assertGeneratedFile { + containsTypeAlias("PublicEmptyEntityTableDefType", KVisibility.PUBLIC) + containsTypeAlias("PublicEmptyEntityCriteriaQuery", KVisibility.PUBLIC) + containsTypeAlias("PublicEmptyEntityJoinCriteriaQuery", KVisibility.PUBLIC) + containsTypeAlias("PublicEmptyEntityProjectedCriteriaQuery", KVisibility.PUBLIC) + } + } +} diff --git a/yawn-integration-test/src/test/kotlin/com/faire/yawn/YawnTestUtils.kt b/yawn-integration-test/src/test/kotlin/com/faire/yawn/YawnTestUtils.kt index 6dac1ff..f661ad3 100644 --- a/yawn-integration-test/src/test/kotlin/com/faire/yawn/YawnTestUtils.kt +++ b/yawn-integration-test/src/test/kotlin/com/faire/yawn/YawnTestUtils.kt @@ -3,6 +3,7 @@ package com.faire.yawn import com.faire.yawn.project.YawnProjectionDef import com.faire.yawn.project.YawnProjectionRef import org.assertj.core.api.Assertions.assertThat +import java.io.File import java.lang.reflect.ParameterizedType import kotlin.reflect.KClass import kotlin.reflect.KVisibility @@ -11,6 +12,9 @@ import kotlin.reflect.full.isSubtypeOf import kotlin.reflect.full.memberProperties import kotlin.reflect.typeOf +// Standard KSP output directory - configured by the KSP Gradle plugin +private const val GENERATED_SOURCE_DIR = "build/generated/ksp/main/kotlin" + internal object YawnTestUtils { inline fun assertGeneratedEntity( expectedVisibility: KVisibility? = null, @@ -38,6 +42,20 @@ internal object YawnTestUtils { ) } + inline fun > assertGeneratedFile( + builder: GeneratedFileAssertContext.() -> Unit, + ) { + val clazz = T::class + val packagePath = clazz.java.packageName.replace('.', '/') + val fileName = "${clazz.getClassNamePrefix()}Def.kt" + + val file = File("$GENERATED_SOURCE_DIR/$packagePath/$fileName") + + val lines = file.readLines() + + builder(GeneratedFileAssertContext(lines)) + } + fun internalAssertGeneratedEntity( expectedSuperClassDef: KClass>, expectedSuperClassRef: KClass>, @@ -71,9 +89,7 @@ internal object YawnTestUtils { ): KClass<*> { val classPath = clazz.java.packageName val defName = expectedSuperClass.simpleName!!.removePrefix("Yawn").removeSuffix("Ref") - val classNamePrefix = clazz.java - .getNestingChain() - .joinToString(separator = "_") { it.simpleName } + val classNamePrefix = clazz.getClassNamePrefix() val className = "$classNamePrefix$defName" return Class.forName("$classPath.$className").kotlin } @@ -139,6 +155,15 @@ internal object YawnTestUtils { assertThat(properties).anyMatch { it.name == columnName && it.returnType.toString() == columnType } } } + + class GeneratedFileAssertContext(private val lines: List) { + fun containsTypeAlias(name: String, visibility: KVisibility) { + val visibilityKeyword = visibility.name.lowercase() + val expectedPrefix = "$visibilityKeyword typealias $name" + + assertThat(lines.singleOrNull { it.startsWith(expectedPrefix) }).isNotNull() + } + } } private fun Class<*>.getNestingChain(): List> { @@ -154,3 +179,7 @@ private fun Class<*>.getNestingChain(): List> { private inline fun getTypeStringWithSourceReplaced(replacement: String): String { return typeOf().toString().replace(replacement, "SOURCE") } + +private fun KClass<*>.getClassNamePrefix(): String { + return java.getNestingChain().joinToString(separator = "_") { it.simpleName!! } +} diff --git a/yawn-processor/src/main/kotlin/com/faire/yawn/generators/typealiases/JoinTypeSafeCriteriaQueryTypeAliasGenerator.kt b/yawn-processor/src/main/kotlin/com/faire/yawn/generators/typealiases/JoinTypeSafeCriteriaQueryTypeAliasGenerator.kt new file mode 100644 index 0000000..28592bb --- /dev/null +++ b/yawn-processor/src/main/kotlin/com/faire/yawn/generators/typealiases/JoinTypeSafeCriteriaQueryTypeAliasGenerator.kt @@ -0,0 +1,23 @@ +package com.faire.yawn.generators.typealiases + +import com.faire.ksp.getUniqueSimpleName +import com.faire.yawn.criteria.query.JoinTypeSafeCriteriaQuery +import com.squareup.kotlinpoet.ClassName +import com.squareup.kotlinpoet.ParameterizedTypeName +import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy +import com.squareup.kotlinpoet.asClassName + +/** + * Generates: `typealias DbBookJoinCriteriaQuery = JoinTypeSafeCriteriaQuery>` + */ +internal object JoinTypeSafeCriteriaQueryTypeAliasGenerator : YawnTableDefTypeAliasGenerator { + override fun getName(entityType: ClassName): String = "${entityType.getUniqueSimpleName()}JoinCriteriaQuery" + + override fun getType(entityType: ClassName, tableDefType: ParameterizedTypeName): ParameterizedTypeName { + return JoinTypeSafeCriteriaQuery::class.asClassName().parameterizedBy( + entityType, + entityType, + tableDefType, + ) + } +} diff --git a/yawn-processor/src/main/kotlin/com/faire/yawn/generators/typealiases/ProjectedTypeSafeCriteriaQueryTypeAliasGenerator.kt b/yawn-processor/src/main/kotlin/com/faire/yawn/generators/typealiases/ProjectedTypeSafeCriteriaQueryTypeAliasGenerator.kt new file mode 100644 index 0000000..45b19e7 --- /dev/null +++ b/yawn-processor/src/main/kotlin/com/faire/yawn/generators/typealiases/ProjectedTypeSafeCriteriaQueryTypeAliasGenerator.kt @@ -0,0 +1,35 @@ +package com.faire.yawn.generators.typealiases + +import com.faire.ksp.getUniqueSimpleName +import com.faire.yawn.criteria.query.ProjectedTypeSafeCriteriaQuery +import com.faire.yawn.util.YawnContext +import com.squareup.kotlinpoet.ANY +import com.squareup.kotlinpoet.ClassName +import com.squareup.kotlinpoet.ParameterizedTypeName +import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy +import com.squareup.kotlinpoet.STAR +import com.squareup.kotlinpoet.TypeAliasSpec +import com.squareup.kotlinpoet.TypeVariableName +import com.squareup.kotlinpoet.asClassName + +/** + * Generates: `typealias DbBookProjectedCriteriaQuery = ProjectedTypeSafeCriteriaQuery` + */ +internal object ProjectedTypeSafeCriteriaQueryTypeAliasGenerator : YawnTableDefTypeAliasGenerator { + private val projectionTypeVariable = TypeVariableName("PROJECTION", ANY.copy(nullable = true)) + + override fun getName(entityType: ClassName): String = "${entityType.getUniqueSimpleName()}ProjectedCriteriaQuery" + + override fun getType(entityType: ClassName, tableDefType: ParameterizedTypeName): ParameterizedTypeName { + return ProjectedTypeSafeCriteriaQuery::class.asClassName().parameterizedBy( + entityType, + STAR, + STAR, + projectionTypeVariable, + ) + } + + override fun TypeAliasSpec.Builder.additionalTypeAliasBuilder(yawnContext: YawnContext): TypeAliasSpec.Builder { + return addTypeVariable(projectionTypeVariable) + } +} diff --git a/yawn-processor/src/main/kotlin/com/faire/yawn/generators/typealiases/TableDefTypeAliasGenerator.kt b/yawn-processor/src/main/kotlin/com/faire/yawn/generators/typealiases/TableDefTypeAliasGenerator.kt new file mode 100644 index 0000000..9866e01 --- /dev/null +++ b/yawn-processor/src/main/kotlin/com/faire/yawn/generators/typealiases/TableDefTypeAliasGenerator.kt @@ -0,0 +1,17 @@ +package com.faire.yawn.generators.typealiases + +import com.faire.ksp.getUniqueSimpleName +import com.squareup.kotlinpoet.ClassName +import com.squareup.kotlinpoet.ParameterizedTypeName + +/** + * Generates: `typealias DbBookTableDefType = DbBookTableDef` + */ +internal object TableDefTypeAliasGenerator : YawnTableDefTypeAliasGenerator { + override fun getName(entityType: ClassName): String = "${entityType.getUniqueSimpleName()}TableDefType" + + override fun getType( + entityType: ClassName, + tableDefType: ParameterizedTypeName, + ): ParameterizedTypeName = tableDefType +} diff --git a/yawn-processor/src/main/kotlin/com/faire/yawn/generators/typealiases/TypeSafeCriteriaQueryTypeAliasGenerator.kt b/yawn-processor/src/main/kotlin/com/faire/yawn/generators/typealiases/TypeSafeCriteriaQueryTypeAliasGenerator.kt new file mode 100644 index 0000000..07fd641 --- /dev/null +++ b/yawn-processor/src/main/kotlin/com/faire/yawn/generators/typealiases/TypeSafeCriteriaQueryTypeAliasGenerator.kt @@ -0,0 +1,20 @@ +package com.faire.yawn.generators.typealiases + +import com.faire.ksp.getUniqueSimpleName +import com.faire.yawn.criteria.query.TypeSafeCriteriaQuery +import com.squareup.kotlinpoet.ClassName +import com.squareup.kotlinpoet.ParameterizedTypeName +import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy +import com.squareup.kotlinpoet.asClassName + +/** + * Generates: `typealias DbBookCriteriaQuery = TypeSafeCriteriaQuery>` + */ +internal object TypeSafeCriteriaQueryTypeAliasGenerator : YawnTableDefTypeAliasGenerator { + override fun getName(entityType: ClassName): String = "${entityType.getUniqueSimpleName()}CriteriaQuery" + + override fun getType( + entityType: ClassName, + tableDefType: ParameterizedTypeName, + ): ParameterizedTypeName = TypeSafeCriteriaQuery::class.asClassName().parameterizedBy(entityType, tableDefType) +} diff --git a/yawn-processor/src/main/kotlin/com/faire/yawn/generators/typealiases/YawnTableDefTypeAliasGenerator.kt b/yawn-processor/src/main/kotlin/com/faire/yawn/generators/typealiases/YawnTableDefTypeAliasGenerator.kt new file mode 100644 index 0000000..aa71857 --- /dev/null +++ b/yawn-processor/src/main/kotlin/com/faire/yawn/generators/typealiases/YawnTableDefTypeAliasGenerator.kt @@ -0,0 +1,34 @@ +package com.faire.yawn.generators.typealiases + +import com.faire.ksp.getEffectiveVisibility +import com.faire.yawn.util.YawnContext +import com.squareup.kotlinpoet.ClassName +import com.squareup.kotlinpoet.ParameterizedTypeName +import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy +import com.squareup.kotlinpoet.TypeAliasSpec +import com.squareup.kotlinpoet.ksp.toClassName + +/** + * [YawnTypeAliasGenerator] for [com.faire.yawn.YawnTableDef]s and related types. + */ +internal interface YawnTableDefTypeAliasGenerator : YawnTypeAliasGenerator { + override fun generate(yawnContext: YawnContext): TypeAliasSpec { + val entityType = yawnContext.classDeclaration.toClassName() + val tableDefType = yawnContext.newClassName.parameterizedBy(entityType) + val visibility = yawnContext.classDeclaration.getEffectiveVisibility() + + val typeAliasName = getName(entityType) + val typeAliasType = getType(entityType, tableDefType) + + return TypeAliasSpec.builder(typeAliasName, typeAliasType) + .addModifiers(visibility) + .additionalTypeAliasBuilder(yawnContext) + .build() + } + + fun getName(entityType: ClassName): String + + fun getType(entityType: ClassName, tableDefType: ParameterizedTypeName): ParameterizedTypeName + + fun TypeAliasSpec.Builder.additionalTypeAliasBuilder(yawnContext: YawnContext): TypeAliasSpec.Builder = this +} diff --git a/yawn-processor/src/main/kotlin/com/faire/yawn/generators/typealiases/YawnTypeAliasGenerator.kt b/yawn-processor/src/main/kotlin/com/faire/yawn/generators/typealiases/YawnTypeAliasGenerator.kt new file mode 100644 index 0000000..53c2694 --- /dev/null +++ b/yawn-processor/src/main/kotlin/com/faire/yawn/generators/typealiases/YawnTypeAliasGenerator.kt @@ -0,0 +1,16 @@ +package com.faire.yawn.generators.typealiases + +import com.faire.yawn.util.YawnContext +import com.squareup.kotlinpoet.TypeAliasSpec + +/** + * These typealias generators are used to generate typealiases for generated content. + * + * For example, + * ``` + * typealias DbBookCriteriaQuery = TypeSafeCriteriaQuery> + * ``` + */ +internal interface YawnTypeAliasGenerator { + fun generate(yawnContext: YawnContext): TypeAliasSpec +} diff --git a/yawn-processor/src/main/kotlin/com/faire/yawn/processors/BaseYawnProcessor.kt b/yawn-processor/src/main/kotlin/com/faire/yawn/processors/BaseYawnProcessor.kt index cc98995..39f7331 100644 --- a/yawn-processor/src/main/kotlin/com/faire/yawn/processors/BaseYawnProcessor.kt +++ b/yawn-processor/src/main/kotlin/com/faire/yawn/processors/BaseYawnProcessor.kt @@ -6,6 +6,7 @@ import com.faire.yawn.YawnTableDefParent import com.faire.yawn.generators.addGeneratedAnnotation import com.faire.yawn.generators.objects.YawnReferenceObjectGenerator import com.faire.yawn.util.YawnContext +import com.faire.yawn.util.addTypeAliases import com.google.devtools.ksp.processing.CodeGenerator import com.google.devtools.ksp.processing.Dependencies import com.google.devtools.ksp.processing.Resolver @@ -20,6 +21,7 @@ import com.squareup.kotlinpoet.KModifier import com.squareup.kotlinpoet.ParameterSpec import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy import com.squareup.kotlinpoet.PropertySpec +import com.squareup.kotlinpoet.TypeAliasSpec import com.squareup.kotlinpoet.TypeSpec import com.squareup.kotlinpoet.TypeVariableName import com.squareup.kotlinpoet.asClassName @@ -63,10 +65,12 @@ internal abstract class BaseYawnProcessor( val yawnContext = buildYawnContext(classDeclaration, newClassName) val objectDef = objectRefGenerator.generate(yawnContext) val classDef = generateClassDefinition(yawnContext) + val typeAliases = generateTypeAliases(yawnContext) val fileSpec = FileSpec.builder(packageName, newClassName) .addType(objectDef) .addType(classDef) + .addTypeAliases(typeAliases) .build() val outputFile = codeGenerator.createNewFile( @@ -132,6 +136,8 @@ internal abstract class BaseYawnProcessor( protected abstract fun generateYawnDefClassName(originalClassName: ClassName): String + protected open fun generateTypeAliases(yawnContext: YawnContext): List = listOf() + companion object { const val PARENT_PARAMETER_NAME = "parent" val parentType = YawnTableDefParent::class.asTypeName() diff --git a/yawn-processor/src/main/kotlin/com/faire/yawn/processors/YawnEntityProcessor.kt b/yawn-processor/src/main/kotlin/com/faire/yawn/processors/YawnEntityProcessor.kt index 041252c..2f9ce3c 100644 --- a/yawn-processor/src/main/kotlin/com/faire/yawn/processors/YawnEntityProcessor.kt +++ b/yawn-processor/src/main/kotlin/com/faire/yawn/processors/YawnEntityProcessor.kt @@ -12,6 +12,10 @@ import com.faire.yawn.generators.properties.EmbeddedIdDefGenerator import com.faire.yawn.generators.properties.JoinColumnDefGenerator import com.faire.yawn.generators.properties.JoinColumnDefWithCompositeKeyGenerator import com.faire.yawn.generators.properties.JoinColumnDefWithForeignKeyGenerator +import com.faire.yawn.generators.typealiases.JoinTypeSafeCriteriaQueryTypeAliasGenerator +import com.faire.yawn.generators.typealiases.ProjectedTypeSafeCriteriaQueryTypeAliasGenerator +import com.faire.yawn.generators.typealiases.TableDefTypeAliasGenerator +import com.faire.yawn.generators.typealiases.TypeSafeCriteriaQueryTypeAliasGenerator import com.faire.yawn.generators.types.EmbeddedIdTypeGenerator import com.faire.yawn.generators.types.EmbeddedTypeGenerator import com.faire.yawn.util.YawnContext @@ -37,6 +41,7 @@ import com.google.devtools.ksp.processing.SymbolProcessorProvider import com.google.devtools.ksp.symbol.KSPropertyDeclaration import com.squareup.kotlinpoet.ClassName import com.squareup.kotlinpoet.PropertySpec +import com.squareup.kotlinpoet.TypeAliasSpec import com.squareup.kotlinpoet.TypeSpec import kotlin.reflect.KClass @@ -95,6 +100,17 @@ internal class YawnEntityProcessor(codeGenerator: CodeGenerator) : BaseYawnProce .addTypes(generateEmbeddedDefinitions(yawnContext)) } + override fun generateTypeAliases(yawnContext: YawnContext): List { + val generators = listOf( + TableDefTypeAliasGenerator, + TypeSafeCriteriaQueryTypeAliasGenerator, + ProjectedTypeSafeCriteriaQueryTypeAliasGenerator, + JoinTypeSafeCriteriaQueryTypeAliasGenerator, + ) + + return generators.map { it.generate(yawnContext) } + } + /** * Within the generated table definition, we might need to define subclasses to represent embedded definitions. * This will be either fields tagged with @Embedded or composite primary keys tagged with @EmbeddedId. diff --git a/yawn-processor/src/main/kotlin/com/faire/yawn/util/FileSpecExtensions.kt b/yawn-processor/src/main/kotlin/com/faire/yawn/util/FileSpecExtensions.kt new file mode 100644 index 0000000..2d90cda --- /dev/null +++ b/yawn-processor/src/main/kotlin/com/faire/yawn/util/FileSpecExtensions.kt @@ -0,0 +1,11 @@ +package com.faire.yawn.util + +import com.squareup.kotlinpoet.FileSpec +import com.squareup.kotlinpoet.TypeAliasSpec + +internal fun FileSpec.Builder.addTypeAliases(typeAliases: Collection): FileSpec.Builder { + for (typeAlias in typeAliases) { + addTypeAlias(typeAlias) + } + return this +}