ByteUtilities/src/dorkbox/bytes/ArrayExtensions.kt

222 lines
5.8 KiB
Kotlin

/*
* Copyright 2023 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.bytes
import java.util.ArrayList
object ArrayExtensions {
/**
* Gets the version number.
*/
const val version = BytesInfo.version
}
/**
* this saves the string in a RAW UTF-16 format of bytes, without the BOM
*
*
* The difference between THIS and .toByteArray(Charsets.UTF_16), is that this DOES NOT prepend a
* BOM (Byte Order Mark) of FEFF. This is against RFC 2781 for charsets.
*
* https://stackoverflow.com/questions/54247407/why-utf-8-bom-bytes-efbbbf-can-be-replaced-by-ufeff
*/
fun String.toBytes16(start: Int = 0, length: Int = this.length): ByteArray {
// NOTE: this saves the char array in UTF-16 format of bytes.
val bytes = ByteArray(length * 2)
var j = 0
val endPosition = start + length
for (i in start until endPosition) {
val code = this[i].code
val srcIndx = j++
bytes[2 * srcIndx] = (code shr 8).toByte()
bytes[2 * srcIndx + 1] = code.toByte()
}
return bytes
}
/**
* this saves the char array in a RAW UTF-16 format of bytes, without the BOM
*
*
* The difference between THIS and .toByteArray(Charsets.UTF_16), is that this DOES NOT prepend a
* BOM (Byte Order Mark) of FEFF. This is against RFC 2781 for charsets.
*
* https://stackoverflow.com/questions/54247407/why-utf-8-bom-bytes-efbbbf-can-be-replaced-by-ufeff
*/
fun CharArray.toBytes16(start: Int = 0, length: Int = this.size): ByteArray {
// NOTE: this saves the char array in UTF-16 format of bytes.
val bytes = ByteArray(length * 2)
var j = 0
val endPosition = start + length
for (i in start until endPosition) {
val code = this[i].code
val srcIndx = j++
bytes[2 * srcIndx] = (code shr 8).toByte()
bytes[2 * srcIndx + 1] = code.toByte()
}
return bytes
}
fun CharArray.toBytes(): ByteArray {
val length = this.size
val bytes = ByteArray(length)
for (i in 0 until length) {
val charValue = this[i]
bytes[i] = charValue.code.toByte()
}
return bytes
}
fun IntArray.toBytes(start: Int = 0, length: Int = this.size): ByteArray {
val bytes = ByteArray(length)
val endPosition = start + length
var j = 0
for (i in start until endPosition) {
val intValue = this[i]
if (intValue < 0 || intValue > 255) {
throw Exception("Int at index $i($intValue) was not a valid byte value (0-255)")
}
bytes[j++] = intValue.toByte()
}
return bytes
}
fun ByteArray.toInts(startPosition: Int = 0, length: Int = this.size): IntArray {
val ints = IntArray(length)
val endPosition = startPosition + length
var j = 0
for (i in startPosition until endPosition) {
ints[j++] = this[i].toInt() and 0xFF
}
return ints
}
/**
* XOR two byte arrays together, and save result in originalArray
*
* @param keyArray this is XOR'd into the original array, repeats if necessary.
*/
fun ByteArray.xor(keyArray: ByteArray) {
var keyIndex = 0
val keyLength = keyArray.size
for (i in this.indices) {
// XOR the data and start over if necessary
this[i] = (this[i].toInt() xor keyArray[keyIndex++ % keyLength].toInt()).toByte()
}
}
private val LINE_SEPARATOR = System.getProperty("line.separator", "\n")
fun ByteArray.printRaw(lineLength: Int = 0): String {
return if (lineLength > 0) {
val length = this.size
val comma = length - 1
val builder = StringBuilder(length + length / lineLength)
for (i in 0 until length) {
builder.append(this[i].toInt())
if (i < comma) {
builder.append(",")
}
if (i > 0 && i % lineLength == 0) {
builder.append(LINE_SEPARATOR)
}
}
builder.toString()
} else {
val length = this.size
val comma = length - 1
val builder = StringBuilder(length + length)
for (i in 0 until length) {
builder.append(this[i].toInt())
if (i < comma) {
builder.append(",")
}
}
builder.toString()
}
}
fun ByteArray.print(length: Int = this.size, includeByteCount: Boolean = true): String {
return this.print(0, length, includeByteCount, 40, null)
}
fun ByteArray.print(
inputOffset: Int,
length: Int,
includeByteCount: Boolean,
lineLength: Int = 40,
header: String? = null
): String {
val comma = length - 1
var builderLength = length + comma + 2
if (includeByteCount) {
builderLength += 7 + length.toString().length
}
if (lineLength > 0) {
builderLength += length / lineLength
}
if (header != null) {
builderLength += header.length + 2
}
val builder = StringBuilder(builderLength)
if (header != null) {
builder.append(header).append(LINE_SEPARATOR)
}
if (includeByteCount) {
builder.append("Bytes: ").append(length).append(LINE_SEPARATOR)
}
builder.append("{")
for (i in inputOffset until length) {
builder.append(this[i].toInt())
if (i < comma) {
builder.append(",")
}
if (i > inputOffset && lineLength > 0 && i % lineLength == 0) {
builder.append(LINE_SEPARATOR)
}
}
builder.append("}")
return builder.toString()
}