Foundations(iOS Sourceset): Implement functions(some, basic) for BitSet(with passing tests that use it).

This commit is contained in:
KotlinGeekDev
2025-12-29 19:00:00 +01:00
parent 356b49dba9
commit 4feaec5e00
5 changed files with 2483 additions and 15 deletions
@@ -20,32 +20,32 @@
*/
package com.vitorpamplona.quartz.utils
import com.vitorpamplona.quartz.utils.io.CustomBitSet
actual class BitSet {
val nativeBitSet: CustomBitSet
actual constructor(nBits: Int) {
TODO("Not yet implemented")
nativeBitSet = CustomBitSet(nBits)
}
constructor(bytes: ByteArray) {
nativeBitSet = CustomBitSet(bytes)
}
actual fun set(bitIndex: Int) {
TODO("Not yet implemented")
nativeBitSet.set(bitIndex, true)
}
actual fun clear(bitIndex: Int) {
TODO("Not yet implemented")
nativeBitSet.clear(bitIndex)
}
actual fun get(bitIndex: Int): Boolean {
TODO("Not yet implemented")
}
actual fun get(bitIndex: Int): Boolean = nativeBitSet[bitIndex]
actual fun size(): Int {
TODO("Not yet implemented")
}
actual fun size(): Int = nativeBitSet.size()
actual fun toByteArray(): ByteArray {
TODO("Not yet implemented")
}
actual fun toByteArray(): ByteArray = nativeBitSet.toByteArray()
}
actual fun bitSetValueOf(bytes: ByteArray): BitSet {
TODO("Not yet implemented")
}
actual fun bitSetValueOf(bytes: ByteArray): BitSet = BitSet(bytes)
@@ -0,0 +1,622 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.utils.io
// Credits: skolson, from https://github.com/skolson/KmpIO
abstract class Buffer<Element, Array> internal constructor(
markPosition: Int,
pos: Int,
lim: Int,
cap: Int,
var order: ByteOrder = ByteOrder.LittleEndian
) {
enum class ByteOrder { LittleEndian, BigEndian }
// Invariants: mark <= position <= limit <= capacity
var position = pos
/**
* Sets this buffer's position. If the mark is defined and larger than the
* new position then it is discarded.
*
* @param newPosition
* The new position value; must be non-negative
* and no larger than the current limit
*
* @throws IllegalArgumentException
* If the preconditions on <tt>newPosition</tt> do not hold
*/
set(newPosition) {
if (newPosition > limit || newPosition < 0)
throw IllegalArgumentException("Position $newPosition exceeds limit:$limit")
field = newPosition
if (mark > position) mark = noMark
}
var limit = lim
/**
* Sets this buffer's limit. If the position is larger than the new limit
* then it is set to the new limit. If the mark is defined and larger than
* the new limit then it is discarded.
*
* @param newLimit
* The new limit value; must be non-negative
* and no larger than this buffer's capacity
*
* @return This buffer
*
* @throws IllegalArgumentException
* If the preconditions on <tt>newLimit</tt> do not hold
*/
set(newLimit) {
if (newLimit > capacity || newLimit < 0)
throw IllegalArgumentException("limit $newLimit exceeds capacity $capacity")
field = newLimit
if (position > limit) position = limit
if (mark > limit) mark = -1
}
var capacity = cap
internal set
var mark = markPosition
set(value) {
if (mark < -1 || mark > position)
throw IllegalArgumentException("Mark $mark out of range of 0 to $position")
field = value
}
/**
* Returns the number of elements between the current position and the
* limit.
*
* @return The number of elements remaining in this buffer
*/
val remaining get() = limit - position
/**
* Tells whether there are any elements between the current position and
* the limit.
*
* @return <tt>true</tt> if, and only if, there is at least one element
* remaining in this buffer
*/
val hasRemaining get() = position < limit
/**
* Tells whether or not this buffer is read-only.
*
* @return <tt>true</tt> if, and only if, this buffer is read-only
*/
abstract val isReadOnly: Boolean
/**
* Subclasses provide a property that get a byte from the buffer, or put a byte in the buffer.
* Position must be incremented in both cases
*/
abstract var byte: Element
/**
* The following functions are all usable by subclasses to encode/decode basic types
* in and Endian-aware fashion. In all cases an exception is thrown if remaining() < length
* to be read/written. Position will be incremented by the appropriate length for both gets
* and sets.
*
* For some reason, the ByteArray in Kotlin Native is the only implementation that provides
* similar function, but assumes all is Little Endian. Since these are not available in the
* common stdlib, they are not used even for little endian.
*/
/**
* Read or write a two-byte Char at the current position. Position will increment by 2.
*/
var char: Char
get() {
val c = when (order) {
ByteOrder.LittleEndian -> ((getElementAsInt(position + 1) shl 8) or
getElementAsInt(position)).toChar()
ByteOrder.BigEndian -> ((getElementAsInt(position) shl 8) or
getElementAsInt(position + 1)).toChar()
}
position += 2
return c
}
set(value) {
putEndian(shortToArray(value.code.toShort()))
}
/**
* Read or write a two-byte Short at the current position. Position will increment by 2.
*/
var short: Short
get() {
if (remaining < shortLength)
throw IllegalArgumentException("Short requires $shortLength bytes. Position: $position, remaining:$remaining")
val s = getShortValue(position)
position += shortLength
return s
}
set(value) {
putEndian(shortToArray(value))
}
/**
* Read or write a two-byte Short at the current position. Position will increment by 2.
*/
var ushort: UShort
get() {
if (remaining < shortLength)
throw IllegalArgumentException("UShort requires $shortLength bytes. Position: $position, remaining:$remaining")
val s = getUShortValue(position)
position += shortLength
return s
}
set(value) {
putEndian(ushortToArray(value))
}
/**
* Read or write a four-byte Int at the current position. Position will increment by 4.
*/
var int: Int
get() {
if (remaining < intLength)
throw IllegalArgumentException("Int requires $intLength bytes. Position: $position, remaining:$remaining")
val s = getIntValue(position)
position += intLength
return s
}
set(value) {
putEndian(intToArray(value))
}
/**
* Read or write a two-byte Short at the current position. Position will increment by 2.
*/
var uint: UInt
get() {
if (remaining < intLength)
throw IllegalArgumentException("UInt requires $intLength bytes. Position: $position, remaining:$remaining")
val s = getUIntValue(position)
position += intLength
return s
}
set(value) {
putEndian(uintToArray(value))
}
/**
* Read or write a four-byte Int at the current position. Position will increment by 4.
*/
var long: Long
get() {
if (remaining < longLength)
throw IllegalArgumentException("Long requires $longLength bytes. Position: $position, remaining:$remaining")
val s = getLongValue(position)
position += longLength
return s
}
set(value) {
putEndian(longToArray(value))
}
/**
* Read or write a four-byte Int at the current position. Position will increment by 4.
*/
var ulong: ULong
get() {
if (remaining < longLength)
throw IllegalArgumentException("ULong requires $longLength bytes. Position: $position, remaining:$remaining")
val s = getULongValue(position)
position += longLength
return s
}
set(value) {
putEndian(ulongToArray(value))
}
var float: Float
get() = Float.fromBits(this.int)
set(value) {
int = value.toBits()
}
var double: Double
get() = Double.Companion.fromBits(this.long)
set(value) {
long = value.toBits()
}
// Creates a new buffer with the given mark, position, limit, and capacity,
// after checking invariants.
init {
if (cap < 0) throw IllegalArgumentException("Negative capacity: $cap")
capacity = cap
limit = lim
position = pos
if (mark >= 0) {
if (mark > pos)
throw IllegalArgumentException("mark > position: ($mark > $pos)")
this.mark = mark
}
}
/**
* Clears this buffer. The position is set to zero, the limit is set to
* the capacity, and the mark is discarded.
*
*
* Invoke this method before using a sequence of channel-read or
* *put* operations to fill this buffer. For example:
*
* <blockquote><pre>
* buf.clear(); // Prepare buffer for reading
* in.read(buf); // Read data</pre></blockquote>
*
*
* This method does not actually erase the data in the buffer, but it
* is named as if it did because it will most often be used in situations
* in which that might as well be the case.
*
* @return This buffer
*/
open fun clear() {
position = 0
limit = capacity
mark = noMark
}
/**
* Increase size of buffer. Capacity and content are increased. Position is unchanged. if limit
* currently set to capacity, it will be set to the new capacity
* @param addCapacity bytes to add. Unsigned as can't be used to shrink.
*/
abstract fun expand(addCapacity: UInt)
/**
* Flips this buffer. The limit is set to the current position and then
* the position is set to zero. If the mark is defined then it is
* discarded.
*
*
* After a sequence of channel-read or *put* operations, invoke
* this method to prepare for a sequence of channel-write or relative
* *get* operations. For example:
*
* <blockquote><pre>
* buf.put(magic); // Prepend header
* in.read(buf); // Read data into rest of buffer
* buf.flip(); // Flip buffer
* out.write(buf); // Write header + data to channel</pre></blockquote>
*
*
* This method is often used in conjunction with the compact method when transferring data from
* one place to another.
*
* @return This buffer
*/
open fun flip(): Buffer<Element, Array> {
limit = position
position = 0
mark = noMark
return this
}
/**
* given the specified index into the backing array, return the current Element. Do not change position
*/
fun get(index: Int): Element {
return getElementAt(index)
}
/**
* This is used by many of the accessor properties of the various types. It must return
* a ByteArray of length bytes, containing the bytes at the current position for the
* specified length. The position will be incremented by the length.
*
* @param length must be > 0 and <= remaining(). Defaults to remaining
*
* @throws IllegalArgumentException if the length is invalid
*/
abstract fun getBytes(length: Int = remaining): Array
/**
* Starting at the current position, copy bytes.size from the buffer into the provided array.
* If the size of the array is greater than [remaining], only [remaining] bytes are copied.
* Position is increased by the number of bytes copied.
*/
abstract fun getBytes(bytes: Array)
/**
* given the specified index into the backing array, return the current Element. Do not change position
*/
abstract fun getElementAt(index: Int): Element
abstract fun setElementAt(index: Int, element: Element)
abstract fun getElementAsInt(index: Int): Int
abstract fun getElementAsUInt(index: Int): UInt
abstract fun getElementAsLong(index: Int): Long
abstract fun getElementAsULong(index: Int): ULong
/**
* Convenience method Sets the limit at position + length, then sets the position
*/
fun positionLimit(position: Int, length: Int) {
if (length < 0 || position < 0 || position + length > capacity)
throw IllegalArgumentException("Position: $position + length: $length = ${position + length} must be between 0 and $capacity")
limit = position + length
this.position = position
}
fun positionLimit(position: Short, length: Short) {
if (length < 0 || position < 0 || position + length > capacity)
throw IllegalArgumentException("Position: $position + length: $length = ${position + length} must be between 0 and $capacity")
limit = position + length
this.position = position.toInt()
}
/**
* This is used by many of the accessor properties of the various types. It must copy the
* content of bytes into the ByteBuffer at the current position.
* The position will be incremented by the length of the array.
*
* @param bytes size of the array must be > 0 and <= remaining()
*
* @throws IllegalArgumentException if the size is invalid
*/
abstract fun put(bytes: Array)
/**
* Resets this buffer's position to the previously-marked position.
*
*
* Invoking this method neither changes nor discards the mark's
* value.
*
* @return This buffer
*
* @throws IllegalStateException
* If the mark has not been set
*/
open fun reset() {
val m = mark
if (m < 0) throw IllegalStateException("Mark:$m must be non-negative")
position = m
}
fun resetMark() {
mark = noMark
}
/**
* Rewinds this buffer. The position is set to zero and the mark is
* discarded.
*
*
* Invoke this method before a sequence of channel-write or *get*
* operations, assuming that the limit has already been set
* appropriately. For example:
*
* <blockquote><pre>
* out.write(buf); // Write remaining data
* buf.rewind(); // Rewind buffer
* buf.get(array); // Copy data into array</pre></blockquote>
*
* @return This buffer
*/
open fun rewind() {
position = 0
mark = -1
}
/**
* Make a new ByteBuffer containing the [remaining bytes] of this one. Length can be overrdiden to
* a shorter value than the default [remaining]. Position is unaffected
* @param length defaults to [remaining]. can be between 1 and [remaining]
*/
abstract fun slice(length: Int = remaining): ByteBufferBase<Element, Array>
// -- Package-private methods for bounds checking, etc. --
/**
* Checks the current position against the limit, throwing a [ ] if it is not smaller than the limit, and then
* increments the position.
*
* @return The current position value, before it is incremented
*/
fun nextGetIndex(): Int {
if (position >= limit) throw IllegalStateException("Position:$position exceeds Limit:$limit")
return position++
}
fun nextGetIndex(nb: Int): Int {
if (limit - position < nb) throw IllegalStateException()
val p = position
position += nb
return p
}
/**
* Checks the current position against the limit, and then increments the position.
*
* @return The current position value, before it is incremented
* @throws IllegalStateException if position exceeds limit
*/
fun nextPutIndex(): Int {
if (position >= limit) throw IllegalStateException("Position:$position exceeds Limit:$limit")
return position++
}
/**
* Checks the current position pluss the specified offset against the limit, and then increments the position.
*
* @param length position will be increased by
* @return The current position value, before it is incremented
* @throws IllegalStateException if increased position will exceed limit
*/
fun nextPutIndex(length: Int): Int {
if (limit - position < length || position < 0)
throw IllegalStateException("Limit:$limit minus position:$position less than $length")
val p = position
position += length
return p
}
/**
* Checks the given index against the limit, throwing an [ ] if it is not smaller than the limit
* or is smaller than zero.
*/
fun checkIndex(i: Int): Int { // package-private
if (i < 0 || i >= limit) throw IndexOutOfBoundsException(
"index=$i out of bounds (limit=$limit)"
)
return i
}
fun checkIndex(i: Int, nb: Int): Int { // package-private
if (i < 0 || nb > limit - i) throw IndexOutOfBoundsException(
"index=$i out of bounds (limit=$limit, nb=$nb)"
)
return i
}
fun markValue(): Int { // package-private
return mark
}
fun truncate() { // package-private
mark = noMark
position = 0
limit = 0
capacity = 0
}
internal fun discardMark() { // package-private
mark = -1
}
private fun getShortValue(index: Int): Short {
return when (order) {
ByteOrder.LittleEndian -> ((getElementAsInt(index + 1) shl 8) or getElementAsInt(index)).toShort()
ByteOrder.BigEndian -> ((getElementAsInt(index) shl 8) or getElementAsInt(index + 1)).toShort()
}
}
fun getUShortValue(index: Int): UShort {
return when (order) {
ByteOrder.LittleEndian -> ((getElementAsUInt(index + 1) shl 8) or
getElementAsUInt(index)).toUShort()
ByteOrder.BigEndian -> ((getElementAsUInt(index) shl 8) or
getElementAsUInt(index + 1)).toUShort()
}
}
private fun getIntValue(index: Int): Int {
return when (order) {
ByteOrder.LittleEndian -> (getElementAsInt(index + 3) shl 24) or
(getElementAsInt(index + 2) shl 16) or
(getElementAsInt(index + 1) shl 8) or
getElementAsInt(index)
ByteOrder.BigEndian -> (getElementAsInt(index) shl 24) or
(getElementAsInt(index + 1) shl 16) or
(getElementAsInt(index + 2) shl 8) or
getElementAsInt(index + 3)
}
}
private fun getUIntValue(index: Int): UInt {
return when (order) {
ByteOrder.LittleEndian -> (getElementAsUInt(index + 3) shl 24) or
(getElementAsUInt(index + 2) shl 16) or
(getElementAsUInt(index + 1) shl 8) or
getElementAsUInt(index)
ByteOrder.BigEndian -> (getElementAsUInt(index) shl 24) or
(getElementAsUInt(index + 1) shl 16) or
(getElementAsUInt(index + 2) shl 8) or
getElementAsUInt(index + 3)
}
}
fun getLongValue(index: Int): Long {
return when (order) {
ByteOrder.LittleEndian -> ((getElementAsLong(index + 7) shl 56)
or (getElementAsLong(index + 6) shl 48)
or (getElementAsLong(index + 5) shl 40)
or (getElementAsLong(index + 4) shl 32)
or (getElementAsLong(index + 3) shl 24)
or (getElementAsLong(index + 2) shl 16)
or (getElementAsLong(index + 1) shl 8)
or getElementAsLong(index))
ByteOrder.BigEndian -> ((getElementAsLong(index) shl 56)
or (getElementAsLong(index + 1) shl 48)
or (getElementAsLong(index + 2) shl 40)
or (getElementAsLong(index + 3) shl 32)
or (getElementAsLong(index + 4) shl 24)
or (getElementAsLong(index + 5) shl 16)
or (getElementAsLong(index + 6) shl 8)
or getElementAsLong(index + 7))
}
}
private fun getULongValue(index: Int): ULong {
return when (order) {
ByteOrder.LittleEndian -> ((getElementAsULong(index + 7) shl 56)
or (getElementAsULong(index + 6) shl 48)
or (getElementAsULong(index + 5) shl 40)
or (getElementAsULong(index + 4) shl 32)
or (getElementAsULong(index + 3) shl 24)
or (getElementAsULong(index + 2) shl 16)
or (getElementAsULong(index + 1) shl 8)
or getElementAsULong(index))
ByteOrder.BigEndian -> ((getElementAsULong(index) shl 56)
or (getElementAsULong(index + 1) shl 48)
or (getElementAsULong(index + 2) shl 40)
or (getElementAsULong(index + 3) shl 32)
or (getElementAsULong(index + 4) shl 24)
or (getElementAsULong(index + 5) shl 16)
or (getElementAsULong(index + 6) shl 8)
or getElementAsULong(index + 7))
}
}
/**
* Helper for property accessors that care about endian encoding.
*
* @param bytes must be in MSB to LSB order. BigEndian will put array unchanged into buffer.
* LittleEndian will put bytes in LSB to MSB order in buffer.
*
*/
abstract fun putEndian(bytes: Array)
abstract fun shortToArray(short: Short): Array
abstract fun ushortToArray(ushort: UShort): Array
abstract fun intToArray(int: Int): Array
abstract fun uintToArray(int: UInt): Array
abstract fun longToArray(long: Long): Array
abstract fun ulongToArray(uLong: ULong): Array
companion object {
protected const val noMark = -1
protected const val shortLength = 2
protected const val intLength = 4
protected const val longLength = 8
fun checkBounds(off: Int, len: Int, size: Int) { // package-private
if (off or len or off + len or size - (off + len) < 0) throw IndexOutOfBoundsException(
"off=$off, len=$len out of bounds (size=$size)"
)
}
}
}
@@ -0,0 +1,892 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.utils.io
// Credits: skolson, from https://github.com/skolson/KmpIO
import kotlin.math.min
/**
* Port of a subset of Java's ByteBuffer with full Endian support. This class should be replaceable
* by an equivalent class in kotlinx-io once that library settles. This ByteBuffer is
* implemented on a ByteArray for simplicity
*/
abstract class ByteBufferBase<Element, Array> constructor(
capacity: Int,
order: ByteOrder = ByteOrder.LittleEndian,
override val isReadOnly: Boolean = false
) :Buffer<Element, Array>(-1, 0, capacity, capacity, order),
Comparable<ByteBufferBase<Element, Array>> {
abstract var buf: Array
protected set
private var offset = 0
val contentBytes get() = buf
/**
* The following properties offer simple encode/decode operations as indicated by the ByteOrder
* currently in effect. Properties are defined for many basic types
*/
/**
* This property offers byte-level read/write. get returns the byte at the current position,
* and increments then position by one. set changes the byte at the current position, then
* increments the position by one.
*
* @throws IllegalStateException if the position is already at the limit
*/
override var byte: Element
get() {
checkPosition()
return getElementAt(position++)
}
set(value) {
checkPosition()
setElementAt(position++, value)
}
/**
* Compacts this buffer&nbsp;&nbsp;<i>(optional operation)</i>.
*
* <p> The bytes between the buffer's current position and its limit,
* if any, are copied to the beginning of the buffer. That is, the
* byte at index <i>p</i>&nbsp;=&nbsp;<tt>position()</tt> is copied
* to index zero, the byte at index <i>p</i>&nbsp;+&nbsp;1 is copied
* to index one, and so forth until the byte at index
* <tt>limit()</tt>&nbsp;-&nbsp;1 is copied to index
* <i>n</i>&nbsp;=&nbsp;<tt>limit()</tt>&nbsp;-&nbsp;<tt>1</tt>&nbsp;-&nbsp;<i>p</i>.
* The buffer's position is then set to <i>n+1</i> and its limit is set to
* its capacity. The mark, if defined, is discarded.
*
* <p> The buffer's position is set to the number of bytes copied,
* rather than to zero, so that an invocation of this method can be
* followed immediately by an invocation of another relative <i>put</i>
* method. </p>
*
*
* <p> Invoke this method after writing data from a buffer in case the
* write was incomplete. The following loop, for example, copies bytes
* from one channel to another via the buffer <tt>buf</tt>:
*
* <blockquote><pre>{@code
* buf.clear(); // Prepare buffer for use
* while (in.read(buf) >= 0 || buf.position != 0) {
* buf.flip();
* out.write(buf);
* buf.compact(); // In case of partial write
* }
* }</pre></blockquote>
*
* @return This buffer
*/
fun compact() {
var destIndex = 0
for (i in position until position + limit) {
setElementAt(destIndex++, getElementAt(i))
}
position = destIndex
limit = capacity
resetMark()
}
/**
* Compares this buffer to another.
*
* <p> Two byte buffers are compared by comparing their sequences of
* remaining elements lexicographically, without regard to the starting
* position of each sequence within its corresponding buffer.
*
* @return A negative integer, zero, or a positive integer as this buffer
* is less than, equal to, or greater than the given buffer
*/
override fun compareTo(other: ByteBufferBase<Element, Array>): Int {
val n: Int = position + min(remaining, other.remaining)
var i: Int = position
var j: Int = other.position
while (i < n) {
val r = compareElement(getElementAt(i), other.getElementAt(j))
if (r != 0) return r
i++
j++
}
return 0
}
abstract fun compareElement(element: Element, other: Element): Int
/**
* The contents of the current buffer are replaced with the contents of the source. Position,
* limit, capacity, order, and mark will all be set to same as the source.
*/
fun copy(source: ByteBufferBase<Element, Array>) {
buf = source.buf
position = source.position
limit = source.limit
mark = source.mark
order = source.order
}
/**
* Similar to [ByteArray] copyInto
* @param destination Array to receive copy
* @param destinationOffset index into destination where copy will start
* @param startIndex index in source array where copy will start
* @param endIndex index in source that copy will end, exclusive. [endIndex - startIndex] will
* be number of bytes copied.
*/
abstract fun copyInto(
destination: Array,
destinationOffset: Int = 0,
startIndex: Int = 0,
endIndex: Int
)
/**
* Starting at the current position, copy bytes into the specified destination array. Increment
* position by the number of bytes retrieved.
*
* @param destination
* The array with sufficient capacity into which bytes will be written
* @param destinationOffset
* The offset into the destination at which the copy will start
* @param length
* The number of bytes to be copied
* @return This buffer
*/
fun fillArray(
destination: Array,
destinationOffset: Int = 0,
length: Int
) {
checkBounds(destinationOffset, length, length)
if (length > remaining)
throw IllegalStateException("Copying length:$length is more than remaining:$remaining")
copyInto(destination, destinationOffset, position, position + length)
position += length
}
/**
* <p> This method transfers the bytes remaining in the given source
* buffer into this buffer. If there are more bytes remaining in the
* source buffer than in this buffer, that is, if
* <tt>src.remaining()</tt>&nbsp;<tt>&gt;</tt>&nbsp;<tt>remaining()</tt>,
* then no bytes are transferred and an IllegalStateException is thrown
* </p>
*
* <p> Otherwise, this method copies
* <i>n</i>&nbsp;=&nbsp;<tt>src.remaining()</tt> bytes from the given
* buffer into this buffer, starting at each buffer's current position.
* The positions of both buffers are then incremented by <i>n</i>.
*/
open fun put(source: ByteBufferBase<Element, Array>) {
if (source == this)
throw IllegalArgumentException("Cannot copy ByteBuffer to itself")
val sourceBytes = source.remaining
if (sourceBytes > remaining)
throw IllegalArgumentException("Remaining source bytes:$sourceBytes exceeds destination remaining:$remaining")
source.fillArray(buf, position, sourceBytes)
position += sourceBytes
}
/**
* The following are all private functions
*/
private fun checkPosition(forLength: Int = 1) {
if (position + forLength > limit)
throw IllegalStateException()
}
}
/**
* Enhance byte array implementing the Buffer interface which provides position/limit/remaining/capacity tracking, and
* support for either little endian or big endian encoding of basic numeric types. See [ByteBufferBase] and [Buffer] for
* more details.
* @param capacity starting size of buffer in bytes
* @param order defaults to little endian encoding of numeric types
* @param isReadOnly true if for some reason opeations that change content should throw an exception
* @param buf defaults to a ByteArray of specified [capacity]
*/
class ByteBuffer(
capacity: Int,
order: ByteOrder = ByteOrder.LittleEndian,
isReadOnly: Boolean = false,
override var buf: ByteArray = ByteArray(capacity)
): ByteBufferBase<Byte, ByteArray>(capacity, order, isReadOnly)
{
/**
* Construct a ByteBuffer from an existing ByteArray
* @param bytes becomes the buffer content (not a copy). capacity is set to the ByteArray size, position is set to
* zero
* @param order defaults to little endian encoding of numeric types
*/
constructor(bytes: ByteArray, order: ByteOrder = ByteOrder.LittleEndian) :
this(bytes.size, order, false, bytes)
override fun flip(): ByteBuffer {
super.flip()
return this
}
override fun getElementAt(index: Int): Byte {
return buf[index]
}
override fun setElementAt(index: Int, element: Byte) {
buf[index] = element
}
/**
* gets one byte at the current position without changing the position. The byte is treated as
* unsigned, so the returned Int will always be positive.
* @param index indicates which element in the current array to retrieve
* @return INt will not have it's high order bits set.
*/
override fun getElementAsInt(index: Int): Int {
return buf toPosInt index
}
/**
* gets one byte at the current position without changing the position, and return a UInt.
* The byte is treated as unsigned.
* @param index indicates which element in the current array to retrieve
* @return UINt will not have it's high order bits set.
*/
override fun getElementAsUInt(index: Int): UInt {
return buf toPosUInt index
}
/**
* gets one byte at the current position without changing the position. The byte is treated as
* unsigned, so the returned Long will always be positive.
* @param index indicates which element in the current array to retrieve
* @return Long will not have it's high order bits set.
*/
override fun getElementAsLong(index: Int): Long {
return buf toPosLong index
}
/**
* gets one byte at the current position without changing the position. The byte is treated as
* unsigned, so the returned ULong will always be positive.
* @param index indicates which element in the current array to retrieve
* @return ULong will not have it's high order bits set.
*/
override fun getElementAsULong(index: Int): ULong {
return buf toPosULong index
}
override fun getBytes(length: Int): ByteArray {
val l = min(remaining, length)
val a = ByteArray(l)
buf.copyInto(a, 0, position, position + l)
position += l
return a
}
override fun getBytes(bytes: ByteArray) {
val l = min(remaining, bytes.size)
buf.copyInto(bytes, 0, position, position + l)
position += l
}
override fun put(bytes: ByteArray) {
val l = min(remaining, bytes.size)
bytes.copyInto(buf, position, 0, l)
position += l
}
override fun putEndian(bytes: ByteArray) {
if (order == ByteOrder.LittleEndian) {
bytes.reverse()
}
put(bytes)
}
override fun shortToArray(short: Short): ByteArray {
return byteArrayOf(
(short.toInt() shr 8).toByte(),
short.toByte()
)
}
override fun ushortToArray(ushort: UShort): ByteArray {
return byteArrayOf(
(ushort.toUInt() shr 8).toByte(),
ushort.toByte()
)
}
override fun intToArray(int: Int): ByteArray {
return byteArrayOf(
(int shr 24 and 0xff).toByte(),
(int shr 16 and 0xff).toByte(),
(int shr 8 and 0xff).toByte(),
(int and 0xff).toByte()
)
}
override fun uintToArray(int: UInt): ByteArray {
return byteArrayOf(
(int shr 24 and 0xffu).toByte(),
(int shr 16 and 0xffu).toByte(),
(int shr 8 and 0xffu).toByte(),
(int and 0xffu).toByte()
)
}
override fun longToArray(long: Long): ByteArray {
return byteArrayOf(
(long shr 56 and 0xff).toByte(),
(long shr 48 and 0xff).toByte(),
(long shr 40 and 0xff).toByte(),
(long shr 32 and 0xff).toByte(),
(long shr 24 and 0xff).toByte(),
(long shr 16 and 0xff).toByte(),
(long shr 8 and 0xff).toByte(),
(long and 0xff).toByte()
)
}
override fun ulongToArray(uLong: ULong): ByteArray {
return byteArrayOf(
(uLong shr 56 and 0xffu).toByte(),
(uLong shr 48 and 0xffu).toByte(),
(uLong shr 40 and 0xffu).toByte(),
(uLong shr 32 and 0xffu).toByte(),
(uLong shr 24 and 0xffu).toByte(),
(uLong shr 16 and 0xffu).toByte(),
(uLong shr 8 and 0xffu).toByte(),
(uLong and 0xffu).toByte()
)
}
/**
* Similar to [ByteArray] copyInto
* @param destination Array to receive copy
* @param destinationOffset index into destination where copy will start
* @param startIndex index in source array where copy will start
* @param endIndex index in source that copy will end, exclusive. [endIndex - startIndex] will
* be number of bytes copied.
*/
override fun copyInto(
destination: ByteArray,
destinationOffset: Int,
startIndex: Int,
endIndex: Int
) {
buf.copyInto(destination, destinationOffset, startIndex, endIndex)
}
override fun compareElement(element: Byte, other: Byte): Int {
return element.compareTo(other)
}
/**
* Increase size of buffer. Capacity and content are increased. Position is unchanged. if limit
* currently set to capacity, it will be set to the new capacity. All data is retained from
* previous buffer, including bytes between old limit and capacity.
* @param addCapacity bytes to add. Unsigned as can't be used to shrink.
*/
override fun expand(addCapacity: UInt) {
val changeLimit = limit == capacity
capacity += addCapacity.toInt()
val newBuf = ByteArray(capacity)
buf.copyInto(newBuf, 0, 0)
buf = newBuf
if (changeLimit) limit = capacity
}
/**
* Appends a buffer starting at its position, to the end of this buffer,
* starting at position of this buffer for appendBuffer remaining size. New position will
* be at new limit. If the contents must be expanded (usually is), then a new byteArray is allocated.
* position is moved to the new limit.
* @param appendBuffer buffer to be appended, starting at its poition for remaining bytes
* @return this
*/
fun expand(appendBuffer: ByteBuffer) {
val newLimit = position + appendBuffer.remaining
if (newLimit > capacity) {
val oldBuf = buf
buf = ByteArray(newLimit)
oldBuf.copyInto(buf, 0, 0, position)
}
appendBuffer.contentBytes.copyInto(
buf,
position,
appendBuffer.position,
appendBuffer.remaining
)
capacity = newLimit
limit = newLimit
position = limit
}
fun get(
destination: ByteArray,
destinationOffset: Int = 0,
size: Int = destination.size
) {
super.fillArray(destination, destinationOffset, size)
}
/**
* Copies specified bytes from source to this buffer, starting at position, for the
* specified length. Position is incremented by the length. Any bounds violation throws
* and IllegalArgumentException
*
* @param source byte array to write from, will be unchanged
* @param sourceOffset starting offset in source, defaults to 0
* @param length number of bytes to copy, defaults to size of source
* @return this
* @throws IllegalArgumentException on bounds violation
*/
fun putBytes(source: ByteArray, sourceOffset: Int = 0, length: Int = source.size) {
checkBounds(sourceOffset, length, length)
if (length > remaining)
throw IllegalArgumentException("Length:$length exceeds remaining:$remaining")
source.copyInto(buf, position, sourceOffset, length)
position += length
}
/**
* Make a new ByteBuffer containing the [remaining bytes] of this one. Length can be overridden to
* a shorter value than the default [remaining]. If length is > [remaining], [remaining] is used.
*
* Position in this ByteBuffer is unaffected. Position in new returned ByteBuffer is 0.
*
* @param length defaults to [remaining]. can be between 1 and [remaining]
*/
override fun slice(length: Int): ByteBuffer {
val l = min(remaining, length)
val bytes = ByteArray(l)
buf.copyInto(bytes, 0, position, position + l)
return ByteBuffer(bytes, this.order)
}
/**
* Convert from a [ByteBuffer] to a [UByteBuffer], retaining the same capacity, position, limit
* and contents
*/
fun toUByteBuffer(): UByteBuffer {
val uBuf = UByteBuffer(capacity, order, isReadOnly, contentBytes.toUByteArray())
uBuf.positionLimit(position, remaining)
return uBuf
}
override fun toString(): String {
return buildString {
append("Position: $position, limit: $limit, remaining: $remaining. Content: 0x")
for (i in position until limit) {
append("${contentBytes[i].toString(16).padStart(2, '0')} ")
}
}
}
/**
* Tells whether or not this buffer is equal to another object.
*
* <p> Two byte buffers are equal if, and only if,
*
* <ol>
*
* <li><p> They have the same element type, </p></li>
*
* <li><p> They have the same number of remaining elements, and
* </p></li>
*
* <li><p> The two sequences of remaining elements, considered
* independently of their starting positions, are pointwise equal.
* </p></li>
*
* </ol>
*
* <p> A byte buffer is not equal to any other type of object. </p>
*
* @param other The object to which this buffer is to be compared
*
* @return <tt>true</tt> if, and only if, this buffer is equal to the
* given object
*/
override fun equals(other: Any?): Boolean {
if (other == null) return false
if (this === other) return true
if (other !is ByteBuffer) return false
if (remaining != other.remaining) return false
var i = limit - 1
var j = other.limit - 1
while (i >= position) {
if (buf[i--] != other.buf[j--]) return false
}
return true
}
/**
* Returns the current hash code of this buffer.
*
* <p> The hash code of a byte buffer depends only upon its remaining
* elements; that is, upon the elements from <tt>position()</tt> up to, and
* including, the element at <tt>limit()</tt>&nbsp;-&nbsp;<tt>1</tt>.
*
* <p> Because buffer hash codes are content-dependent, it is inadvisable
* to use buffers as keys in hash maps or similar data structures unless it
* is known that their contents will not change. </p>
*
* @return The current hash code of this buffer
*/
override fun hashCode(): Int {
var h = 1
val p: Int = position
for (i in limit - 1 downTo p) h = 31 * h + buf[i].toInt()
return h
}
}
class UByteBuffer(
capacity: Int,
order: ByteOrder = ByteOrder.LittleEndian,
isReadOnly: Boolean = false,
override var buf: UByteArray = UByteArray(capacity)
): ByteBufferBase<UByte, UByteArray>(capacity, order, isReadOnly) {
constructor(bytes: UByteArray, order: ByteOrder = ByteOrder.LittleEndian) :
this(bytes.size, order, false, bytes)
/**
* Similar to [ByteArray] copyInto
* @param destination Array to receive copy
* @param destinationOffset index into destination where copy will start
* @param startIndex index in source array where copy will start
* @param endIndex index in source that copy will end, exclusive. [endIndex - startIndex] will
* be number of bytes copied.
*/
override fun copyInto(
destination: UByteArray,
destinationOffset: Int,
startIndex: Int,
endIndex: Int
) {
buf.copyInto(destination, destinationOffset, startIndex, endIndex)
}
override fun compareElement(element: UByte, other: UByte): Int {
return element.compareTo(other)
}
/**
* Increase size of buffer. Capacity and content are increased. Position is unchanged. if limit
* currently set to capacity, it will be set to the new capacity. All data is retained from
* previous buffer, including bytes between old limit and capacity.
* @param addCapacity bytes to add. Unsigned as can't be used to shrink.
*/
override fun expand(addCapacity: UInt) {
val changeLimit = limit == capacity
capacity += addCapacity.toInt()
val newBuf = UByteArray(capacity)
buf.copyInto(newBuf, 0, 0)
buf = newBuf
if (changeLimit) limit = capacity
}
/**
* Appends a buffer starting at its position, to the end of this buffer,
* starting at position of this buffer for appendBuffer remaining size. New position will
* be at new limit. If the contents must be expanded (usually is), then a new byteArray is allocated.
* position is moved to the new limit.
* @param appendBuffer buffer to be appended, starting at its poition for remaining bytes
* @return this
*/
fun expand(appendBuffer: UByteBuffer) {
val newLimit = position + appendBuffer.remaining
if (newLimit > capacity) {
val oldBuf = buf
buf = UByteArray(newLimit)
oldBuf.copyInto(buf, 0, 0, position)
}
appendBuffer.contentBytes.copyInto(
buf,
position,
appendBuffer.position,
appendBuffer.remaining
)
capacity = newLimit
limit = newLimit
position = limit
}
override fun flip(): UByteBuffer {
super.flip()
return this
}
override fun getElementAt(index: Int): UByte {
return buf[index]
}
/**
* gets one byte at the current position without changing the position. The byte is treated as
* unsigned, so the returned Int will always be positive.
* @param index indicates which element in the current array to retrieve
* @return Int will not have it's high order bits set.
*/
override fun getElementAsInt(index: Int): Int {
return buf toPosInt index
}
/**
* gets one byte at the current position without changing the position. The byte is treated as
* unsigned, so the returned UInt will always be positive.
* @param index indicates which element in the current array to retrieve
* @return UInt will not have it's high order bits set.
*/
override fun getElementAsUInt(index: Int): UInt {
return buf toPosUInt index
}
/**
* gets one byte at the current position without changing the position. The byte is treated as
* unsigned, so the returned Long will always be positive.
* @param index indicates which element in the current array to retrieve
* @return Long will not have it's high order bits set.
*/
override fun getElementAsLong(index: Int): Long {
return buf toPosLong index
}
/**
* gets one byte at the current position without changing the position. The byte is treated as
* unsigned, so the returned ULong will always be positive.
* @param index indicates which element in the current array to retrieve
* @return ULong will not have it's high order bits set.
*/
override fun getElementAsULong(index: Int): ULong {
return buf toPosULong index
}
fun get(
destination: UByteArray,
destinationOffset: Int = 0,
size: Int = destination.size
) {
super.fillArray(destination, destinationOffset, size)
}
override fun getBytes(length: Int): UByteArray {
val l = min(remaining, length)
val a = UByteArray(l)
buf.copyInto(a, 0, position, position + l)
position += l
return a
}
override fun getBytes(bytes: UByteArray) {
val l = min(remaining, bytes.size)
buf.copyInto(bytes, 0, position, position + l)
position += l
}
override fun put(bytes: UByteArray) {
val l = min(remaining, bytes.size)
bytes.copyInto(buf, position, 0, l)
position += l
}
/**
* Copies specified bytes from source to this buffer, starting at position, for the
* specified length. Position is incremented by the length. Any bounds violation throws
* an IllegalArgumentException
*
* @param source byte array to write from, will be unchanged
* @param sourceOffset starting offset in source, defaults to 0
* @param length number of bytes to copy, defaults to size of source
* @return this
* @throws IllegalArgumentException on bounds violation
*/
fun putBytes(source: UByteArray, sourceOffset: Int = 0, length: Int = source.size) {
checkBounds(sourceOffset, length, length)
if (length > remaining)
throw IllegalArgumentException("Length:$length exceeds remaining:$remaining")
for (i in sourceOffset until sourceOffset + length) {
byte = source[i]
}
}
override fun putEndian(bytes: UByteArray) {
if (order == ByteOrder.LittleEndian) {
bytes.reverse()
}
put(bytes)
}
override fun setElementAt(index: Int, element: UByte) {
buf[index] = element
}
/**
* Make a new ByteBuffer containing the [remaining bytes] of this one. Length can be overriden to
* a shorter value than the default [remaining]. Position is unaffected
* @param length defaults to [remaining]. can be between 1 and [remaining]
*/
override fun slice(length: Int): UByteBuffer {
val bytes = UByteArray(length)
buf.copyInto(bytes, 0, position, position + length)
return UByteBuffer(bytes, this.order)
}
override fun shortToArray(short: Short): UByteArray {
return ubyteArrayOf(
(short.toInt() shr 8).toUByte(),
short.toUByte()
)
}
override fun ushortToArray(ushort: UShort): UByteArray {
return ubyteArrayOf(
(ushort.toUInt() shr 8).toUByte(),
ushort.toUByte()
)
}
override fun intToArray(int: Int): UByteArray {
return ubyteArrayOf(
(int shr 24 and 0xff).toUByte(),
(int shr 16 and 0xff).toUByte(),
(int shr 8 and 0xff).toUByte(),
(int and 0xff).toUByte()
)
}
override fun uintToArray(int: UInt): UByteArray {
return ubyteArrayOf(
(int shr 24 and 0xffu).toUByte(),
(int shr 16 and 0xffu).toUByte(),
(int shr 8 and 0xffu).toUByte(),
(int and 0xffu).toUByte()
)
}
override fun longToArray(long: Long): UByteArray {
return ubyteArrayOf(
(long shr 56 and 0xff).toUByte(),
(long shr 48 and 0xff).toUByte(),
(long shr 40 and 0xff).toUByte(),
(long shr 32 and 0xff).toUByte(),
(long shr 24 and 0xff).toUByte(),
(long shr 16 and 0xff).toUByte(),
(long shr 8 and 0xff).toUByte(),
(long and 0xff).toUByte()
)
}
override fun ulongToArray(uLong: ULong): UByteArray {
return ubyteArrayOf(
(uLong shr 56 and 0xffu).toUByte(),
(uLong shr 48 and 0xffu).toUByte(),
(uLong shr 40 and 0xffu).toUByte(),
(uLong shr 32 and 0xffu).toUByte(),
(uLong shr 24 and 0xffu).toUByte(),
(uLong shr 16 and 0xffu).toUByte(),
(uLong shr 8 and 0xffu).toUByte(),
(uLong and 0xffu).toUByte()
)
}
/**
* Convert from a [UByteBuffer] to a [ByteBuffer], retaining the same capacity, position, limit
* and contents
*/
fun toByteBuffer(): ByteBuffer {
val uBuf = ByteBuffer(capacity, order, isReadOnly, contentBytes.toByteArray())
uBuf.positionLimit(position, remaining)
return uBuf
}
override fun toString(): String {
return buildString {
append("Position: $position, limit: $limit, remaining: $remaining. Content: 0x")
for (i in position until limit) {
append("${contentBytes[i].toString(16).padStart(2, '0')} ")
}
}
}
/**
* Tells whether or not this buffer is equal to another object.
*
* <p> Two byte buffers are equal if, and only if,
*
* <ol>
*
* <li><p> They have the same element type, </p></li>
*
* <li><p> They have the same number of remaining elements, and
* </p></li>
*
* <li><p> The two sequences of remaining elements, considered
* independently of their starting positions, are pointwise equal.
* </p></li>
*
* </ol>
*
* <p> A byte buffer is not equal to any other type of object. </p>
*
* @param other The object to which this buffer is to be compared
*
* @return <tt>true</tt> if, and only if, this buffer is equal to the
* given object
*/
override fun equals(other: Any?): Boolean {
if (other == null) return false
if (this === other) return true
if (other !is UByteBuffer) return false
if (remaining != other.remaining) return false
var i = limit - 1
var j = other.limit - 1
while (i >= position) {
if (buf[i--] != other.buf[j--]) return false
}
return true
}
/**
* Returns the current hash code of this buffer.
*
* <p> The hash code of a byte buffer depends only upon its remaining
* elements; that is, upon the elements from <tt>position()</tt> up to, and
* including, the element at <tt>limit()</tt>&nbsp;-&nbsp;<tt>1</tt>.
*
* <p> Because buffer hash codes are content-dependent, it is inadvisable
* to use buffers as keys in hash maps or similar data structures unless it
* is known that their contents will not change. </p>
*
* @return The current hash code of this buffer
*/
override fun hashCode(): Int {
var h = 1
val p: Int = position
for (i in limit - 1 downTo p) h = 31 * h + buf[i].toInt()
return h
}
}
@@ -0,0 +1,474 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.utils.io
import kotlin.math.max
/**
* Most of the functionality of a java.util.BitSet
*/
class CustomBitSet(
val numberOfBits: Int,
) {
private var words =
LongArray(wordIndex(numberOfBits - 1) + 1) { 0L }
private val wordsInUse: Int
get() {
var i = words.size - 1
while (i >= 0) {
if (words[i] != 0L) break
i--
}
return i + 1
}
val length: Int
get() {
if (wordsInUse == 0) {
return 0
}
return BITS_PER_WORD * (wordsInUse - 1) +
(BITS_PER_WORD - numberOfLeadingZeros(words[wordsInUse - 1]))
}
/**
* Initializes a new bit set containing all the bits in the given byte
* buffer between its position and limit.
*
* <p>The byte buffer is not modified by this method, and no
* reference to the buffer is retained by the bit set.
*
* @param bytes a byte array containing a little-endian representation
* of a sequence of bits, to be
* used as the initial bits of the new bit set
*/
constructor(bytes: ByteArray, bitsCount: Int = bytes.size * 8) : this(bitsCount) {
transformBuffer(ByteBuffer(bytes))
}
/**
* Initializes a new bit set containing all the bits in the given byte
* buffer between its position and limit.
*
* <p>The byte buffer is not modified by this method, and no
* reference to the buffer is retained by the bit set.
*
* @param buffer a byte buffer containing a little-endian representation
* of a sequence of bits between its position and limit, to be
* used as the initial bits of the new bit set
*/
constructor(buffer: ByteBuffer, bitsCount: Int = buffer.remaining * 8) : this(bitsCount) {
transformBuffer(buffer)
}
/**
* Initializes a new bit set containing all the bits in the given byte
* buffer between its position and limit.
*
* <p>The byte buffer content is not modified by this method, but the position ill be set to limit.
* Bitmask is treated as LittleEndian for the bytes containing the mask.
*
* @param buffer a byte buffer containing a little-endian representation
* of a sequence of bits between its position and limit, to be
* used as the initial bits of the new bit set
*/
private fun transformBuffer(buffer: ByteBuffer) {
words = LongArray((buffer.remaining + 7) / 8) { 0 }
var i = 0
while (buffer.remaining >= 8) words[i++] = buffer.long
var j = 0
while (buffer.remaining > 0) {
words[i] = words[i] or ((buffer.byte.toLong() and 0xffL) shl (8 * j))
j++
}
}
val empty: Boolean
get() {
return wordsInUse == 0
}
fun toByteArray(): ByteArray {
val n = wordsInUse
if (n == 0) return ByteArray(0)
var len = 8 * (n - 1)
var x = words[n - 1]
while (x != 0L) {
len++
x = x ushr 8
}
val sz = if (numberOfBits % 8 > 0) (numberOfBits / 8) + 1 else numberOfBits / 8
val bytes = ByteArray(sz)
val bb = ByteBuffer(bytes)
bb.order = Buffer.ByteOrder.LittleEndian
for (i in 0 until n - 1) bb.long = words[i]
x = words[n - 1]
while (x != 0L) {
bb.byte = (x and 0xff).toByte()
x = x ushr 8
}
return bytes
}
/**
* Sets the bit at the specified index to the complement of its
* current value.
*
* @param bitIndex the index of the bit to flip
* @throws IndexOutOfBoundsException if the specified index is negative
*/
fun flip(bitIndex: Int) {
if (bitIndex < 0) throw IndexOutOfBoundsException("bitIndex < 0: $bitIndex")
val wordIndex = wordIndex(bitIndex)
expandTo(wordIndex)
words[wordIndex] = words[wordIndex] xor (1L shl bitIndex)
checkInvariants()
}
/**
* Sets each bit from the specified `fromIndex` (inclusive) to the
* specified `toIndex` (exclusive) to the complement of its current
* value.
*
* @param fromIndex index of the first bit to flip
* @param toIndex index after the last bit to flip
* @throws IndexOutOfBoundsException if `fromIndex` is negative,
* or `toIndex` is negative, or `fromIndex` is
* larger than `toIndex`
* @since 1.4
*/
fun flip(
fromIndex: Int,
toIndex: Int,
) {
checkRange(fromIndex, toIndex)
if (fromIndex == toIndex) return
val startWordIndex: Int = wordIndex(fromIndex)
val endWordIndex: Int = wordIndex(toIndex - 1)
expandTo(endWordIndex)
val firstWordMask: Long = WORD_MASK shl fromIndex
val lastWordMask: Long = WORD_MASK ushr -toIndex
if (startWordIndex == endWordIndex) {
// Case 1: One word
words[startWordIndex] =
words[startWordIndex] xor (firstWordMask and lastWordMask)
} else {
// Case 2: Multiple words
// Handle first word
words[startWordIndex] = words[startWordIndex] xor firstWordMask
// Handle intermediate words, if any
for (i in startWordIndex + 1 until endWordIndex) {
words[i] =
words[i] xor WORD_MASK
}
// Handle last word
words[endWordIndex] = words[endWordIndex] xor lastWordMask
}
checkInvariants()
}
operator fun get(bitIndex: Int): Boolean {
if (bitIndex < 0) throw IndexOutOfBoundsException("bitIndex < 0: $bitIndex")
checkInvariants()
val wordIndex = wordIndex(bitIndex)
return (
wordIndex < wordsInUse &&
words[wordIndex] and (1L shl bitIndex) != 0L
)
}
operator fun set(
bitIndex: Int,
on: Boolean,
) {
if (!on) {
clear(bitIndex)
return
}
if (bitIndex < 0) throw IndexOutOfBoundsException("bitIndex < 0: $bitIndex")
val wordIndex: Int = wordIndex(bitIndex)
expandTo(wordIndex)
words[wordIndex] = words[wordIndex] or (1L shl bitIndex) // Restores invariants
checkInvariants()
}
/**
* find all set bits at or after startIndex. For each bit, invoke the lambda passing the index
* of the set bit. The lamda can return true to continue, or false to stop iterating.
*
* @return number of bits set
*/
fun iterateSetBits(
startIndex: Int = 0,
onSetBit: (Int) -> Boolean,
): Int {
var index = nextSetBit(startIndex)
var count = 0
while (index >= 0) {
count++
if (!onSetBit(index)) break
index = nextSetBit(index + 1)
}
return count
}
fun nextSetBit(fromIndex: Int): Int {
if (fromIndex < 0) throw IndexOutOfBoundsException("fromIndex < 0: $fromIndex")
checkInvariants()
var u = wordIndex(fromIndex)
if (u >= wordsInUse) return -1
var word = words[u] and (WORD_MASK shl fromIndex)
while (true) {
if (word != 0L) return u * BITS_PER_WORD + numberOfTrailingZeros(word)
if (++u == wordsInUse) return -1
word = words[u]
}
}
/**
* find all set bits at or after startIndex. For each bit, invoke the lambda passing the index
* of the set bit. The lamda can return true to continue, or false to stop iterating.
*
* @return number of bits set
*/
fun iterateClearBits(
startIndex: Int = 0,
onClearedBit: (Int) -> Boolean,
): Int {
var index = nextClearBit(startIndex)
var count = 0
while (index < numberOfBits) {
count++
if (!onClearedBit(index)) break
index = nextClearBit(index + 1)
}
return count
}
fun nextClearBit(fromIndex: Int): Int {
if (fromIndex < 0) throw IndexOutOfBoundsException("fromIndex < 0: $fromIndex")
checkInvariants()
var u = wordIndex(fromIndex)
if (u >= wordsInUse) return fromIndex
var word = words[u].inv() and (WORD_MASK shl fromIndex)
while (true) {
if (word != 0L) return u * BITS_PER_WORD + numberOfTrailingZeros(word)
if (++u == wordsInUse) return wordsInUse * BITS_PER_WORD
word = words[u].inv()
}
}
/**
* Sets the bit specified by the index to `false`.
*
* @param bitIndex the index of the bit to be cleared
* @throws IndexOutOfBoundsException if the specified index is negative
*/
fun clear(bitIndex: Int) {
if (bitIndex < 0) throw IndexOutOfBoundsException("bitIndex < 0: $bitIndex")
val wordIndex = wordIndex(bitIndex)
if (wordIndex >= wordsInUse) return
words[wordIndex] = words[wordIndex] and (1L shl bitIndex).inv()
checkInvariants()
}
/**
* Sets all of the bits in this CustomBitSet to `false`.
*/
fun clear() {
for (i in 0..words.size) words[i] = 0
}
private constructor(longArray: LongArray) : this(0) {
words = longArray
}
private fun ensureCapacity(wordsRequired: Int) {
if (words.size < wordsRequired) {
// Allocate larger of doubled size or required size
val request: Int = max(2 * words.size, wordsRequired)
words = words.copyOf(request)
}
}
private fun expandTo(wordIndex: Int) {
val wordsRequired = wordIndex + 1
if (wordsInUse < wordsRequired) {
ensureCapacity(wordsRequired)
}
}
private fun checkInvariants() {
if (wordsInUse == 0 || words[wordsInUse - 1] != 0L) {
if (wordsInUse >= 0 && wordsInUse <= words.size) {
if (wordsInUse == words.size || words[wordsInUse] == 0L) {
return
}
}
}
throw IllegalStateException("CustomBitSet check failed. wordsInUse:$wordsInUse, words:${words.size}")
}
override fun toString(): String {
var text = ""
var count = 0
iterateSetBits {
text = "$text, $it"
count++ < 50
}
return text
}
/**
* Returns the number of bits of space actually in use by this
* `BitSet` to represent bit values.
* The maximum element in the set is the size - 1st element.
*
* @return the number of bits currently in this bit set
*/
fun size(): Int = words.size * BITS_PER_WORD
companion object {
private const val ADDRESS_BITS_PER_WORD = 6
private const val BITS_PER_WORD = 1 shl ADDRESS_BITS_PER_WORD
private const val BIT_INDEX_MASK = BITS_PER_WORD - 1
private const val WORD_MASK = -0x1L
private fun wordIndex(bitIndex: Int): Int = bitIndex shr ADDRESS_BITS_PER_WORD
fun numberOfLeadingZeros(i: Long): Int {
// HD, Figure 5-6
if (i == 0L) return 64
var n = 1
var x = (i ushr 32).toInt()
if (x == 0) {
n += 32
x = i.toInt()
}
if (x ushr 16 == 0) {
n += 16
x = x shl 16
}
if (x ushr 24 == 0) {
n += 8
x = x shl 8
}
if (x ushr 28 == 0) {
n += 4
x = x shl 4
}
if (x ushr 30 == 0) {
n += 2
x = x shl 2
}
n -= x ushr 31
return n
}
fun numberOfTrailingZeros(i: Long): Int {
// HD, Figure 5-14
var x: Int
var y: Int
if (i == 0L) return 64
var n = 63
y = i.toInt()
if (y != 0) {
n -= 32
x = y
} else {
x = (i ushr 32).toInt()
}
y = x shl 16
if (y != 0) {
n -= 16
x = y
}
y = x shl 8
if (y != 0) {
n -= 8
x = y
}
y = x shl 4
if (y != 0) {
n -= 4
x = y
}
y = x shl 2
if (y != 0) {
n -= 2
x = y
}
return n - (x shl 1 ushr 31)
}
fun create(longs: LongArray): CustomBitSet {
var n: Int
n = longs.size
while (n > 0 && longs[n - 1] == 0L) {
n--
}
return CustomBitSet(longs.copyOf(n))
}
private fun checkRange(
fromIndex: Int,
toIndex: Int,
) {
if (fromIndex < 0) throw IndexOutOfBoundsException("fromIndex < 0: $fromIndex")
if (toIndex < 0) throw IndexOutOfBoundsException("toIndex < 0: $toIndex")
if (fromIndex > toIndex) {
throw IndexOutOfBoundsException(
"fromIndex: " + fromIndex +
" > toIndex: " + toIndex,
)
}
}
fun create(bbIn: ByteBuffer): CustomBitSet {
val bb = bbIn.slice()
bb.order = Buffer.ByteOrder.LittleEndian
var n = bb.remaining
while (n > 0 && bb.getElementAsInt(n - 1) == 0) {
n--
}
val words = LongArray((n + 7) / 8)
bb.limit = n
var i = 0
while (bb.remaining >= 8) words[i++] = bb.long
val remaining: Int = bb.remaining
var j = 0
while (j < remaining) {
words[i] = words[i] or ((bb.byte.toLong() and 0xffL) shl (8 * j))
j++
}
return CustomBitSet(words)
}
}
}
@@ -0,0 +1,480 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.utils.io
// Credits: skolson, from https://github.com/skolson/KmpIO
/**
* Simple extension to translate a ByteArray to a hex string
* @param startIndex index in array to start, defaults to zero
* @param length number of bytes to turn to hex
* @return String of size [2 * length], all lower case
* @throws IndexOutOfBoundsException if argument(s) specified are wrong
*/
fun ByteArray.toHex(startIndex: Int = 0, length: Int = size): String {
val hexChars = "0123456789abcdef"
val result = StringBuilder(length * 2)
for (i in startIndex until startIndex + length) {
result.append(hexChars[(this[i].toInt() and 0xF0).ushr(4)])
result.append(hexChars[this[i].toInt() and 0x0F])
}
return result.toString()
}
/**
* Convenience method, forces a Byte to an Int while stripping all sign bits. the Byte becomes
* the LSB of the Int produced. For example, byte of 0xFF becomes 0x000000FF or 255 as the result Int.
*/
infix fun ByteArray.toPosInt(index: Int): Int {
return this[index].toInt() and 0xFF
}
/**
* Convenience method, forces a Byte to an UInt while stripping all sign bits. the Byte becomes
* the LSB of the Int produced. For example, byte of 0xFF becomes 0x000000FF or 255 as the result Int.
*/
infix fun ByteArray.toPosUInt(index: Int): UInt {
return this[index].toUInt() and 0xFFu
}
/**
* Convenience method, forces a UByte to an Int while stripping all sign bits. the Byte becomes
* the LSB of the Int produced. For example, byte of 0xFF becomes 0x000000FF or 255 as the result Int.
*/
infix fun UByteArray.toPosInt(index: Int): Int {
return this[index].toInt() and 0xFF
}
/**
* Convenience method, forces a Byte to an UInt while stripping all sign bits. the Byte becomes
* the LSB of the Int produced. For example, byte of 0xFF becomes 0x000000FF or 255 as the result Int.
*/
infix fun UByteArray.toPosUInt(index: Int): UInt {
return this[index].toUInt() and 0xFFu
}
/**
* Convenience method, forces a Byte to a Long while stripping all sign bits. the Byte becomes
* the LSB of the Long produced. For example, byte of 0xFF becomes 0x00000000000000FF or 255 as
* the result Long.
*/
infix fun ByteArray.toPosLong(index: Int): Long {
return this[index].toLong() and 0xFF
}
/**
* Convenience method, forces a Byte to a ULong while stripping all sign bits. the Byte becomes
* the LSB of the Long produced. For example, byte of 0xFF becomes 0x00000000000000FF or 255 as
* the result Long.
*/
infix fun ByteArray.toPosULong(index: Int): ULong {
return this[index].toULong() and 0xFFu
}
/**
* Convenience method, forces a UByte to a Long while stripping all sign bits. the Byte becomes
* the LSB of the Long produced. For example, byte of 0xFF becomes 0x00000000000000FF or 255 as
* the result Int.
*/
infix fun UByteArray.toPosLong(index: Int): Long {
return this[index].toLong() and 0xFF
}
/**
* Convenience method, forces a UByte to a ULong while using only the LSB.
* For example, byte of 0xFF becomes 0x00000000000000FF or 255 as the result ULong.
*/
infix fun UByteArray.toPosULong(index: Int): ULong {
return this[index].toULong() and 0xFFu
}
/**
* These endian-aware extensions functions assist with retrieving Short, UShort, Int, UInt, Long, Ulong, Float,
* and Double values from ByteArray and UByteArray. For some reason Kotlin only offers these for Little
* Endian (you have to do your own reverse() for BigEndian) and only in Kotlin Native.
*/
/**
* Change one byte into an Int with toPosInt, then Binary Shift left the specified number of times.
* @param index of byte to change to an Int.
* @param shift number of times value is binary shifted left.
* @return resulting Int
*/
fun ByteArray.toIntShl(index: Int, shift: Int = 0): Int {
return this toPosInt index shl shift
}
/**
* Change one byte into an Int with toPosInt, then Binary Shift left the specified number of times.
* @param index of byte to change to an Int.
* @param shift number of times value is binary shifted left.
* @return result as UInt
*/
fun ByteArray.toUIntShl(index: Int, shift: Int = 0): UInt {
return this toPosUInt index shl shift
}
/**
* Change one byte into a Long, after the byte value is Binary Shifted left the specified number of times.
* @param index of byte to change to an Long.
* @param shift number of times value is binary shifted left.
* @return resulting Long
*/
fun ByteArray.toLongShl(index: Int, shift: Int = 0): Long {
return this toPosLong index shl shift
}
/**
* Change one byte into a ULong, after the byte value is Binary Shifted left the specified number of times.
* @param index of byte to change to an Long.
* @param shift number of times value is binary shifted left.
* @return resulting ULong
*/
fun ByteArray.toULongShl(index: Int, shift: Int = 0): ULong {
return this toPosULong index shl shift
}
/**
* starting at the specified index, change bytes at index and index+1 to a Short. Both LittleEndian
* and BigEndian encoding schemes are supported.
* @param index where two bytes to be converted to short start
* @param littleEndian defaults to true for LittleEndian, or false for BigEndian
* @return the resulting Short
*/
fun ByteArray.getShortAt(
index: Int,
littleEndian: Boolean = true
): Short {
return if (littleEndian) (toIntShl(index + 1, 8) or toIntShl(index)).toShort()
else (toIntShl(index, 8) or toIntShl(index + 1)).toShort()
}
/**
* starting at the specified index, change bytes at index and index+1 to a UShort. Both LittleEndian
* and BigEndian encoding schemes are supported.
* @param index where two bytes to be converted to UShort start
* @param littleEndian defaults to true for LittleEndian, or false for BigEndian
* @return the resulting UShort
*/
fun ByteArray.getUShortAt(
index: Int,
littleEndian: Boolean = true
): UShort {
return if (littleEndian)
(toUIntShl(index + 1, 8) or toUIntShl(index)).toUShort()
else (toUIntShl(index, 8) or toUIntShl(index + 1)).toUShort()
}
/**
* starting at the specified index, change bytes at index, index+1, index+2, and index+3 to an Int.
* Both LittleEndian and BigEndian encoding schemes are supported.
* @param index where four bytes to be converted to Int start
* @param littleEndian defaults to true for LittleEndian, or false for BigEndian
* @return the resulting Int
*/
fun ByteArray.getIntAt(
index: Int,
littleEndian: Boolean = true
): Int {
return if (littleEndian)
toIntShl(index + 3, 24) or
toIntShl(index + 2, 16) or
toIntShl(index + 1, 8) or
toIntShl(index)
else toIntShl(index, 24) or
toIntShl(index + 1, 16) or
toIntShl(index + 2, 8) or
toIntShl(index + 3)
}
/**
* starting at the specified index, change bytes at index, index+1, index+2, and index+3 to an UInt.
* Both LittleEndian and BigEndian encoding schemes are supported.
* @param index where four bytes to be converted to UInt start
* @param littleEndian defaults to true for LittleEndian, or false for BigEndian
* @return the resulting UInt
*/
fun ByteArray.getUIntAt(
index: Int,
littleEndian: Boolean = true
): UInt {
return if (littleEndian)
toUIntShl(index + 3, 24) or
toUIntShl(index + 2, 16) or
toUIntShl(index + 1, 8) or
toUIntShl(index)
else toUIntShl(index, 24) or
toUIntShl(index + 1, 16) or
toUIntShl(index + 2, 8) or
toUIntShl(index + 3)
}
/**
* Starting at the specified index, change bytes at [index..index+7] to a Long.
* Both LittleEndian and BigEndian encoding schemes are supported.
* @param index where eight bytes to be converted to Long start
* @param littleEndian defaults to true for LittleEndian, or false for BigEndian
* @return the resulting Long
*/
fun ByteArray.getLongAt(
index: Int,
littleEndian: Boolean = true
): Long {
return if (littleEndian)
toLongShl(index + 7, 56) or
toLongShl(index + 6, 48) or
toLongShl(index + 5, 40) or
toLongShl(index + 4, 32) or
toLongShl(index + 3, 24) or
toLongShl(index + 2, 16) or
toLongShl(index + 1, 8) or
toLongShl(index)
else toLongShl(index, 56) or
toLongShl(index + 1, 48) or
toLongShl(index + 2, 40) or
toLongShl(index + 3, 32) or
toLongShl(index + 4, 24) or
toLongShl(index + 5, 16) or
toLongShl(index + 6, 8) or
toLongShl(index + 7)
}
/**
* Starting at the specified index, change bytes at [index..index+7] to a ULong.
* Both LittleEndian and BigEndian encoding schemes are supported.
* @param index where eight bytes to be converted to ULong start
* @param littleEndian defaults to true for LittleEndian, or false for BigEndian
* @return the resulting ULong
*/
fun ByteArray.getULongAt(
index: Int,
littleEndian: Boolean = true
): ULong {
return if (littleEndian)
toULongShl(index + 7, 56) or
toULongShl(index + 6, 48) or
toULongShl(index + 5, 40) or
toULongShl(index + 4, 32) or
toULongShl(index + 3, 24) or
toULongShl(index + 2, 16) or
toULongShl(index + 1, 8) or
toULongShl(index)
else toULongShl(index, 56) or
toULongShl(index + 1, 48) or
toULongShl(index + 2, 40) or
toULongShl(index + 3, 32) or
toULongShl(index + 4, 24) or
toULongShl(index + 5, 16) or
toULongShl(index + 6, 8) or
toULongShl(index + 7)
}
/**
* Simple extension to translate a ByteArray to a hex string
* @param startIndex index in array to start, defaults to zero
* @param length number of bytes to turn to hex
* @return String of size [2 * length], all lower case
* @throws IndexOutOfBoundsException if argument(s) specified are wrong
*/
fun UByteArray.toHex(startIndex: Int = 0, length: Int = size): String {
val hexChars = "0123456789abcdef"
val result = StringBuilder(length * 2)
for (i in startIndex until startIndex + length) {
result.append(hexChars[(this[i].toInt() and 0xF0).ushr(4)])
result.append(hexChars[this[i].toInt() and 0x0F])
}
return result.toString()
}
/**
* Change one byte into an Int, after the byte value is Binary Shifted left the specified number of times.
* @param index of byte to change to an Int.
* @param shift number of times value is binary shifted left.
* @return resulting Int
*/
fun UByteArray.toIntShl(index: Int, shift: Int = 0): Int {
return this toPosInt index shl shift
}
/**
* Change one byte into an UInt, after the byte value is Binary Shifted left the specified number of times.
* @param index of byte to change to an Int.
* @param shift number of times value is binary shifted left.
* @return resulting UInt
*/
fun UByteArray.toUIntShl(index: Int, shift: Int = 0): UInt {
return this toPosUInt index shl shift
}
/**
* Change one byte into a Long, after the byte value is Binary Shifted left the specified number of times.
* @param index of byte to change to an Long.
* @param shift number of times value is binary shifted left.
* @return resulting Long
*/
fun UByteArray.toLongShl(index: Int, shift: Int = 0): Long {
return this toPosLong index shl shift
}
/**
* Change one byte into a ULong, after the byte value is Binary Shifted left the specified number of times.
* @param index of byte to change to an Long.
* @param shift number of times value is binary shifted left.
* @return resulting ULong
*/
fun UByteArray.toULongShl(index: Int, shift: Int = 0): ULong {
return this toPosULong index shl shift
}
/**
* starting at the specified index, change bytes at index and index+1 to a Short. Both LittleEndian
* and BigEndian encoding schemes are supported.
* @param index where two bytes to be converted to short start
* @param littleEndian defaults to true for LittleEndian, or false for BigEndian
* @return the resulting Short
*/
fun UByteArray.getShortAt(
index: Int,
littleEndian: Boolean = true
): Short {
return if (littleEndian)
(toIntShl(index + 1, 8) or toIntShl(index)).toShort()
else (toIntShl(index, 8) or toIntShl(index + 1)).toShort()
}
/**
* starting at the specified index, change bytes at index and index+1 to a UShort. Both LittleEndian
* and BigEndian encoding schemes are supported.
* @param index where two bytes to be converted to UShort start
* @param littleEndian defaults to true for LittleEndian, or false for BigEndian
* @return the resulting UShort
*/
fun UByteArray.getUShortAt(
index: Int,
littleEndian: Boolean = true
): UShort {
return if (littleEndian)
(toUIntShl(index + 1, 8) or toUIntShl(index)).toUShort()
else (toUIntShl(index, 8) or toUIntShl(index + 1)).toUShort()
}
/**
* starting at the specified index, change bytes at index, index+1, index+2, and index+3 to an Int.
* Both LittleEndian and BigEndian encoding schemes are supported.
* @param index where four bytes to be converted to Int start
* @param littleEndian defaults to true for LittleEndian, or false for BigEndian
* @return the resulting Int
*/
fun UByteArray.getIntAt(
index: Int,
littleEndian: Boolean = true
): Int {
return if (littleEndian)
toIntShl(index + 3, 24) or
toIntShl(index + 2, 16) or
toIntShl(index + 1, 8) or
toIntShl(index)
else toIntShl(index, 24) or
toIntShl(index + 1, 16) or
toIntShl(index + 2, 8) or
toIntShl(index + 3)
}
/**
* starting at the specified index, change bytes at index, index+1, index+2, and index+3 to an UInt.
* Both LittleEndian and BigEndian encoding schemes are supported.
* @param index where four bytes to be converted to UInt start
* @param littleEndian defaults to true for LittleEndian, or false for BigEndian
* @return the resulting UInt
*/
fun UByteArray.getUIntAt(
index: Int,
littleEndian: Boolean = true
): UInt {
return if (littleEndian)
toUIntShl(index + 3, 24) or
toUIntShl(index + 2, 16) or
toUIntShl(index + 1, 8) or
toUIntShl(index)
else toUIntShl(index, 24) or
toUIntShl(index + 1, 16) or
toUIntShl(index + 2, 8) or
toUIntShl(index + 3)
}
/**
* Starting at the specified index, change bytes at [index..index+7] to a Long.
* Both LittleEndian and BigEndian encoding schemes are supported.
* @param index where eight bytes to be converted to Long start
* @param littleEndian defaults to true for LittleEndian, or false for BigEndian
* @return the resulting Long
*/
fun UByteArray.getLongAt(
index: Int,
littleEndian: Boolean = true
): Long {
return if (littleEndian)
toLongShl(index + 7, 56) or
toLongShl(index + 6, 48) or
toLongShl(index + 5, 40) or
toLongShl(index + 4, 32) or
toLongShl(index + 3, 24) or
toLongShl(index + 2, 16) or
toLongShl(index + 1, 8) or
toLongShl(index)
else toLongShl(index, 56) or
toLongShl(index + 1, 48) or
toLongShl(index + 2, 40) or
toLongShl(index + 3, 32) or
toLongShl(index + 4, 24) or
toLongShl(index + 5, 16) or
toLongShl(index + 6, 8) or
toLongShl(index + 7)
}
/**
* Starting at the specified index, change bytes at [index..index+7] to a ULong.
* Both LittleEndian and BigEndian encoding schemes are supported.
* @param index where eight bytes to be converted to ULong start
* @param littleEndian defaults to true for LittleEndian, or false for BigEndian
* @return the resulting ULong
*/
fun UByteArray.getULongAt(
index: Int,
littleEndian: Boolean = true
): ULong {
return if (littleEndian)
toULongShl(index + 7, 56) or
toULongShl(index + 6, 48) or
toULongShl(index + 5, 40) or
toULongShl(index + 4, 32) or
toULongShl(index + 3, 24) or
toULongShl(index + 2, 16) or
toULongShl(index + 1, 8) or
toULongShl(index)
else toULongShl(index, 56) or
toULongShl(index + 1, 48) or
toULongShl(index + 2, 40) or
toULongShl(index + 3, 32) or
toULongShl(index + 4, 24) or
toULongShl(index + 5, 16) or
toULongShl(index + 6, 8) or
toULongShl(index + 7)
}