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

se.ansman.kotshi.kapt.MetadataAccessor.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.kapt

import com.squareup.kotlinpoet.*
import com.squareup.kotlinpoet.metadata.specs.ClassInspector
import com.squareup.kotlinpoet.metadata.specs.toTypeSpec
import com.squareup.kotlinpoet.metadata.toKmClass
import kotlinx.metadata.KmClass
import kotlinx.metadata.isLocal
import se.ansman.kotshi.Errors.javaClassNotSupported
import javax.lang.model.element.Element
import javax.lang.model.element.TypeElement

class MetadataAccessor(private val classInspector: ClassInspector) {
    private val metadataPerType = mutableMapOf()
    private val kmClassPerMetadata = mutableMapOf()
    private val typeSpecPerKmClass = mutableMapOf()

    @OptIn(DelicateKotlinPoetApi::class) // OK because we are using the class name for comparison
    fun getMetadataOrNull(type: Element): Metadata? =
        metadataPerType.getOrPut((type as TypeElement).asClassName()) {
            type.getAnnotation(Metadata::class.java)
        }

    fun getMetadata(type: Element): Metadata =
        getMetadataOrNull(type)
            ?: throw KaptProcessingError(javaClassNotSupported, type)

    fun getKmClass(metadata: Metadata): KmClass = kmClassPerMetadata.getOrPut(metadata) { metadata.toKmClass() }
    fun getKmClass(type: Element): KmClass = getKmClass(getMetadata(type))
    fun getKmClassOrNull(type: Element): KmClass? = getMetadataOrNull(type)?.let(::getKmClass)

    fun getTypeSpec(type: Element): TypeSpec = getTypeSpec(getKmClass(type))
    fun getTypeSpecOrNull(type: Element): TypeSpec? = getKmClassOrNull(type)?.let(::getTypeSpec)

    fun getTypeSpec(metadata: KmClass): TypeSpec =
        typeSpecPerKmClass.getOrPut(metadata) {
            metadata.toTypeSpec(classInspector)
                .toBuilder()
                .tag(createClassName(metadata.name))
                .build()
        }
}

fun createClassName(kotlinMetadataName: String): ClassName {
    require(!kotlinMetadataName.isLocal) {
        "Local/anonymous classes are not supported!"
    }
    // Top-level: package/of/class/MyClass
    // Nested A:  package/of/class/MyClass.NestedClass
    val simpleName = kotlinMetadataName.substringAfterLast(
        '/', // Drop the package name, e.g. "package/of/class/"
        '.' // Drop any enclosing classes, e.g. "MyClass."
    )
    val packageName = kotlinMetadataName.substringBeforeLast(
        delimiter = "/",
        missingDelimiterValue = ""
    )
    val simpleNames = kotlinMetadataName.removeSuffix(simpleName)
        .removeSuffix(".") // Trailing "." if any
        .removePrefix(packageName)
        .removePrefix("/")
        .let {
            if (it.isNotEmpty()) {
                it.split(".")
            } else {
                // Don't split, otherwise we end up with an empty string as the first element!
                emptyList()
            }
        }
        .plus(simpleName)

    return ClassName(
        packageName = packageName.replace("/", "."),
        simpleNames = simpleNames
    )
}

@Suppress("SameParameterValue")
private fun String.substringAfterLast(vararg delimiters: Char): String {
    val index = lastIndexOfAny(delimiters)
    return if (index == -1) this else substring(index + 1, length)
}

val Metadata.languageVersion: KotlinVersion
    get() = KotlinVersion(
        major = metadataVersion[0],
        minor = metadataVersion[1],
        patch = metadataVersion.getOrElse(2) { 0 },
    )

val Metadata.supportsCreatingAnnotationsWithConstructor: Boolean
    get() = languageVersion.isAtLeast(1, 6)




© 2015 - 2024 Weber Informatics LLC | Privacy Policy