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

se.ansman.kotshi.renderer.JsonAdapterFactoryRenderer.kt Maven / Gradle / Ivy

Go to download

An annotations processor that generates Moshi adapters from Kotlin data classes

The newest version!
package se.ansman.kotshi.renderer

import com.squareup.kotlinpoet.AnnotationSpec
import com.squareup.kotlinpoet.CodeBlock
import com.squareup.kotlinpoet.FileSpec
import com.squareup.kotlinpoet.FunSpec
import com.squareup.kotlinpoet.KModifier
import com.squareup.kotlinpoet.NOTHING
import com.squareup.kotlinpoet.NameAllocator
import com.squareup.kotlinpoet.ParameterSpec
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
import com.squareup.kotlinpoet.PropertySpec
import com.squareup.kotlinpoet.STAR
import com.squareup.kotlinpoet.TypeName
import com.squareup.kotlinpoet.TypeSpec
import com.squareup.kotlinpoet.asClassName
import com.squareup.moshi.JsonAdapter
import se.ansman.kotshi.Functions
import se.ansman.kotshi.KotshiConstructor
import se.ansman.kotshi.Types
import se.ansman.kotshi.addControlFlow
import se.ansman.kotshi.applyEachIndexed
import se.ansman.kotshi.applyIf
import se.ansman.kotshi.hasParameters
import se.ansman.kotshi.mapTypeArguments
import se.ansman.kotshi.model.GeneratedAnnotation
import se.ansman.kotshi.model.JsonAdapterFactory
import se.ansman.kotshi.model.render
import se.ansman.kotshi.nullable
import se.ansman.kotshi.rawType
import se.ansman.kotshi.unwrapTypeVariables

internal class JsonAdapterFactoryRenderer(
    private val factory: JsonAdapterFactory,
    private val createAnnotationsUsingConstructor: Boolean
) {
    private val nameAllocator = NameAllocator()

    fun render(generatedAnnotation: GeneratedAnnotation?, typeSpecModifier: TypeSpec.Builder.() -> Unit): FileSpec {
        val annotations = mutableSetOf()
        val properties = mutableSetOf()
        val createFunction = makeCreateFunction(
            typeParam = ParameterSpec(nameAllocator.newName("type"), Types.Java.type),
            annotationsParam = ParameterSpec(
                nameAllocator.newName("annotations"),
                Types.Kotlin.set.parameterizedBy(Types.Kotlin.annotation)
            ),
            moshiParam = ParameterSpec(nameAllocator.newName("moshi"), Types.Moshi.moshi),
            annotations = annotations,
            properties = properties,
        )
        return FileSpec.builder(factory.factoryClassName.packageName, factory.factoryClassName.simpleName)
            .addFileComment("Code generated by Kotshi. Do not edit.")
            .addAnnotation(
                AnnotationSpec.builder(Types.Kotlin.suppress)
                    .addMember("%S", "EXPERIMENTAL_IS_NOT_ENABLED")
                    .addMember("%S", "REDUNDANT_PROJECTION")
                    .build()
            )
            .addType(
                TypeSpec.objectBuilder(factory.factoryClassName)
                    .addModifiers(KModifier.INTERNAL)
                    .apply { generatedAnnotation?.toAnnotationSpec()?.let(::addAnnotation) }
                    .addSuperinterface(Types.Moshi.jsonAdapterFactory)
                    .addAnnotations(annotations)
                    .addProperties(properties)
                    .addFunction(createFunction)
                    .apply(typeSpecModifier)
                    .addAnnotation(
                        AnnotationSpec.builder(Types.Kotlin.optIn)
                            .addMember("%T::class", Types.Kotshi.internalKotshiApi)
                            .build()
                    )
                    .build()
            )
            .build()
    }

    private fun makeCreateFunction(
        typeParam: ParameterSpec,
        annotationsParam: ParameterSpec,
        moshiParam: ParameterSpec,
        annotations: MutableSet,
        properties: MutableSet,
    ): FunSpec {
        val createSpec = FunSpec.builder("create")
            .addModifiers(KModifier.OVERRIDE)
            .returns(JsonAdapter::class.asClassName().parameterizedBy(STAR).nullable())
            .addParameter(typeParam)
            .addParameter(annotationsParam)
            .addParameter(moshiParam)

        if (factory.isEmpty) {
            return createSpec
                .addStatement("return null")
                .build()
        }


        val rawType = PropertySpec.builder(nameAllocator.newName("rawType"), Types.Java.clazz.parameterizedBy(STAR))
            .initializer("%M(%N)", Functions.Moshi.getRawType, typeParam)
            .build()

        return createSpec
            .addCode("%L", rawType)
            .applyIf(factory.manuallyRegisteredAdapters.isNotEmpty()) {
                addRegisteredAdapters(typeParam, annotationsParam, moshiParam, rawType, annotations, properties)
            }
            .addGeneratedAdapters(typeParam, annotationsParam, moshiParam, rawType)
            .build()
    }

    private fun FunSpec.Builder.addRegisteredAdapters(
        typeParam: ParameterSpec,
        annotationsParam: ParameterSpec,
        moshiParam: ParameterSpec,
        rawType: PropertySpec,
        annotations: MutableSet,
        properties: MutableSet,
    ): FunSpec.Builder = addControlFlow("when") {
        for (adapter in factory.manuallyRegisteredAdapters.sorted()) {
            addCode("%N·==·%T::class.java", rawType, adapter.targetType.rawType())
            if (adapter.qualifiers.isNotEmpty()) {
                val qualifiers = PropertySpec
                    .builder(
                        nameAllocator.newName(adapter.adapterClassName.simpleName.replaceFirstChar(Char::lowercaseChar) + "Qualifiers"),
                        Set::class.parameterizedBy(Annotation::class)
                    )
                    .addModifiers(KModifier.PRIVATE)
                    .initializer(CodeBlock.Builder()
                        .add("%M(⇥", Functions.Kotlin.setOf)
                        .applyEachIndexed(adapter.qualifiers) { i, qualifier ->
                            if (i > 0) add(",")
                            add("\n")
                            add(qualifier.render(createAnnotationsUsingConstructor))
                        }
                        .add("⇤\n)")
                        .build())
                    .build()
                    .also(properties::add)
                addCode("·&&·\n⇥%N == %N⇤", annotationsParam, qualifiers)
            }
            if (adapter.requiresDeepTypeCheck) {
                addCode(
                    "·&&·\n⇥%M(%M<%T>().%M, %N)⇤",
                    Functions.Kotshi.matches,
                    Functions.Kotlin.typeOf,
                    adapter.targetType.unwrapTypeVariables(),
                    Functions.Kotlin.javaType,
                    typeParam
                )
                annotations.add(AnnotationSpec.builder(Types.Kotlin.experimentalStdlibApi).build())
            }
            addCode("·->« return·")
            val constructor = adapter.constructor
            if (constructor == null) {
                addCode("%T", adapter.adapterTypeName)
            } else {
                addAdapterConstructorCall(
                    adapterTypeName = adapter.adapterTypeName,
                    constructor = adapter.constructor,
                    moshiParam = moshiParam,
                    typeParam = typeParam,
                )
            }
            addCode("»\n")
        }
    }

    private fun FunSpec.Builder.addGeneratedAdapters(
        typeParam: ParameterSpec,
        annotationsParam: ParameterSpec,
        moshiParam: ParameterSpec,
        rawType: PropertySpec,
    ): FunSpec.Builder =
        if (factory.generatedAdapters.isEmpty()) {
            addCode("return·null")
        } else {
            addStatement("if·(%N.isNotEmpty()) return·null", annotationsParam)
                .addCode("\n")
                .addControlFlow("return·when·(%N)", rawType) {
                    for (adapter in factory.generatedAdapters.sorted()) {
                        addCode("%T::class.java·->«\n", adapter.adapter.targetType.rawType())
                        addAdapterConstructorCall(
                            adapterTypeName = adapter.adapter.adapterTypeName,
                            constructor = adapter.constructor,
                            moshiParam = moshiParam,
                            typeParam = typeParam,
                        )
                        addCode("»\n")
                    }
                    addStatement("else -> null")
                }
        }

    private fun FunSpec.Builder.addAdapterConstructorCall(
        adapterTypeName: TypeName,
        constructor: KotshiConstructor,
        moshiParam: ParameterSpec,
        typeParam: ParameterSpec,
    ): FunSpec.Builder = apply {
        addCode("%T(", adapterTypeName.mapTypeArguments { NOTHING })
        if (constructor.hasParameters) {
            addCode("⇥")
        }

        if (constructor.moshiParameterName != null) {
            addCode("\n%N = %N", constructor.moshiParameterName, moshiParam)
        }
        if (constructor.typesParameterName != null) {
            if (constructor.moshiParameterName != null) {
                addCode(",")
            }
            addCode(
                "\n%N = %N.%M",
                constructor.typesParameterName,
                typeParam,
                Functions.Kotshi.typeArgumentsOrFail
            )
        }
        if (constructor.hasParameters) {
            addCode("⇤\n")
        }
        addCode(")")
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy