se.ansman.kotshi.renderer.JsonAdapterFactoryRenderer.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.renderer
import com.squareup.kotlinpoet.*
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
import com.squareup.moshi.JsonAdapter
import se.ansman.kotshi.*
import se.ansman.kotshi.model.GeneratedAnnotation
import se.ansman.kotshi.model.JsonAdapterFactory
import se.ansman.kotshi.model.render
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) }
.apply {
when (factory.usageType) {
JsonAdapterFactory.UsageType.Standalone -> addSuperinterface(Types.Moshi.jsonAdapterFactory)
is JsonAdapterFactory.UsageType.Subclass -> {
if (factory.usageType.parentIsInterface) {
addSuperinterface(factory.usageType.parent)
} else {
superclass(factory.usageType.parent)
}
}
}
}
.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(")")
}
}