Coverage Summary for Class: GhostWrappedKeysCapture (com.ghost.serialization.parser)
| Class |
Method, %
|
Branch, %
|
Line, %
|
Instruction, %
|
| GhostWrappedKeysCapture |
100%
(4/4)
|
87.5%
(28/32)
|
96%
(48/50)
|
98.7%
(228/231)
|
| GhostWrappedKeysCapture$Companion |
|
| Total |
100%
(4/4)
|
87.5%
(28/32)
|
96%
(48/50)
|
98.7%
(228/231)
|
@file:OptIn(InternalGhostApi::class)
package com.ghost.serialization.parser
import com.ghost.serialization.InternalGhostApi
import com.ghost.serialization.acquireScratchBuffer
import com.ghost.serialization.types.RawJson
/**
* Stack-scoped accumulator for [@GhostWrappedKeys][com.ghost.serialization.annotations.GhostWrappedKeys]
* deserialization.
*
* Each slot stores a zero-copy [RawJson] slice for one wire key. [materializeWrappedObject] builds
* a synthetic `{ "k1": v1, "k2": v2, ... }` object in a pooled scratch buffer (amortized zero
* allocation on the hot path).
*/
class GhostWrappedKeysCapture(
private val slotCount: Int,
) {
private val values = arrayOfNulls<RawJson>(slotCount)
private var presentMask = 0
/**
* Records a captured JSON value for [index] (position in the annotation `keys` array).
*/
fun put(index: Int, value: RawJson) {
values[index] = value
presentMask = presentMask or (1 shl index)
}
/**
* Builds UTF-8 bytes for a synthetic wrapper object, or `null` when [omitIfEmpty] /
* [omitIfAbsentIndices] rules reject assembly.
*
* @param keyUtf8Literals Pre-encoded `"key":` prefixes (including quotes and colon) in key order.
* @param omitIfEmpty Skip assembly when every slot is absent or JSON `null`.
* @param omitIfAbsentIndices Slot indices that must be present and non-null.
*/
fun materializeWrappedObject(
keyUtf8Literals: Array<ByteArray>,
omitIfEmpty: Boolean,
omitIfAbsentIndices: IntArray,
): ByteArray? {
if (omitIfEmpty && !hasNonNullValue()) {
return null
}
for (index in omitIfAbsentIndices) {
val captured = values[index]
if (captured == null || captured.isJsonNull) {
return null
}
}
var estimatedSize = OPEN_CLOSE_BRACE_SIZE
var presentCount = 0
for (index in 0 until slotCount) {
if ((presentMask and (1 shl index)) == 0) {
continue
}
presentCount++
estimatedSize += keyUtf8Literals[index].size
estimatedSize += values[index]!!.storageLength
}
if (presentCount == 0) {
return if (omitIfEmpty) {
null
} else {
byteArrayOf(OPEN_BRACE, CLOSE_BRACE)
}
}
if (presentCount > 1) {
estimatedSize += presentCount - 1
}
val scratch = acquireScratchBuffer(estimatedSize)
var writePos = 0
scratch[writePos++] = OPEN_BRACE
var wroteAny = false
for (index in 0 until slotCount) {
if ((presentMask and (1 shl index)) == 0) {
continue
}
if (wroteAny) {
scratch[writePos++] = COMMA
}
wroteAny = true
val keyPrefix = keyUtf8Literals[index]
keyPrefix.copyInto(scratch, writePos)
writePos += keyPrefix.size
val captured = values[index]!!
captured.storage.copyInto(
scratch,
writePos,
captured.storageOffset,
captured.endExclusive,
)
writePos += captured.storageLength
}
scratch[writePos++] = CLOSE_BRACE
return scratch.copyOf(writePos)
}
private fun hasNonNullValue(): Boolean {
for (index in 0 until slotCount) {
val captured = values[index]
if (captured != null && !captured.isJsonNull) {
return true
}
}
return false
}
private companion object {
private const val OPEN_BRACE: Byte = '{'.code.toByte()
private const val CLOSE_BRACE: Byte = '}'.code.toByte()
private const val COMMA: Byte = ','.code.toByte()
private const val OPEN_CLOSE_BRACE_SIZE = 2
}
}
/**
* Captures the next JSON value into [capture] at [slotIndex] using a zero-copy [RawJson] slice.
*/
fun GhostJsonFlatReader.captureWrappedKey(capture: GhostWrappedKeysCapture, slotIndex: Int) {
capture.put(slotIndex, captureRawJson())
}
/**
* @see captureWrappedKey
*/
fun GhostJsonReader.captureWrappedKey(capture: GhostWrappedKeysCapture, slotIndex: Int) {
capture.put(slotIndex, captureRawJson())
}
/**
* @see captureWrappedKey
*/
fun GhostJsonStringReader.captureWrappedKey(capture: GhostWrappedKeysCapture, slotIndex: Int) {
capture.put(slotIndex, captureRawJson())
}