se.ansman.kotshi.kapt.MetadataAccessor.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of compiler Show documentation
Show all versions of compiler Show documentation
An annotations processor that generates Moshi adapters from Kotlin data classes
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)