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

se.ansman.kotshi.kapt.FactoryProcessingStep.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
@file:Suppress("UnstableApiUsage")

package se.ansman.kotshi.kapt

import com.google.auto.common.MoreElements
import com.google.auto.common.MoreTypes
import com.google.common.collect.SetMultimap
import com.squareup.kotlinpoet.DelicateKotlinPoetApi
import com.squareup.kotlinpoet.asTypeName
import com.squareup.kotlinpoet.asTypeVariableName
import com.squareup.kotlinpoet.metadata.*
import com.squareup.moshi.JsonAdapter
import se.ansman.kotshi.*
import se.ansman.kotshi.Errors.abstractFactoriesAreDeprecated
import se.ansman.kotshi.model.*
import se.ansman.kotshi.model.JsonAdapterFactory.Companion.getManualAdapter
import se.ansman.kotshi.renderer.JsonAdapterFactoryRenderer
import javax.annotation.processing.Filer
import javax.annotation.processing.Messager
import javax.annotation.processing.RoundEnvironment
import javax.lang.model.element.Element
import javax.lang.model.element.ElementKind
import javax.lang.model.element.Modifier
import javax.lang.model.element.TypeElement
import javax.lang.model.type.TypeKind
import javax.lang.model.type.TypeMirror
import javax.lang.model.util.Elements
import javax.lang.model.util.Types
import kotlin.reflect.KClass

class FactoryProcessingStep(
    override val processor: KotshiProcessor,
    private val messager: Messager,
    override val filer: Filer,
    private val types: Types,
    private val elements: Elements,
    private val generatedAnnotation: GeneratedAnnotation?,
    private val generatedAdapters: List,
    private val metadataAccessor: MetadataAccessor,
    private val createAnnotationsUsingConstructor: Boolean?,
) : KotshiProcessor.GeneratingProcessingStep() {

    @OptIn(ExperimentalKotshiApi::class)
    override val annotations: Set> = setOf(
        KotshiJsonAdapterFactory::class.java,
        RegisterJsonAdapter::class.java,
    )

    override fun process(
        elementsByAnnotation: SetMultimap, Element>,
        roundEnv: RoundEnvironment
    ) {
        val elements = elementsByAnnotation[KotshiJsonAdapterFactory::class.java]
            .map(MoreElements::asType)
        val manuallyRegisteredAdapters = elementsByAnnotation.getManualAdapters(elements.isNotEmpty()).toList()
        if (elements.size > 1) {
            messager.logKotshiError(Errors.multipleFactories(elements.map { it.qualifiedName.toString() }), elements.first())
        } else for (element in elements) {
            try {
                generateFactory(element, manuallyRegisteredAdapters)
            } catch (e: KaptProcessingError) {
                messager.logKotshiError(e)
            }
        }
    }

    private fun generateFactory(element: TypeElement, manuallyRegisteredAdapters: List) {
        val elementClassName = createClassName(metadataAccessor.getKmClass(element).name)
        val factory = JsonAdapterFactory(
            targetType = elementClassName,
            usageType = if (
                element.asType().implements(JsonAdapter.Factory::class) &&
                Modifier.ABSTRACT in element.modifiers
            ) {
                messager.logKotshiWarning(abstractFactoriesAreDeprecated, element)
                JsonAdapterFactory.UsageType.Subclass(elementClassName, parentIsInterface = element.kind == ElementKind.INTERFACE)
            } else {
                JsonAdapterFactory.UsageType.Standalone
            },
            generatedAdapters = generatedAdapters,
            manuallyRegisteredAdapters = manuallyRegisteredAdapters,
        )

        val createAnnotationsUsingConstructor =
            createAnnotationsUsingConstructor ?: metadataAccessor.getMetadata(element).supportsCreatingAnnotationsWithConstructor

        JsonAdapterFactoryRenderer(factory, createAnnotationsUsingConstructor)
            .render(generatedAnnotation) {
                addOriginatingElement(element)
            }
            .writeTo(filer)
    }

    @OptIn(DelicateKotlinPoetApi::class, ExperimentalKotshiApi::class)
    private fun SetMultimap, Element>.getManualAdapters(hasFactory: Boolean): Sequence =
        this[RegisterJsonAdapter::class.java]
            .asSequence()
            .map { MoreElements.asType(it) }
            .mapNotNull { element ->
                val kmClass = metadataAccessor.getKmClassOrNull(element) ?: run {
                    messager.logKotshiError(Errors.javaClassNotSupported, element)
                    return@mapNotNull null
                }
                if (!kmClass.isObject && (!kmClass.isClass ||
                        kmClass.flags.isAbstract ||
                        kmClass.flags.isLocal ||
                        kmClass.flags.isInnerClass ||
                        kmClass.flags.isSealed ||
                        kmClass.flags.isEnumClass
                        )
                ) {
                    messager.logKotshiError(Errors.invalidRegisterAdapterType, element)
                    return@mapNotNull null
                }
                if (!kmClass.flags.isPublic && !kmClass.flags.isInternal) {
                    messager.logKotshiError(Errors.invalidRegisterAdapterVisibility, element)
                    return@mapNotNull null
                }

                if (!hasFactory) {
                    messager.logKotshiError(Errors.registeredAdapterWithoutFactory, element)
                }

                val annotation = element.getAnnotation(RegisterJsonAdapter::class.java)!!
                element.getManualAdapter(
                    logError = messager::logKotshiError,
                    getSuperClass = {
                        superclass.takeUnless { it.kind == TypeKind.NONE }?.let(MoreTypes::asTypeElement)
                    },
                    getSuperTypeName = { metadataAccessor.getTypeSpecOrNull(this)?.superclass ?: superclass.asTypeName() },
                    adapterClassName = createClassName(kmClass.name),
                    typeVariables = { metadataAccessor.getTypeSpecOrNull(this)?.typeVariables ?: typeParameters.map { it.asTypeVariableName() } },
                    isObject = kmClass.flags.isObjectClass || kmClass.flags.isCompanionObjectClass,
                    isAbstract = kmClass.flags.isInterface || kmClass.flags.isAbstract || kmClass.flags.isSealed,
                    priority = annotation.priority,
                    getKotshiConstructor = {
                        metadataAccessor.getTypeSpec(this).constructors().findKotshiConstructor(
                            parameters = { parameters },
                            type = { type },
                            hasDefaultValue = { defaultValue != null }
                        ) { name }
                    },
                    getJsonQualifiers = {
                        metadataAccessor.getTypeSpec(this).annotationSpecs.qualifiers(metadataAccessor)
                    }
                )
            }

    private fun TypeMirror.implements(someType: KClass<*>): Boolean =
        types.isSubtype(this, elements.getTypeElement(someType.java.canonicalName).asType())
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy