Initial commit
This commit is contained in:
45
src/commonMain/kotlin/nl/astraeus/tba/ByteArrayDefinition.kt
Normal file
45
src/commonMain/kotlin/nl/astraeus/tba/ByteArrayDefinition.kt
Normal file
@@ -0,0 +1,45 @@
|
||||
package nl.astraeus.tba
|
||||
|
||||
enum class DataType(val size: Int) {
|
||||
BYTE(1),
|
||||
SHORT(2),
|
||||
INT(4),
|
||||
LONG(8),
|
||||
FLOAT(4),
|
||||
DOUBLE(8),
|
||||
STRING(-1), // max length 65535
|
||||
CLOB(-1), // max length 2^32-1
|
||||
BLOB(-1),
|
||||
ARRAY(-1),
|
||||
;
|
||||
}
|
||||
|
||||
class Type(
|
||||
val name: String,
|
||||
val type: DataType,
|
||||
val size: Int = if (type.size == -1) {
|
||||
throw IllegalArgumentException("Size must be defined for type ${type.name}")
|
||||
} else {
|
||||
type.size
|
||||
},
|
||||
)
|
||||
|
||||
interface ByteArrayDefinition {
|
||||
val types: List<Type>
|
||||
val size: Int
|
||||
}
|
||||
|
||||
open class BaseByteArrayDefinition(
|
||||
vararg types: Type,
|
||||
) : ByteArrayDefinition{
|
||||
override val types: List<Type> = types.toList()
|
||||
|
||||
override val size: Int by lazy {
|
||||
this.types.sumOf { it.size }
|
||||
}
|
||||
}
|
||||
|
||||
interface BinaryType {
|
||||
val name: String
|
||||
val typeId: Long
|
||||
}
|
||||
69
src/commonMain/kotlin/nl/astraeus/tba/ByteArrayExtentions.kt
Normal file
69
src/commonMain/kotlin/nl/astraeus/tba/ByteArrayExtentions.kt
Normal file
@@ -0,0 +1,69 @@
|
||||
package nl.astraeus.tba
|
||||
|
||||
fun ByteArray.setShort(index: Int, value: Short) {
|
||||
this[index] = (value.toInt() shr 8).toByte()
|
||||
this[index + 1] = value.toByte()
|
||||
}
|
||||
|
||||
fun ByteArray.getShort(index: Int): Short {
|
||||
return ((this[index].toInt() shl 8) or (this[index + 1].toInt() and 0xff)).toShort()
|
||||
}
|
||||
|
||||
fun ByteArray.setInt(index: Int, value: Int) {
|
||||
this[index] = ((value shr 24) and 0xff).toByte()
|
||||
this[index + 1] = ((value shr 16) and 0xff).toByte()
|
||||
this[index + 2] = ((value shr 8) and 0xff).toByte()
|
||||
this[index + 3] = (value and 0xff).toByte()
|
||||
}
|
||||
|
||||
fun ByteArray.getInt(index: Int): Int {
|
||||
return (
|
||||
(this[index].toInt() shl 24) or
|
||||
((this[index+1].toInt() shl 16) and 0xff0000) or
|
||||
((this[index+2].toInt() shl 8) and 0xff00) or
|
||||
(this[index+3].toInt() and 0xff)
|
||||
)
|
||||
}
|
||||
|
||||
fun ByteArray.setLong(index: Int, value: Long) {
|
||||
this[index] = ((value shr 56) and 0xff).toByte()
|
||||
this[index + 1] = ((value shr 48) and 0xff).toByte()
|
||||
this[index + 2] = ((value shr 40) and 0xff).toByte()
|
||||
this[index + 3] = ((value shr 32) and 0xff).toByte()
|
||||
this[index + 4] = ((value shr 24) and 0xff).toByte()
|
||||
this[index + 5] = ((value shr 16) and 0xff).toByte()
|
||||
this[index + 6] = ((value shr 8) and 0xff).toByte()
|
||||
this[index + 7] = (value and 0xff).toByte()
|
||||
}
|
||||
|
||||
fun ByteArray.getLong(index: Int): Long {
|
||||
return (
|
||||
(this[index].toLong() shl 56) or
|
||||
((this[index+1].toLong() and 0xff) shl 48) or
|
||||
((this[index+2].toLong() and 0xff) shl 40) or
|
||||
((this[index+3].toLong() and 0xff) shl 32) or
|
||||
((this[index+4].toLong() and 0xff) shl 24) or
|
||||
((this[index+5].toLong() and 0xff) shl 16) or
|
||||
((this[index+6].toLong() and 0xff) shl 8) or
|
||||
(this[index+7].toLong() and 0xff)
|
||||
)
|
||||
}
|
||||
|
||||
fun ByteArray.setDouble(index: Int, value: Double) = this.setLong(index, value.toRawBits())
|
||||
fun ByteArray.getDouble(index: Int): Double = Double.fromBits(this.getLong(index))
|
||||
|
||||
fun ByteArray.setFloat(index: Int, value: Float) = this.setInt(index, value.toRawBits())
|
||||
fun ByteArray.getFloat(index: Int): Float = Float.fromBits(this.getInt(index))
|
||||
|
||||
fun ByteArray.getString(index: Int): Pair<String, Int> {
|
||||
val length = this.getInt(index)
|
||||
val str = this.copyOfRange(index + 4, index + 4 + length).decodeToString()
|
||||
return Pair(str, index + 4 + length)
|
||||
}
|
||||
|
||||
fun ByteArray.setString(index: Int, value: String): Int {
|
||||
val bytes = value.encodeToByteArray()
|
||||
this.setInt(index, bytes.size)
|
||||
bytes.copyInto(this, index + 4)
|
||||
return index + bytes.size + 4
|
||||
}
|
||||
118
src/commonMain/kotlin/nl/astraeus/tba/ByteArrayHandler.kt
Normal file
118
src/commonMain/kotlin/nl/astraeus/tba/ByteArrayHandler.kt
Normal file
@@ -0,0 +1,118 @@
|
||||
package nl.astraeus.tba
|
||||
|
||||
class MutableByteArrayHandler(
|
||||
initialSize: Int = 1024,
|
||||
buffer: ByteArray = ByteArray(initialSize)
|
||||
) : ByteArrayHandler(initialSize, buffer) {
|
||||
|
||||
operator fun set(index: Int, value: Byte) {
|
||||
buffer[index] = value
|
||||
}
|
||||
|
||||
fun setShort(index: Int, value: Short) {
|
||||
buffer[index] = (value.toInt() shr 8).toByte()
|
||||
buffer[index + 1] = value.toByte()
|
||||
}
|
||||
|
||||
fun setInt(index: Int, value: Int) {
|
||||
buffer[index] = ((value shr 24) and 0xff).toByte()
|
||||
buffer[index + 1] = ((value shr 16) and 0xff).toByte()
|
||||
buffer[index + 2] = ((value shr 8) and 0xff).toByte()
|
||||
buffer[index + 3] = (value and 0xff).toByte()
|
||||
}
|
||||
|
||||
fun setLong(index: Int, value: Long) {
|
||||
buffer[index] = ((value shr 56) and 0xff).toByte()
|
||||
buffer[index + 1] = ((value shr 48) and 0xff).toByte()
|
||||
buffer[index + 2] = ((value shr 40) and 0xff).toByte()
|
||||
buffer[index + 3] = ((value shr 32) and 0xff).toByte()
|
||||
buffer[index + 4] = ((value shr 24) and 0xff).toByte()
|
||||
buffer[index + 5] = ((value shr 16) and 0xff).toByte()
|
||||
buffer[index + 6] = ((value shr 8) and 0xff).toByte()
|
||||
buffer[index + 7] = (value and 0xff).toByte()
|
||||
}
|
||||
|
||||
fun setFloat(index: Int, value: Float) = buffer.setInt(index, value.toRawBits())
|
||||
fun setDouble(index: Int, value: Double) = buffer.setLong(index, value.toRawBits())
|
||||
|
||||
fun setString(index: Int, value: String, maxLength: Int): Int {
|
||||
val bytes = value.encodeToByteArray()
|
||||
if (bytes.size > maxLength) {
|
||||
throw IllegalArgumentException("String is too long")
|
||||
}
|
||||
this.setShort(index, bytes.size.toShort())
|
||||
bytes.copyInto(buffer, index + 2)
|
||||
return index + bytes.size + 2
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
open class ByteArrayHandler(
|
||||
size: Int = 1024,
|
||||
var buffer: ByteArray = ByteArray(size),
|
||||
val firstIndex: Int = 0,
|
||||
val lastIndex: Int = buffer.size
|
||||
) {
|
||||
|
||||
constructor(buffer: ByteArray, range: IntRange) : this(
|
||||
buffer = buffer,
|
||||
firstIndex = range.first,
|
||||
lastIndex = range.last
|
||||
)
|
||||
|
||||
operator fun get(index: Int): Byte {
|
||||
return buffer[index]
|
||||
}
|
||||
|
||||
fun getShort(index: Int): Short {
|
||||
return ((buffer[index].toInt() shl 8) or (buffer[index + 1].toInt() and 0xff)).toShort()
|
||||
}
|
||||
|
||||
fun getInt(index: Int): Int {
|
||||
return (
|
||||
(buffer[index].toInt() shl 24) or
|
||||
((buffer[index+1].toInt() shl 16) and 0xff0000) or
|
||||
((buffer[index+2].toInt() shl 8) and 0xff00) or
|
||||
(buffer[index+3].toInt() and 0xff)
|
||||
)
|
||||
}
|
||||
|
||||
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 getFloat(index: Int): Float = Float.fromBits(buffer.getInt(index))
|
||||
fun getDouble(index: Int): Double = Double.fromBits(buffer.getLong(index))
|
||||
|
||||
fun getString(index: Int): String {
|
||||
val length = getShort(index)
|
||||
val str = buffer.copyOfRange(index + 2, index + 2 + length).decodeToString()
|
||||
return str
|
||||
}
|
||||
|
||||
fun getClob(index: Int): String {
|
||||
val length = getInt(index)
|
||||
val str = buffer.copyOfRange(index + 4, index + 4 + length).decodeToString()
|
||||
return str
|
||||
}
|
||||
|
||||
fun slice(range: IntRange): ByteArrayHandler {
|
||||
return ByteArrayHandler(
|
||||
buffer,
|
||||
range
|
||||
)
|
||||
}
|
||||
|
||||
fun asMutable(): MutableByteArrayHandler {
|
||||
return MutableByteArrayHandler(buffer = buffer.copyOfRange(firstIndex, lastIndex))
|
||||
}
|
||||
}
|
||||
123
src/commonMain/kotlin/nl/astraeus/tba/ByteArrayProperties.kt
Normal file
123
src/commonMain/kotlin/nl/astraeus/tba/ByteArrayProperties.kt
Normal file
@@ -0,0 +1,123 @@
|
||||
package nl.astraeus.tba
|
||||
|
||||
import kotlin.properties.ReadWriteProperty
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
class ByteProperty(
|
||||
val name: String
|
||||
) : ReadWriteProperty<TypedByteArray, Byte> {
|
||||
var index: Int? = null
|
||||
|
||||
private fun getIndex(thisRef: TypedByteArray): Int {
|
||||
if (index == null) {
|
||||
index = thisRef.indexMap[name] ?: throw IllegalArgumentException("Property $name not found")
|
||||
}
|
||||
return index!!
|
||||
}
|
||||
|
||||
override fun getValue(thisRef: TypedByteArray, property: KProperty<*>): Byte {
|
||||
return thisRef.data[getIndex(thisRef)]
|
||||
}
|
||||
|
||||
override fun setValue(thisRef: TypedByteArray, property: KProperty<*>, value: Byte) {
|
||||
thisRef.data [getIndex(thisRef)] = value
|
||||
}
|
||||
}
|
||||
|
||||
class ShortProperty(
|
||||
val name: String
|
||||
) : ReadWriteProperty<TypedByteArray, Short> {
|
||||
var index: Int? = null
|
||||
|
||||
private fun getIndex(thisRef: TypedByteArray): Int {
|
||||
if (index == null) {
|
||||
index = thisRef.indexMap[name] ?: throw IllegalArgumentException("Property $name not found")
|
||||
}
|
||||
return index!!
|
||||
}
|
||||
|
||||
override fun getValue(thisRef: TypedByteArray, property: KProperty<*>): Short {
|
||||
return thisRef.data.getShort(getIndex(thisRef))
|
||||
}
|
||||
|
||||
override fun setValue(thisRef: TypedByteArray, property: KProperty<*>, value: Short) {
|
||||
thisRef.data.setShort(getIndex(thisRef), value)
|
||||
}
|
||||
}
|
||||
|
||||
class IntProperty(
|
||||
val name: String
|
||||
) : ReadWriteProperty<TypedByteArray, Int> {
|
||||
var index: Int? = null
|
||||
|
||||
private fun getIndex(thisRef: TypedByteArray): Int {
|
||||
if (index == null) {
|
||||
index = thisRef.indexMap[name] ?: throw IllegalArgumentException("Property $name not found")
|
||||
}
|
||||
return index!!
|
||||
}
|
||||
|
||||
override fun getValue(thisRef: TypedByteArray, property: KProperty<*>): Int {
|
||||
return thisRef.data.getInt(getIndex(thisRef))
|
||||
}
|
||||
|
||||
override fun setValue(thisRef: TypedByteArray, property: KProperty<*>, value: Int) {
|
||||
thisRef.data.setInt(getIndex(thisRef), value)
|
||||
}
|
||||
}
|
||||
|
||||
class LongProperty(
|
||||
val name: String
|
||||
) : ReadWriteProperty<TypedByteArray, Long> {
|
||||
var index: Int? = null
|
||||
|
||||
private fun getIndex(thisRef: TypedByteArray): Int {
|
||||
if (index == null) {
|
||||
index = thisRef.indexMap[name] ?: throw IllegalArgumentException("Property $name not found")
|
||||
}
|
||||
return index!!
|
||||
}
|
||||
|
||||
override fun getValue(thisRef: TypedByteArray, property: KProperty<*>): Long {
|
||||
return thisRef.data.getLong(getIndex(thisRef))
|
||||
}
|
||||
|
||||
override fun setValue(thisRef: TypedByteArray, property: KProperty<*>, value: Long) {
|
||||
thisRef.data.setLong(getIndex(thisRef), value)
|
||||
}
|
||||
}
|
||||
|
||||
class StringProperty(
|
||||
val name: String
|
||||
) : ReadWriteProperty<TypedByteArray, String> {
|
||||
var index: Int? = null
|
||||
var maxLength: Int? = null
|
||||
|
||||
private fun getIndex(thisRef: TypedByteArray): Int {
|
||||
if (index == null) {
|
||||
index = thisRef.indexMap[name] ?: throw IllegalArgumentException("Property $name not found")
|
||||
}
|
||||
return index!!
|
||||
}
|
||||
|
||||
private fun getMaxLength(thisRef: TypedByteArray): Int {
|
||||
if (maxLength == null) {
|
||||
maxLength = thisRef.typeMap[name]!!.size - 2
|
||||
}
|
||||
return maxLength!!
|
||||
}
|
||||
|
||||
override fun getValue(thisRef: TypedByteArray, property: KProperty<*>): String {
|
||||
return thisRef.data.getString(getIndex(thisRef))
|
||||
}
|
||||
|
||||
override fun setValue(thisRef: TypedByteArray, property: KProperty<*>, value: String) {
|
||||
thisRef.data.setString(getIndex(thisRef), value, getMaxLength(thisRef))
|
||||
}
|
||||
}
|
||||
|
||||
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 string(propertyName: String) = StringProperty(propertyName)
|
||||
21
src/commonMain/kotlin/nl/astraeus/tba/TypedByteArray.kt
Normal file
21
src/commonMain/kotlin/nl/astraeus/tba/TypedByteArray.kt
Normal file
@@ -0,0 +1,21 @@
|
||||
package nl.astraeus.tba
|
||||
|
||||
open class TypedByteArray(
|
||||
val definition: ByteArrayDefinition,
|
||||
var data: MutableByteArrayHandler = MutableByteArrayHandler(definition.size),
|
||||
) {
|
||||
val typeMap = mutableMapOf<String, Type>()
|
||||
val indexMap = mutableMapOf<String, Int>()
|
||||
|
||||
init {
|
||||
var index = 0
|
||||
for (type in definition.types) {
|
||||
check (!typeMap.containsKey(type.name)) { "Duplicate type name ${type.name}" }
|
||||
check (!indexMap.containsKey(type.name)) { "Duplicate type name ${type.name}" }
|
||||
|
||||
typeMap[type.name] = type
|
||||
indexMap[type.name] = index
|
||||
index += type.size
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package nl.astraeus.tba
|
||||
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFailsWith
|
||||
|
||||
class ExtendedByteArrayDefinition {
|
||||
|
||||
open class MessageByteArray(
|
||||
vararg types: Type
|
||||
) : TypedByteArray(
|
||||
BaseByteArrayDefinition(
|
||||
Type("messageType", DataType.BYTE),
|
||||
Type("messageLength", DataType.SHORT),
|
||||
Type("message", DataType.STRING, 1024),
|
||||
*types,
|
||||
)
|
||||
) {
|
||||
var messageType by byte("messageType")
|
||||
var messageLength by short("messageLength")
|
||||
var message by string("message")
|
||||
}
|
||||
|
||||
class MessageBlaat() : MessageByteArray(
|
||||
Type("name", DataType.STRING, 10),
|
||||
Type("age", DataType.BYTE),
|
||||
Type("length", DataType.SHORT),
|
||||
) {
|
||||
var name by string("name")
|
||||
var age by byte("age")
|
||||
var length by short("length")
|
||||
|
||||
constructor(
|
||||
type: Byte,
|
||||
messageLength: Short,
|
||||
message: String,
|
||||
name: String,
|
||||
age: Byte,
|
||||
length: Short
|
||||
) : this() {
|
||||
this.messageType = type
|
||||
this.messageLength = messageLength
|
||||
this.message = message
|
||||
this.name = name
|
||||
this.age = age
|
||||
this.length = length
|
||||
}
|
||||
|
||||
constructor(data: ByteArray) : this() {
|
||||
this.data = MutableByteArrayHandler(buffer = data)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSimpleTypedByteArray() {
|
||||
val exception = assertFailsWith(IllegalArgumentException::class) {
|
||||
MessageBlaat(
|
||||
12,
|
||||
123,
|
||||
"This is the message!",
|
||||
"Test is a very long name, now what?",
|
||||
42,
|
||||
180
|
||||
)
|
||||
}
|
||||
|
||||
val msg = MessageBlaat(
|
||||
12,
|
||||
123,
|
||||
"This is the message!",
|
||||
"12345678",
|
||||
42,
|
||||
180
|
||||
)
|
||||
|
||||
assertEquals("12345678", msg.name)
|
||||
assertEquals(42.toByte(), msg.age)
|
||||
assertEquals(180.toShort(), msg.length)
|
||||
}
|
||||
|
||||
}
|
||||
53
src/commonTest/kotlin/nl/astraeus/tba/TypedByteArrayTest.kt
Normal file
53
src/commonTest/kotlin/nl/astraeus/tba/TypedByteArrayTest.kt
Normal file
@@ -0,0 +1,53 @@
|
||||
package nl.astraeus.tba
|
||||
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class TypedByteArrayTest {
|
||||
|
||||
class Person() : TypedByteArray(
|
||||
BaseByteArrayDefinition(
|
||||
Type("name", DataType.STRING, 102),
|
||||
Type("age", DataType.BYTE),
|
||||
Type("length", DataType.SHORT),
|
||||
)
|
||||
) {
|
||||
var name by string("name")
|
||||
var age by byte("age")
|
||||
var length by short("length")
|
||||
|
||||
constructor(name: String, age: Byte, length: Short): this() {
|
||||
this.name = name
|
||||
this.age = age
|
||||
this.length = length
|
||||
}
|
||||
|
||||
constructor(data: ByteArray): this() {
|
||||
this.data = MutableByteArrayHandler(buffer = data)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSimpleTypedByteArray() {
|
||||
val person = Person("Test", 42, 180)
|
||||
|
||||
assertEquals(4, person.data.buffer[1])
|
||||
assertEquals(42, person.data.buffer[102])
|
||||
|
||||
assertEquals("Test", person.name)
|
||||
assertEquals(42.toByte(), person.age)
|
||||
assertEquals(180.toShort(), person.length)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testToByteArrayAndBack() {
|
||||
val person = Person("Test", 42, 180)
|
||||
val bytes: ByteArray = person.data.buffer
|
||||
|
||||
val person2 = Person(bytes)
|
||||
|
||||
assertEquals("Test", person2.name)
|
||||
assertEquals(42.toByte(), person2.age)
|
||||
assertEquals(180.toShort(), person2.length)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user