/* * 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. */ /* * Copyright (c) 2008, Nathan Sweet * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * * Neither the name of Esoteric Software nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * Modified by dorkbox, llc */ package dorkbox.bytes import java.io.IOException import java.nio.BufferUnderflowException import java.util.* import kotlin.experimental.and /** * A self-growing byte array wrapper. * * Utility methods are provided for efficiently writing primitive types and strings. * * Encoding of integers: BIG_ENDIAN is used for storing fixed native size integer values LITTLE_ENDIAN is used for a variable * length encoding of integer values * * @author Nathan Sweet @n4te.com> */ @Suppress("unused", "DuplicatedCode", "DuplicatedCode", "MemberVisibilityCanBePrivate") class ByteArrayBuffer { companion object { /** * Gets the version number. */ const val version = BytesInfo.version /** * Returns the number of bytes that would be written with [.writeInt]. */ fun intLength(value: Int, optimizePositive: Boolean): Int { @Suppress("NAME_SHADOWING") var value = value if (!optimizePositive) { value = value shl 1 xor (value shr 31) } if (value ushr 7 == 0) { return 1 } if (value ushr 14 == 0) { return 2 } if (value ushr 21 == 0) { return 3 } return if (value ushr 28 == 0) { 4 } else 5 } /** * Returns the number of bytes that would be written with [.writeLong]. */ fun longLength(value: Long, optimizePositive: Boolean): Int { @Suppress("NAME_SHADOWING") var value = value if (!optimizePositive) { value = value shl 1 xor (value shr 63) } if (value ushr 7 == 0L) { return 1 } if (value ushr 14 == 0L) { return 2 } if (value ushr 21 == 0L) { return 3 } if (value ushr 28 == 0L) { return 4 } if (value ushr 35 == 0L) { return 5 } if (value ushr 42 == 0L) { return 6 } if (value ushr 49 == 0L) { return 7 } return if (value ushr 56 == 0L) { 8 } else 9 } } private var capacity = 0 // exactly how many bytes have been allocated private var maxCapacity = 0 // how large we can grow private var position = 0 // current pointer to the point where data is read/written private lateinit var bytes : ByteArray // the backing buffer private var chars = CharArray(32) // small buffer for reading strings /** * Creates an uninitialized object. [.setBuffer] must be called before the object is used. */ constructor() /** * Creates a new object for writing to a byte array. * * @param bufferSize The initial size of the buffer. * * @param maxBufferSize The buffer is doubled as needed until it exceeds maxBufferSize and an exception is thrown. Can be -1 * for no maximum. */ constructor(bufferSize: Int, maxBufferSize: Int = bufferSize) { require(maxBufferSize >= -1) { "maxBufferSize cannot be < -1: $maxBufferSize" } capacity = bufferSize maxCapacity = if (maxBufferSize == -1) Int.MAX_VALUE else maxBufferSize bytes = ByteArray(bufferSize) } /** * Creates a new object for writing to a byte array. * * @see .setBuffer */ constructor(buffer: ByteArray, maxBufferSize: Int = buffer.size) { setBuffer(buffer, maxBufferSize) } /** * Sets the buffer that will be written to. The position and total are reset, discarding any buffered bytes. * * @param maxBufferSize * The buffer is doubled as needed until it exceeds maxBufferSize and an exception is thrown. */ fun setBuffer(buffer: ByteArray, maxBufferSize: Int) { require(maxBufferSize >= -1) { "maxBufferSize cannot be < -1: $maxBufferSize" } bytes = buffer maxCapacity = if (maxBufferSize == -1) Int.MAX_VALUE else maxBufferSize capacity = buffer.size position = 0 } /** * Returns the buffer. The bytes between zero and [.position] are the data that has been written. */ fun getBuffer(): ByteArray { return bytes } /** * Sets the buffer that will be written to. [.setBuffer] is called with the specified buffer's * length as the maxBufferSize. */ fun setBuffer(buffer: ByteArray) { setBuffer(buffer, buffer.size) } /** * Returns a new byte array containing the bytes currently in the buffer between zero and [.position]. */ fun toBytes(): ByteArray { val newBuffer = ByteArray(position) if (position > 0) { System.arraycopy(bytes, 0, newBuffer, 0, position) } return newBuffer } /** * Returns the remaining read/write bytes available before the end of the buffer */ fun remaining(): Int { return capacity - position } /** * Returns the size of the backing byte buffer */ fun capacity(): Int { return capacity } /** * Returns the current position in the buffer. This is the number of bytes that have not been flushed. */ fun position(): Int { return position } /** * Sets the current position in the buffer. */ fun setPosition(position: Int) { this.position = position } /** * Sets the position to zero. */ fun clear() { position = 0 } /** * Sets the position to zero. */ fun rewind() { position = 0 } /** * Sets the position to zero, and write 0 to all bytes in the buffer */ fun clearSecure() { position = 0 val buffer = bytes for (i in 0 until capacity) { buffer[i] = 0 } } /** * Discards the specified number of bytes. */ fun skip(count: Int) { @Suppress("NAME_SHADOWING") var count = count var skipCount = Math.min(capacity - position, count) while (true) { position += skipCount count -= skipCount if (count == 0) { break } skipCount = Math.min(count, capacity) require(skipCount) } } /** * @return true if the buffer has been resized. */ private fun require(required: Int): Boolean { if (capacity - position >= required) { return false } if (required > maxCapacity) { throw IOException("Buffer overflow. Max capacity: $maxCapacity, required: $required") } while (capacity - position < required) { if (capacity == maxCapacity) { throw IOException("Buffer overflow. Available: " + (capacity - position) + ", required: " + required) } // Grow buffer. if (capacity == 0) { capacity = 1 } capacity = (capacity * 1.6).toInt().coerceAtMost(maxCapacity) if (capacity < 0) { capacity = maxCapacity } val newBuffer = ByteArray(capacity) System.arraycopy(bytes, 0, newBuffer, 0, position) bytes = newBuffer } return true } // byte /** * Writes a byte. */ fun writeByte(value: Byte) { if (position == capacity) { require(1) } bytes[position++] = value } /** * Writes a byte. */ fun writeByte(value: Int) { if (position == capacity) { require(1) } bytes[position++] = value.toByte() } /** * Writes the bytes. Note the byte[] length is not written. */ fun writeBytes(bytes: ByteArray) { writeBytes(bytes, 0, bytes.size) } /** * Writes the bytes. Note the byte[] length is not written. */ fun writeBytes(bytes: ByteArray, offset: Int, count: Int) { @Suppress("NAME_SHADOWING") var offset = offset @Suppress("NAME_SHADOWING") var count = count var copyCount = (capacity - position).coerceAtMost(count) while (true) { System.arraycopy(bytes, offset, this.bytes, position, copyCount) position += copyCount count -= copyCount if (count == 0) { return } offset += copyCount copyCount = Math.min(capacity, count) require(copyCount) } } /** * Reads a single byte. */ fun readByte(): Byte { return bytes[position++] } /** * Reads a byte as an int from 0 to 255. */ fun readByteUnsigned(): Int { return bytes[position++].toInt() and 0xFF } /** * Reads a single byte, does not advance the position */ fun readByte(position: Int): Byte { return bytes[position] } /** * Reads a byte as an int from 0 to 255, does not advance the position */ fun readByteUnsigned(position: Int): Int { return bytes[position].toInt() and 0xFF } /** * Reads the specified number of bytes into a new byte[]. */ fun readBytes(length: Int): ByteArray { val bytes = ByteArray(length) readBytes(bytes, 0, length) return bytes } /** * Reads count bytes and writes them to the specified byte[], starting at offset (or 0) in target byte array. */ fun readBytes(bytes: ByteArray, offset: Int = 0, count: Int = bytes.size) { System.arraycopy(this.bytes, position, bytes, offset, count) position += count } // int /** * Writes a 4 byte int. Uses BIG_ENDIAN byte order. */ fun writeInt(value: Int) { require(4) val buffer = bytes buffer[position++] = (value shr 24).toByte() buffer[position++] = (value shr 16).toByte() buffer[position++] = (value shr 8).toByte() buffer[position++] = value.toByte() } /** * Writes a 1-5 byte int. This stream may consider such a variable length encoding request as a hint. It is not * guaranteed that a variable length encoding will be really used. The stream may decide to use native-sized integer * representation for efficiency reasons. * * @param optimizePositive * If true, small positive numbers will be more efficient (1 byte) and small negative numbers will be * inefficient (5 bytes). */ fun writeInt(value: Int, optimizePositive: Boolean): Int { return writeVarInt(value, optimizePositive) } /** * Writes a 1-5 byte int. It is guaranteed that a varible length encoding will be used. * * @param optimizePositive * If true, small positive numbers will be more efficient (1 byte) and small negative numbers will be * inefficient (5 bytes). */ fun writeVarInt(value: Int, optimizePositive: Boolean): Int { @Suppress("NAME_SHADOWING") var value = value if (!optimizePositive) value = value shl 1 xor (value shr 31) if (value ushr 7 == 0) { require(1) bytes[position++] = value.toByte() return 1 } if (value ushr 14 == 0) { require(2) val buffer = bytes buffer[position++] = (value and 0x7F or 0x80).toByte() buffer[position++] = (value ushr 7).toByte() return 2 } if (value ushr 21 == 0) { require(3) val buffer = bytes buffer[position++] = (value and 0x7F or 0x80).toByte() buffer[position++] = (value ushr 7 or 0x80).toByte() buffer[position++] = (value ushr 14).toByte() return 3 } if (value ushr 28 == 0) { require(4) val buffer = bytes buffer[position++] = (value and 0x7F or 0x80).toByte() buffer[position++] = (value ushr 7 or 0x80).toByte() buffer[position++] = (value ushr 14 or 0x80).toByte() buffer[position++] = (value ushr 21).toByte() return 4 } require(5) val buffer = bytes buffer[position++] = (value and 0x7F or 0x80).toByte() buffer[position++] = (value ushr 7 or 0x80).toByte() buffer[position++] = (value ushr 14 or 0x80).toByte() buffer[position++] = (value ushr 21 or 0x80).toByte() buffer[position++] = (value ushr 28).toByte() return 5 } /** * Reads a 4 byte int. */ fun readInt(): Int { val buffer = bytes val position = position val value: Int = buffer[position].toInt() and 0xFF shl 24 or (buffer[position + 1].toInt() and 0xFF shl 16 ) or (buffer[position + 2].toInt() and 0xFF shl 8 ) or (buffer[position + 3].toInt() and 0xFF) this.position = position + 4 return value } /** * Reads a 4 byte int, does not advance the position */ fun readInt(position: Int): Int { val buffer = bytes val value: Int = buffer[position].toInt() and 0xFF shl 24 or (buffer[position + 1].toInt() and 0xFF shl 16 ) or (buffer[position + 2].toInt() and 0xFF shl 8 ) or (buffer[position + 3].toInt() and 0xFF) this.position = position + 4 return value } /** * Reads a 1-5 byte int. This stream may consider such a variable length encoding request as a hint. It is not * guaranteed that a variable length encoding will be really used. The stream may decide to use native-sized integer * representation for efficiency reasons. */ fun readInt(optimizePositive: Boolean): Int { return readVarInt(optimizePositive) } /** * Reads a 1-5 byte int. This stream may consider such a variable length encoding request as a hint. It is not * guaranteed that a variable length encoding will be really used. The stream may decide to use native-sized integer * representation for efficiency reasons. * * * does not advance the position */ fun readInt(position: Int, optimizePositive: Boolean): Int { val pos = this.position this.position = position val value = readVarInt(optimizePositive) this.position = pos return value } /** * Reads a 1-5 byte int. It is guaranteed that a variable length encoding will be used. */ private fun readVarInt(optimizePositive: Boolean): Int { val buffer = bytes var b = buffer[position++].toInt() var result = b and 0x7F if (b and 0x80 != 0) { b = buffer[position++].toInt() result = result or (b and 0x7F shl 7) if (b and 0x80 != 0) { b = buffer[position++].toInt() result = result or (b and 0x7F shl 14) if (b and 0x80 != 0) { b = buffer[position++].toInt() result = result or (b and 0x7F shl 21) if (b and 0x80 != 0) { b = buffer[position++].toInt() result = result or (b and 0x7F shl 28) } } } } return if (optimizePositive) result else result ushr 1 xor -(result and 1) } /** * Returns true if enough bytes are available to read an int with [.readInt]. */ fun canReadInt(): Boolean { if (capacity - position >= 5) { return true } if (position + 1 > capacity) { return false } val buffer = bytes var p = position if (buffer[p++].toInt() and 0x80 == 0) { return true } if (p == capacity) { return false } if (buffer[p++].toInt() and 0x80 == 0) { return true } if (p == capacity) { return false } if (buffer[p++].toInt() and 0x80 == 0) { return true } if (p == capacity) { return false } return if (buffer[p++].toInt() and 0x80 == 0) { true } else p != capacity } /** * Returns true if enough bytes are available to read an int with [.readInt]. */ fun canReadInt(position: Int): Boolean { if (capacity - position >= 5) { return true } if (position + 1 > capacity) { return false } val buffer = bytes var p = position if (buffer[p++].toInt() and 0x80 == 0) { return true } if (p == capacity) { return false } if (buffer[p++].toInt() and 0x80 == 0) { return true } if (p == capacity) { return false } if (buffer[p++].toInt() and 0x80 == 0) { return true } if (p == capacity) { return false } return if (buffer[p++].toInt() and 0x80 == 0) { true } else p != capacity } // string /** * Writes the length and string, or null. Short strings are checked and if ASCII they are written more efficiently, * else they are written as UTF8. If a string is known to be ASCII, [ByteArrayBuffer.writeAscii] may be used. The * string can be read using [ByteArrayBuffer.readString] or [ByteArrayBuffer.readStringBuilder]. * * @param value * May be null. */ fun writeString(value: String?) { if (value == null) { writeByte(0x80) // 0 means null, bit 8 means UTF8. return } val charCount = value.length if (charCount == 0) { writeByte(1 or 0x80) // 1 means empty string, bit 8 means UTF8. return } // Detect ASCII, we only do this for small strings // since 1 char is used for bit-masking if we use for 1 char string, reading the string will not work! var permitAscii = charCount in 2..32 if (permitAscii) { for (i in 0 until charCount) { if (value[i].code > 127) { permitAscii = false break // not ascii } } if (permitAscii) { // this is ascii if (capacity - position < charCount) { writeAscii_slow(value, charCount) } else { val stringBytes = value.encodeToByteArray(0, charCount) stringBytes.copyInto(bytes, position) // value.toByteArray(0, charCount, bytes, position) position += charCount // var i = 0 // val n = value.length // while (i < n) { // bytes[position++] = value[i].code.toByte() // ++i // } } // mod the last written byte with 0x80 so we can use that when reading ascii bytes to see what the end of the string is val value1: Byte = (bytes[position - 1].toInt() or 0x80).toByte() bytes[position - 1] = value1 return } } writeUtf8Length(charCount + 1) var charIndex = 0 if (capacity - position >= charCount) { // Try to write 8 bit chars. val buffer = bytes var position = position while (charIndex < charCount) { val c = value[charIndex].code if (c > 127) { break } buffer[position++] = c.toByte() charIndex++ } this.position = position } if (charIndex < charCount) { writeUtf8_slow(value, charCount, charIndex) } } /** * Writes the length and CharSequence as UTF8, or null. The string can be read using [ByteArrayBuffer.readString] or * [ByteArrayBuffer.readStringBuilder]. * * @param value * May be null. */ fun writeString(value: CharSequence?) { if (value == null) { writeByte(0x80) // 0 means null, bit 8 means UTF8. return } val charCount = value.length if (charCount == 0) { writeByte(1 or 0x80) // 1 means empty string, bit 8 means UTF8. return } writeUtf8Length(charCount + 1) var charIndex = 0 if (capacity - position >= charCount) { // Try to write 8 bit chars. val buffer = bytes var position = position while (charIndex < charCount) { val c = value[charIndex].code if (c > 127) { break } buffer[position++] = c.toByte() charIndex++ } this.position = position } if (charIndex < charCount) { writeUtf8_slow(value, charCount, charIndex) } } /** * Writes a string that is known to contain only ASCII characters. Non-ASCII strings passed to this method will be * corrupted. Each byte is a 7 bit character with the remaining byte denoting if another character is available. * This is slightly more efficient than [ByteArrayBuffer.writeString]. The string can be read using * [ByteArrayBuffer.readString] or [ByteArrayBuffer.readStringBuilder]. * * @param value * May be null. */ fun writeAscii(value: String?) { if (value == null) { writeByte(0x80) // 0 means null, bit 8 means UTF8. return } val charCount = value.length when (charCount) { 0 -> { writeByte(1 or 0x80) // 1 is string length + 1, bit 8 means UTF8. return } 1 -> { writeByte(2 or 0x80) // 2 is string length + 1, bit 8 means UTF8. writeByte(value[0].code) return } } if (capacity - position < charCount) { writeAscii_slow(value, charCount) } else { val stringBytes = value.encodeToByteArray(0, charCount) stringBytes.copyInto(bytes, position) // value.toByteArray(0, charCount, bytes, position) position += charCount } bytes[position - 1] = (bytes[position - 1].toInt() or 0x80).toByte() // Bit 8 means end of ASCII. } /** * Writes the length of a string, which is a variable length encoded int except the first byte uses bit 8 to denote * UTF8 and bit 7 to denote if another byte is present. */ private fun writeUtf8Length(value: Int) { if (value ushr 6 == 0) { require(1) bytes[position++] = (value or 0x80).toByte() // Set bit 8. } else if (value ushr 13 == 0) { require(2) val buffer = bytes buffer[position++] = (value or 0x40 or 0x80).toByte() // Set bit 7 and 8. buffer[position++] = (value ushr 6).toByte() } else if (value ushr 20 == 0) { require(3) val buffer = bytes buffer[position++] = (value or 0x40 or 0x80).toByte() // Set bit 7 and 8. buffer[position++] = (value ushr 6 or 0x80).toByte() // Set bit 8. buffer[position++] = (value ushr 13).toByte() } else if (value ushr 27 == 0) { require(4) val buffer = bytes buffer[position++] = (value or 0x40 or 0x80).toByte() // Set bit 7 and 8. buffer[position++] = (value ushr 6 or 0x80).toByte() // Set bit 8. buffer[position++] = (value ushr 13 or 0x80).toByte() // Set bit 8. buffer[position++] = (value ushr 20).toByte() } else { require(5) val buffer = bytes buffer[position++] = (value or 0x40 or 0x80).toByte() // Set bit 7 and 8. buffer[position++] = (value ushr 6 or 0x80).toByte() // Set bit 8. buffer[position++] = (value ushr 13 or 0x80).toByte() // Set bit 8. buffer[position++] = (value ushr 20 or 0x80).toByte() // Set bit 8. buffer[position++] = (value ushr 27).toByte() } } private fun writeUtf8_slow(value: CharSequence, charCount: Int, charIndex: Int) { @Suppress("NAME_SHADOWING") var charIndex = charIndex while (charIndex < charCount) { if (position == capacity) { require(capacity.coerceAtMost(charCount - charIndex)) } val c = value[charIndex].code if (c <= 0x007F) { bytes[position++] = c.toByte() } else if (c > 0x07FF) { bytes[position++] = (0xE0 or (c shr 12 and 0x0F)).toByte() require(2) val buffer = bytes buffer[position++] = (0x80 or (c shr 6 and 0x3F)).toByte() buffer[position++] = (0x80 or (c and 0x3F)).toByte() } else { bytes[position++] = (0xC0 or (c shr 6 and 0x1F)).toByte() require(1) bytes[position++] = (0x80 or (c and 0x3F)).toByte() } charIndex++ } } private fun writeAscii_slow(value: String, charCount: Int) { var buffer = bytes var charIndex = 0 var charsToWrite = charCount.coerceAtMost(capacity - position) while (charIndex < charCount) { val stringBytes = value.encodeToByteArray(charIndex, charIndex + charsToWrite) stringBytes.copyInto(buffer, position) // value.toByteArray(charIndex, charIndex + charsToWrite, buffer, position) charIndex += charsToWrite position += charsToWrite charsToWrite = Math.min(charCount - charIndex, capacity) if (require(charsToWrite)) { buffer = bytes } } } /** * Reads the length and string of UTF8 characters, or null. This can read strings written by * [ByteArrayBuffer.writeString] , [ByteArrayBuffer.writeString], and * [ByteArrayBuffer.writeAscii]. * * @return May be null. */ fun readString(): String? { val available = capacity - position val b = bytes[position++].toInt() if (b and 0x80 == 0) { return readAscii() // ASCII. } // Null, empty, or UTF8. var charCount = if (available >= 5) readUtf8Length(b) else readUtf8Length_slow(b) when (charCount) { 0 -> return null 1 -> return "" } charCount-- if (chars.size < charCount) { chars = CharArray(charCount) } if (available < charCount) { throw BufferUnderflowException() } readUtf8(charCount) return String(chars, 0, charCount) } private fun readUtf8Length(b: Int): Int { @Suppress("NAME_SHADOWING") var b = b var result = b and 0x3F // Mask all but first 6 bits. if (b and 0x40 != 0) { // Bit 7 means another byte, bit 8 means UTF8. val buffer = bytes b = buffer[position++].toInt() result = result or (b and 0x7F shl 6) if (b and 0x80 != 0) { b = buffer[position++].toInt() result = result or (b and 0x7F shl 13) if (b and 0x80 != 0) { b = buffer[position++].toInt() result = result or (b and 0x7F shl 20) if (b and 0x80 != 0) { b = buffer[position++].toInt() result = result or (b and 0x7F shl 27) } } } } return result } private fun readUtf8Length_slow(b: Int): Int { @Suppress("NAME_SHADOWING") var b = b var result = b and 0x3F // Mask all but first 6 bits. if (b and 0x40 != 0) { // Bit 7 means another byte, bit 8 means UTF8. val buffer = bytes b = buffer[position++].toInt() result = result or (b and 0x7F shl 6) if (b and 0x80 != 0) { b = buffer[position++].toInt() result = result or (b and 0x7F shl 13) if (b and 0x80 != 0) { b = buffer[position++].toInt() result = result or (b and 0x7F shl 20) if (b and 0x80 != 0) { b = buffer[position++].toInt() result = result or (b and 0x7F shl 27) } } } } return result } private fun readUtf8(charCount: Int) { val buffer = bytes var position = position val chars = chars // Try to read 7 bit ASCII chars. var charIndex = 0 val spaceAvailable = capacity - this.position val count = Math.min(spaceAvailable, charCount) var b: Int while (charIndex < count) { b = buffer[position++].toInt() if (b < 0) { position-- break } chars[charIndex++] = b.toChar() } this.position = position // If buffer didn't hold all chars or any were not ASCII, use slow path for remainder. if (charIndex < charCount) { readUtf8_slow(charCount, charIndex) } } private fun readUtf8_slow(charCount: Int, charIndex: Int) { @Suppress("NAME_SHADOWING") var charIndex = charIndex val chars = chars val buffer = bytes while (charIndex < charCount) { val b: Int = buffer[position++].toInt() and 0xFF when (b shr 4) { 0, 1, 2, 3, 4, 5, 6, 7 -> chars[charIndex] = b.toChar() 12, 13 -> chars[charIndex] = (b and 0x1F shl 6 or (buffer[position++].toInt() and 0x3F)).toChar() 14 -> chars[charIndex] = (b and 0x0F shl 12 or (buffer[position++].toInt() and 0x3F shl 6) or (buffer[position++].toInt() and 0x3F)).toChar() } charIndex++ } } private fun readAscii(): String { val buffer = bytes var end = position val start = end - 1 val limit = capacity var b: Int do { if (end == limit) { return readAscii_slow() } b = buffer[end++].toInt() } while (b and 0x80 == 0) buffer[end - 1] = buffer[end - 1] and 0x7F // Mask end of ascii bit. val value = String(buffer, start, end - start) buffer[end - 1] = (buffer[end - 1].toInt() or 0x80).toByte() position = end return value } private fun readAscii_slow(): String { position-- // Re-read the first byte. // Copy chars currently in buffer. var charCount = capacity - position if (charCount > chars.size) { chars = CharArray(charCount * 2) } var chars = chars val buffer = bytes var i = position var ii = 0 val n = capacity while (i < n) { chars[ii] = buffer[i].toInt().toChar() i++ ii++ } position = capacity // Copy additional chars one by one. while (true) { val b = buffer[position++].toInt() if (charCount == chars.size) { val newChars = CharArray(charCount * 2) System.arraycopy(chars, 0, newChars, 0, charCount) chars = newChars this.chars = newChars } if (b and 0x80 == 0x80) { chars[charCount++] = (b and 0x7F).toChar() break } chars[charCount++] = b.toChar() } return String(chars, 0, charCount) } /** * Reads the length and string of UTF8 characters, or null. This can read strings written by * [ByteArrayBuffer.writeString] , [ByteArrayBuffer.writeString], and * [ByteArrayBuffer.writeAscii]. * * @return May be null. */ fun readStringBuilder(): StringBuilder? { val available = capacity - position val b = bytes[position++].toInt() if (b and 0x80 == 0) { return StringBuilder(readAscii()) // ASCII. } // Null, empty, or UTF8. var charCount = if (available >= 5) readUtf8Length(b) else readUtf8Length_slow(b) when (charCount) { 0 -> return null 1 -> return StringBuilder() } charCount-- if (chars.size < charCount) { chars = CharArray(charCount) } readUtf8(charCount) val builder = StringBuilder(charCount) builder.append(chars, 0, charCount) return builder } // float /** * Writes a 4 byte float. */ fun writeFloat(value: Float) { writeInt(java.lang.Float.floatToIntBits(value)) } /** * Writes a 1-5 byte float with reduced precision. * * @param optimizePositive * If true, small positive numbers will be more efficient (1 byte) and small negative numbers will be * inefficient (5 bytes). */ fun writeFloat(value: Float, precision: Float, optimizePositive: Boolean): Int { return writeInt((value * precision).toInt(), optimizePositive) } /** * Reads a 4 byte float. */ fun readFloat(): Float { return java.lang.Float.intBitsToFloat(readInt()) } /** * Reads a 1-5 byte float with reduced precision. */ fun readFloat(precision: Float, optimizePositive: Boolean): Float { return readInt(optimizePositive) / precision } /** * Reads a 4 byte float, does not advance the position */ fun readFloat(position: Int): Float { return java.lang.Float.intBitsToFloat(readInt(position)) } /** * Reads a 1-5 byte float with reduced precision, does not advance the position */ fun readFloat(position: Int, precision: Float, optimizePositive: Boolean): Float { return readInt(position, optimizePositive) / precision } // short /** * Writes a 2 byte short. Uses BIG_ENDIAN byte order. */ fun writeShort(value: Int) { require(2) val buffer = bytes buffer[position++] = (value ushr 8).toByte() buffer[position++] = value.toByte() } /** * Reads a 2 byte short. */ fun readShort(): Short { val buffer = bytes return ((buffer[position++].toInt() and 0xFF) shl 8 or (buffer[position++].toInt() and 0xFF)).toShort() } /** * Reads a 2 byte short as an int from 0 to 65535. */ fun readShortUnsigned(): Int { val buffer = bytes return buffer[position++].toInt() and 0xFF shl 8 or buffer[position++].toInt() and 0xFF } /** * Reads a 2 byte short, does not advance the position */ fun readShort(position: Int): Short { @Suppress("NAME_SHADOWING") var position = position val buffer = bytes return (buffer[position++].toInt() and 0xFF shl 8 or buffer[position].toInt() and 0xFF).toShort() } /** * Reads a 2 byte short as an int from 0 to 65535, does not advance the position */ fun readShortUnsigned(position: Int): Int { @Suppress("NAME_SHADOWING") var position = position val buffer = bytes return buffer[position++].toInt() and 0xFF shl 8 or buffer[position].toInt() and 0xFF } // long /** * Writes an 8 byte long. Uses BIG_ENDIAN byte order. */ fun writeLong(value: Long) { require(8) val buffer = bytes buffer[position++] = (value ushr 56).toByte() buffer[position++] = (value ushr 48).toByte() buffer[position++] = (value ushr 40).toByte() buffer[position++] = (value ushr 32).toByte() buffer[position++] = (value ushr 24).toByte() buffer[position++] = (value ushr 16).toByte() buffer[position++] = (value ushr 8).toByte() buffer[position++] = value.toByte() } /** * Writes a 1-9 byte long. This stream may consider such a variable length encoding request as a hint. It is not * guaranteed that a variable length encoding will be really used. The stream may decide to use native-sized integer * representation for efficiency reasons. * * @param optimizePositive * If true, small positive numbers will be more efficient (1 byte) and small negative numbers will be * inefficient (9 bytes). */ fun writeLong(value: Long, optimizePositive: Boolean): Int { return writeVarLong(value, optimizePositive) } /** * Writes a 1-9 byte long. It is guaranteed that a varible length encoding will be used. * * @param optimizePositive * If true, small positive numbers will be more efficient (1 byte) and small negative numbers will be * inefficient (9 bytes). */ fun writeVarLong(value: Long, optimizePositive: Boolean): Int { @Suppress("NAME_SHADOWING") var value = value if (!optimizePositive) value = value shl 1 xor (value shr 63) if (value ushr 7 == 0L) { require(1) bytes[position++] = value.toByte() return 1 } if (value ushr 14 == 0L) { require(2) val buffer = bytes buffer[position++] = (value and 0x7F or 0x80).toByte() buffer[position++] = (value ushr 7).toByte() return 2 } if (value ushr 21 == 0L) { require(3) val buffer = bytes buffer[position++] = (value and 0x7F or 0x80).toByte() buffer[position++] = (value ushr 7 or 0x80).toByte() buffer[position++] = (value ushr 14).toByte() return 3 } if (value ushr 28 == 0L) { require(4) val buffer = bytes buffer[position++] = (value and 0x7F or 0x80).toByte() buffer[position++] = (value ushr 7 or 0x80).toByte() buffer[position++] = (value ushr 14 or 0x80).toByte() buffer[position++] = (value ushr 21).toByte() return 4 } if (value ushr 35 == 0L) { require(5) val buffer = bytes buffer[position++] = (value and 0x7F or 0x80).toByte() buffer[position++] = (value ushr 7 or 0x80).toByte() buffer[position++] = (value ushr 14 or 0x80).toByte() buffer[position++] = (value ushr 21 or 0x80).toByte() buffer[position++] = (value ushr 28).toByte() return 5 } if (value ushr 42 == 0L) { require(6) val buffer = bytes buffer[position++] = (value and 0x7F or 0x80).toByte() buffer[position++] = (value ushr 7 or 0x80).toByte() buffer[position++] = (value ushr 14 or 0x80).toByte() buffer[position++] = (value ushr 21 or 0x80).toByte() buffer[position++] = (value ushr 28 or 0x80).toByte() buffer[position++] = (value ushr 35).toByte() return 6 } if (value ushr 49 == 0L) { require(7) val buffer = bytes buffer[position++] = (value and 0x7F or 0x80).toByte() buffer[position++] = (value ushr 7 or 0x80).toByte() buffer[position++] = (value ushr 14 or 0x80).toByte() buffer[position++] = (value ushr 21 or 0x80).toByte() buffer[position++] = (value ushr 28 or 0x80).toByte() buffer[position++] = (value ushr 35 or 0x80).toByte() buffer[position++] = (value ushr 42).toByte() return 7 } if (value ushr 56 == 0L) { require(8) val buffer = bytes buffer[position++] = (value and 0x7F or 0x80).toByte() buffer[position++] = (value ushr 7 or 0x80).toByte() buffer[position++] = (value ushr 14 or 0x80).toByte() buffer[position++] = (value ushr 21 or 0x80).toByte() buffer[position++] = (value ushr 28 or 0x80).toByte() buffer[position++] = (value ushr 35 or 0x80).toByte() buffer[position++] = (value ushr 42 or 0x80).toByte() buffer[position++] = (value ushr 49).toByte() return 8 } require(9) val buffer = bytes buffer[position++] = (value and 0x7F or 0x80).toByte() buffer[position++] = (value ushr 7 or 0x80).toByte() buffer[position++] = (value ushr 14 or 0x80).toByte() buffer[position++] = (value ushr 21 or 0x80).toByte() buffer[position++] = (value ushr 28 or 0x80).toByte() buffer[position++] = (value ushr 35 or 0x80).toByte() buffer[position++] = (value ushr 42 or 0x80).toByte() buffer[position++] = (value ushr 49 or 0x80).toByte() buffer[position++] = (value ushr 56).toByte() return 9 } /** * Returns true if enough bytes are available to read a long with [.readLong]. */ fun canReadLong(): Boolean { if (capacity - position >= 9) { return true } if (position + 1 > capacity) { return false } val buffer = bytes var p = position if (buffer[p++].toInt() and 0x80 == 0) { return true } if (p == capacity) { return false } if (buffer[p++].toInt() and 0x80 == 0) { return true } if (p == capacity) { return false } if (buffer[p++].toInt() and 0x80 == 0) { return true } if (p == capacity) { return false } if (buffer[p++].toInt() and 0x80 == 0) { return true } if (p == capacity) { return false } if (buffer[p++].toInt() and 0x80 == 0) { return true } if (p == capacity) { return false } if (buffer[p++].toInt() and 0x80 == 0) { return true } if (p == capacity) { return false } if (buffer[p++].toInt() and 0x80 == 0) { return true } if (p == capacity) { return false } return if (buffer[p++].toInt() and 0x80 == 0) { true } else p != capacity } /** * Returns true if enough bytes are available to read a long with [.readLong]. */ fun canReadLong(position: Int): Boolean { if (capacity - position >= 9) { return true } if (position + 1 > capacity) { return false } val buffer = bytes var p = position if (buffer[p++].toInt() and 0x80 == 0) { return true } if (p == capacity) { return false } if (buffer[p++].toInt() and 0x80 == 0) { return true } if (p == capacity) { return false } if (buffer[p++].toInt() and 0x80 == 0) { return true } if (p == capacity) { return false } if (buffer[p++].toInt() and 0x80 == 0) { return true } if (p == capacity) { return false } if (buffer[p++].toInt() and 0x80 == 0) { return true } if (p == capacity) { return false } if (buffer[p++].toInt() and 0x80 == 0) { return true } if (p == capacity) { return false } if (buffer[p++].toInt() and 0x80 == 0) { return true } if (p == capacity) { return false } return if (buffer[p++].toInt() and 0x80 == 0) { true } else p != capacity } /** * Reads an 8 byte long. */ fun readLong(): Long { val buffer = bytes return buffer[position++].toLong() shl 56 or ((buffer[position++].toInt() and 0xFF).toLong() shl 48 ) or ((buffer[position++].toInt() and 0xFF).toLong() shl 40 ) or ((buffer[position++].toInt() and 0xFF).toLong() shl 32 ) or ((buffer[position++].toInt() and 0xFF).toLong() shl 24 ) or ((buffer[position++].toInt() and 0xFF).toLong() shl 16 ) or ((buffer[position++].toInt() and 0xFF).toLong() shl 8 ) or (buffer[position++].toInt() and 0xFF).toLong() } /** * Reads a 1-9 byte long. This stream may consider such a variable length encoding request as a hint. It is not * guaranteed that a variable length encoding will be really used. The stream may decide to use native-sized integer * representation for efficiency reasons. */ fun readLong(optimizePositive: Boolean): Long { return readVarLong(optimizePositive) } /** * Reads an 8 byte long, does not advance the position */ fun readLong(position: Int): Long { @Suppress("NAME_SHADOWING") var position = position val buffer = bytes return buffer[position++].toLong() shl 56 or ((buffer[position++].toInt() and 0xFF).toLong() shl 48 ) or ((buffer[position++].toInt() and 0xFF).toLong() shl 40 ) or ((buffer[position++].toInt() and 0xFF).toLong() shl 32 ) or ((buffer[position++].toInt() and 0xFF).toLong() shl 24 ) or ((buffer[position++].toInt() and 0xFF).toLong() shl 16 ) or ((buffer[position++].toInt() and 0xFF).toLong() shl 8 ) or (buffer[position].toInt() and 0xFF).toLong() } /** * Reads a 1-9 byte long. This stream may consider such a variable length encoding request as a hint. It is not * guaranteed that a variable length encoding will be really used. The stream may decide to use native-sized integer * representation for efficiency reasons. * * * does not advance the position */ fun readLong(position: Int, optimizePositive: Boolean): Long { val pos = this.position this.position = position val value = readVarLong(optimizePositive) this.position = pos return value } /** * Reads a 1-9 byte long. It is guaranteed that a varible length encoding will be used. */ private fun readVarLong(optimizePositive: Boolean): Long { if (capacity - position < 9) { return readLong_slow(optimizePositive) } var b = bytes[position++].toInt() var result = (b and 0x7F).toLong() if (b and 0x80 != 0) { val buffer = bytes b = buffer[position++].toInt() result = result or (b and 0x7F shl 7).toLong() if (b and 0x80 != 0) { b = buffer[position++].toInt() result = result or (b and 0x7F shl 14).toLong() if (b and 0x80 != 0) { b = buffer[position++].toInt() result = result or (b and 0x7F shl 21).toLong() if (b and 0x80 != 0) { b = buffer[position++].toInt() result = result or ((b and 0x7F).toLong() shl 28) if (b and 0x80 != 0) { b = buffer[position++].toInt() result = result or ((b and 0x7F).toLong() shl 35) if (b and 0x80 != 0) { b = buffer[position++].toInt() result = result or ((b and 0x7F).toLong() shl 42) if (b and 0x80 != 0) { b = buffer[position++].toInt() result = result or ((b and 0x7F).toLong() shl 49) if (b and 0x80 != 0) { b = buffer[position++].toInt() result = result or (b.toLong() shl 56) } } } } } } } } if (!optimizePositive) { result = result ushr 1 xor -(result and 1) } return result } private fun readLong_slow(optimizePositive: Boolean): Long { // The buffer is guaranteed to have at least 1 byte. val buffer = bytes var b = buffer[position++].toInt() var result = (b and 0x7F).toLong() if (b and 0x80 != 0) { b = buffer[position++].toInt() result = result or (b and 0x7F shl 7).toLong() if (b and 0x80 != 0) { b = buffer[position++].toInt() result = result or (b and 0x7F shl 14).toLong() if (b and 0x80 != 0) { b = buffer[position++].toInt() result = result or (b and 0x7F shl 21).toLong() if (b and 0x80 != 0) { b = buffer[position++].toInt() result = result or ((b and 0x7F).toLong() shl 28) if (b and 0x80 != 0) { b = buffer[position++].toInt() result = result or ((b and 0x7F).toLong() shl 35) if (b and 0x80 != 0) { b = buffer[position++].toInt() result = result or ((b and 0x7F).toLong() shl 42) if (b and 0x80 != 0) { b = buffer[position++].toInt() result = result or ((b and 0x7F).toLong() shl 49) if (b and 0x80 != 0) { b = buffer[position++].toInt() result = result or (b.toLong() shl 56) } } } } } } } } if (!optimizePositive) { result = result ushr 1 xor -(result and 1) } return result } // boolean /** * Writes a 1 byte boolean. */ fun writeBoolean(value: Boolean) { require(1) bytes[position++] = (if (value) 1 else 0).toByte() } /** * Reads a 1 byte boolean. */ fun readBoolean(): Boolean { return bytes[position++].toInt() == 1 } /** * Reads a 1 byte boolean, does not advance the position */ fun readBoolean(position: Int): Boolean { return bytes[position] .toInt()== 1 } // char /** * Writes a 2 byte char. Uses BIG_ENDIAN byte order. */ fun writeChar(value: Char) { require(2) val buffer = bytes buffer[position++] = (value.code ushr 8).toByte() buffer[position++] = value.code.toByte() } /** * Reads a 2 byte char. */ fun readChar(): Char { val buffer = bytes return ((buffer[position++].toInt() and 0xFF) shl 8 or (buffer[position++].toInt() and 0xFF)).toChar() } /** * Reads a 2 byte char, does not advance the position */ fun readChar(position: Int): Char { @Suppress("NAME_SHADOWING") var position = position val buffer = bytes return (buffer[position++].toInt() and 0xFF shl 8 or buffer[position].toInt() and 0xFF).toChar() } // double /** * Writes an 8 byte double. */ fun writeDouble(value: Double) { writeLong(java.lang.Double.doubleToLongBits(value)) } /** * Writes a 1-9 byte double with reduced precision * * @param optimizePositive * If true, small positive numbers will be more efficient (1 byte) and small negative numbers will be * inefficient (9 bytes). */ fun writeDouble(value: Double, precision: Double, optimizePositive: Boolean): Int { return writeLong((value * precision).toLong(), optimizePositive) } /** * Reads an 8 bytes double. */ fun readDouble(): Double { return java.lang.Double.longBitsToDouble(readLong()) } /** * Reads a 1-9 byte double with reduced precision. */ fun readDouble(precision: Double, optimizePositive: Boolean): Double { return readLong(optimizePositive) / precision } /** * Reads an 8 bytes double, does not advance the position */ fun readDouble(position: Int): Double { return java.lang.Double.longBitsToDouble(readLong(position)) } /** * Reads a 1-9 byte double with reduced precision, does not advance the position */ fun readDouble(position: Int, precision: Double, optimizePositive: Boolean): Double { return readLong(position, optimizePositive) / precision } // Methods implementing bulk operations on arrays of primitive types /** * Bulk output of an int array. */ fun writeInts(`object`: IntArray, optimizePositive: Boolean) { var i = 0 val n = `object`.size while (i < n) { writeInt(`object`[i], optimizePositive) i++ } } /** * Bulk input of an int array. */ fun readInts(length: Int, optimizePositive: Boolean): IntArray { val array = IntArray(length) for (i in 0 until length) { array[i] = readInt(optimizePositive) } return array } /** * Bulk output of an long array. */ fun writeLongs(`object`: LongArray, optimizePositive: Boolean) { var i = 0 val n = `object`.size while (i < n) { writeLong(`object`[i], optimizePositive) i++ } } /** * Bulk input of a long array. */ fun readLongs(length: Int, optimizePositive: Boolean): LongArray { val array = LongArray(length) for (i in 0 until length) { array[i] = readLong(optimizePositive) } return array } /** * Bulk output of an int array. */ fun writeInts(`object`: IntArray) { var i = 0 val n = `object`.size while (i < n) { writeInt(`object`[i]) i++ } } /** * Bulk input of an int array. */ fun readInts(length: Int): IntArray { val array = IntArray(length) for (i in 0 until length) { array[i] = readInt() } return array } /** * Bulk output of an long array. */ fun writeLongs(`object`: LongArray) { var i = 0 val n = `object`.size while (i < n) { writeLong(`object`[i]) i++ } } /** * Bulk input of a long array. */ fun readLongs(length: Int): LongArray { val array = LongArray(length) for (i in 0 until length) { array[i] = readLong() } return array } /** * Bulk output of a float array. */ fun writeFloats(`object`: FloatArray) { var i = 0 val n = `object`.size while (i < n) { writeFloat(`object`[i]) i++ } } /** * Bulk input of a float array. */ fun readFloats(length: Int): FloatArray { val array = FloatArray(length) for (i in 0 until length) { array[i] = readFloat() } return array } /** * Bulk output of a short array. */ fun writeShorts(`object`: ShortArray) { var i = 0 val n = `object`.size while (i < n) { writeShort(`object`[i].toInt()) i++ } } /** * Bulk input of a short array. */ fun readShorts(length: Int): ShortArray { val array = ShortArray(length) for (i in 0 until length) { array[i] = readShort() } return array } /** * Bulk output of a char array. */ fun writeChars(`object`: CharArray) { var i = 0 val n = `object`.size while (i < n) { writeChar(`object`[i]) i++ } } /** * Bulk input of a char array. */ fun readChars(length: Int): CharArray { val array = CharArray(length) for (i in 0 until length) { array[i] = readChar() } return array } /** * Bulk output of a double array. */ fun writeDoubles(`object`: DoubleArray) { var i = 0 val n = `object`.size while (i < n) { writeDouble(`object`[i]) i++ } } /** * Bulk input of a double array */ fun readDoubles(length: Int): DoubleArray { val array = DoubleArray(length) for (i in 0 until length) { array[i] = readDouble() } return array } override fun equals(other: Any?): Boolean { return if (other !is ByteArrayBuffer) { false } else Arrays.equals(bytes, other.bytes) // CANNOT be null, so we don't have to null check! } override fun hashCode(): Int { // might be null for a thread because it's stale. who cares, get the value again return Arrays.hashCode(bytes) } override fun toString(): String { return "ByteBuffer2 " + Arrays.toString(bytes) } }