Coverage Summary for Class: GhostCodeGenerator (com.ghost.serialization.compiler)
| Class |
Class, %
|
Method, %
|
Branch, %
|
Line, %
|
Instruction, %
|
| GhostCodeGenerator |
100%
(1/1)
|
100%
(3/3)
|
90%
(27/30)
|
98.4%
(122/124)
|
98.7%
(528/535)
|
@file:Suppress("ReplaceSizeCheckWithIsNotEmpty")
package com.ghost.serialization.compiler
import com.squareup.kotlinpoet.AnnotationSpec
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.FileSpec
import com.squareup.kotlinpoet.KModifier
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
import com.squareup.kotlinpoet.PropertySpec
import com.squareup.kotlinpoet.TypeSpec
import com.google.devtools.ksp.symbol.KSClassDeclaration
import com.ghost.serialization.compiler.GhostEmitterConstants as C
/**
* Orchestrates generation of a specialized GhostSerializer companion object.
*
* Delegates import planning to [SerializerImportResolver], companion constants to
* [SerializerSetupEmitter], and serialize/deserialize bodies to the *Emitter types.
*/
internal class GhostCodeGenerator(
properties: List<GhostPropertyModel>,
classDeclaration: KSClassDeclaration,
textChannel: Boolean = false,
envelopeModel: GhostEnvelopeModel? = null,
) {
private val ctx = GhostSerializerContext.from(
properties = properties,
classDeclaration = classDeclaration,
textChannel = textChannel,
envelopeModel = envelopeModel,
)
private val importResolver = SerializerImportResolver(ctx)
private val setupEmitter = SerializerSetupEmitter(ctx)
fun createSpec(): FileSpec {
val fileBuilder = FileSpec.builder(ctx.packageName, ctx.serializerName)
.addAnnotation(
AnnotationSpec.builder(ClassName(C.PKG_KOTLIN, C.STR_OPT_IN))
.addMember(
C.MARKER_CLASS,
ClassName(C.PKG_GHOST, C.STR_INTERNAL_GHOST_API)
)
.build()
)
importResolver.applyTo(fileBuilder)
return fileBuilder
.apply {
if (ctx.envelopeModel?.payloadMappings?.any { it.targetType != null } == true) {
addImport(C.PKG_TYPES, C.STR_RAW_JSON_DECODE)
addImport(C.PKG_GHOST, C.STR_GHOST)
addImport(C.PKG_CONTRACT, C.STR_GHOST_SERIALIZER)
}
}
.addType(buildSerializerObject())
.build()
}
private fun buildSerializerObject(): TypeSpec {
val serializeEmitter = SerializeCodeEmitter(
ctx.properties,
ctx.originalClassName,
ctx.isSealed,
ctx.isValue,
ctx.isEnum,
ctx.sealedSubclasses,
ctx.discriminator,
ctx.sealedDiscriminatorKey
)
val deserializeEmitterStreaming = DeserializeCodeEmitter(
ctx.properties,
ctx.originalClassName,
ctx.streamingReaderClass,
ctx.isSealed,
ctx.isValue,
ctx.isEnum,
ctx.sealedSubclasses,
ctx.sealedDiscriminatorKey,
ctx.isResilient,
ctx.isInferred,
ctx.isObject,
hasFallback = ctx.hasFallbackEnum
)
val deserializeEmitterFlat = DeserializeCodeEmitter(
ctx.properties,
ctx.originalClassName,
ctx.flatReaderClass,
ctx.isSealed,
ctx.isValue,
ctx.isEnum,
ctx.sealedSubclasses,
ctx.sealedDiscriminatorKey,
ctx.isResilient,
ctx.isInferred,
ctx.isObject,
hasFallback = ctx.hasFallbackEnum
)
val deserializeEmitterString = if (ctx.textChannel) {
DeserializeCodeEmitter(
ctx.properties,
ctx.originalClassName,
ctx.stringReaderClass,
ctx.isSealed,
ctx.isValue,
ctx.isEnum,
ctx.sealedSubclasses,
ctx.sealedDiscriminatorKey,
ctx.isResilient,
ctx.isInferred,
ctx.isObject,
hasFallback = ctx.hasFallbackEnum
)
} else {
null
}
val typeSpecBuilder = TypeSpec.objectBuilder(ctx.serializerName)
.addKdoc(C.STR_KDOC_HIGH_PERF, ctx.originalClassName)
.addKdoc(C.STR_KDOC_GENERATED)
.addSuperinterface(ctx.serializerInterface.parameterizedBy(ctx.originalClassName))
.addProperty(
PropertySpec.builder(C.STR_TYPE_NAME_PROP, String::class)
.addModifiers(KModifier.OVERRIDE)
.initializer(C.MARKER, ctx.finalTypeName)
.build()
)
if (ctx.isProto) {
typeSpecBuilder.addProperty(
PropertySpec.builder(C.STR_IS_PROTO, com.squareup.kotlinpoet.BOOLEAN)
.addModifiers(KModifier.OVERRIDE)
.initializer(C.STR_TRUE)
.build()
)
}
if (ctx.needsObjectParsingImports()) {
setupEmitter.addPerfectHashOptions(typeSpecBuilder)
}
if (ctx.needsCachedByteStringHeaders()) {
setupEmitter.addCachedHeaderProperties(typeSpecBuilder)
}
if (ctx.isEnum && ctx.enumValues != null) {
setupEmitter.addEnumOptions(typeSpecBuilder)
}
FlattenOptionsGenerator.generateNestedOptions(
typeSpecBuilder,
ctx.properties,
ctx.fullPaths,
ctx.readerClass,
ctx.textChannel
)
deserializeEmitterStreaming.build(typeSpecBuilder, isFlatPath = false)
deserializeEmitterFlat.build(typeSpecBuilder, isFlatPath = true)
if (ctx.textChannel) {
deserializeEmitterString?.build(typeSpecBuilder, isFlatPath = true)
}
serializeEmitter.injectContextualSerializers(typeSpecBuilder)
ctx.envelopeModel?.let { envelope ->
EnvelopeRouterEmitter(
envelope = envelope,
originalClassName = ctx.originalClassName,
flatReaderClass = ctx.flatReaderClass
).emit(typeSpecBuilder)
}
return typeSpecBuilder
.addFunction(serializeEmitter.build(ctx.streamingWriterClass, typeSpecBuilder))
.addFunction(serializeEmitter.build(ctx.flatWriterClass, typeSpecBuilder))
.apply {
if (ctx.textChannel) {
addFunction(serializeEmitter.build(ctx.stringWriterClass, typeSpecBuilder))
}
}
.addFunction(setupEmitter.buildWarmUpMethod())
.build()
}
}