Coverage Summary for Class: SerializeCodeEmitter (com.ghost.serialization.compiler)

Class Class, % Method, % Branch, % Line, % Instruction, %
SerializeCodeEmitter 100% (1/1) 100% (7/7) 58.7% (27/46) 90% (90/100) 89.9% (426/474)


 package com.ghost.serialization.compiler
 
 import com.google.devtools.ksp.symbol.KSClassDeclaration
 import com.squareup.kotlinpoet.ClassName
 import com.squareup.kotlinpoet.CodeBlock
 import com.squareup.kotlinpoet.FunSpec
 import com.squareup.kotlinpoet.KModifier
 import com.squareup.kotlinpoet.TypeSpec
 import com.squareup.kotlinpoet.ksp.toClassName
 import com.ghost.serialization.compiler.GhostEmitterConstants as C
 
 /**
  * Main coordinator (Orchestrator) for the serialization code generation process.
  *
  * This class implements the Strategy Pattern to select the most efficient serialization
  * strategy for a given DTO. It inspects the DTO's metadata (sealed hierarchy, enum, size)
  * and delegates the generation task to specialized emitters.
  */
 internal class SerializeCodeEmitter(
     private val properties: List<GhostPropertyModel>,
     private val originalClassName: ClassName,
     private val isSealed: Boolean,
     private val isValue: Boolean,
     private val isEnum: Boolean,
     private val sealedSubclasses: List<KSClassDeclaration>,
     private val discriminator: String? = null,
     private val sealedDiscriminatorKey: String = C.DEFAULT_DISCRIMINATOR_KEY
 ) {
     // Sorts properties to group flattened paths together, avoiding duplicate bracket opens/closes.
     private val sortedProperties = run {
         val rootToFirstIndex = mutableMapOf<String, Int>()
 
         properties.forEachIndexed { index, prop ->
             val root = prop.flattenPath?.firstOrNull()
                 ?: prop.wrapPath?.firstOrNull()
                 ?: prop.jsonName
 
             rootToFirstIndex.putIfAbsent(root, index)
         }
 
         val propertyPaths = properties.associateWith { prop ->
             prop.flattenPath?.joinToString(C.STR_DOT) ?: prop.jsonName
         }
 
         properties.sortedWith { p1, p2 ->
             val root1 = p1.flattenPath?.firstOrNull()
                 ?: p1.wrapPath?.firstOrNull()
                 ?: p1.jsonName
 
             val root2 = p2.flattenPath?.firstOrNull()
                 ?: p2.wrapPath?.firstOrNull()
                 ?: p2.jsonName
 
             val index1 = rootToFirstIndex[root1]!!
             val index2 = rootToFirstIndex[root2]!!
 
             if (index1 != index2) {
                 index1.compareTo(index2)
             } else {
                 val path1 = propertyPaths[p1]!!
                 val path2 = propertyPaths[p2]!!
                 path1.compareTo(path2)
             }
         }
     }
 
     private var activeEmitter: BaseSerializeEmitter? = null
 
     /**
      * Builds the [FunSpec] of the serialize function.
      *
      * @param writerClass The JSON writer class being targeted.
      * @param typeSpecBuilder Companion object builder where nested options or shards can be registered.
      * @return Generated KotlinPoet FunSpec for serialization.
      */
     fun build(
         writerClass: ClassName,
         typeSpecBuilder: TypeSpec.Builder
     ): FunSpec {
         val code = CodeBlock.builder()
 
         when {
             isSealed -> {
                 emitSealedDispatch(code)
             }
             isValue -> {
                 emitValueUnboxing(code)
             }
             isEnum -> {
                 emitEnumSerialization(code)
             }
             properties.size > C.PROPERTY_MAX_SIZE -> {
                 val fragmented = FragmentedSerializeEmitter(
                     sortedProperties,
                     originalClassName,
                     writerClass
                 )
                 activeEmitter = fragmented
                 fragmented.emit(
                     code,
                     typeSpecBuilder,
                     discriminator,
                     sealedDiscriminatorKey
                 )
             }
             else -> {
                 val standard = StandardSerializeEmitter(
                     sortedProperties,
                     originalClassName,
                     writerClass
                 )
                 activeEmitter = standard
                 standard.emit(
                     code,
                     discriminator,
                     sealedDiscriminatorKey
                 )
             }
         }
 
         return FunSpec.builder(C.STR_FUN_SERIALIZE)
             .addModifiers(KModifier.OVERRIDE)
             .addParameter(C.STR_PARAM_WRITER, writerClass)
             .addParameter(C.STR_PARAM_VALUE, originalClassName)
             .addCode(code.build())
             .build()
     }
 
     /**
      * Forwards the contextual serializers injection call to the active delegated emitter.
      */
     fun injectContextualSerializers(typeSpecBuilder: TypeSpec.Builder) {
         activeEmitter?.injectContextualSerializers(typeSpecBuilder)
     }
 
     /**
      * Emits enum serialization statements.
      */
     private fun emitEnumSerialization(code: CodeBlock.Builder) {
         val enumValues = properties.firstOrNull()?.enumValues
         if (enumValues != null) {
             code.beginControlFlow(C.STR_WHEN_VALUE)
             enumValues.forEach { (kotlinName, serialName) ->
                 code.addStatement(
                     C.STR_ENUM_MEMBER_VAL,
                     originalClassName,
                     kotlinName,
                     serialName
                 )
             }
             code.endControlFlow()
         } else {
             code.addStatement(C.STR_WRITER_VAL_ENUM_NAME)
         }
     }
 
     /**
      * Emits polymorphic sealed class type-matching dispatch blocks.
      */
     private fun emitSealedDispatch(code: CodeBlock.Builder) {
         code.beginControlFlow(C.STR_WHEN_VALUE)
         sealedSubclasses.forEach { subclass ->
             val subClassName = subclass.toClassName()
             val serializerName = subClassName.serializerClassName()
             code.addStatement(
                 C.STR_IS_T_ARROW_T_SERIALIZE,
                 subClassName,
                 serializerName
             )
         }
         code.endControlFlow()
     }
 
     /**
      * Emits value class unboxing statement.
      */
     private fun emitValueUnboxing(code: CodeBlock.Builder) {
         val prop = properties.firstOrNull() ?: return
         val accessor = CodeBlock.of(
             C.TEMPLATE_ACCESSOR,
             C.STR_PARAM_VALUE,
             prop.kotlinName
         )
         // Instantiates a standard emitter to resolve value writing of the unboxed value.
         val valueEmitter = StandardSerializeEmitter(
             properties,
             originalClassName,
             ClassName(C.PKG_WRITER, C.STR_GHOST_JSON_WRITER)
         )
         valueEmitter.emitValue(code, prop, accessor)
     }
 }