Add little-endian support for numeric types

Introduced `Little-Endian` functionality for `Short`, `Int`, `Long`, `Float`, and `Double` across `SlicedByteArray`, `MutableByteArrayHandler`, and `TypedByteArray`. Enhanced property delegates to handle little-endian byte order with the `littleEndian` parameter. Added `LittleEndianTest` to validate functionality. Bumped version to 0.3.0-SNAPSHOT.
This commit is contained in:
2025-05-31 14:08:28 +02:00
parent 4e7fa72a5e
commit 353c2c4f33
6 changed files with 447 additions and 45 deletions

View File

@@ -8,7 +8,7 @@ plugins {
}
group = "nl.astraeus"
version = "0.2.12-SNAPSHOT"
version = "0.3.0-SNAPSHOT"
repositories {
mavenCentral()

View File

@@ -14,23 +14,18 @@ open class ByteArrayHandler(
fun getShort(index: Int) = buffer.getShort(index)
fun getInt(index: Int) = buffer.getInt(index)
fun getShortLE(index: Int) = buffer.getShortLE(index)
fun getLong(index: Int): Long {
return (
(buffer[index].toLong() shl 56) or
((buffer[index+1].toLong() and 0xff) shl 48) or
((buffer[index+2].toLong() and 0xff) shl 40) or
((buffer[index+3].toLong() and 0xff) shl 32) or
((buffer[index+4].toLong() and 0xff) shl 24) or
((buffer[index+5].toLong() and 0xff) shl 16) or
((buffer[index+6].toLong() and 0xff) shl 8) or
(buffer[index+7].toLong() and 0xff)
)
}
fun getInt(index: Int) = buffer.getInt(index)
fun getIntLE(index: Int) = buffer.getIntLE(index)
fun getLong(index: Int) = buffer.getLong(index)
fun getLongLE(index: Int) = buffer.getLongLE(index)
fun getFloat(index: Int): Float = buffer.getFloat(index)
fun getFloatLE(index: Int): Float = buffer.getFloatLE(index)
fun getDouble(index: Int): Double = buffer.getDouble(index)
fun getDoubleLE(index: Int): Double = buffer.getDoubleLE(index)
fun getString(index: Int): String {
val length = buffer.getShort(index)
@@ -76,13 +71,18 @@ class MutableByteArrayHandler(
}
fun setShort(index: Int, value: Short) = buffer.setShort(index, value)
fun setShortLE(index: Int, value: Short) = buffer.setShortLE(index, value)
fun setInt(index: Int, value: Int) = buffer.setInt(index, value)
fun setIntLE(index: Int, value: Int) = buffer.setIntLE(index, value)
fun setLong(index: Int, value: Long) = buffer.setLong(index, value)
fun setLongLE(index: Int, value: Long) = buffer.setLongLE(index, value)
fun setFloat(index: Int, value: Float) = buffer.setFloat(index, value)
fun setFloatLE(index: Int, value: Float) = buffer.setFloatLE(index, value)
fun setDouble(index: Int, value: Double) = buffer.setDouble(index, value)
fun setDoubleLE(index: Int, value: Double) = buffer.setDoubleLE(index, value)
fun setString(index: Int, value: String, maxLength: Int) {
val bytes = value.encodeToByteArray()

View File

@@ -64,7 +64,19 @@ class ShortProperty(
}
override fun setValue(thisRef: TypedByteArray, property: KProperty<*>, value: Short) {
thisRef.data.setShort(getIndex(thisRef), value)
return thisRef.data.setShort(getIndex(thisRef), value)
}
}
class ShortPropertyLE(
name: String
) : ByteArrayProperty<Short>(name) {
override fun getValue(thisRef: TypedByteArray, property: KProperty<*>): Short {
return thisRef.data.getShortLE(getIndex(thisRef))
}
override fun setValue(thisRef: TypedByteArray, property: KProperty<*>, value: Short) {
return thisRef.data.setShortLE(getIndex(thisRef), value)
}
}
@@ -80,6 +92,18 @@ class IntProperty(
}
}
class IntPropertyLE(
name: String
) : ByteArrayProperty<Int>(name) {
override fun getValue(thisRef: TypedByteArray, property: KProperty<*>): Int {
return thisRef.data.getIntLE(getIndex(thisRef))
}
override fun setValue(thisRef: TypedByteArray, property: KProperty<*>, value: Int) {
thisRef.data.setIntLE(getIndex(thisRef), value)
}
}
class FloatProperty(
name: String
) : ByteArrayProperty<Float>(name) {
@@ -92,6 +116,18 @@ class FloatProperty(
}
}
class FloatPropertyLE(
name: String
) : ByteArrayProperty<Float>(name) {
override fun getValue(thisRef: TypedByteArray, property: KProperty<*>): Float {
return thisRef.data.getFloatLE(getIndex(thisRef))
}
override fun setValue(thisRef: TypedByteArray, property: KProperty<*>, value: Float) {
thisRef.data.setFloatLE(getIndex(thisRef), value)
}
}
class LongProperty(
name: String
) : ByteArrayProperty<Long>(name) {
@@ -104,6 +140,18 @@ class LongProperty(
}
}
class LongPropertyLE(
name: String
) : ByteArrayProperty<Long>(name) {
override fun getValue(thisRef: TypedByteArray, property: KProperty<*>): Long {
return thisRef.data.getLongLE(getIndex(thisRef))
}
override fun setValue(thisRef: TypedByteArray, property: KProperty<*>, value: Long) {
thisRef.data.setLongLE(getIndex(thisRef), value)
}
}
class DoubleProperty(
name: String
) : ByteArrayProperty<Double>(name) {
@@ -116,6 +164,18 @@ class DoubleProperty(
}
}
class DoublePropertyLE(
name: String
) : ByteArrayProperty<Double>(name) {
override fun getValue(thisRef: TypedByteArray, property: KProperty<*>): Double {
return thisRef.data.getDoubleLE(getIndex(thisRef))
}
override fun setValue(thisRef: TypedByteArray, property: KProperty<*>, value: Double) {
thisRef.data.setDoubleLE(getIndex(thisRef), value)
}
}
class StringProperty(
name: String,
) : ByteArrayPropertyWithLength<String>(name) {
@@ -175,11 +235,31 @@ class BlobProperty(
fun boolean(propertyName: String) = BooleanProperty(propertyName)
fun byte(propertyName: String) = ByteProperty(propertyName)
fun short(propertyName: String) = ShortProperty(propertyName)
fun int(propertyName: String) = IntProperty(propertyName)
fun long(propertyName: String) = LongProperty(propertyName)
fun float(propertyName: String) = FloatProperty(propertyName)
fun double(propertyName: String) = DoubleProperty(propertyName)
fun short(propertyName: String, littleEndian: Boolean = false) = if (littleEndian) {
ShortPropertyLE(propertyName)
} else {
ShortProperty(propertyName)
}
fun int(propertyName: String, littleEndian: Boolean = false) = if (littleEndian) {
IntPropertyLE(propertyName)
} else {
IntProperty(propertyName)
}
fun long(propertyName: String, littleEndian: Boolean = false) = if (littleEndian) {
LongPropertyLE(propertyName)
} else {
LongProperty(propertyName)
}
fun float(propertyName: String, littleEndian: Boolean = false) = if (littleEndian) {
FloatPropertyLE(propertyName)
} else {
FloatProperty(propertyName)
}
fun double(propertyName: String, littleEndian: Boolean = false) = if (littleEndian) {
DoubleProperty(propertyName)
} else {
DoublePropertyLE(propertyName)
}
fun string(propertyName: String) = StringProperty(propertyName)
fun cachedString(propertyName: String) = CachedStringProperty(propertyName)
fun clob(propertyName: String) = ClobProperty(propertyName)

View File

@@ -27,12 +27,25 @@ class SlicedByteArray(
data[offset + index + 1] = value.toByte()
}
fun setShortLE(index: Int, value: Short) {
check(offset + index + 1 <= offset + length) { "Index out of bounds" }
data[offset + index + 1] = (value.toInt() shr 8).toByte()
data[offset + index] = value.toByte()
}
fun getShort(index: Int): Short {
check(offset + index + 1 <= offset + length) { "Index out of bounds, ${offset + index + 1} > ${offset + length} ($offset, $index, $length)" }
return ((data[offset + index].toInt() shl 8) or (data[offset + index + 1].toInt() and 0xff)).toShort()
}
fun getShortLE(index: Int): Short {
check(offset + index + 1 <= offset + length) { "Index out of bounds, ${offset + index + 1} > ${offset + length} ($offset, $index, $length)" }
return ((data[offset + index + 1].toInt() shl 8) or (data[offset + index].toInt() and 0xff)).toShort()
}
fun setInt(index: Int, value: Int) {
check(offset + index + 3 <= offset + length) { "Index out of bounds" }
@@ -42,13 +55,32 @@ class SlicedByteArray(
data[offset + index + 3] = (value and 0xff).toByte()
}
fun setIntLE(index: Int, value: Int) {
check(offset + index + 3 <= offset + length) { "Index out of bounds" }
data[offset + index + 3] = ((value shr 24) and 0xff).toByte()
data[offset + index + 2] = ((value shr 16) and 0xff).toByte()
data[offset + index + 1] = ((value shr 8) and 0xff).toByte()
data[offset + index + 0] = (value and 0xff).toByte()
}
fun getInt(index: Int): Int {
check(offset + index + 3 <= offset + length) { "Index out of bounds" }
return (
(data[offset + index].toInt() shl 24) or
((data[offset + index+1].toInt() shl 16) and 0xff0000) or
((data[offset + index+2].toInt() shl 8) and 0xff00) or
(data[offset + index+3].toInt() and 0xff)
((data[offset + index + 1].toInt() shl 16) and 0xff0000) or
((data[offset + index + 2].toInt() shl 8) and 0xff00) or
(data[offset + index + 3].toInt() and 0xff)
)
}
fun getIntLE(index: Int): Int {
check(offset + index + 3 <= offset + length) { "Index out of bounds" }
return (
(data[offset + index + 3].toInt() shl 24) or
((data[offset + index + 2].toInt() shl 16) and 0xff0000) or
((data[offset + index + 1].toInt() shl 8) and 0xff00) or
(data[offset + index + 0].toInt() and 0xff)
)
}
@@ -64,27 +96,57 @@ class SlicedByteArray(
data[offset + index + 7] = (value and 0xff).toByte()
}
fun setLongLE(index: Int, value: Long) {
check(offset + index + 7 <= offset + length) { "Index out of bounds" }
data[offset + index + 7] = ((value shr 56) and 0xff).toByte()
data[offset + index + 6] = ((value shr 48) and 0xff).toByte()
data[offset + index + 5] = ((value shr 40) and 0xff).toByte()
data[offset + index + 4] = ((value shr 32) and 0xff).toByte()
data[offset + index + 3] = ((value shr 24) and 0xff).toByte()
data[offset + index + 2] = ((value shr 16) and 0xff).toByte()
data[offset + index + 1] = ((value shr 8) and 0xff).toByte()
data[offset + index + 0] = (value and 0xff).toByte()
}
fun getLong(index: Int): Long {
check(offset + index + 7 <= offset + length) { "Index out of bounds" }
return (
(data[offset + index].toLong() shl 56) or
((data[offset + index+1].toLong() and 0xff) shl 48) or
((data[offset + index+2].toLong() and 0xff) shl 40) or
((data[offset + index+3].toLong() and 0xff) shl 32) or
((data[offset + index+4].toLong() and 0xff) shl 24) or
((data[offset + index+5].toLong() and 0xff) shl 16) or
((data[offset + index+6].toLong() and 0xff) shl 8) or
(data[offset + index+7].toLong() and 0xff)
((data[offset + index + 1].toLong() and 0xff) shl 48) or
((data[offset + index + 2].toLong() and 0xff) shl 40) or
((data[offset + index + 3].toLong() and 0xff) shl 32) or
((data[offset + index + 4].toLong() and 0xff) shl 24) or
((data[offset + index + 5].toLong() and 0xff) shl 16) or
((data[offset + index + 6].toLong() and 0xff) shl 8) or
(data[offset + index + 7].toLong() and 0xff)
)
}
fun getLongLE(index: Int): Long {
check(offset + index + 7 <= offset + length) { "Index out of bounds" }
return (
(data[offset + index + 7].toLong() shl 56) or
((data[offset + index + 6].toLong() and 0xff) shl 48) or
((data[offset + index + 5].toLong() and 0xff) shl 40) or
((data[offset + index + 4].toLong() and 0xff) shl 32) or
((data[offset + index + 3].toLong() and 0xff) shl 24) or
((data[offset + index + 2].toLong() and 0xff) shl 16) or
((data[offset + index + 1].toLong() and 0xff) shl 8) or
(data[offset + index + 0].toLong() and 0xff)
)
}
fun setFloat(index: Int, value: Float) = setInt(index, value.toRawBits())
fun setFloatLE(index: Int, value: Float) = setIntLE(index, value.toRawBits())
fun getFloat(index: Int): Float = Float.fromBits(getInt(index))
fun getFloatLE(index: Int): Float = Float.fromBits(getIntLE(index))
fun setDouble(index: Int, value: Double) = setLong(index, value.toRawBits())
fun setDoubleLE(index: Int, value: Double) = setLongLE(index, value.toRawBits())
fun getDouble(index: Int): Double = Double.fromBits(getLong(index))
fun getDoubleLE(index: Int): Double = Double.fromBits(getLongLE(index))
fun getBlob(index: Int, length: Int): SlicedByteArray {
fun getBlob(index: Int, length: Int): SlicedByteArray {
check(index + length <= this.length) { "Index out of bounds ($index, $length, ${this.length}" }
val str = SlicedByteArray(data, offset + index, length)
@@ -96,17 +158,17 @@ class SlicedByteArray(
}
fun iterator(): Iterator<Byte> = object : Iterator<Byte> {
var index = offset
var index = offset
override fun hasNext(): Boolean {
return index < offset + length
}
override fun next(): Byte {
return data[offset + index++]
}
override fun hasNext(): Boolean {
return index < offset + length
}
override fun next(): Byte {
return data[offset + index++]
}
}
fun toByteArray(): ByteArray = data.copyOfRange(offset, offset + length)
fun setBlob(index: Int, bytes: SlicedByteArray, blobLength: Int) {

View File

@@ -0,0 +1,260 @@
package nl.astraeus.tba
/**
* This test file was created by AI Assistant to test the little endian functionality
* in the typed-byte-arrays library. It tests the functionality at three levels:
* 1. SlicedByteArray (low-level implementation)
* 2. MutableByteArrayHandler (mid-level implementation)
* 3. TypedByteArray (high-level implementation with property delegates)
*/
import kotlin.test.Test
import kotlin.test.assertEquals
class LittleEndianTest {
@Test
fun testSlicedByteArrayShortLittleEndian() {
val array = SlicedByteArray(10)
// Test positive value
val shortValue: Short = 12345
array.setShortLE(0, shortValue)
assertEquals(shortValue, array.getShortLE(0))
// Test negative value
val negShortValue: Short = -12345
array.setShortLE(2, negShortValue)
assertEquals(negShortValue, array.getShortLE(2))
// Verify byte order is actually little endian
val bytes = array.data
assertEquals(57, bytes[0].toInt() and 0xff) // 12345 & 0xff = 57
assertEquals(48, bytes[1].toInt() and 0xff) // (12345 >> 8) & 0xff = 48
assertEquals(199, bytes[2].toInt() and 0xff) // -12345 & 0xff = 199
assertEquals(207, bytes[3].toInt() and 0xff) // (-12345 >> 8) & 0xff = 207
}
@Test
fun testSlicedByteArrayIntLittleEndian() {
val array = SlicedByteArray(10)
// Test positive value
val intValue = 123456789
array.setIntLE(0, intValue)
assertEquals(intValue, array.getIntLE(0))
// Test negative value
val negIntValue = -123456789
array.setIntLE(4, negIntValue)
assertEquals(negIntValue, array.getIntLE(4))
// Verify byte order is actually little endian
val bytes = array.data
assertEquals(21, bytes[0].toInt() and 0xff)
assertEquals(205, bytes[1].toInt() and 0xff)
assertEquals(91, bytes[2].toInt() and 0xff)
assertEquals(7, bytes[3].toInt() and 0xff)
assertEquals(235, bytes[4].toInt() and 0xff)
assertEquals(50, bytes[5].toInt() and 0xff)
assertEquals(164, bytes[6].toInt() and 0xff)
assertEquals(248, bytes[7].toInt() and 0xff)
}
@Test
fun testSlicedByteArrayLongLittleEndian() {
val array = SlicedByteArray(20)
// Test positive value
val longValue = 1234567890123456789L
array.setLongLE(0, longValue)
assertEquals(longValue, array.getLongLE(0))
// Test negative value
val negLongValue = -1234567890123456789L
array.setLongLE(8, negLongValue)
assertEquals(negLongValue, array.getLongLE(8))
}
@Test
fun testSlicedByteArrayFloatLittleEndian() {
val array = SlicedByteArray(10)
// Test positive value
val floatValue = 123.456f
array.setFloatLE(0, floatValue)
assertEquals(floatValue, array.getFloatLE(0))
// Test negative value
val negFloatValue = -123.456f
array.setFloatLE(4, negFloatValue)
assertEquals(negFloatValue, array.getFloatLE(4))
}
@Test
fun testSlicedByteArrayDoubleLittleEndian() {
val array = SlicedByteArray(20)
// Test positive value
val doubleValue = 123.456789
array.setDoubleLE(0, doubleValue)
assertEquals(doubleValue, array.getDoubleLE(0))
// Test negative value
val negDoubleValue = -123.456789
array.setDoubleLE(8, negDoubleValue)
assertEquals(negDoubleValue, array.getDoubleLE(8))
}
@Test
fun testShortLittleEndian() {
val handler = MutableByteArrayHandler(ByteArray(10))
// Test positive value
val shortValue: Short = 12345
handler.setShortLE(0, shortValue)
assertEquals(shortValue, handler.getShortLE(0))
// Test negative value
val negShortValue: Short = -12345
handler.setShortLE(2, negShortValue)
assertEquals(negShortValue, handler.getShortLE(2))
// Verify byte order is actually little endian
val bytes = handler.buffer.data
assertEquals(57, bytes[0].toInt() and 0xff) // 12345 & 0xff = 57
assertEquals(48, bytes[1].toInt() and 0xff) // (12345 >> 8) & 0xff = 48
assertEquals(199, bytes[2].toInt() and 0xff) // -12345 & 0xff = 199
assertEquals(207, bytes[3].toInt() and 0xff) // (-12345 >> 8) & 0xff = 207
}
@Test
fun testIntLittleEndian() {
val handler = MutableByteArrayHandler(ByteArray(10))
// Test positive value
val intValue = 123456789
handler.setIntLE(0, intValue)
assertEquals(intValue, handler.getIntLE(0))
// Test negative value
val negIntValue = -123456789
handler.setIntLE(4, negIntValue)
assertEquals(negIntValue, handler.getIntLE(4))
// Verify byte order is actually little endian
val bytes = handler.buffer.data
assertEquals(21, bytes[0].toInt() and 0xff)
assertEquals(205, bytes[1].toInt() and 0xff)
assertEquals(91, bytes[2].toInt() and 0xff)
assertEquals(7, bytes[3].toInt() and 0xff)
assertEquals(235, bytes[4].toInt() and 0xff)
assertEquals(50, bytes[5].toInt() and 0xff)
assertEquals(164, bytes[6].toInt() and 0xff)
assertEquals(248, bytes[7].toInt() and 0xff)
}
@Test
fun testLongLittleEndian() {
val handler = MutableByteArrayHandler(ByteArray(20))
// Test positive value
val longValue = 1234567890123456789L
handler.setLongLE(0, longValue)
assertEquals(longValue, handler.getLongLE(0))
// Test negative value
val negLongValue = -1234567890123456789L
handler.setLongLE(8, negLongValue)
assertEquals(negLongValue, handler.getLongLE(8))
// Verify byte order is actually little endian
val bytes = handler.buffer.data
assertEquals(21, bytes[0].toInt() and 0xff)
assertEquals(129, bytes[1].toInt() and 0xff)
assertEquals(233, bytes[2].toInt() and 0xff)
assertEquals(125, bytes[3].toInt() and 0xff)
assertEquals(244, bytes[4].toInt() and 0xff)
assertEquals(16, bytes[5].toInt() and 0xff)
assertEquals(34, bytes[6].toInt() and 0xff)
assertEquals(17, bytes[7].toInt() and 0xff)
}
@Test
fun testFloatLittleEndian() {
val handler = MutableByteArrayHandler(ByteArray(10))
// Test positive value
val floatValue = 123.456f
handler.setFloatLE(0, floatValue)
assertEquals(floatValue, handler.getFloatLE(0))
// Test negative value
val negFloatValue = -123.456f
handler.setFloatLE(4, negFloatValue)
assertEquals(negFloatValue, handler.getFloatLE(4))
}
@Test
fun testDoubleLittleEndian() {
val handler = MutableByteArrayHandler(ByteArray(20))
// Test positive value
val doubleValue = 123.456789
handler.setDoubleLE(0, doubleValue)
assertEquals(doubleValue, handler.getDoubleLE(0))
// Test negative value
val negDoubleValue = -123.456789
handler.setDoubleLE(8, negDoubleValue)
assertEquals(negDoubleValue, handler.getDoubleLE(8))
}
@Test
fun testTypedByteArrayLittleEndian() {
class LittleEndianData : TypedByteArray(
Type("shortValue", DataType.SHORT),
Type("intValue", DataType.INT),
Type("longValue", DataType.LONG),
Type("floatValue", DataType.FLOAT),
Type("doubleValue", DataType.DOUBLE)
) {
var shortValue by short("shortValue", littleEndian = true)
var intValue by int("intValue", littleEndian = true)
var longValue by long("longValue", littleEndian = true)
var floatValue by float("floatValue", littleEndian = true)
var doubleValue by double("doubleValue", littleEndian = true)
}
val data = LittleEndianData()
// Test with positive values
data.shortValue = 12345
data.intValue = 123456789
data.longValue = 1234567890123456789L
data.floatValue = 123.456f
data.doubleValue = 123.456789
assertEquals(12345, data.shortValue)
assertEquals(123456789, data.intValue)
assertEquals(1234567890123456789L, data.longValue)
assertEquals(123.456f, data.floatValue)
assertEquals(123.456789, data.doubleValue)
// Test with negative values
data.shortValue = -12345
data.intValue = -123456789
data.longValue = -1234567890123456789L
data.floatValue = -123.456f
data.doubleValue = -123.456789
assertEquals(-12345, data.shortValue)
assertEquals(-123456789, data.intValue)
assertEquals(-1234567890123456789L, data.longValue)
assertEquals(-123.456f, data.floatValue)
assertEquals(-123.456789, data.doubleValue)
}
}

View File

@@ -21,13 +21,13 @@ class TestCachedString {
val cachedPerson = PersonCached()
val notCachedPerson = PersonNotCached()
cachedPerson.name = "A Longer Longer Longer Longer Longer Test"
notCachedPerson.name = "A Longer Longer Longer Longer Longer Test"
cachedPerson.name = "A Longer Longer Longer Longer Longer 長い 長い 長い 更长 更长 更长 Test"
notCachedPerson.name = "A Longer Longer Longer Longer Longer 長い 長い 長い 更长 更长 更长 Test"
var total = 0
repeat(10000000) {
total += cachedPerson.name.hashCode()
total += notCachedPerson.name.hashCode()
total += cachedPerson.name.length
total += notCachedPerson.name.length
}
println("Total warmup: $total")
@@ -35,14 +35,14 @@ class TestCachedString {
var start1 = System.nanoTime()
var total1 = 0
repeat(10000000) {
total1 += cachedPerson.name.hashCode()
total1 += cachedPerson.name.length
}
val stop1 = System.nanoTime()
var start2 = System.nanoTime()
var total2 = 0
repeat(10000000) {
total2 += notCachedPerson.name.hashCode()
total2 += notCachedPerson.name.length
}
val stop2 = System.nanoTime()