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

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

The newest version!
@file:Suppress("RedundantModalityModifier")

package kotlinx.io.core

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

/**
 * Read-only immutable byte packet. Could be consumed only once however it does support [copy] that doesn't copy every byte
 * but creates a new view instead. Once packet created it should be either completely read (consumed) or released
 * via [release].
 */
@DangerousInternalIoApi
abstract class ByteReadPacketBase(@PublishedApi internal var head: IoBuffer,
                                  remaining: Long = head.remainingAll(),
                                  val pool: ObjectPool) : Input {

    init {
        head.setByteOrderForNonEmpty(ByteOrder.BIG_ENDIAN)
    }

    @Deprecated(
        "Suppress warning.",
        level = DeprecationLevel.HIDDEN
    )
    @Suppress("unused")
    final override val doNotImplementInputButExtendAbstractInputInstead: Nothing
        get() = error("Should be never accessed.")

    @Deprecated("Use readXXXLittleEndian or readXXX then X.reverseByteOrder() instead.")
    final override var byteOrder: ByteOrder = ByteOrder.BIG_ENDIAN
        set(newOrder) {
            field = newOrder
            head.setByteOrderForNonEmpty(newOrder)
        }

    /**
     * Number of bytes available for read
     */
    val remaining: Long get() = headRemaining.toLong() + tailRemaining

    @Deprecated("For compatibility purpose", level = DeprecationLevel.HIDDEN)
    fun getRemaining(): Int = remaining.coerceAtMostMaxInt()

    /**
     * @return `true` if there is at least one byte to read
     */
    fun canRead() = tailRemaining != 0L || head.canRead()

    /**
     * @return `true` if there are at least [n] bytes to read
     */
    fun hasBytes(n: Int) = headRemaining + tailRemaining >= n

    @PublishedApi
    internal var headRemaining = head.readRemaining

    private var tailRemaining: Long = remaining - headRemaining

    /**
     * `true` if no bytes available for read
     */
    val isEmpty: Boolean
        get() = headRemaining == 0 && tailRemaining == 0L && noMoreChunksAvailable

    val isNotEmpty: Boolean
        get() = headRemaining > 0 || tailRemaining > 0L || !noMoreChunksAvailable

    private var noMoreChunksAvailable = false
    override val endOfInput: Boolean
        get() = isEmpty && (noMoreChunksAvailable || doFill() == null)

    /**
     * Returns a copy of the packet. The original packet and the copy could be used concurrently. Both need to be
     * either completely consumed or released via [release]
     */
    fun copy(): ByteReadPacket = ByteReadPacket(head.copyAll(), remaining, pool)

    /**
     * Release packet. After this function invocation the packet becomes empty. If it has been copied via [copy]
     * then the copy should be released as well.
     */
    fun release() {
        val head = head
        val empty = IoBuffer.Empty

        if (head !== empty) {
            this.head = empty
            headRemaining = 0
            tailRemaining = 0
            head.releaseAll(pool)
        }
    }

    override fun close() {
        release()
        if (!noMoreChunksAvailable) {
            noMoreChunksAvailable = true
        }
        closeSource()
    }

    internal fun stealAll(): IoBuffer? {
        val head = head
        val empty = IoBuffer.Empty

        if (head === empty) return null
        this.head = empty
        headRemaining = 0
        tailRemaining = 0
        return head
    }

    internal fun steal(): IoBuffer? {
        val head = head
        val next = head.next
        val empty = IoBuffer.Empty
        if (head === empty) return null

        val nextRemaining = next?.readRemaining ?: 0

        this.head = next ?: empty
        this.headRemaining = nextRemaining
        this.tailRemaining -= nextRemaining
        head.next = null

        return head
    }

    internal fun append(chain: IoBuffer) {
        if (chain === IoBuffer.Empty) return

        val size = chain.remainingAll()
        if (head === IoBuffer.Empty) {
            head = chain
            headRemaining = chain.readRemaining
            tailRemaining = size - headRemaining
        } else {
            head.findTail().next = chain
            tailRemaining += size
        }
        @Suppress("DEPRECATION")
        chain.byteOrder = byteOrder
    }

    internal fun tryWriteAppend(chain: IoBuffer): Boolean {
        val tail = head.findTail()
        val size = chain.readRemaining

        if (size == 0 || tail.writeRemaining < size) return false
        tail.writeBufferAppend(chain, size)

        if (head === tail) {
            headRemaining += size
        } else {
            tailRemaining += size
        }

        return true
    }

    final override fun readByte(): Byte {
        val headRemaining = headRemaining
        if (headRemaining > 1) {
            this.headRemaining = headRemaining - 1
            return head.readByte()
        }

        return readByteSlow2()
    }

    private fun readByteSlow2(): Byte {
        val head = head
        val headRemaining = headRemaining

        if (headRemaining == 1) {
            this.headRemaining = headRemaining - 1
            return head.readByte().also { ensureNext(head) }
        } else {
            return readByteSlow(head)
        }
    }

    private fun readByteSlow(head: IoBuffer): Byte {
        ensureNext(head) ?: throw EOFException("One more byte required but reached end of input")
        return readByte()
    }

    final override fun readShort() = readN(2) { readShort() }
    final override fun readFloat() = readN(4) { readFloat() }
    final override fun readDouble() = readN(8) { readDouble() }

    final override fun readInt(): Int {
        val headRemaining = headRemaining
        if (headRemaining > 4) {
            this.headRemaining = headRemaining - 4
            return head.readInt()
        }

        return readIntSlow()
    }

    private fun readIntSlow(): Int = readN(4) { readInt() }

    final override fun readLong(): Long {
        val headRemaining = headRemaining
        if (headRemaining > 8) {
            this.headRemaining = headRemaining - 8
            return head.readLong()
        }

        return readLongSlow()
    }

    private fun readLongSlow(): Long = readN(8) { readLong() }

    /**
     * Read as much bytes as possible to [dst] array
     * @return number of bytes copied
     */
    fun readAvailable(dst: ByteArray): Int = readAvailable(dst, 0, dst.size)

    /**
     * Read at most [length] bytes to [dst] array and write them at [offset]
     * @return number of bytes copied to the array
     */
    final override fun readAvailable(dst: ByteArray, offset: Int, length: Int): Int {
        require(offset >= 0) { "offset shouldn't be negative: $offset" }
        require(length >= 0) { "length shouldn't be negative: $length" }
        require(offset + length <= dst.size) { "offset ($offset) + length ($length) > dst.size (${dst.size})" }

        return readAsMuchAsPossible(dst, offset, length, 0)
    }

    /**
     * Read exactly [length] bytes to [dst] array at specified [offset]
     */
    final override fun readFully(dst: ByteArray, offset: Int, length: Int) {
        val rc = readAvailable(dst, offset, length)
        if (rc != length) throw EOFException("Not enough data in packet to fill buffer: ${length - rc} more bytes required")
    }

    /**
     * Discards at most [n] bytes
     * @return number of bytes has been discarded
     */
    @Deprecated("Use discardExact instead.")
    final fun discard(n: Int) = discardAsMuchAsPossible(n, 0)

    /**
     * Discards exactly [n] bytes or fails with [EOFException]
     */
    @Suppress("DEPRECATION")
    @Deprecated("Binary compatibility.", level = DeprecationLevel.HIDDEN)
    final fun discardExact(n: Int) {
        if (discard(n) != n) throw EOFException("Unable to discard $n bytes due to end of packet")
    }

    @PublishedApi
    internal inline fun read(block: (IoBuffer) -> Unit) {
        read(1, block)
    }

    @PublishedApi
    internal inline fun read(n: Int, block: (IoBuffer) -> Unit) {
        val head = head
        var before = head.readRemaining
        val buffer = if (before < n) {
            prepareRead(n, head).also { before = it?.readRemaining ?: 0 }
        } else {
            head
        }

        if (buffer != null) {
            block(buffer)
            val after = buffer.readRemaining
            val delta = before - after
            if (delta > 0) {
                headRemaining -= delta
            }
            if (after == 0) {
                ensureNext(buffer)
            }
        }
    }

    final override fun readFully(dst: ShortArray, offset: Int, length: Int) {
        var copied = 0
        takeWhile { buffer ->
            val rc = buffer.readAvailable(dst, offset + copied, length - copied)
            if (rc == -1) throw EOFException("Unexpected EOF while reading $length bytes")
            copied += rc
            copied < length
        }

        if (copied != length) {
            throw EOFException("Not enough bytes available to read $length short integers, $copied were copied")
        }
    }

    final override fun readAvailable(dst: ShortArray, offset: Int, length: Int): Int {
        val remaining = remaining
        if (remaining == 0L) {
            if (doFill() == null) return -1
            return readAvailable(dst, offset, length)
        }
        val size = minOf(remaining, length.toLong()).toInt()
        readFully(dst, offset, size)
        return size
    }

    final override fun readFully(dst: IntArray, offset: Int, length: Int) {
        var copied = 0
        takeWhile { buffer ->
            val rc = buffer.readAvailable(dst, offset + copied, length - copied)
            if (rc == -1) throw EOFException("Unexpected EOF while read $length short integers")
            copied += rc
            copied < length
        }

        if (copied != length) {
            throw EOFException("Not enough bytes available to read $length integers, $copied were copied")
        }
    }

    final override fun readAvailable(dst: IntArray, offset: Int, length: Int): Int {
        val remaining = remaining
        if (remaining == 0L) {
            if (doFill() == null) return -1
            return readAvailable(dst, offset, length)
        }
        val size = minOf(remaining, length.toLong()).toInt()
        readFully(dst, offset, size)
        return size
    }

    final override fun readFully(dst: LongArray, offset: Int, length: Int) {
        var copied = 0
        takeWhile { buffer ->
            val rc = buffer.readAvailable(dst, offset + copied, length - copied)
            if (rc == -1) throw EOFException("Unexpected EOF while reading $length long integers")
            copied += rc
            copied < length
        }

        if (copied != length) {
            throw EOFException("Not enough bytes available to read $length long integers, $copied were copied")
        }
    }

    final override fun readAvailable(dst: LongArray, offset: Int, length: Int): Int {
        val remaining = remaining
        if (remaining == 0L) {
            if (doFill() == null) return -1
            return readAvailable(dst, offset, length)
        }
        val size = minOf(remaining, length.toLong()).toInt()
        readFully(dst, offset, size)
        return size
    }

    final override fun readFully(dst: FloatArray, offset: Int, length: Int) {
        var copied = 0
        takeWhile { buffer ->
            val rc = buffer.readAvailable(dst, offset + copied, length - copied)
            if (rc == -1) throw EOFException("Unexpected EOF while read $length float number")
            copied += rc
            copied < length
        }

        if (copied != length) {
            throw EOFException("Not enough bytes available to read $length float numbers, $copied were copied")
        }
    }

    final override fun readAvailable(dst: FloatArray, offset: Int, length: Int): Int {
        val remaining = remaining
        if (remaining == 0L) {
            if (doFill() == null) return -1
            return readAvailable(dst, offset, length)
        }
        val size = minOf(remaining, length.toLong()).toInt()
        readFully(dst, offset, size)
        return size
    }

    final override fun readFully(dst: DoubleArray, offset: Int, length: Int) {
        var copied = 0
        takeWhile { buffer ->
            val rc = buffer.readAvailable(dst, offset + copied, length - copied)
            if (rc == -1) throw EOFException("Unexpected EOF while reading $length double float numbers")
            copied += rc
            copied < length
        }

        if (copied != length) {
            throw EOFException("Not enough bytes available to read $length double numbers, $copied were copied")
        }
    }

    final override fun readAvailable(dst: DoubleArray, offset: Int, length: Int): Int {
        val remaining = remaining
        if (remaining == 0L) {
            if (doFill() == null) return -1
            return readAvailable(dst, offset, length)
        }
        val size = minOf(remaining, length.toLong()).toInt()
        readFully(dst, offset, size)
        return size
    }

    final override fun readFully(dst: IoBuffer, length: Int) {
        require(length <= dst.writeRemaining) { "Not enough free space in destination buffer to write $length bytes" }

        var copied = 0
        takeWhile { buffer ->
            val rc = buffer.readAvailable(dst, length - copied)
            if (rc > 0) copied += rc
            copied < length
        }

        if (copied != length) {
            throw EOFException("Not enough bytes available to read $length bytes, $copied were copied")
        }
    }

    final override fun readAvailable(dst: IoBuffer, length: Int): Int {
        val remaining = remaining
        if (remaining == 0L) {
            if (doFill() == null) return -1
            return readAvailable(dst, length)
        }
        val size = minOf(remaining, length.toLong(), dst.writeRemaining.toLong()).toInt()
        readFully(dst, size)
        return size
    }

    /*
     * Returns next byte (unsigned) or `-1` if no more bytes available
     */
    final override fun tryPeek(): Int {
        val head = head
        if (headRemaining > 0) {
            return head.tryPeek()
        }

        if (tailRemaining == 0L && noMoreChunksAvailable) return -1

        return prepareRead(1, head)?.tryPeek() ?: -1
    }

    @Deprecated("Binary compatibility.", level = DeprecationLevel.HIDDEN)
    final override fun peekTo(buffer: IoBuffer): Int {
        return peekTo(buffer)
    }

    @Deprecated("Use discardExact instead.")
    final override fun discard(n: Long): Long {
        return discardAsMuchAsPossible(minOf(Int.MAX_VALUE.toLong(), n).toInt(), 0).toLong()
    }

    internal fun readCbuf(cbuf: CharArray, off: Int, len: Int): Int {
        if (isEmpty) return -1

        val out = object : Appendable {
            private var idx = off

            override fun append(c: Char): Appendable {
                cbuf[idx++] = c
                return this
            }

            override fun append(csq: CharSequence?): Appendable {
                if (csq is String) {
                    csq.getCharsInternal(cbuf, idx)
                    idx += csq.length
                }
                else if (csq != null) {
                    for (i in 0 until csq.length) {
                        cbuf[idx++] = csq[i]
                    }
                }

                return this
            }

            override fun append(csq: CharSequence?, start: Int, end: Int): Appendable {
                throw UnsupportedOperationException()
            }
        }

        return readText(out, 0, len)
    }

    /**
     * Read at least [min] and at most [max] characters and append them to [out]
     * @return number of characters appended
     */
    fun readText(out: Appendable, min: Int = 0, max: Int = Int.MAX_VALUE): Int {
        if (max.toLong() >= remaining) {
            val s = readTextExactBytes(bytesCount = remaining.toInt())
            out.append(s)
            return s.length
        }
        return readASCII(out, min, max)
    }

    /**
     * Read exactly [exactCharacters] characters and append them to [out]
     */
    fun readTextExact(out: Appendable, exactCharacters: Int) {
        readText(out, exactCharacters, exactCharacters)
    }

    /**
     * Read a string at last [min] and at most [max] characters length
     */
    fun readText(min: Int = 0, max: Int = Int.MAX_VALUE): String {
        if (min == 0 && (max == 0 || isEmpty)) return ""
        val remaining = remaining
        if (remaining > 0 && max.toLong() >= remaining) return readTextExactBytes(bytesCount = remaining.toInt())

        return buildString(min.coerceAtLeast(16).coerceAtMost(max)) {
            readASCII(this, min, max)
        }
    }

    /**
     * Read a string exactly [exactCharacters] length
     */
    fun readTextExact(exactCharacters: Int): String {
        return readText(exactCharacters, exactCharacters)
    }

    private fun readASCII(out: Appendable, min: Int, max: Int): Int {
        when {
            max == 0 && min == 0 -> return 0
            isEmpty -> if (min == 0) return 0 else atLeastMinCharactersRequire(min)
            max < min -> minShouldBeLess(min, max)
        }

        var copied = 0
        var utf8 = false

        takeWhile { buffer ->
            val rc = buffer.decodeASCII {
                if (copied == max) false
                else {
                    out.append(it)
                    copied++
                    true
                }
            }

            when {
                rc -> true
                copied == max -> false
                else -> {
                    utf8 = true
                    false
                }
            }
        }

        if (utf8) {
            return copied + readUtf8(out, min - copied, max - copied)
        }
        if (copied < min) prematureEndOfStreamChars(min, copied)
        return copied
    }

    internal final fun peekToImpl(destination: IoBuffer, offset: Int, min: Int, max: Int): Int {
        var current: IoBuffer = head
        var copied = 0
        var skip = offset

        while (copied < min) {
            val chunkSize = current.readRemaining
            if (chunkSize > skip) {
                val size = minOf(chunkSize - skip, max - copied)
                current.readFully(destination, size)
                skip = 0
                copied += size
            } else {
                skip -= chunkSize
            }

            current = current.next ?: break
        }

        return copied
    }

    private fun atLeastMinCharactersRequire(min: Int): Nothing =
        throw EOFException("at least $min characters required but no bytes available")

    private fun minShouldBeLess(min: Int, max: Int): Nothing =
        throw IllegalArgumentException("min should be less or equal to max but min = $min, max = $max")

    private fun prematureEndOfStreamChars(min: Int, copied: Int): Nothing = throw MalformedUTF8InputException(
        "Premature end of stream: expected at least $min chars but had only $copied"
    )

    private fun prematureEndOfStream(size: Int): Nothing =
        throw MalformedUTF8InputException("Premature end of stream: expected $size bytes")

    private fun readUtf8(out: Appendable, min: Int, max: Int): Int {
        var copied = 0

        takeWhileSize { buffer ->
            val size = buffer.decodeUTF8 {
                if (copied == max) false
                else {
                    out.append(it)
                    copied++
                    true
                }
            }

            when {
                size == 0 -> 1
                size > 0 -> size
                else -> 0
            }
        }

        if (copied < min) prematureEndOfStreamChars(min, copied)

        return copied
    }

    private tailrec fun discardAsMuchAsPossible(n: Int, skipped: Int): Int {
        if (n == 0) return skipped
        val current = prepareRead(1) ?: return skipped
        val size = minOf(current.readRemaining, n)
        current.discardExact(size)
        headRemaining -= size
        afterRead()

        return discardAsMuchAsPossible(n - size, skipped + size)
    }

    private tailrec fun readAsMuchAsPossible(array: ByteArray, offset: Int, length: Int, copied: Int): Int {
        if (length == 0) return copied
        val current = prepareRead(1) ?: return copied
        val size = minOf(length, current.readRemaining)

        current.readFully(array, offset, size)
        headRemaining -= size

        return if (size != length || current.readRemaining == 0) {
            afterRead()
            readAsMuchAsPossible(array, offset + size, length - size, copied + size)
        } else {
            copied + size
        }
    }

    private inline fun  readN(n: Int, block: IoBuffer.() -> R): R {
        val bb = prepareRead(n) ?: notEnoughBytesAvailable(n)
        val rc = block(bb)

        val after = bb.readRemaining
        if (after == 0) {
            ensureNext(bb)
        } else {
            headRemaining = after
        }

        return rc
    }

    private fun notEnoughBytesAvailable(n: Int): Nothing {
        throw EOFException("Not enough data in packet ($remaining) to read $n byte(s)")
    }

    @DangerousInternalIoApi
    fun updateHeadRemaining(remaining: Int) {
        headRemaining = remaining
    }

    @DangerousInternalIoApi
    fun prepareReadHead(minSize: Int): IoBuffer? = prepareRead(minSize, head)

    @DangerousInternalIoApi
    fun ensureNextHead(current: IoBuffer): IoBuffer? = ensureNext(current)

    @PublishedApi
    internal fun ensureNext(current: IoBuffer) = ensureNext(current, IoBuffer.Empty)

    @DangerousInternalIoApi
    fun fixGapAfterRead(current: IoBuffer) {
        val next = current.next ?: return fixGapAfterReadFallback(current)

        val remaining = current.readRemaining
        val overrunSize = minOf(remaining, IoBuffer.ReservedSize - current.endGap)
        if (next.startGap < overrunSize) return fixGapAfterReadFallback(current)

        next.restoreStartGap(overrunSize)

        if (remaining > overrunSize) {
            current.restoreEndGap(overrunSize)

            this.headRemaining = remaining - overrunSize
            this.tailRemaining += overrunSize
        } else {
            this.head = next
            val nextSize = next.readRemaining
            this.headRemaining = nextSize
            this.tailRemaining -= nextSize - overrunSize

            current.release(pool)
        }
    }

    private fun fixGapAfterReadFallback(current: IoBuffer) {
        if (noMoreChunksAvailable) {
            this.headRemaining = current.readRemaining
            this.tailRemaining = 0
            return
        }

        val size = current.readRemaining
        val overrun = minOf(size, IoBuffer.ReservedSize - current.endGap)

        if (size > overrun) {
            fixGapAfterReadFallbackUnreserved(current, size, overrun)
        } else {
            val new = pool.borrow()
            new.reserveEndGap(IoBuffer.ReservedSize)
            new.next = current.next

            new.writeBufferAppend(current, size)
            this.head = new
            this.headRemaining = size
            this.tailRemaining = 0L
        }

        current.release(pool)
    }

    private fun fixGapAfterReadFallbackUnreserved(current: IoBuffer, size: Int, overrun: Int) {
        // if we have a chunk with no end reservation
        // we can split it into two to fix it

        val chunk1 = pool.borrow()
        val chunk2 = pool.borrow()

        chunk1.reserveEndGap(IoBuffer.ReservedSize)
        chunk2.reserveEndGap(IoBuffer.ReservedSize)
        chunk1.next = chunk2
        chunk2.next = current.next

        chunk1.writeBufferAppend(current, size - overrun)
        chunk2.writeBufferAppend(current, overrun)

        this.head = chunk1
        this.headRemaining = chunk1.readRemaining
        this.tailRemaining = chunk2.readRemaining.toLong()
    }

    private tailrec fun ensureNext(current: IoBuffer, empty: IoBuffer): IoBuffer? {
        if (current === empty) {
            return doFill()
        }

        val next = current.next
        current.release(pool)

        return when {
            next == null -> {
                this.headRemaining = 0
                this.tailRemaining = 0L
                this.head = empty
                ensureNext(empty, empty)
            }
            next.canRead() -> {
                head = next
                @Suppress("DEPRECATION")
                next.byteOrder = byteOrder
                val nextRemaining = next.readRemaining
                headRemaining = nextRemaining
                tailRemaining -= nextRemaining
                next
            }
            else -> ensureNext(next, empty)
        }
    }

    /**
     * Reads the next chunk suitable for reading or `null` if no more chunks available. It is also allowed
     * to return a chain of chunks linked through [IoBuffer.next]. The last chunk should have `null` next reference.
     * Could rethrow exceptions from the underlying source.
     */
    protected abstract fun fill(): IoBuffer?

    /**
     * Should close the underlying bytes source. Could do nothing or throw exceptions.
     */
    protected abstract fun closeSource()

    internal fun markNoMoreChunksAvailable() {
        if (!noMoreChunksAvailable) {
            noMoreChunksAvailable = true
        }
    }

    private fun doFill(): IoBuffer? {
        if (noMoreChunksAvailable) return null
        val chunk = fill()
        if (chunk == null) {
            noMoreChunksAvailable = true
            return null
        }
        appendView(chunk)
        return chunk
    }

    private fun appendView(chunk: IoBuffer) {
        val tail = head.findTail()
        if (tail === IoBuffer.Empty) {
            head = chunk
            @Suppress("DEPRECATION")
            chunk.byteOrder = byteOrder
            require(tailRemaining == 0L) { throw IllegalStateException("It should be no tail remaining bytes if current tail is EmptyBuffer") }
            headRemaining = chunk.readRemaining
            tailRemaining = chunk.next?.remainingAll() ?: 0L
        } else {
            tail.next = chunk
            tailRemaining += chunk.remainingAll()
        }
    }

    @Suppress("NOTHING_TO_INLINE")
    internal inline fun prepareRead(minSize: Int): IoBuffer? = prepareRead(minSize, head)

    @PublishedApi
    internal tailrec fun prepareRead(minSize: Int, head: IoBuffer): IoBuffer? {
        val headSize = headRemaining
        if (headSize >= minSize) return head

        val next = head.next ?: doFill() ?: return null
        @Suppress("DEPRECATION")
        next.byteOrder = byteOrder

        if (headSize == 0) {
            if (head !== IoBuffer.Empty) {
                releaseHead(head)
            }

            return prepareRead(minSize, next)
        } else {
            val before = next.readRemaining
            head.writeBufferAppend(next, minSize - headSize)
            val after = next.readRemaining
            headRemaining = head.readRemaining
            tailRemaining -= before - after
            if (after == 0) {
                head.next = next.next
                next.release(pool)
            }
        }

        if (head.readRemaining >= minSize) return head
        if (minSize > IoBuffer.ReservedSize) minSizeIsTooBig(minSize)

        return prepareRead(minSize, head)
    }

    internal fun prefetch(size: Int) {
        if (headRemaining >= size) return
        val head = head

        val currentSize = head.remainingAll()
        if (currentSize >= size || noMoreChunksAvailable) return

        if (head === IoBuffer.Empty) {
            doFill()
            return prefetch(size)
        }

        prefetchLoop(size, currentSize, head)
    }

    private fun prefetchLoop(size: Int, currentSize0: Long, head: IoBuffer) {
        var tail = head.findTail()
        var currentSize = currentSize0
        var tailRemaining = tailRemaining

        do {
            val chunk = fill()
            if (chunk == null) {
                noMoreChunksAvailable = true
                break
            }
            val chunkSize = chunk.readRemaining
            tail.next = chunk
            tailRemaining += chunkSize
            currentSize += chunkSize
            tail = chunk
        } while (currentSize < size)

        this.tailRemaining = tailRemaining
    }

    private fun minSizeIsTooBig(minSize: Int): Nothing {
        throw IllegalStateException("minSize of $minSize is too big (should be less than ${IoBuffer.ReservedSize}")
    }

    private fun afterRead() {
        val head = head
        if (head.readRemaining == 0) {
            releaseHead(head)
        }
    }

    internal fun releaseHead(head: IoBuffer): IoBuffer {
        val next = head.next ?: IoBuffer.Empty
        this.head = next
        val nextRemaining = next.readRemaining
        this.headRemaining = nextRemaining
        this.tailRemaining -= nextRemaining
        head.release(pool)

        return next
    }

    companion object {
        @Deprecated(
            "Use ByteReadPacket.Empty instead",
            ReplaceWith("ByteReadPacket.Empty"),
            level = DeprecationLevel.ERROR
        )
        val Empty: ByteReadPacket
            get() = ByteReadPacket.Empty

        @Deprecated("This implementation detail is going to become internal.", level = DeprecationLevel.ERROR)
        val ReservedSize: Int = IoBuffer.ReservedSize
    }
}

expect class EOFException(message: String) : IOException

private fun IoBuffer.setByteOrderForNonEmpty(newByteOrder: ByteOrder) {
    if (canRead()) {
        @Suppress("DEPRECATION")
        byteOrder = newByteOrder
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy