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

dev.onenowy.moshipolymorphicadapter.sealed.codegen.api.metadata.kt Maven / Gradle / Ivy

package dev.onenowy.moshipolymorphicadapter.sealed.codegen.api

import com.squareup.kotlinpoet.*
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
import com.squareup.kotlinpoet.metadata.isInternal
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.JsonReader
import com.squareup.moshi.JsonWriter
import dev.onenowy.moshipolymorphicadapter.*
import dev.onenowy.moshipolymorphicadapter.sealed.codegen.kapt.KotlinSealedCodegenProcessor
import dev.onenowy.moshipolymorphicadapter.sealed.codegen.kapt.KotlinSealedCodegenProcessor.Companion.COMMON_SUPPRESS
import dev.onenowy.moshipolymorphicadapter.sealed.codegen.kapt.KotlinSealedCodegenProcessor.Companion.defaultNullAnnotation
import dev.onenowy.moshipolymorphicadapter.sealed.codegen.kapt.KotlinSealedCodegenProcessor.Companion.moshiClass
import kotlinx.metadata.KmClass
import javax.annotation.processing.Messager
import javax.lang.model.element.TypeElement
import javax.tools.Diagnostic

internal fun adapterPropertySpec(
    generatorTag: List,
    element: TypeElement,
    sealedSubClasses: List,
    targetType: ClassName,
    moshiParam: ParameterSpec,
    jsonAdapterType: ParameterizedTypeName,
    allocator: NameAllocator,
    messager: Messager
): PropertySpec? {
    val adapterPropertyInit = if (generatorTag[0] == PolymorphicAdapterType.NAME_POLYMORPHIC_ADAPTER) {
        nameAdapterInitializer(element, sealedSubClasses).toBuilder()
    } else {
        valueAdapterInitializer(generatorTag, element, sealedSubClasses, messager)?.toBuilder() ?: return null
    }

    if (element.getAnnotation(defaultNullAnnotation) != null) {
        adapterPropertyInit.add(".withDefaultValue(%L)\n", null)
    }

    adapterPropertyInit.add(
        "  .create(%T::class.java, %M(), %N) as %T\n",
        targetType,
        MemberName("kotlin.collections", "emptySet"),
        moshiParam,
        jsonAdapterType
    )
    return PropertySpec.builder(allocator.newName("polymorphicAdapter"), jsonAdapterType, KModifier.PRIVATE)
        .addAnnotation(AnnotationSpec.builder(Suppress::class).addMember("%S", "UNCHECKED_CAST").build())
        .initializer(adapterPropertyInit.build()).build()
}

internal fun adapterClassSpec(
    element: TypeElement,
    kmClass: KmClass,
    adapterName: String,
    targetType: ClassName,
    generatorTag: List,
    sealedSubClasses: List,
    messager: Messager,
    generatedAnnotation: ClassName?
): TypeSpec? {
    val allocator = NameAllocator()
    val visibilityModifier = if (kmClass.flags.isInternal) KModifier.INTERNAL else KModifier.PUBLIC
    val moshiParam = ParameterSpec.builder(allocator.newName("moshi"), moshiClass).build()
    val jsonAdapterType = JsonAdapter::class.asClassName().parameterizedBy(targetType)
    val primaryConstructor = FunSpec.constructorBuilder().addParameter(moshiParam).build()

    val classBuilder =
        TypeSpec.classBuilder(adapterName).addAnnotation(COMMON_SUPPRESS).addModifiers(visibilityModifier)
            .superclass(jsonAdapterType).primaryConstructor(primaryConstructor).addOriginatingElement(element)

    generatedAnnotation?.let {
        classBuilder.addAnnotation(it)
    }
    val polymorphicAdapterProperty = adapterPropertySpec(
        generatorTag,
        element,
        sealedSubClasses,
        targetType,
        moshiParam,
        jsonAdapterType,
        allocator,
        messager
    ) ?: return null
    val nullableTargetType = targetType.copy(nullable = true)
    val readerParam = ParameterSpec(allocator.newName("reader"), JsonReader::class.asClassName())
    val writerParam = ParameterSpec(allocator.newName("writer"), JsonWriter::class.asClassName())
    val valueParam = ParameterSpec(allocator.newName("value"), nullableTargetType)
    classBuilder.addProperty(polymorphicAdapterProperty)
        .addFunction(
            FunSpec.builder("fromJson")
                .addModifiers(KModifier.OVERRIDE)
                .addParameter(readerParam)
                .returns(nullableTargetType)
                .addStatement("return %N.fromJson(%N)", polymorphicAdapterProperty, readerParam)
                .build()
        )
        .addFunction(
            FunSpec.builder("toJson")
                .addModifiers(KModifier.OVERRIDE)
                .addParameter(writerParam)
                .addParameter(valueParam)
                .addStatement("%N.toJson(%N, %N)", polymorphicAdapterProperty, writerParam, valueParam)
                .build()
        )
    return classBuilder.build()
}

@OptIn(DelicateKotlinPoetApi::class)
private fun nameAdapterInitializer(baseType: TypeElement, subClasses: List): CodeBlock {
    return buildCodeBlock {
        add(
            " %T.of(%T::class.java)\n",
            NamePolymorphicAdapterFactory::class,
            baseType.asClassName()
        )
        for (type in subClasses) {
            val nameLabel =
                type.getAnnotation(KotlinSealedCodegenProcessor.nameLabelAnnotation)
            add(
                ".withSubtype(%T::class.java, %S)\n",
                type.asClassName(),
                nameLabel.name
            )
        }
    }
}

@OptIn(DelicateKotlinPoetApi::class)
private fun valueAdapterInitializer(
    generatorTag: List,
    baseType: TypeElement,
    subClasses: List,
    messager: Messager
): CodeBlock? {
    val labelType = getSupportedTypeClass(generatorTag[0])
    return buildCodeBlock {
        add(
            "%T.of(%T::class.java, %S, %T::class.java)\n",
            ValuePolymorphicAdapterFactory::class,
            baseType.asClassName(),
            generatorTag[1],
            labelType
        )
        for (type in subClasses) {
            val labelValue =
                type.getAnnotation(KotlinSealedCodegenProcessor.valueLabelAnnotation)
            val value = labelValue.value.toSupportedTypeValueOrNull(generatorTag[0])
            if (value == null) {
                messager.printMessage(
                    Diagnostic.Kind.ERROR,
                    "${labelValue.value} cannot be cast to ${getSupportedTypeClass(generatorTag[1]).simpleName}"
                )
                return null
            }
            add(
                ".withSubtype(%T::class.java, %L)\n",
                type.asClassName(),
                value
            )
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy