All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jetbrains.dataframe.ksp.PropertyRenderer.kt Maven / Gradle / Ivy

package org.jetbrains.dataframe.ksp

import com.google.devtools.ksp.KspExperimental
import com.google.devtools.ksp.innerArguments
import com.google.devtools.ksp.isAnnotationPresent
import com.google.devtools.ksp.symbol.KSClassDeclaration
import com.google.devtools.ksp.symbol.KSType
import com.google.devtools.ksp.symbol.KSTypeArgument
import com.google.devtools.ksp.symbol.KSTypeParameter
import com.google.devtools.ksp.symbol.KSTypeReference
import com.google.devtools.ksp.symbol.Variance
import org.jetbrains.kotlinx.dataframe.annotations.DataSchema
import org.jetbrains.kotlinx.dataframe.codeGen.AbstractMarker
import org.jetbrains.kotlinx.dataframe.codeGen.BaseField
import org.jetbrains.kotlinx.dataframe.codeGen.ExtensionsCodeGenerator
import org.jetbrains.kotlinx.dataframe.codeGen.FieldType
import org.jetbrains.kotlinx.dataframe.codeGen.MarkerVisibility
import org.jetbrains.kotlinx.dataframe.codeGen.ValidFieldName

@OptIn(KspExperimental::class)
internal fun renderExtensions(
    declaration: KSClassDeclaration,
    interfaceName: String,
    visibility: MarkerVisibility,
    properties: List,
): String {
    val generator = ExtensionsCodeGenerator.create()
    val typeArguments = declaration.typeParameters.map {
        it.name.asString()
    }

    val typeParameters = declaration.typeParameters.map {
        buildString {
            append(it.name.asString())
            val bounds = it.bounds.toList()
            if (bounds.isNotEmpty()) {
                append(" : ")
                append(bounds.joinToString(",") { it.resolve().render() })
            }
        }
    }
    return generator.generate(object : AbstractMarker(typeParameters, typeArguments) {
        override val name: String = interfaceName
        override val fields: List = properties.map {
            val type = it.propertyType.resolve()
            val qualifiedTypeReference = getQualifiedTypeReference(type)
            val fieldType = when {
                qualifiedTypeReference == "kotlin.collections.List" &&
                    type.singleTypeArgumentIsDataSchema() ||
                    qualifiedTypeReference == DataFrameNames.DATA_FRAME ->
                    FieldType.FrameFieldType(
                        markerName = type.renderTypeArguments(),
                        nullable = type.isMarkedNullable,
                        renderAsList = true,
                    )

                type.declaration.isAnnotationPresent(DataSchema::class) ->
                    FieldType.GroupFieldType(markerName = type.render(), renderAsObject = true)

                qualifiedTypeReference == DataFrameNames.DATA_ROW ->
                    FieldType.GroupFieldType(
                        markerName = type.renderTypeArguments(),
                        renderAsObject = false,
                    )

                else -> FieldType.ValueFieldType(type.render())
            }

            BaseFieldImpl(
                fieldName = ValidFieldName.of(it.fieldName),
                columnName = it.columnName,
                fieldType = fieldType,
            )
        }

        override val visibility: MarkerVisibility = visibility
    }).declarations
}

private fun getQualifiedTypeReference(type: KSType) =
    when (val declaration = type.declaration) {
        is KSTypeParameter -> declaration.name.getShortName()
        else -> declaration.getQualifiedNameOrThrow()
    }

@OptIn(KspExperimental::class)
private fun KSType.singleTypeArgumentIsDataSchema() =
    innerArguments.singleOrNull()?.type?.resolve()?.declaration?.isAnnotationPresent(DataSchema::class) ?: false

private fun KSType.render(): String {
    val fqTypeReference = getQualifiedTypeReference(this)
    return buildString {
        append(fqTypeReference)
        if (innerArguments.isNotEmpty()) {
            append("<")
            append(renderTypeArguments())
            append(">")
        }
        if (isMarkedNullable) {
            append("?")
        }
    }
}

private fun KSType.renderTypeArguments(): String = innerArguments.joinToString(", ") { render(it) }

private fun render(typeArgument: KSTypeArgument): String =
    when (val variance = typeArgument.variance) {
        Variance.STAR -> variance.label

        Variance.INVARIANT, Variance.COVARIANT, Variance.CONTRAVARIANT ->
            buildString {
                append(variance.label)
                if (variance.label.isNotEmpty()) {
                    append(" ")
                }
                append(
                    typeArgument.type?.resolve()?.render()
                        ?: error("typeArgument.type should only be null for Variance.STAR"),
                )
            }
    }

internal class Property(val columnName: String, val fieldName: String, val propertyType: KSTypeReference)

internal class BaseFieldImpl(
    override val fieldName: ValidFieldName,
    override val columnName: String,
    override val fieldType: FieldType,
) : BaseField




© 2015 - 2024 Weber Informatics LLC | Privacy Policy