se.ansman.kotshi.renderer.AdapterRenderer.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.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)
}