Skip to content

Commit

Permalink
provide list and map, clean up and refactor, fixes #51
Browse files Browse the repository at this point in the history
  • Loading branch information
jangalinski committed Sep 11, 2024
1 parent 125c836 commit 1c7066f
Show file tree
Hide file tree
Showing 34 changed files with 541 additions and 277 deletions.
41 changes: 41 additions & 0 deletions _itest/builder-itest/src/test/kotlin/DelegateStringListITest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package io.toolisticon.kotlin.generation.itest

import com.squareup.kotlinpoet.ExperimentalKotlinPoetApi
import com.tschuchort.compiletesting.KotlinCompilation
import io.toolisticon.kotlin.generation.KotlinCodeGeneration
import io.toolisticon.kotlin.generation.itest.KotlinCodeGenerationITestConfig.ROOT_PACKAGE
import io.toolisticon.kotlin.generation.spec.toFileSpec
import io.toolisticon.kotlin.generation.test.KotlinCodeGenerationTest
import io.toolisticon.kotlin.generation.test.model.KotlinCompilationCommand
import org.assertj.core.api.Assertions.assertThat
import org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi
import org.junit.jupiter.api.Test
import kotlin.reflect.KClass
import kotlin.reflect.full.primaryConstructor
import io.toolisticon.kotlin.generation.test.KotlinCodeGenerationTest.assertThat as compileAssertThat

@Suppress("UNCHECKED_CAST")
@OptIn(ExperimentalKotlinPoetApi::class, ExperimentalCompilerApi::class)
internal class DelegateStringListITest {

@Test
fun `create and use string list`() {
val list = KotlinCodeGeneration.buildDelegateListValueClass(ROOT_PACKAGE, "StringList", String::class) {

}.toFileSpec()

val result = KotlinCodeGenerationTest.compile(KotlinCompilationCommand(list))


compileAssertThat(result).errorMessages().isEmpty()
compileAssertThat(result).hasExitCode(KotlinCompilation.ExitCode.OK)

val klass: KClass<out Any> = result.loadClass(list.className)

val values = listOf("a", "b", "c")

val instance: List<String> = klass.primaryConstructor!!.call(values) as List<String>

assertThat(instance).hasToString("StringList(delegate=[a, b, c])")
}
}
42 changes: 42 additions & 0 deletions _itest/builder-itest/src/test/kotlin/DelegateStringLongMapITest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package io.toolisticon.kotlin.generation.itest

import com.squareup.kotlinpoet.ExperimentalKotlinPoetApi
import com.squareup.kotlinpoet.asTypeName
import com.tschuchort.compiletesting.KotlinCompilation
import io.toolisticon.kotlin.generation.KotlinCodeGeneration
import io.toolisticon.kotlin.generation.itest.KotlinCodeGenerationITestConfig.ROOT_PACKAGE
import io.toolisticon.kotlin.generation.spec.toFileSpec
import io.toolisticon.kotlin.generation.test.KotlinCodeGenerationTest
import io.toolisticon.kotlin.generation.test.model.KotlinCompilationCommand
import org.assertj.core.api.Assertions.assertThat
import org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi
import org.junit.jupiter.api.Test
import kotlin.reflect.KClass
import kotlin.reflect.full.primaryConstructor
import io.toolisticon.kotlin.generation.test.KotlinCodeGenerationTest.assertThat as compileAssertThat

@Suppress("UNCHECKED_CAST")
@OptIn(ExperimentalKotlinPoetApi::class, ExperimentalCompilerApi::class)
internal class DelegateStringLongMapITest {

@Test
fun `create and use string long map`() {
val map = KotlinCodeGeneration.buildDelegateMapValueClass(
packageName = ROOT_PACKAGE,
simpleName = "StringLongMap",
valueType = Long::class.asTypeName()
).toFileSpec()

val result = KotlinCodeGenerationTest.compile(KotlinCompilationCommand(map))
compileAssertThat(result).errorMessages().isEmpty()
compileAssertThat(result).hasExitCode(KotlinCompilation.ExitCode.OK)

val klass: KClass<out Any> = result.loadClass(map.className)

val values = mapOf("a" to 1, "b" to 2, "c" to 3)

val instance: Map<String, Long> = klass.primaryConstructor!!.call(values) as Map<String, Long>

assertThat(instance).hasToString("StringLongMap(delegate={a=1, b=2, c=3})")
}
}
52 changes: 52 additions & 0 deletions _itest/builder-itest/src/test/kotlin/DummyExceptionITest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package io.toolisticon.kotlin.generation.itest

import com.squareup.kotlinpoet.ExperimentalKotlinPoetApi
import com.tschuchort.compiletesting.KotlinCompilation
import io.toolisticon.kotlin.generation.KotlinCodeGeneration.buildRuntimeExceptionClass
import io.toolisticon.kotlin.generation.itest.KotlinCodeGenerationITestConfig.ROOT_PACKAGE
import io.toolisticon.kotlin.generation.spec.toFileSpec
import io.toolisticon.kotlin.generation.test.KotlinCodeGenerationTest
import io.toolisticon.kotlin.generation.test.model.KotlinCompilationCommand
import org.assertj.core.api.Assertions.assertThat
import org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi
import org.junit.jupiter.api.Test
import kotlin.reflect.KClass
import kotlin.reflect.KProperty1
import kotlin.reflect.full.memberProperties
import kotlin.reflect.full.primaryConstructor
import io.toolisticon.kotlin.generation.test.KotlinCodeGenerationTest.assertThat as compileAssertThat

@OptIn(ExperimentalKotlinPoetApi::class, ExperimentalCompilerApi::class)
internal class DummyExceptionITest {

@Test
fun `generate and create dummy exception`() {
val exceptionFile = buildRuntimeExceptionClass(ROOT_PACKAGE, "DummyException") {
messageTemplate("Dummy exception: expected: \$expected, actual: '\$actual'.")
addConstructorProperty("expected", Boolean::class)
addParameter("actual", String::class)
includeCause()
}.toFileSpec()


val result = KotlinCodeGenerationTest.compile(KotlinCompilationCommand(exceptionFile))

compileAssertThat(result).errorMessages().isEmpty()
compileAssertThat(result).hasExitCode(KotlinCompilation.ExitCode.OK)

val c: KClass<out Any> = result.loadClass(exceptionFile.className)

val cause = IllegalStateException("foo")
val e: RuntimeException = c.primaryConstructor!!.call(true, "false", cause) as RuntimeException

assertThat(e.localizedMessage).isEqualTo("Dummy exception: expected: true, actual: 'false'.")

// TODO try to get value via pure kotlin without falling back to java
val expectedProperty: KProperty1<out Any, *> = c.memberProperties.single { it.name == "expected" }
val field = c.java.getDeclaredField("expected").apply { isAccessible = true }

val expectedValue = field.get(e) as Boolean

assertThat(expectedValue).isTrue()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ internal class HelloWorldExampleITest {
val file = buildFile(name) {
addType(type)
}
println(file.code)

assertThat(file.packageName).isEqualTo("foo.bar")

Expand Down
40 changes: 35 additions & 5 deletions kotlin-code-generation/src/main/kotlin/KotlinCodeGeneration.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import io.toolisticon.kotlin.generation.KotlinCodeGeneration.builder.classBuilde
import io.toolisticon.kotlin.generation.KotlinCodeGeneration.builder.companionObjectBuilder
import io.toolisticon.kotlin.generation.KotlinCodeGeneration.builder.constructorPropertyBuilder
import io.toolisticon.kotlin.generation.KotlinCodeGeneration.builder.delegateListValueClassBuilder
import io.toolisticon.kotlin.generation.KotlinCodeGeneration.builder.delegateMapValueClassBuilder
import io.toolisticon.kotlin.generation.KotlinCodeGeneration.builder.fileBuilder
import io.toolisticon.kotlin.generation.KotlinCodeGeneration.builder.funBuilder
import io.toolisticon.kotlin.generation.KotlinCodeGeneration.builder.interfaceBuilder
Expand All @@ -20,11 +21,10 @@ import io.toolisticon.kotlin.generation.KotlinCodeGeneration.builder.propertyBui
import io.toolisticon.kotlin.generation.KotlinCodeGeneration.builder.runtimeExceptionClassBuilder
import io.toolisticon.kotlin.generation.KotlinCodeGeneration.builder.typeAliasBuilder
import io.toolisticon.kotlin.generation.KotlinCodeGeneration.builder.valueClassBuilder
import io.toolisticon.kotlin.generation.KotlinCodeGeneration.className
import io.toolisticon.kotlin.generation.builder.*
import io.toolisticon.kotlin.generation.builder.extra.DelegateListValueClassSpecBuilder
import io.toolisticon.kotlin.generation.builder.extra.DelegateListValueClassSpecBuilderReceiver
import io.toolisticon.kotlin.generation.builder.extra.RuntimeExceptionSpecBuilder
import io.toolisticon.kotlin.generation.builder.extra.RuntimeExceptionSpecBuilderReceiver
import io.toolisticon.kotlin.generation.builder.extra.*
import io.toolisticon.kotlin.generation.builder.extra.DelegateMapValueClassSpecBuilder.Companion.DEFAULT_KEY_TYPE
import io.toolisticon.kotlin.generation.poet.FormatSpecifier.asCodeBlock
import io.toolisticon.kotlin.generation.spec.*
import io.toolisticon.kotlin.generation.spi.KotlinCodeGenerationContext
Expand All @@ -33,7 +33,6 @@ import io.toolisticon.kotlin.generation.spi.registry.KotlinCodeGenerationService
import io.toolisticon.kotlin.generation.spi.strategy.KotlinFileSpecStrategy
import io.toolisticon.kotlin.generation.spi.strategy.executeAll
import io.toolisticon.kotlin.generation.support.SUPPRESS_MEMBER_VISIBILITY_CAN_BE_PRIVATE
import io.toolisticon.kotlin.generation.support.SUPPRESS_UNUSED
import mu.KLogging
import kotlin.reflect.KClass

Expand Down Expand Up @@ -119,6 +118,29 @@ object KotlinCodeGeneration : KLogging() {
block: DelegateListValueClassSpecBuilderReceiver = {}
) = delegateListValueClassBuilder(className, items).also(block).build()


/**
* @see [DelegateMapValueClassSpecBuilder]
*/
inline fun buildDelegateMapValueClass(
packageName: PackageName,
simpleName: SimpleName,
keyType: TypeName = DEFAULT_KEY_TYPE,
valueType: TypeName,
block: DelegateMapValueClassSpecBuilderReceiver = {}
) = buildDelegateMapValueClass(className(packageName, simpleName), keyType, valueType, block)


/**
* @see [DelegateMapValueClassSpecBuilder]
*/
inline fun buildDelegateMapValueClass(
className: ClassName,
keyType: TypeName = DEFAULT_KEY_TYPE,
valueType: TypeName,
block: DelegateMapValueClassSpecBuilderReceiver = {}
) = delegateMapValueClassBuilder(className, keyType, valueType).also(block).build()

/**
* @see RuntimeExceptionSpecBuilder
*/
Expand Down Expand Up @@ -348,8 +370,16 @@ object KotlinCodeGeneration : KLogging() {
@SuppressWarnings("unused")
fun dataClassBuilder(packageName: PackageName, simpleName: SimpleName) = dataClassBuilder(className(packageName, simpleName))

/**
* @see DelegateListValueClassSpecBuilder
*/
fun delegateListValueClassBuilder(className: ClassName, items: TypeName) = DelegateListValueClassSpecBuilder.builder(className, items)

/**
* @see DelegateMapValueClassSpecBuilder
*/
fun delegateMapValueClassBuilder(className: ClassName, keyType: TypeName = DEFAULT_KEY_TYPE, valueType: TypeName) = DelegateMapValueClassSpecBuilder.builder(className, keyType, valueType)

/**
* @see KotlinEnumClassSpecBuilder
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,28 +44,16 @@ class KotlinAnnotationClassSpecBuilder internal constructor(

fun mustBeDocumented() = apply { this.mustBeDocumented = true }
fun repeatable() = apply { this.repeatable = true }
fun retention(retention: AnnotationRetention) = apply { this._retention = retention }

Check warning on line 47 in kotlin-code-generation/src/main/kotlin/builder/KotlinAnnotationClassSpecBuilder.kt

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

kotlin-code-generation/src/main/kotlin/builder/KotlinAnnotationClassSpecBuilder.kt#L47

The function retention is missing documentation.
fun target(vararg targets: AnnotationTarget) = apply { this.targets.addAll(targets) }

fun retention(retention: AnnotationRetention) = apply {
this._retention = retention
}

override fun addAnnotation(spec: KotlinAnnotationSpecSupplier) = apply { delegate.addAnnotation(spec.get()) }
override fun addConstructorProperty(spec: KotlinConstructorPropertySpecSupplier) = apply { constructorProperties[spec.name] = spec }
override fun contextReceivers(vararg receiverTypes: TypeName) = builder { this.contextReceivers(*receiverTypes) }
override fun addFunction(funSpec: KotlinFunSpecSupplier) = apply { delegate.addFunction(funSpec.get()) }
override fun addKdoc(kdoc: KDoc) = apply { delegate.addKdoc(kdoc.get()) }
override fun addModifiers(vararg modifiers: KModifier) = builder { this.addModifiers(*modifiers) }
override fun addProperty(propertySpec: KotlinPropertySpecSupplier) = apply { delegate.addProperty(propertySpec.get()) }
override fun addType(typeSpec: TypeSpecSupplier) = builder { this.addType(typeSpec.get()) }
override fun addTag(type: KClass<*>, tag: Any?) = builder { this.tag(type, tag) }

fun addOriginatingElement(originatingElement: Element) = builder { this.addOriginatingElement(originatingElement) }

override fun builder(block: TypeSpecBuilderReceiver) = apply { delegate.builder.block() }

override fun build(): KotlinAnnotationClassSpec {
if (constructorProperties.isNotEmpty()) {
delegate.primaryConstructorWithProperties(toList(constructorProperties.values))
val constructor = delegate.primaryConstructorWithProperties(toList(constructorProperties.values))
delegate.primaryConstructor(constructor.build())
}
if (targets.isNotEmpty()) {
delegate.addAnnotation(buildAnnotation(Target::class) {
Expand All @@ -87,6 +75,19 @@ class KotlinAnnotationClassSpecBuilder internal constructor(

return KotlinAnnotationClassSpec(className = className, spec = delegate.build())
}

// <overrides>
override fun addAnnotation(spec: KotlinAnnotationSpecSupplier) = apply { delegate.addAnnotation(spec.get()) }
override fun addConstructorProperty(spec: KotlinConstructorPropertySpecSupplier) = apply { constructorProperties[spec.name] = spec }
override fun contextReceivers(vararg receiverTypes: TypeName) = builder { this.contextReceivers(*receiverTypes) }
override fun addFunction(funSpec: KotlinFunSpecSupplier) = apply { delegate.addFunction(funSpec.get()) }
override fun addKdoc(kdoc: KDoc) = apply { delegate.addKdoc(kdoc.get()) }
override fun addModifiers(vararg modifiers: KModifier) = builder { this.addModifiers(*modifiers) }
override fun addProperty(propertySpec: KotlinPropertySpecSupplier) = apply { delegate.addProperty(propertySpec.get()) }
override fun addType(typeSpec: TypeSpecSupplier) = builder { this.addType(typeSpec.get()) }
override fun addTag(type: KClass<*>, tag: Any?) = builder { this.tag(type, tag) }
override fun builder(block: TypeSpecBuilderReceiver) = apply { delegate.builder.block() }
// </overrides>
}

@ExperimentalKotlinPoetApi
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ class KotlinAnnotationSpecBuilder internal constructor(
private var multiLine = false
private val members: MutableList<CodeBlock> = mutableListOf()

override fun addTag(type: KClass<*>, tag: Any?) = builder { this.tag(type, tag) }


fun multiLine() = apply { multiLine = true }

Expand All @@ -93,7 +93,6 @@ class KotlinAnnotationSpecBuilder internal constructor(

fun clearMembers() = apply { members.clear() }

override fun builder(block: AnnotationSpecBuilderReceiver) = apply { delegate.builder.block() }

override fun build(): KotlinAnnotationSpec {
if (members.isNotEmpty()) {
Expand All @@ -106,8 +105,12 @@ class KotlinAnnotationSpecBuilder internal constructor(
return KotlinAnnotationSpec(spec = delegate.build())
}

override fun spec(): KotlinAnnotationSpec = build()
// <overrides>
override fun addTag(type: KClass<*>, tag: Any?) = builder { this.tag(type, tag) }
override fun builder(block: AnnotationSpecBuilderReceiver) = apply { delegate.builder.block() }
override fun get(): AnnotationSpec = build().get()
override fun spec(): KotlinAnnotationSpec = build()
// </overrides>
}

@ExperimentalKotlinPoetApi
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,6 @@ class KotlinAnonymousClassSpecBuilder internal constructor(

internal constructor() : this(delegate = TypeSpecBuilder.anonymousClassBuilder())

override fun addAnnotation(spec: KotlinAnnotationSpecSupplier) = apply { delegate.addAnnotation(spec.get()) }
override fun contextReceivers(vararg receiverTypes: TypeName) = builder { this.contextReceivers(*receiverTypes) }
override fun addFunction(funSpec: KotlinFunSpecSupplier) = apply { delegate.addFunction(funSpec.get()) }
override fun addKdoc(kdoc: KDoc) = apply { delegate.addKdoc(kdoc.get()) }
override fun addModifiers(vararg modifiers: KModifier) = builder { this.addModifiers(*modifiers) }
override fun addProperty(propertySpec: KotlinPropertySpecSupplier) = apply { delegate.addProperty(propertySpec.get()) }
override fun addType(typeSpec: TypeSpecSupplier) = builder { this.addType(typeSpec.get()) }
override fun addTag(type: KClass<*>, tag: Any?) = builder { this.tag(type, tag) }

fun addOriginatingElement(originatingElement: Element) = builder { this.addOriginatingElement(originatingElement) }

fun addTypeVariable(typeVariable: TypeVariableName) = builder { this.addTypeVariable(typeVariable) }
Expand All @@ -51,13 +42,23 @@ class KotlinAnonymousClassSpecBuilder internal constructor(
fun addSuperclassConstructorParameter(format: String, vararg args: Any) = builder { this.addSuperclassConstructorParameter(format, *args) }
fun addSuperclassConstructorParameter(codeBlock: CodeBlock) = builder { this.addSuperclassConstructorParameter(codeBlock) }

override fun addSuperinterface(superinterface: TypeName, constructorParameter: String) = builder { this.addSuperinterface(superinterface, constructorParameter) }
override fun addSuperinterface(superinterface: TypeName, delegate: CodeBlock) = builder { this.addSuperinterface(superinterface, delegate) }

fun addInitializerBlock(block: CodeBlock) = builder { this.addInitializerBlock(block) }

override fun builder(block: TypeSpecBuilderReceiver) = apply { delegate.builder.block() }
override fun build(): KotlinAnonymousClassSpec = KotlinAnonymousClassSpec(delegate.build())

// <overrides>
override fun addAnnotation(spec: KotlinAnnotationSpecSupplier) = apply { delegate.addAnnotation(spec.get()) }
override fun contextReceivers(vararg receiverTypes: TypeName) = builder { this.contextReceivers(*receiverTypes) }
override fun addFunction(funSpec: KotlinFunSpecSupplier) = apply { delegate.addFunction(funSpec.get()) }
override fun addKdoc(kdoc: KDoc) = apply { delegate.addKdoc(kdoc.get()) }
override fun addModifiers(vararg modifiers: KModifier) = builder { this.addModifiers(*modifiers) }
override fun addProperty(propertySpec: KotlinPropertySpecSupplier) = apply { delegate.addProperty(propertySpec.get()) }
override fun addSuperinterface(superinterface: TypeName, constructorParameter: String) = builder { this.addSuperinterface(superinterface, constructorParameter) }
override fun addSuperinterface(superinterface: TypeName, delegate: CodeBlock) = builder { this.addSuperinterface(superinterface, delegate) }
override fun addType(typeSpec: TypeSpecSupplier) = builder { this.addType(typeSpec.get()) }
override fun addTag(type: KClass<*>, tag: Any?) = builder { this.tag(type, tag) }
override fun builder(block: TypeSpecBuilderReceiver) = apply { delegate.builder.block() }
// </overrides>
}

@ExperimentalKotlinPoetApi
Expand Down
Loading

0 comments on commit 1c7066f

Please sign in to comment.