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

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

Go to download

An annotations processor that generates Moshi adapters from Kotlin data classes

There is a newer version: 3.0.0
Show newest version
package se.ansman.kotshi.renderer

import com.squareup.kotlinpoet.*
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.plusParameter
import com.squareup.kotlinpoet.jvm.throws
import se.ansman.kotshi.*
import se.ansman.kotshi.Types.Moshi.jsonReaderOptions
import se.ansman.kotshi.model.*

abstract class AdapterRenderer(private val adapter: GeneratableJsonAdapter) {
    private var isUsed = false
    protected val nameAllocator: NameAllocator = NameAllocator().apply {
        newName(moshiParameterName)
        newName(typesParameterName)
        newName("value")
        newName("writer")
        newName("reader")
        newName("stringBuilder")
        newName("it")
    }

    protected open fun TypeSpec.createProguardRule(): ProguardConfig? = null

    fun render(generatedAnnotation: GeneratedAnnotation?, typeSpecModifier: TypeSpec.Builder.() -> Unit = {}): GeneratedAdapter {
        check(!isUsed)
        isUsed = true
        val value = ParameterSpec.builder("value", adapter.targetType.nullable()).build()
        val writer = ParameterSpec.builder("writer", Types.Moshi.jsonWriter).build()
        val reader = ParameterSpec.builder("reader", Types.Moshi.jsonReader).build()
        val typeSpec = TypeSpec.classBuilder(adapter.adapterName)
            .addModifiers(KModifier.INTERNAL)
            .apply { generatedAnnotation?.toAnnotationSpec()?.let(::addAnnotation) }
            .addAnnotation(Types.Kotshi.internalKotshiApi)
            .addAnnotation(AnnotationSpec.builder(Types.Kotlin.suppress)
                // https://github.com/square/moshi/issues/1023
                .addMember("%S", "DEPRECATION")
                // Because we look it up reflectively
                .addMember("%S", "unused")
                // Because we include underscores
                .addMember("%S", "ClassName")
                // Because we generate redundant `out` variance for some generics and there's no way
                // for us to know when it's redundant.
                .addMember("%S", "REDUNDANT_PROJECTION")
                // Because we may generate redundant explicit types for local vars with default values.
                // Example: 'var fooSet: Boolean = false'
                .addMember("%S", "RedundantExplicitType")
                // NameAllocator will just add underscores to differentiate names, which Kotlin doesn't
                // like for stylistic reasons.
                .addMember("%S", "LocalVariableName")
                // KotlinPoet always generates explicit public modifiers for public members.
                .addMember("%S", "RedundantVisibilityModifier")
                // For LambdaTypeNames we have to import kotlin.functions.* types
                .addMember("%S", "PLATFORM_CLASS_MAPPED_TO_KOTLIN")
                // Cover for calling fromJson() on a Nothing property type. Theoretically nonsensical but we
                // support it
                .addMember("%S", "IMPLICIT_NOTHING_TYPE_ARGUMENT_IN_RETURN_POSITION")
                // In case the consumer is using experimental APIs
                .addMember("%S", "EXPERIMENTAL_API_USAGE")
                // Similarly for opt in usage
                .addMember("%S", "OPT_IN_USAGE")
                .build())
            .addTypeVariables(adapter.targetTypeVariables.map { it.withoutVariance() })
            .superclass(Types.Kotshi.namedJsonAdapter.plusParameter(adapter.targetType))
            .addSuperclassConstructorParameter(
                "%S",
                "KotshiJsonAdapter(${adapter.targetSimpleNames.joinToString(".")})"
            )
            .apply { renderSetup() }
            .addFunction(FunSpec.builder("toJson")
                .addModifiers(KModifier.OVERRIDE)
                .throws(Types.Java.ioException)
                .addParameter(writer)
                .addParameter(value)
                .apply { renderToJson(writer, value) }
                .build())
            .addFunction(FunSpec.builder("fromJson")
                .addModifiers(KModifier.OVERRIDE)
                .throws(Types.Java.ioException)
                .addParameter(reader)
                .returns(adapter.targetType.nullable())
                .apply { renderFromJson(reader) }
                .build())
            .apply(typeSpecModifier)
            .build()
        return GeneratedAdapter(
            adapter = adapter,
            fileSpec = FileSpec.builder(adapter.targetPackageName, adapter.adapterName)
                .addFileComment("Code generated by Kotshi. Do not edit.")
                .addType(typeSpec)
                .build(),
            proguardConfig = typeSpec.createProguardRule()
        )
    }

    protected open fun TypeSpec.Builder.renderSetup() {}
    protected abstract fun FunSpec.Builder.renderFromJson(readerParameter: ParameterSpec)
    protected abstract fun FunSpec.Builder.renderToJson(writerParameter: ParameterSpec, valueParameter: ParameterSpec)

    protected fun jsonOptionsProperty(jsonNames: Collection): PropertySpec =
        PropertySpec.builder(nameAllocator.newName("options"), jsonReaderOptions, KModifier.PRIVATE)
            .initializer(
                CodeBlock.Builder()
                    .add("%T.of(«", jsonReaderOptions)
                    .applyIf(jsonNames.size > 1) { add("\n") }
                    .applyEachIndexed(jsonNames) { index, name ->
                        if (index > 0) {
                            add(",\n")
                        }
                        add("%S", name)
                    }
                    .applyIf(jsonNames.size > 1) { add("\n") }
                    .add("»)")
                    .build())
            .build()

    companion object {
        const val moshiParameterName = "moshi"
        const val typesParameterName = "types"
    }
}

fun GeneratableJsonAdapter.createRenderer(
    createAnnotationsUsingConstructor: Boolean,
    useLegacyDataClassRenderer: Boolean,
    error: (String) -> Throwable,
): AdapterRenderer =
    when (this) {
        is DataClassJsonAdapter ->
            if (useLegacyDataClassRenderer) {
                LegacyDataClassAdapterRenderer(this, createAnnotationsUsingConstructor)
            } else {
                DataClassAdapterRenderer(this, createAnnotationsUsingConstructor)
            }
        is EnumJsonAdapter -> EnumAdapterRenderer(this)
        is ObjectJsonAdapter -> ObjectAdapterRenderer(this)
        is SealedClassJsonAdapter -> SealedClassAdapterRenderer(this, error)
    }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy