Coverage Summary for Class: JsonReaderOptions (com.ghost.serialization.parser)

Class Method, % Branch, % Line, % Instruction, %
JsonReaderOptions 85.7% (6/7) 86.4% (57/66) 98.8% (82/83) 99.2% (513/517)
JsonReaderOptions$Companion 83.3% (5/6) 100% (12/12) 97.1% (34/35) 94.7% (196/207)
Total 84.6% (11/13) 88.5% (69/78) 98.3% (116/118) 97.9% (709/724)


 @file:Suppress("ReplaceSizeCheckWithIsNotEmpty")
 
 package com.ghost.serialization.parser
 
 import com.ghost.serialization.parser.GhostJsonConstants.BYTE_MASK
 import com.ghost.serialization.parser.GhostJsonConstants.COLLISION_HASH_MULTIPLIER
 import com.ghost.serialization.parser.GhostJsonConstants.SHIFT_16
 import com.ghost.serialization.parser.GhostJsonConstants.SHIFT_24
 import com.ghost.serialization.parser.GhostJsonConstants.SHIFT_8
 import com.ghost.serialization.parser.GhostJsonConstants.SINGLE_CHAR_SIZE
 import com.ghost.serialization.parser.GhostJsonConstants.UNICODE_HEX_LENGTH
 import kotlin.jvm.JvmStatic
 import com.ghost.serialization.parser.GhostJsonConstants as C
 
 /**
  * Dispatch options for optimized JSON field identification.
  * Uses a 4-byte hashing engine to minimize collisions during field lookup.
  *
  * [rawBytes] stores field names as raw [ByteArray] instead of Okio ByteString
  * so that [verifyKeyMatch] can compare bytes directly without virtual dispatch
  * or redundant bounds checks inside Okio's `rangeEquals`.
  *
  * @property rawBytes Array of field names represented as raw byte arrays (UTF-8).
  * @property shift The bit-shift amount used to normalize key distributions.
  * @property multiplier The prime multiplier used to spread key entropy across the address space.
  * @property rawStrings Array of field names as original strings.
  */
 class JsonReaderOptions(
     public val rawBytes: Array<ByteArray>,
     @PublishedApi internal val shift: Int,
     @PublishedApi internal val multiplier: Int,
     @PublishedApi internal val tableSize: Int,
     public val rawStrings: Array<String>,
     @PublishedApi internal val enableStringDispatch: Boolean = false,
     @PublishedApi internal val extendedKeyHash: Boolean? = null
 ) {
     @PublishedApi
     internal val dispatch = IntArray(tableSize) { -1 }
 
     @PublishedApi
     internal var stringDispatch = if (enableStringDispatch) {
         IntArray(tableSize) { -1 }
     } else {
         EMPTY_DISPATCH_TABLE
     }
         get() {
             val table = field
             if (table === EMPTY_DISPATCH_TABLE) {
                 val newTable = IntArray(tableSize) { -1 }
                 buildStringDispatchTable(newTable)
                 field = newTable
                 return newTable
             }
             return table
         }
 
     @PublishedApi
     internal val hasCollisions: Boolean
 
     init {
         var detectedCollision = false
         val seen = HashSet<Long>()
         for (bytes in rawBytes) {
             if (bytes.isNotEmpty()) {
                 var key = 0L
                 if (bytes.size >= SINGLE_CHAR_SIZE) {
                     key = key or (bytes[0].toLong() and C.LONG_BYTE_MASK)
                 }
                 if (bytes.size >= C.UNICODE_ESCAPE_PREFIX_SIZE) {
                     key = key or ((bytes[1].toLong() and C.LONG_BYTE_MASK) shl SHIFT_8)
                 }
                 if (bytes.size >= C.UNICODE_ESCAPE_PREFIX_SIZE + 1) {
                     key = key or ((bytes[2].toLong() and C.LONG_BYTE_MASK) shl SHIFT_16)
                 }
                 if (bytes.size >= UNICODE_HEX_LENGTH) {
                     key = key or ((bytes[3].toLong() and C.LONG_BYTE_MASK) shl SHIFT_24)
                 }
                 val packed = key or (bytes.size.toLong() shl (SHIFT_24 + SHIFT_8))
                 if (!seen.add(packed)) {
                     detectedCollision = true
                     break
                 }
             }
         }
         hasCollisions = if (extendedKeyHash == true) {
             true
         } else {
             detectedCollision
         }
 
         val tableMask = tableSize - 1
         for (index in rawBytes.indices) {
             val bytes = rawBytes[index]
             if (bytes.isNotEmpty()) {
                 var key = 0
                 if (bytes.size >= SINGLE_CHAR_SIZE) {
                     key = key or (bytes[0].toInt() and BYTE_MASK)
                 }
                 if (bytes.size >= C.UNICODE_ESCAPE_PREFIX_SIZE) {
                     key = key or ((bytes[1].toInt() and BYTE_MASK) shl SHIFT_8)
                 }
                 if (bytes.size >= C.UNICODE_ESCAPE_PREFIX_SIZE + 1) {
                     key = key or ((bytes[2].toInt() and BYTE_MASK) shl SHIFT_16)
                 }
                 if (bytes.size >= UNICODE_HEX_LENGTH) {
                     key = key or ((bytes[3].toInt() and BYTE_MASK) shl SHIFT_24)
                 }
                 if (hasCollisions) {
                     var ci = UNICODE_HEX_LENGTH
                     while (ci < bytes.size) { key = key * COLLISION_HASH_MULTIPLIER + (bytes[ci].toInt() and BYTE_MASK); ci++ }
                 }
 
                 val perfectHashKey = ((key * multiplier + bytes.size) shr shift) and tableMask
                 if (dispatch[perfectHashKey] == -1) {
                     dispatch[perfectHashKey] = index
                 }
             }
         }
 
         if (enableStringDispatch) {
             buildStringDispatchTable(stringDispatch)
         }
     }
 
     private fun buildStringDispatchTable(table: IntArray) {
         val tableMask = tableSize - 1
         for (index in rawStrings.indices) {
             val keyString = rawStrings[index]
             if (keyString.isNotEmpty()) {
                 var key = 0
                 if (keyString.length >= SINGLE_CHAR_SIZE) {
                     key = key or (keyString[0].code and BYTE_MASK)
                 }
                 if (keyString.length >= C.UNICODE_ESCAPE_PREFIX_SIZE) {
                     key = key or ((keyString[1].code and BYTE_MASK) shl SHIFT_8)
                 }
                 if (keyString.length >= C.UNICODE_ESCAPE_PREFIX_SIZE + 1) {
                     key = key or ((keyString[2].code and BYTE_MASK) shl SHIFT_16)
                 }
                 if (keyString.length >= UNICODE_HEX_LENGTH) {
                     key = key or ((keyString[3].code and BYTE_MASK) shl SHIFT_24)
                 }
                 if (hasCollisions) {
                     var ci = UNICODE_HEX_LENGTH
                     while (ci < keyString.length) { key = key * COLLISION_HASH_MULTIPLIER + (keyString[ci].code and BYTE_MASK); ci++ }
                 }
 
                 val perfectHashKey = ((key * multiplier + keyString.length) shr shift) and tableMask
                 if (table[perfectHashKey] == -1) {
                     table[perfectHashKey] = index
                 }
             }
         }
     }
 
     companion object {
         // Collision disambiguation uses polynomial accumulation inlined directly in init,
         // buildStringDispatchTable, and each computeKeyHash. See COLLISION_HASH_MULTIPLIER.
         // All five sites must stay identical; PerfectHashFinder (compiler-side) is the sixth.
 
         fun of(vararg names: String): JsonReaderOptions = of(
             C.DEFAULT_DISPATCH_SHIFT,
             C.DEFAULT_DISPATCH_MULTIPLIER,
             C.DEFAULT_DISPATCH_TABLE_SIZE,
             *names
         )
 
         fun of(shift: Int, multiplier: Int, vararg names: String): JsonReaderOptions {
             return of(shift, multiplier, C.DEFAULT_DISPATCH_TABLE_SIZE, *names)
         }
 
         fun of(
             shift: Int,
             multiplier: Int,
             tableSize: Int,
             vararg names: String
         ): JsonReaderOptions {
             val rawBytes = Array(names.size) { names[it].encodeToByteArray() }
             val rawStrings = Array(names.size) { names[it] }
             return JsonReaderOptions(
                 rawBytes,
                 shift,
                 multiplier,
                 tableSize,
                 rawStrings,
                 enableStringDispatch = true
             )
         }
 
         fun of(
             shift: Int,
             multiplier: Int,
             enableStringDispatch: Boolean,
             vararg names: String
         ): JsonReaderOptions {
             return of(shift, multiplier, 1024, enableStringDispatch, *names)
         }
 
         fun of(
             shift: Int,
             multiplier: Int,
             tableSize: Int,
             enableStringDispatch: Boolean,
             vararg names: String
         ): JsonReaderOptions {
             val rawBytes = Array(names.size) { names[it].encodeToByteArray() }
             val rawStrings = Array(names.size) { names[it] }
             return JsonReaderOptions(
                 rawBytes,
                 shift,
                 multiplier,
                 tableSize,
                 rawStrings,
                 enableStringDispatch = enableStringDispatch
             )
         }
 
         fun of(
             shift: Int,
             multiplier: Int,
             tableSize: Int,
             enableStringDispatch: Boolean,
             extendedKeyHash: Boolean,
             vararg names: String
         ): JsonReaderOptions {
             val rawBytes = Array(names.size) { names[it].encodeToByteArray() }
             val rawStrings = Array(names.size) { names[it] }
             return JsonReaderOptions(
                 rawBytes,
                 shift,
                 multiplier,
                 tableSize,
                 rawStrings,
                 enableStringDispatch = enableStringDispatch,
                 extendedKeyHash = extendedKeyHash
             )
         }
 
         private val EMPTY_DISPATCH_TABLE = IntArray(0)
     }
 }