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

Class Method, % Branch, % Line, % Instruction, %
FlattenOptionsGenerator 100% (2/2) 80% (16/20) 89.5% (68/76) 86.5% (262/303)
FlattenOptionsGenerator$FlattenNode 100% (1/1) 100% (3/3) 100% (23/23)
Total 100% (3/3) 80% (16/20) 89.9% (71/79) 87.4% (285/326)


 package com.ghost.serialization.compiler
 
 import com.squareup.kotlinpoet.ClassName
 import com.squareup.kotlinpoet.CodeBlock
 import com.squareup.kotlinpoet.KModifier
 import com.squareup.kotlinpoet.PropertySpec
 import com.squareup.kotlinpoet.TypeSpec
 import com.ghost.serialization.compiler.GhostEmitterConstants as C
 
 /**
  * Generator that handles recursive emission of nested perfect hash lookup options
  * for flattened properties structures in a serializer type builder.
  */
 internal object FlattenOptionsGenerator {
 
     /**
      * Collects and triggers recursive generation of nested perfect hash lookup options
      * for flattened properties structures.
      *
      * @param typeSpecBuilder The target class builder to inject the options properties into.
      * @param properties The list of target properties.
      * @param fullPaths The pre-extracted paths for each property.
      * @param readerClass The JSON reader class reference.
      */
     fun generateNestedOptions(
         typeSpecBuilder: TypeSpec.Builder,
         properties: List<GhostPropertyModel>,
         fullPaths: List<List<String>>,
         readerClass: ClassName,
         textChannel: Boolean
     ) {
         val rootNodes = mutableMapOf<String, FlattenNode>()
 
         properties.forEach { prop ->
             val path = prop.flattenPath ?: prop.wrapPath ?: return@forEach
             var currentMap = rootNodes
             path.forEachIndexed { index, segment ->
                 val isLast = index == path.size - 1
                 val node = currentMap.getOrPut(segment) { FlattenNode(segment) }
                 if (isLast) {
                     node.properties.add(prop)
                 } else {
                     currentMap = node.children
                 }
             }
         }
 
         rootNodes.values.forEach { node ->
             emitNestedOptionsRecursive(
                 typeSpecBuilder = typeSpecBuilder,
                 properties = properties,
                 fullPaths = fullPaths,
                 node = node,
                 readerClass = readerClass,
                 parentPrefix = C.STR_EMPTY,
                 currentPath = listOf(node.segment),
                 textChannel = textChannel
             )
         }
     }
 
     /**
      * Recursively traverses nodes and emits private nested perfect hash options properties.
      *
      * @param typeSpecBuilder The target class builder to inject the options property into.
      * @param properties The list of target properties.
      * @param fullPaths The pre-extracted paths for each property.
      * @param node The current flattened node.
      * @param readerClass The JSON reader class reference.
      * @param parentPrefix Prefix derived from parent segments.
      * @param currentPath Path segments list up to this node.
      */
     private fun emitNestedOptionsRecursive(
         typeSpecBuilder: TypeSpec.Builder,
         properties: List<GhostPropertyModel>,
         fullPaths: List<List<String>>,
         node: FlattenNode,
         readerClass: ClassName,
         parentPrefix: String,
         currentPath: List<String>,
         textChannel: Boolean
     ) {
         if (node.children.isEmpty() && node.properties.isEmpty()) {
             return
         }
 
         val currentPrefix =
             if (parentPrefix.isEmpty()) {
                 node.segment
             } else {
                 parentPrefix + C.STR_UNDERSCORE + node.segment
             }
         val optionsName = C.STR_OPTIONS_PREFIX + currentPrefix.uppercase()
 
         val depth = currentPath.size
         val names = properties.mapIndexedNotNull { index, _ ->
             val path = fullPaths[index]
             if (path.size > depth && path.subList(0, depth) == currentPath) {
                 path[depth]
             } else {
                 null
             }
         }.distinct()
 
         val hashConfig = PerfectHashFinder.findPerfectHash(names)
 
         val optionsClass = readerClass.peerClass(C.STR_OPTIONS_CLASS)
         val optionsBuilder = CodeBlock.builder()
         if (hashConfig.extendedKeyHash) {
             optionsBuilder.add(
                 C.TEMPLATE_OPTIONS_OF_SEEDS_EXTENDED_START,
                 optionsClass,
                 hashConfig.shift,
                 hashConfig.multiplier,
                 hashConfig.tableSize,
                 textChannel,
                 true
             )
         } else {
             optionsBuilder.add(
                 C.TEMPLATE_OPTIONS_OF_SEEDS_START,
                 optionsClass,
                 hashConfig.shift,
                 hashConfig.multiplier,
                 hashConfig.tableSize,
                 textChannel
             )
         }
 
         names.forEach { name ->
             optionsBuilder.add(C.TEMPLATE_COMMA_FORMAT_S, name)
         }
         optionsBuilder.add(C.STR_PAREN_CLOSE)
 
         typeSpecBuilder.addProperty(
             PropertySpec.builder(optionsName, optionsClass)
                 .addModifiers(KModifier.PRIVATE)
                 .initializer(optionsBuilder.build())
                 .build()
         )
 
         node.children.values.forEach { child ->
             emitNestedOptionsRecursive(
                 typeSpecBuilder = typeSpecBuilder,
                 properties = properties,
                 fullPaths = fullPaths,
                 node = child,
                 readerClass = readerClass,
                 parentPrefix = currentPrefix,
                 currentPath = currentPath + child.segment,
                 textChannel = textChannel
             )
         }
     }
 
     /**
      * Node descriptor representing a segment in a flattened path hierarchy tree.
      */
     private class FlattenNode(val segment: String) {
         val children = mutableMapOf<String, FlattenNode>()
         val properties = mutableListOf<GhostPropertyModel>()
     }
 }