All Downloads are FREE. Search and download functionalities are using the official Maven repository.

kotlinx.io.core.Builder.kt Maven / Gradle / Ivy

There is a newer version: 0.1.16
Show newest version
package kotlinx.io.core

import kotlinx.io.core.internal.*
import kotlinx.io.pool.*

expect val PACKET_MAX_COPY_SIZE: Int

//fun BytePacketBuilder() = BytePacketBuilder(0)

/**
 * Build a byte packet in [block] lambda. Creates a temporary builder and releases it in case of failure
 */
inline fun buildPacket(headerSizeHint: Int = 0, block: BytePacketBuilder.() -> Unit): ByteReadPacket {
    val builder = BytePacketBuilder(headerSizeHint)
    try {
        block(builder)
        return builder.build()
    } catch (t: Throwable) {
        builder.release()
        throw t
    }
}

expect fun BytePacketBuilder(headerSizeHint: Int = 0): BytePacketBuilder

/**
 * A builder that provides ability to build byte packets with no knowledge of it's size.
 * Unlike Java's ByteArrayOutputStream it doesn't copy the whole content every time it's internal buffer overflows
 * but chunks buffers instead. Packet building via [build] function is O(1) operation and only does instantiate
 * a new [ByteReadPacket]. Once a byte packet has been built via [build] function call, the builder could be
 * reused again. You also can discard all written bytes via [reset] or [release]. Please note that an instance of
 * builder need to be terminated either via [build] function invocation or via [release] call otherwise it will
 * cause byte buffer leak so that may have performance impact.
 *
 * Byte packet builder is also an [Appendable] so it does append UTF-8 characters to a packet
 *
 * ```
 * buildPacket {
 *     listOf(1,2,3).joinTo(this, separator = ",")
 * }
 * ```
 */
class BytePacketBuilder(private var headerSizeHint: Int, pool: ObjectPool): BytePacketBuilderPlatformBase(pool) {
    init {
        require(headerSizeHint >= 0) { "shouldn't be negative: headerSizeHint = $headerSizeHint" }
    }

    /**
     * Number of bytes written to the builder
     */
    val size: Int get() {
        val size = _size
        if (size == -1) {
            _size = head.remainingAll().toInt()
            return _size
        }
        return size
    }

    val isEmpty: Boolean get() {
        val _size = _size
        return when {
            _size > 0 -> false
            _size == 0 -> true
            head.canRead() -> false
            size == 0 -> true
            else -> false
        }
    }

    val isNotEmpty: Boolean get() {
        val _size = _size
        return when {
            _size > 0 -> true
            _size == 0 -> false
            head.canRead() -> true
            size > 0 -> true
            else -> false
        }
    }

    @PublishedApi
    internal var head: IoBuffer = IoBuffer.Empty

    override fun append(c: Char): BytePacketBuilder {
        return super.append(c) as BytePacketBuilder
    }

    override fun append(csq: CharSequence?): BytePacketBuilder {
        return super.append(csq) as BytePacketBuilder
    }

    override fun append(csq: CharSequence?, start: Int, end: Int): BytePacketBuilder {
        return super.append(csq, start, end) as BytePacketBuilder
    }

    /**
     * Release any resources that the builder holds. Builder shouldn't be used after release
     */
    override fun release() {
        val head = this.head
        val empty = IoBuffer.Empty

        if (head !== empty) {
            this.head = empty
            this.tail = empty
            head.releaseAll(pool)
            _size = 0
        }
    }

    override fun flush() {
    }

    override fun close() {
        release()
    }

    /**
     * Creates a temporary packet view of the packet being build without discarding any bytes from the builder.
     * This is similar to `build().copy()` except that the builder keeps already written bytes untouched.
     * A temporary view packet is passed as argument to [block] function and it shouldn't leak outside of this block
     * otherwise an unexpected behaviour may occur.
     */
    fun  preview(block: (tmp: ByteReadPacket) -> R): R {
        val head = head.copyAll()
        val pool = if (head === IoBuffer.Empty) IoBuffer.EmptyPool else pool
        val packet = ByteReadPacket(head, pool)

        return try {
            block(packet)
        } finally {
            packet.release()
        }
    }

    /**
     * Builds byte packet instance and resets builder's state to be able to build another one packet if needed
     */
    fun build(): ByteReadPacket {
        val size = size
        val head = stealAll()

        return when (head) {
            null -> ByteReadPacket.Empty
            else -> ByteReadPacket(head, size.toLong(), pool)
        }
    }

    /**
     * Detach all chunks and cleanup all internal state so builder could be reusable again
     * @return a chain of buffer views or `null` of it is empty
     */
    internal fun stealAll(): IoBuffer? {
        val head = this.head
        val empty = IoBuffer.Empty

        this.head = empty
        this.tail = empty
        this._size = 0

        return if (head === empty) null else head
    }

    internal fun afterBytesStolen() {
        val head = head
        check(head.next == null)
        _size = 0
        head.resetForWrite()
        head.reserveStartGap(headerSizeHint)
        head.reserveEndGap(ByteReadPacketBase.ReservedSize)
    }

    /**
     * Writes another packet to the end. Please note that the instance [p] gets consumed so you don't need to release it
     */
    override fun writePacket(p: ByteReadPacket) {
        val foreignStolen = p.stealAll()
        if (foreignStolen == null) {
            p.release()
            return
        }

        val tail = tail
        if (tail === IoBuffer.Empty) {
            head = foreignStolen
            this.tail = foreignStolen.findTail()
            _size = foreignStolen.remainingAll().toInt()
            return
        }

        writePacketSlow(tail, foreignStolen, p)
    }

    private fun writePacketSlow(tail: IoBuffer, foreignStolen: IoBuffer, p: ByteReadPacket) {
        val lastSize = tail.readRemaining
        val nextSize = foreignStolen.readRemaining

        val maxCopySize = PACKET_MAX_COPY_SIZE
        val appendSize = if (nextSize < maxCopySize && nextSize <= (tail.endGap + tail.writeRemaining)) {
            nextSize
        } else -1

        val prependSize = if (lastSize < maxCopySize && lastSize <= foreignStolen.startGap && foreignStolen.isExclusivelyOwned()) {
            lastSize
        } else -1

        if (appendSize == -1 && prependSize == -1) {
            // simply enqueue
            tail.next = foreignStolen
            this.tail = foreignStolen.findTail()
            _size = head.remainingAll().toInt()
        } else if (prependSize == -1 || appendSize <= prependSize) {
            // do append
            tail.writeBufferAppend(foreignStolen, tail.writeRemaining + tail.endGap)
            tail.next = foreignStolen.next
            this.tail = foreignStolen.findTail().takeUnless { it === foreignStolen } ?: tail
            foreignStolen.release(p.pool)
            _size = head.remainingAll().toInt()
        } else if (appendSize == -1 || prependSize < appendSize) {
            writePacketSlowPrepend(foreignStolen, tail)
        } else {
            throw IllegalStateException("prep = $prependSize, app = $appendSize")
        }
    }

    private fun writePacketSlowPrepend(foreignStolen: IoBuffer, tail: IoBuffer) {
        // do prepend
        foreignStolen.writeBufferPrepend(tail)

        if (head === tail) {
            head = foreignStolen
        } else {
            var pre = head
            while (true) {
                val next = pre.next!!
                if (next === tail) break
                pre = next
            }

            pre.next = foreignStolen
        }
        tail.release(pool)

        this.tail = foreignStolen.findTail()
        _size = head.remainingAll().toInt()
    }

    override fun last(buffer: IoBuffer) {
        if (head === IoBuffer.Empty) {
            if (buffer.isEmpty()) { // headerSize is just a hint so we shouldn't force to reserve space
                buffer.reserveStartGap(headerSizeHint) // it will always fail for non-empty buffer
            }
            tail = buffer
            head = buffer
            _size = buffer.remainingAll().toInt()
        } else {
            tail.next = buffer
            tail = buffer
            _size = -1
        }
    }
}

expect abstract class BytePacketBuilderPlatformBase
    internal constructor(pool: ObjectPool) : BytePacketBuilderBase

/**
 * This is the default [Output] implementation
 */
@ExperimentalIoApi
abstract class AbstractOutput(pool: ObjectPool = IoBuffer.Pool) : BytePacketBuilderPlatformBase(pool) {
    protected var currentTail: IoBuffer
        get() = this.tail
        set(newValue) {
            this.tail = newValue
        }

    /**
     * Invoked when a new [buffer] is appended for writing (usually it's empty)
     */
    abstract override fun last(buffer: IoBuffer)

    open override fun release() {
        if (currentTail != IoBuffer.Empty) {
            currentTail.release(pool)
            currentTail = IoBuffer.Empty
        }
    }

    abstract override fun flush()

    /**
     * Should flush and close the destination
     */
    open override fun close() {
        flush()
    }
}

abstract class BytePacketBuilderBase internal constructor(protected val pool: ObjectPool) : Appendable, Output {

    /**
     * Number of bytes currently buffered or -1 if not known (need to be recomputed)
     */
    protected var _size: Int = 0

    /**
     * Byte order (Endianness) to be used by future write functions calls on this builder instance. Doesn't affect any
     * previously written values. Note that [reset] doesn't change this value back to the default byte order.
     * @default [ByteOrder.BIG_ENDIAN]
     */
    final override var byteOrder: ByteOrder = ByteOrder.BIG_ENDIAN
        set(value) {
            field = value
            val tail = tail
            if (tail.canWrite()) {
                // it is important to not set byte order for IoBuffer.Empty as it will crash on native
                tail.byteOrder = value
            }
        }

    @PublishedApi
    internal var tail: IoBuffer = IoBuffer.Empty

    final override fun writeFully(src: ByteArray, offset: Int, length: Int) {
        if (length == 0) return

        var copied = 0

        writeLoop(1, { copied < length }) { buffer, bufferRemaining ->
            val size = minOf(bufferRemaining, length - copied)
            buffer.writeFully(src, offset + copied, size)
            copied += size
            size
        }
    }

    final override fun writeLong(v: Long) {
        write(8) { it.writeLong(v); 8 }
    }

    final override fun writeInt(v: Int) {
        write(4) { it.writeInt(v); 4 }
    }

    final override fun writeShort(v: Short) {
        write(2) { it.writeShort(v); 2 }
    }

    final override fun writeByte(v: Byte) {
        write(1) { it.writeByte(v); 1 }
    }

    final override fun writeDouble(v: Double) {
        write(8) { it.writeDouble(v); 8 }
    }

    final override fun writeFloat(v: Float) {
        write(4) { it.writeFloat(v); 4 }
    }

    override fun writeFully(src: ShortArray, offset: Int, length: Int) {
        require(length >= 0) { "length shouldn't be negative: $length" }
        require(offset + length < src.lastIndex) { "offset ($offset) + length ($length) >= src.lastIndex (${src.lastIndex})" }

        if (length == 0) return

        var start = offset
        var remaining = length

        writeLoop(2, { remaining > 0 }) { buffer, chunkRemaining ->
            val qty = minOf(chunkRemaining shr 1, remaining)
            buffer.writeFully(src, start, qty)
            start += qty
            remaining -= qty
            qty * 2
        }
    }

    override fun writeFully(src: IntArray, offset: Int, length: Int) {
        require(length >= 0) { "length shouldn't be negative: $length" }
        require(offset + length < src.lastIndex) { "offset ($offset) + length ($length) >= src.lastIndex (${src.lastIndex})" }

        var start = offset
        var remaining = length

        writeLoop(4, { remaining > 0 }) { buffer, chunkRemaining ->
            val qty = minOf(chunkRemaining shr 2, remaining)
            buffer.writeFully(src, start, qty)
            start += qty
            remaining -= qty
            qty * 4
        }
    }

    override fun writeFully(src: LongArray, offset: Int, length: Int) {
        require(length >= 0) { "length shouldn't be negative: $length" }
        require(offset + length < src.lastIndex) { "offset ($offset) + length ($length) >= src.lastIndex (${src.lastIndex})" }

        var start = offset
        var remaining = length

        writeLoop(8, { remaining > 0 }) { buffer, chunkRemaining ->
            val qty = minOf(chunkRemaining shr 3, remaining)
            buffer.writeFully(src, start, qty)
            start += qty
            remaining -= qty
            qty * 8
        }
    }

    override fun writeFully(src: FloatArray, offset: Int, length: Int) {
        require(length >= 0) { "length shouldn't be negative: $length" }
        require(offset + length < src.lastIndex) { "offset ($offset) + length ($length) >= src.lastIndex (${src.lastIndex})" }

        var start = offset
        var remaining = length

        writeLoop(4, { remaining > 0 }) { buffer, chunkRemaining ->
            val qty = minOf(chunkRemaining shr 2, remaining)
            buffer.writeFully(src, start, qty)
            start += qty
            remaining -= qty
            qty * 4
        }
    }

    override fun writeFully(src: DoubleArray, offset: Int, length: Int) {
        require(length >= 0) { "length shouldn't be negative: $length" }
        require(offset + length < src.lastIndex) { "offset ($offset) + length ($length) >= src.lastIndex (${src.lastIndex})" }

        var start = offset
        var remaining = length

        writeLoop(8, { remaining > 0 }) { buffer, chunkRemaining ->
            val qty = minOf(chunkRemaining shr 3, remaining)
            buffer.writeFully(src, start, qty)
            start += qty
            remaining -= qty
            qty * 8
        }
    }

    override fun writeFully(src: IoBuffer, length: Int) {
        require(length >= 0) { "length shouldn't be negative: $length" }
        require(length <= src.readRemaining) { "Not enough bytes available in src buffer to read $length bytes" }

        val totalSize = minOf(src.readRemaining, length)
        if (totalSize == 0) return
        var remaining = totalSize

        var tail = tail
        if (!tail.canWrite()) {
            tail = appendNewBuffer()
        }

        do {
            val size = minOf(tail.writeRemaining, remaining)
            tail.writeFully(src, size)
            remaining -= size

            if (remaining == 0) break
            tail = appendNewBuffer()
        } while (true)

        addSize(totalSize)
    }

    override fun fill(n: Long, v: Byte) {
        require(n >= 0L) { "n shouldn't be negative: $n" }

        var rem = n
        writeLoop(1, { rem > 0L }) { buffer, chunkRemaining ->
            val size = minOf(chunkRemaining.toLong(), n).toInt()
            buffer.fill(size.toLong(), v)
            rem -= size
            size
        }
    }

    /**
     * Append single UTF-8 character
     */
    override fun append(c: Char): BytePacketBuilderBase {
        write(3) {
            it.putUtf8Char(c.toInt())
        }
        return this
    }

    override fun append(csq: CharSequence?): BytePacketBuilderBase {
        if (csq == null) {
            appendChars("null", 0, 4)
        } else {
            appendChars(csq, 0, csq.length)
        }
        return this
    }

    override fun append(csq: CharSequence?, start: Int, end: Int): BytePacketBuilderBase {
        if (csq == null) {
            return append("null", start, end)
        }

        appendChars(csq, start, end)

        return this
    }

    open fun writePacket(p: ByteReadPacket) {
        while (true) {
            val buffer = p.steal() ?: break
            last(buffer)
        }
    }

    /**
     * Write exact [n] bytes from packet to the builder
     */
    fun writePacket(p: ByteReadPacket, n: Int) {
        var remaining = n

        while (remaining > 0) {
            val headRemaining = p.headRemaining
            if (headRemaining <= remaining) {
                remaining -= headRemaining
                last(p.steal() ?: throw EOFException("Unexpected end of packet"))
            } else {
                p.read { view ->
                    writeFully(view, remaining)
                }
                break
            }
        }
    }

    /**
     * Write exact [n] bytes from packet to the builder
     */
    fun writePacket(p: ByteReadPacket, n: Long) {
        var remaining = n

        while (remaining > 0L) {
            val headRemaining = p.headRemaining.toLong()
            if (headRemaining <= remaining) {
                remaining -= headRemaining
                last(p.steal() ?: throw EOFException("Unexpected end of packet"))
            } else {
                p.read { view ->
                    writeFully(view, remaining.toInt())
                }
                break
            }
        }
    }

    override fun append(csq: CharArray, start: Int, end: Int): Appendable {
        appendChars(csq, start, end)
        return this
    }

    private fun appendChars(csq: CharSequence, start: Int, end: Int): Int {
        var idx = start
        if (idx >= end) return idx
        val tail = tail
        if (tail.canWrite()) {
            idx = tail.appendChars(csq, idx, end)
        }

        while (idx < end) {
            idx = appendNewBuffer().appendChars(csq, idx, end)
        }

        this._size = -1
        return idx
    }

    private fun appendChars(csq: CharArray, start: Int, end: Int): Int {
        var idx = start
        if (idx >= end) return idx
        val tail = tail
        if (tail.canWrite()) {
            idx = tail.appendChars(csq, idx, end)
        }

        while (idx < end) {
            idx = appendNewBuffer().appendChars(csq, idx, end)
        }

        this._size = -1
        return idx
    }

    fun writeStringUtf8(s: String) {
        append(s, 0, s.length)
    }

    fun writeStringUtf8(cs: CharSequence) {
        append(cs, 0, cs.length)
    }

//    fun writeStringUtf8(cb: CharBuffer) {
//        append(cb, 0, cb.remaining())
//    }

    @Suppress("NOTHING_TO_INLINE")
    private inline fun IoBuffer.putUtf8Char(v: Int) = when {
        v in 1..0x7f -> {
            writeByte(v.toByte())
            1
        }
        v > 0x7ff -> {
            writeByte((0xe0 or ((v shr 12) and 0x0f)).toByte())
            writeByte((0x80 or ((v shr  6) and 0x3f)).toByte())
            writeByte((0x80 or ( v         and 0x3f)).toByte())
            3
        }
        else -> {
            writeByte((0xc0 or ((v shr  6) and 0x1f)).toByte())
            writeByte((0x80 or ( v         and 0x3f)).toByte())
            2
        }
    }

    /**
     * Release any resources that the builder holds. Builder shouldn't be used after release
     */
    abstract fun release()

    @DangerousInternalIoApi
    fun prepareWriteHead(n: Int): IoBuffer {
        if (tail.writeRemaining >= n) return tail
        return appendNewBuffer()
    }

    @DangerousInternalIoApi
    fun afterHeadWrite() {
        _size = -1
    }

    /**
     * Discard all written bytes and prepare to build another packet.
     */
    fun reset() {
        release()
    }

    @PublishedApi
    internal inline fun write(size: Int, block: (IoBuffer) -> Int) {
        var buffer = tail
        if (buffer.writeRemaining < size) {
            buffer = appendNewBuffer()
        }

        addSize(block(buffer))
    }

    private inline fun writeLoop(size: Int, predicate: () -> Boolean, block: (IoBuffer, Int) -> Int) {
        if (!predicate()) return
        var written = 0
        var buffer = tail
        var rem = buffer.writeRemaining

        do {
            if (rem < size) {
                buffer = appendNewBuffer()
                rem = buffer.writeRemaining
            }

            val result = block(buffer, rem)
            written += result
            rem -= result
        } while (predicate())

        addSize(written)
    }

    @PublishedApi
    internal fun addSize(n: Int) {
        val size = _size
        if (size != -1) {
            _size = size + n
        }
    }

    internal abstract fun last(buffer: IoBuffer)

    @PublishedApi
    internal fun appendNewBuffer(): IoBuffer {
        val new = pool.borrow()
        new.reserveEndGap(ByteReadPacket.ReservedSize)
        new.byteOrder = byteOrder

        last(new)

        return new
    }
}

private inline fun  T.takeUnless(predicate: (T) -> Boolean): T? {
    return if (!predicate(this)) this else null
}






© 2015 - 2025 Weber Informatics LLC | Privacy Policy