Foundations(iOS Sourceset): Implement functions(some, basic) for BitSet(with passing tests that use it).
This commit is contained in:
@@ -20,32 +20,32 @@
|
|||||||
*/
|
*/
|
||||||
package com.vitorpamplona.quartz.utils
|
package com.vitorpamplona.quartz.utils
|
||||||
|
|
||||||
|
import com.vitorpamplona.quartz.utils.io.CustomBitSet
|
||||||
|
|
||||||
actual class BitSet {
|
actual class BitSet {
|
||||||
|
val nativeBitSet: CustomBitSet
|
||||||
|
|
||||||
actual constructor(nBits: Int) {
|
actual constructor(nBits: Int) {
|
||||||
TODO("Not yet implemented")
|
nativeBitSet = CustomBitSet(nBits)
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(bytes: ByteArray) {
|
||||||
|
nativeBitSet = CustomBitSet(bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
actual fun set(bitIndex: Int) {
|
actual fun set(bitIndex: Int) {
|
||||||
TODO("Not yet implemented")
|
nativeBitSet.set(bitIndex, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
actual fun clear(bitIndex: Int) {
|
actual fun clear(bitIndex: Int) {
|
||||||
TODO("Not yet implemented")
|
nativeBitSet.clear(bitIndex)
|
||||||
}
|
}
|
||||||
|
|
||||||
actual fun get(bitIndex: Int): Boolean {
|
actual fun get(bitIndex: Int): Boolean = nativeBitSet[bitIndex]
|
||||||
TODO("Not yet implemented")
|
|
||||||
|
actual fun size(): Int = nativeBitSet.size()
|
||||||
|
|
||||||
|
actual fun toByteArray(): ByteArray = nativeBitSet.toByteArray()
|
||||||
}
|
}
|
||||||
|
|
||||||
actual fun size(): Int {
|
actual fun bitSetValueOf(bytes: ByteArray): BitSet = BitSet(bytes)
|
||||||
TODO("Not yet implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
actual fun toByteArray(): ByteArray {
|
|
||||||
TODO("Not yet implemented")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
actual fun bitSetValueOf(bytes: ByteArray): BitSet {
|
|
||||||
TODO("Not yet implemented")
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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 <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> = <tt>position()</tt> is copied
|
||||||
|
* to index zero, the byte at index <i>p</i> + 1 is copied
|
||||||
|
* to index one, and so forth until the byte at index
|
||||||
|
* <tt>limit()</tt> - 1 is copied to index
|
||||||
|
* <i>n</i> = <tt>limit()</tt> - <tt>1</tt> - <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> <tt>></tt> <tt>remaining()</tt>,
|
||||||
|
* then no bytes are transferred and an IllegalStateException is thrown
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* <p> Otherwise, this method copies
|
||||||
|
* <i>n</i> = <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> - <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> - <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)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user