aboutsummaryrefslogtreecommitdiff
path: root/okio/src/commonMain/kotlin/okio/internal/ByteString.kt
diff options
context:
space:
mode:
Diffstat (limited to 'okio/src/commonMain/kotlin/okio/internal/ByteString.kt')
-rw-r--r--okio/src/commonMain/kotlin/okio/internal/ByteString.kt344
1 files changed, 344 insertions, 0 deletions
diff --git a/okio/src/commonMain/kotlin/okio/internal/ByteString.kt b/okio/src/commonMain/kotlin/okio/internal/ByteString.kt
new file mode 100644
index 00000000..7a1a488b
--- /dev/null
+++ b/okio/src/commonMain/kotlin/okio/internal/ByteString.kt
@@ -0,0 +1,344 @@
+/*
+ * Copyright (C) 2018 Square, Inc.
+ *
+ * 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 okio.internal
+
+import okio.BASE64_URL_SAFE
+import okio.Buffer
+import okio.ByteString
+import okio.REPLACEMENT_CODE_POINT
+import okio.and
+import okio.arrayRangeEquals
+import okio.asUtf8ToByteArray
+import okio.checkOffsetAndCount
+import okio.decodeBase64ToArray
+import okio.encodeBase64
+import okio.isIsoControl
+import okio.processUtf8CodePoints
+import okio.shr
+import okio.toUtf8String
+
+// TODO Kotlin's expect classes can't have default implementations, so platform implementations
+// have to call these functions. Remove all this nonsense when expect class allow actual code.
+
+@Suppress("NOTHING_TO_INLINE")
+internal inline fun ByteString.commonUtf8(): String {
+ var result = utf8
+ if (result == null) {
+ // We don't care if we double-allocate in racy code.
+ result = internalArray().toUtf8String()
+ utf8 = result
+ }
+ return result
+}
+
+@Suppress("NOTHING_TO_INLINE")
+internal inline fun ByteString.commonBase64(): String = data.encodeBase64()
+
+@Suppress("NOTHING_TO_INLINE")
+internal inline fun ByteString.commonBase64Url() = data.encodeBase64(map = BASE64_URL_SAFE)
+
+internal val HEX_DIGIT_CHARS =
+ charArrayOf('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f')
+
+@Suppress("NOTHING_TO_INLINE")
+internal inline fun ByteString.commonHex(): String {
+ val result = CharArray(data.size * 2)
+ var c = 0
+ for (b in data) {
+ result[c++] = HEX_DIGIT_CHARS[b shr 4 and 0xf]
+ result[c++] = HEX_DIGIT_CHARS[b and 0xf] // ktlint-disable no-multi-spaces
+ }
+ return String(result)
+}
+
+@Suppress("NOTHING_TO_INLINE")
+internal inline fun ByteString.commonToAsciiLowercase(): ByteString {
+ // Search for an uppercase character. If we don't find one, return this.
+ var i = 0
+ while (i < data.size) {
+ var c = data[i]
+ if (c < 'A'.toByte() || c > 'Z'.toByte()) {
+ i++
+ continue
+ }
+
+ // This string is needs to be lowercased. Create and return a new byte string.
+ val lowercase = data.copyOf()
+ lowercase[i++] = (c - ('A' - 'a')).toByte()
+ while (i < lowercase.size) {
+ c = lowercase[i]
+ if (c < 'A'.toByte() || c > 'Z'.toByte()) {
+ i++
+ continue
+ }
+ lowercase[i] = (c - ('A' - 'a')).toByte()
+ i++
+ }
+ return ByteString(lowercase)
+ }
+ return this
+}
+
+@Suppress("NOTHING_TO_INLINE")
+internal inline fun ByteString.commonToAsciiUppercase(): ByteString {
+ // Search for an lowercase character. If we don't find one, return this.
+ var i = 0
+ while (i < data.size) {
+ var c = data[i]
+ if (c < 'a'.toByte() || c > 'z'.toByte()) {
+ i++
+ continue
+ }
+
+ // This string is needs to be uppercased. Create and return a new byte string.
+ val lowercase = data.copyOf()
+ lowercase[i++] = (c - ('a' - 'A')).toByte()
+ while (i < lowercase.size) {
+ c = lowercase[i]
+ if (c < 'a'.toByte() || c > 'z'.toByte()) {
+ i++
+ continue
+ }
+ lowercase[i] = (c - ('a' - 'A')).toByte()
+ i++
+ }
+ return ByteString(lowercase)
+ }
+ return this
+}
+
+@Suppress("NOTHING_TO_INLINE")
+internal inline fun ByteString.commonSubstring(beginIndex: Int, endIndex: Int): ByteString {
+ require(beginIndex >= 0) { "beginIndex < 0" }
+ require(endIndex <= data.size) { "endIndex > length(${data.size})" }
+
+ val subLen = endIndex - beginIndex
+ require(subLen >= 0) { "endIndex < beginIndex" }
+
+ if (beginIndex == 0 && endIndex == data.size) {
+ return this
+ }
+ return ByteString(data.copyOfRange(beginIndex, endIndex))
+}
+
+@Suppress("NOTHING_TO_INLINE")
+internal inline fun ByteString.commonGetByte(pos: Int) = data[pos]
+
+@Suppress("NOTHING_TO_INLINE")
+internal inline fun ByteString.commonGetSize() = data.size
+
+@Suppress("NOTHING_TO_INLINE")
+internal inline fun ByteString.commonToByteArray() = data.copyOf()
+
+@Suppress("NOTHING_TO_INLINE")
+internal inline fun ByteString.commonInternalArray() = data
+
+@Suppress("NOTHING_TO_INLINE")
+internal inline fun ByteString.commonRangeEquals(
+ offset: Int,
+ other: ByteString,
+ otherOffset: Int,
+ byteCount: Int
+): Boolean = other.rangeEquals(otherOffset, this.data, offset, byteCount)
+
+@Suppress("NOTHING_TO_INLINE")
+internal inline fun ByteString.commonRangeEquals(
+ offset: Int,
+ other: ByteArray,
+ otherOffset: Int,
+ byteCount: Int
+): Boolean {
+ return (
+ offset >= 0 && offset <= data.size - byteCount &&
+ otherOffset >= 0 && otherOffset <= other.size - byteCount &&
+ arrayRangeEquals(data, offset, other, otherOffset, byteCount)
+ )
+}
+
+@Suppress("NOTHING_TO_INLINE")
+internal inline fun ByteString.commonStartsWith(prefix: ByteString) =
+ rangeEquals(0, prefix, 0, prefix.size)
+
+@Suppress("NOTHING_TO_INLINE")
+internal inline fun ByteString.commonStartsWith(prefix: ByteArray) =
+ rangeEquals(0, prefix, 0, prefix.size)
+
+@Suppress("NOTHING_TO_INLINE")
+internal inline fun ByteString.commonEndsWith(suffix: ByteString) =
+ rangeEquals(size - suffix.size, suffix, 0, suffix.size)
+
+@Suppress("NOTHING_TO_INLINE")
+internal inline fun ByteString.commonEndsWith(suffix: ByteArray) =
+ rangeEquals(size - suffix.size, suffix, 0, suffix.size)
+
+@Suppress("NOTHING_TO_INLINE")
+internal inline fun ByteString.commonIndexOf(other: ByteArray, fromIndex: Int): Int {
+ val limit = data.size - other.size
+ for (i in maxOf(fromIndex, 0)..limit) {
+ if (arrayRangeEquals(data, i, other, 0, other.size)) {
+ return i
+ }
+ }
+ return -1
+}
+
+@Suppress("NOTHING_TO_INLINE")
+internal inline fun ByteString.commonLastIndexOf(
+ other: ByteString,
+ fromIndex: Int
+) = lastIndexOf(other.internalArray(), fromIndex)
+
+@Suppress("NOTHING_TO_INLINE")
+internal inline fun ByteString.commonLastIndexOf(other: ByteArray, fromIndex: Int): Int {
+ val limit = data.size - other.size
+ for (i in minOf(fromIndex, limit) downTo 0) {
+ if (arrayRangeEquals(data, i, other, 0, other.size)) {
+ return i
+ }
+ }
+ return -1
+}
+
+@Suppress("NOTHING_TO_INLINE")
+internal inline fun ByteString.commonEquals(other: Any?): Boolean {
+ return when {
+ other === this -> true
+ other is ByteString -> other.size == data.size && other.rangeEquals(0, data, 0, data.size)
+ else -> false
+ }
+}
+
+@Suppress("NOTHING_TO_INLINE")
+internal inline fun ByteString.commonHashCode(): Int {
+ val result = hashCode
+ if (result != 0) return result
+ return data.contentHashCode().also {
+ hashCode = it
+ }
+}
+
+@Suppress("NOTHING_TO_INLINE")
+internal inline fun ByteString.commonCompareTo(other: ByteString): Int {
+ val sizeA = size
+ val sizeB = other.size
+ var i = 0
+ val size = minOf(sizeA, sizeB)
+ while (i < size) {
+ val byteA = this[i] and 0xff
+ val byteB = other[i] and 0xff
+ if (byteA == byteB) {
+ i++
+ continue
+ }
+ return if (byteA < byteB) -1 else 1
+ }
+ if (sizeA == sizeB) return 0
+ return if (sizeA < sizeB) -1 else 1
+}
+
+@Suppress("NOTHING_TO_INLINE")
+internal inline fun commonOf(data: ByteArray) = ByteString(data.copyOf())
+
+@Suppress("NOTHING_TO_INLINE")
+internal inline fun ByteArray.commonToByteString(offset: Int, byteCount: Int): ByteString {
+ checkOffsetAndCount(size.toLong(), offset.toLong(), byteCount.toLong())
+ return ByteString(copyOfRange(offset, offset + byteCount))
+}
+
+@Suppress("NOTHING_TO_INLINE")
+internal inline fun String.commonEncodeUtf8(): ByteString {
+ val byteString = ByteString(asUtf8ToByteArray())
+ byteString.utf8 = this
+ return byteString
+}
+
+@Suppress("NOTHING_TO_INLINE")
+internal inline fun String.commonDecodeBase64(): ByteString? {
+ val decoded = decodeBase64ToArray()
+ return if (decoded != null) ByteString(decoded) else null
+}
+
+@Suppress("NOTHING_TO_INLINE")
+internal inline fun String.commonDecodeHex(): ByteString {
+ require(length % 2 == 0) { "Unexpected hex string: $this" }
+
+ val result = ByteArray(length / 2)
+ for (i in result.indices) {
+ val d1 = decodeHexDigit(this[i * 2]) shl 4
+ val d2 = decodeHexDigit(this[i * 2 + 1])
+ result[i] = (d1 + d2).toByte()
+ }
+ return ByteString(result)
+}
+
+/** Writes the contents of this byte string to `buffer`. */
+internal fun ByteString.commonWrite(buffer: Buffer, offset: Int, byteCount: Int) {
+ buffer.write(data, offset, byteCount)
+}
+
+private fun decodeHexDigit(c: Char): Int {
+ return when (c) {
+ in '0'..'9' -> c - '0'
+ in 'a'..'f' -> c - 'a' + 10
+ in 'A'..'F' -> c - 'A' + 10
+ else -> throw IllegalArgumentException("Unexpected hex digit: $c")
+ }
+}
+
+@Suppress("NOTHING_TO_INLINE")
+internal inline fun ByteString.commonToString(): String {
+ if (data.isEmpty()) return "[size=0]"
+
+ val i = codePointIndexToCharIndex(data, 64)
+ if (i == -1) {
+ return if (data.size <= 64) {
+ "[hex=${hex()}]"
+ } else {
+ "[size=${data.size} hex=${commonSubstring(0, 64).hex()}…]"
+ }
+ }
+
+ val text = utf8()
+ val safeText = text.substring(0, i)
+ .replace("\\", "\\\\")
+ .replace("\n", "\\n")
+ .replace("\r", "\\r")
+ return if (i < text.length) {
+ "[size=${data.size} text=$safeText…]"
+ } else {
+ "[text=$safeText]"
+ }
+}
+
+private fun codePointIndexToCharIndex(s: ByteArray, codePointCount: Int): Int {
+ var charCount = 0
+ var j = 0
+ s.processUtf8CodePoints(0, s.size) { c ->
+ if (j++ == codePointCount) {
+ return charCount
+ }
+
+ if ((c != '\n'.toInt() && c != '\r'.toInt() && isIsoControl(c)) ||
+ c == REPLACEMENT_CODE_POINT
+ ) {
+ return -1
+ }
+
+ charCount += if (c < 0x10000) 1 else 2
+ }
+ return charCount
+}