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

loggersoft.kotlin.streams.BitStream.kt Maven / Gradle / Ivy

There is a newer version: 0.33
Show newest version
/*
 * Copyright (C) 2018 Alexander Kornilov ([email protected])
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package loggersoft.kotlin.streams

import java.io.Closeable
import java.io.EOFException
import java.io.Flushable
import java.math.BigInteger
import kotlin.experimental.and
import kotlin.experimental.inv
import kotlin.experimental.or
import kotlin.math.min

/**
 * Bit access over [Stream].
 *
 * @author Alexander Kornilov ([email protected]).
 */
@Suppress("NOTHING_TO_INLINE")
class BitStream(private val stream: Stream) : Closeable, AutoCloseable, Flushable {

    /**
     * Constructs stream from [StreamInput]
     */
    constructor(input: StreamInput): this(StreamAdapterInput(input))

    /**
     * Constructs stream from [StreamOutput]
     */
    constructor(output: StreamOutput): this(StreamAdapterOutput(output))

    /**
     * Indicates that [position] of the stream can be changed.
     */
    val isSeekable: Boolean = stream.isSeekable

    /**
     * Indicates that stream has fixed [size].
     */
    val isFixedSize: Boolean = stream.isFixedSize

    /**
     * Indicates that stream is readable.
     */
    val isReadable: Boolean = stream.isReadable

    /**
     * Indicates that stream is writable.
     */
    val isWritable: Boolean = stream.isWritable

    /**
     * Current stream size in bytes or negative value if size isn't available at moment.
     */
    val size: Long = stream.size

    /**
     * Current absolute byte position in the stream or -1 if not supported or not available at moment.
     */
    var position: Long
        get() = calculateActualPosition().first
        set(value) {
            if (!seekInCache(value, 0)) {
                resetCache(false)
                stream.position = value
            }
            bitOffset = 0
        }

    /**
     * Bit offset in current byte (0..7).
     */
    var offset: Int
        get() = calculateActualPosition().second
        set(value) {
            val newOffset = when {
                value < 0 -> 0
                value > 7 -> 7
                else -> value
            }
            if (!seekInCache(-1, newOffset)) {
                resetCache()
                bitOffset = newOffset
            }
        }

    /**
     * Current absolute bit position in the stream.
     */
    var bitPosition: Long
        get() {
            val (actualPosition, actualOffset) = calculateActualPosition()
            return if (actualPosition >= 0) actualPosition * 8 + actualOffset else -1
        }
        set(value) {
            val newBitPosition = if (value >= 0) value else 0
            val newPosition = newBitPosition / 8
            val newOffset = (newBitPosition % 8).toInt()
            if (!seekInCache(newPosition, newOffset)) {
                resetCache(false)
                position = newPosition
                bitOffset = newOffset
            }
        }

    /**
     * Skips [bits] in the stream.
     */
    fun skip(bits: Int): Boolean {
        if (bits < 0) return false
        if (bits == 0) return true
        return if (seekInCache(bits)) true else {
            val hasReadCache = cacheMode == CacheMode.Read && cacheBits >= 8
            val availableBytes = if (cacheMode == CacheMode.Read) cacheAvailable / 8 else 0
            resetCache(false)
            val bitDelta = bits - if (hasReadCache) (8 - bitOffset) else 0
            val skipBytes = bitDelta / 8 - availableBytes
            bitOffset = bitDelta % 8
            skipBytes(skipBytes)
        }
    }

    /**
     * Reads one bit from the stream.
     * @throws EOFException
     */
    fun readBit(): Boolean
        = if (loadCache() >= 1) cache.and(1L.shl(cacheOffset++)) != 0L else throw EOFException()

    /**
     * Reads specified [bits] from the stream.
     * The [bits] should be in range 1..64.
     * If [signed] is true the last bit is interpreted as indicator of sign.
     * Bits order is sequential that means BigEndian byte order for the
     * integers which contain more then one byte.
     * @throws EOFException
     */
    fun readBits(bits: Int, signed: Boolean = false): Long {
        require(bits in 1..64)
        return readNumber(bits, signed, 0L, ::processBitsLong) { value, b, s ->
            if (s && bits < 64 && value.shr(b - 1) != 0L) value.or(-1L shl(b)) else value
        }
    }

    /**
     * Reads byte from the stream.
     * @throws EOFException
     */
    fun readByte(): Byte = readBits(8, true).toByte()

    /**
     * Reads byte as unsigned integer from the stream.
     * @throws EOFException
     */
    fun readByteUnsigned(): Int = readBits(8, false).toInt()

    /**
     * Reads arbitrary number of [bits] from the stream.
     * If [signed] is true the last bit is interpreted as indicator of sign.
     * Bits order is sequential that means BigEndian byte order for the
     * integers which contain more then one byte.
     * @throws EOFException
     */
    fun readBigInteger(bits: Int, signed: Boolean = false): BigInteger {
        require(bits > 0)
        return readNumber(bits, signed, BigInteger.ZERO, ::processBitsBigInteger) { value, b, s ->
            if (s && value.shr(b - 1) != BigInteger.ZERO) {
                var mask = BigInteger.ZERO
                for (i in 0 until b) mask = mask.or(BigInteger.ONE.shl(i))
                value.not().and(mask).negate() - BigInteger.ONE
            } else value
        }
    }

    /**
     * Writes one bit to the stream.
     */
    fun write(value: Boolean) {
        prepareWrite()
        cache = if (value) cache.or(1L.shl(cacheOffset))
                else cache.and(1L.shl(cacheOffset).inv())
        bitsWritten(1)
    }

    /**
     * Writes one byte to the stream.
     */
    fun write(value: Byte) = write(value.toLong(), 8)

    /**
     * Writes [bits] from the [Long]. The [bits] should be in 1..64.
     * The bits are written in natural order that means BigEndian byte
     * order for integers which have more than one byte.
     */
    fun write(value: Long, bits: Int) {
        require(bits in 1..64)
        writeNumber(value, bits) { v, offset, bitmask, move ->
            v.shr(offset).and(bitmask).shl(move)
        }
    }

    /**
     * Writes arbitrary number of [bits] from the [BigInteger].
     * The bits are written in natural order that means BigEndian byte
     * order for integers which have more than one byte.
     */
    fun write(value: BigInteger, bits: Int) {
        require(bits > 0)
        writeNumber(value, bits) { v, offset, bitmask, move ->
            v.shr(offset).toLong().and(bitmask).shl(move)
        }
    }

    /**
     * Flushes write cache into underlying stream.
     */
    override fun flush() {
        flushCache()
        stream.flush()
    }

    /**
     * Closes the stream (include underlying stream as well).
     */
    override fun close() {
        flushCache()
        stream.close()
    }

    /**
     * Writes bit as += operation.
     * @see [write]
     */
    operator fun plusAssign(value: Boolean) = write(value)

    /**
     * Writes byte as += operator.
     * @see [write]
     */
    operator fun plusAssign(value: Byte) = write(value)

    private enum class CacheMode {
        Empty,
        Read,
        Write
    }

    private var bitOffset = 0
    private var cache = 0L
    private var cacheOffset = 0
    private var cacheBits = 0
    private var cacheMode = CacheMode.Empty
    private val readBuffer: ByteArray = ByteArray(8)

    private inline val cacheAvailable
        inline get() = cacheBits - cacheOffset

    private inline val cacheCapacity
        inline get() = 64 - cacheBits

    private fun skipBytes(bytes: Int): Boolean {
        if (bytes < 0) return false
        if (bytes == 0) return true
        val skipped = stream.skip(bytes.toLong()).toInt()
        if (skipped == bytes) return true
        if (skipped > bytes) return false
        val rest = if (skipped <= 0) bytes else bytes - skipped
        if (stream.isSeekable) {
            val currentPosition = stream.position
            if (currentPosition >= 0) {
                stream.position = currentPosition + rest
                return stream.position == currentPosition + rest
            }
        }
        for(i in 0 until (readBuffer.size / rest)) if (stream.readBytes(readBuffer) != readBuffer.size) return false
        val lastBlock = readBuffer.size % rest
        return if (lastBlock > 0) stream.readBytes(readBuffer, lastBlock) == lastBlock else true
    }

    private inline fun processBitsLong(value: Long, offset: Int, cacheBits: Long): Long
            = value.or(cacheBits.shl(offset))

    private inline fun processBitsBigInteger(value: BigInteger, offset: Int, cacheBits: Long): BigInteger
            = value.or(cacheBits.toBigIntegerUnsigned().shl(offset))

    private inline fun  readNumber(bits: Int, signed: Boolean, initValue: T, processBits: (value: T, offset: Int, cacheBits: Long) -> T, finalize: (value: T, bits: Int, signed: Boolean) -> T): T {
        var count = 0
        var result = initValue
        while (count < bits && loadCache() > 0) {
            val portion = min(cacheAvailable, bits - count)
            result = processBits(result, count, getCacheBits(portion))
            cacheOffset += portion
            count += portion
        }
        return if (count < bits) throw EOFException() else finalize(result, bits, signed)
    }

    private inline fun getCacheBits(bits: Int): Long = cache.shr(cacheOffset).and(longBitmask(bits))

    private inline fun canReadByte() = stream.isReadable && stream.isSeekable && stream.canRead(1)

    private inline fun prepareWrite() {
        cacheMode = when(cacheMode) {
            CacheMode.Empty -> CacheMode.Write
            CacheMode.Read -> { resetCache(); CacheMode.Write }
            CacheMode.Write -> { if (cacheCapacity <= 0) flushCache(); return }
        }
        cacheOffset = bitOffset
        cacheBits = if (bitOffset > 0 && canReadByte()) {
            cache = stream.readByteUnsigned().toLong()
            stream.position--
            8
        } else bitOffset
    }

    private inline fun bitsWritten(bits: Int) {
        cacheOffset += bits
        if (cacheOffset > cacheBits) cacheBits = cacheOffset
    }

    private inline fun  writeNumber(value: T, bits: Int, getBits: (value: T, offset: Int, bitmask: Long, move: Int) -> Long) {
        prepareWrite()
        var count = 0
        while (count < bits) {
            if (cacheCapacity <= 0) flushCache()
            val portion = min(cacheCapacity, bits - count)
            val bitmask = longBitmask(portion)
            cache = cache.and(bitmask.shl(cacheOffset).inv())
            cache = cache.or(getBits(value, count, bitmask, cacheOffset))
            bitsWritten(portion)
            count += portion
        }
    }

    private fun loadCache(): Int {
        when(cacheMode) {
            CacheMode.Read -> if (cacheAvailable > 0) return cacheAvailable
            CacheMode.Write -> {
                resetCache()
                cacheMode = CacheMode.Read
            }
            CacheMode.Empty -> {
                cacheOffset = bitOffset
                cacheMode = CacheMode.Read
            }
        }
        if (cacheCapacity < 8 || cacheCapacity % 8 != 0) clearCache()
        val retrieved = stream.readBytes(readBuffer, cacheCapacity / 8)
        if (retrieved <= 0) return cacheAvailable
        for (index in 0 until retrieved) {
            cache = cache.and(0xFFL.shl(cacheBits).inv())
            cache = cache.or(readBuffer[index].toLong().and(0xFF).shl(cacheBits))
            cacheBits += 8
        }
        return cacheAvailable
    }

    private fun resetCache(updatePosition: Boolean = true) {
        val (actualPosition, actualOffset) = calculateActualPosition()
        when (cacheMode) {
            CacheMode.Read -> if (updatePosition) stream.position = actualPosition
            CacheMode.Write -> flushCache()
            else -> {}
        }
        cacheMode = CacheMode.Empty
        bitOffset = actualOffset
        clearCache()
    }

    private fun flushCache() {
        if (cacheMode != CacheMode.Write || cacheBits <= 0) return
        val bitRest = cacheBits % 8
        if (bitRest == 0 || !canReadByte()) {
            writeCache((cacheBits + 7) / 8)
            clearCache()
            return
        }

        val bytes = cacheOffset / 8
        if (bytes > 0) writeCache(bytes)
        val bitMask = byteBitmask(bitRest)
        val lastBits = cache.shr(bytes * 8).toByte().and(bitMask)
        val mergedLastByte = stream.readByte().and(bitMask.inv()).or(lastBits)
        stream.position--
        stream.writeByte(mergedLastByte)
        stream.position--
        clearCache()
    }

    private inline fun clearCache() {
        cache = 0L
        cacheOffset = 0
        cacheBits = 0
    }

    private inline fun writeCache(bytes: Int) = stream.writeInt(cache, bytes, nativeByteOrder)

    private fun calculateActualPosition(): Pair {
        val streamPosition = stream.position
        if (cacheBits <= 0 || cacheOffset < 0) return Pair(streamPosition, bitOffset)
        val cacheBytes = cacheBits / 8
        return when(cacheMode) {
            CacheMode.Empty -> Pair(streamPosition, bitOffset)
            CacheMode.Read -> if (streamPosition >= cacheBytes) Pair(streamPosition - cacheBytes + cacheOffset / 8, cacheOffset % 8)
                              else Pair(streamPosition, cacheOffset % 8)
            CacheMode.Write -> Pair(streamPosition + cacheOffset / 8, cacheOffset % 8)
        }
    }

    private fun seekInCache(position: Long, offset: Int = -1): Boolean =
            seekInCache((if (position >= 0) (position - this.position).toInt() * 8 else 0) +
                    (if (offset >= 0) offset - this.offset else 0))

    private fun seekInCache(bitDelta: Int): Boolean =
            when (cacheMode) {
                CacheMode.Empty -> false
                CacheMode.Read -> if (cacheOffset + bitDelta in 0..cacheBits) { cacheOffset += bitDelta; true } else false
                CacheMode.Write -> if (bitDelta < 0 && cacheOffset + bitDelta >= 0) { cacheOffset += bitDelta; true } else false
            }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy