From 492c601b467f49ebb893f11b0bf70f230a943244 Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Thu, 25 May 2023 16:35:26 +0200 Subject: [PATCH 01/83] Remove ByteString-related API --- core/common/src/-Base64.kt | 154 -------- core/common/src/-Util.kt | 4 - core/common/src/Buffer.kt | 27 -- core/common/src/ByteString.kt | 218 ----------- core/common/src/SegmentedByteString.kt | 55 --- core/common/src/Source.kt | 33 -- core/common/src/internal/-Buffer.kt | 125 +++---- core/common/src/internal/-ByteString.kt | 349 ------------------ core/common/src/internal/-RealBufferedSink.kt | 16 - .../src/internal/-RealBufferedSource.kt | 33 -- .../src/internal/-SegmentedByteString.kt | 243 ------------ core/jvm/src/Buffer.kt | 2 +- core/jvm/src/ByteString.kt | 341 ----------------- core/jvm/src/RealSink.kt | 3 - core/jvm/src/RealSource.kt | 3 - core/jvm/src/SegmentedByteString.kt | 135 ------- core/jvm/src/Sink.kt | 6 - core/jvm/src/Source.kt | 9 - core/native/src/Buffer.kt | 52 +-- core/native/src/ByteString.kt | 161 -------- core/native/src/SegmentedByteString.kt | 98 ----- 21 files changed, 54 insertions(+), 2013 deletions(-) delete mode 100644 core/common/src/-Base64.kt delete mode 100644 core/common/src/ByteString.kt delete mode 100644 core/common/src/SegmentedByteString.kt delete mode 100644 core/common/src/internal/-ByteString.kt delete mode 100644 core/common/src/internal/-SegmentedByteString.kt delete mode 100644 core/jvm/src/ByteString.kt delete mode 100644 core/jvm/src/SegmentedByteString.kt delete mode 100644 core/native/src/ByteString.kt delete mode 100644 core/native/src/SegmentedByteString.kt diff --git a/core/common/src/-Base64.kt b/core/common/src/-Base64.kt deleted file mode 100644 index 4cb3495b9..000000000 --- a/core/common/src/-Base64.kt +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright 2017-2023 JetBrains s.r.o. and respective authors and developers. - * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENCE file. - */ - -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You 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 kotlinx.io - -import kotlinx.io.ByteString.Companion.encodeUtf8 -import kotlin.native.concurrent.SharedImmutable - -/** @author Alexander Y. Kleymenov */ - -@SharedImmutable -internal val BASE64 = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".encodeUtf8().data -@SharedImmutable -internal val BASE64_URL_SAFE = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_".encodeUtf8().data - -internal fun String.decodeBase64ToArray(): ByteArray? { - // Ignore trailing '=' padding and whitespace from the input. - var limit = length - while (limit > 0) { - val c = this[limit - 1] - if (c != '=' && c != '\n' && c != '\r' && c != ' ' && c != '\t') { - break - } - limit-- - } - - // If the input includes whitespace, this output array will be longer than necessary. - val out = ByteArray((limit * 6L / 8L).toInt()) - var outCount = 0 - var inCount = 0 - - var word = 0 - for (pos in 0 until limit) { - val c = this[pos] - - val bits: Int - if (c in 'A'..'Z') { - // char ASCII value - // A 65 0 - // Z 90 25 (ASCII - 65) - bits = c.code - 65 - } else if (c in 'a'..'z') { - // char ASCII value - // a 97 26 - // z 122 51 (ASCII - 71) - bits = c.code - 71 - } else if (c in '0'..'9') { - // char ASCII value - // 0 48 52 - // 9 57 61 (ASCII + 4) - bits = c.code + 4 - } else if (c == '+' || c == '-') { - bits = 62 - } else if (c == '/' || c == '_') { - bits = 63 - } else if (c == '\n' || c == '\r' || c == ' ' || c == '\t') { - continue - } else { - return null - } - - // Append this char's 6 bits to the word. - word = word shl 6 or bits - - // For every 4 chars of input, we accumulate 24 bits of output. Emit 3 bytes. - inCount++ - if (inCount % 4 == 0) { - out[outCount++] = (word shr 16).toByte() - out[outCount++] = (word shr 8).toByte() - out[outCount++] = word.toByte() - } - } - - val lastWordChars = inCount % 4 - when (lastWordChars) { - 1 -> { - // We read 1 char followed by "===". But 6 bits is a truncated byte! Fail. - return null - } - 2 -> { - // We read 2 chars followed by "==". Emit 1 byte with 8 of those 12 bits. - word = word shl 12 - out[outCount++] = (word shr 16).toByte() - } - 3 -> { - // We read 3 chars, followed by "=". Emit 2 bytes for 16 of those 18 bits. - word = word shl 6 - out[outCount++] = (word shr 16).toByte() - out[outCount++] = (word shr 8).toByte() - } - } - - // If we sized our out array perfectly, we're done. - if (outCount == out.size) return out - - // Copy the decoded bytes to a new, right-sized array. - return out.copyOf(outCount) -} - -internal fun ByteArray.encodeBase64(map: ByteArray = BASE64): String { - val length = (size + 2) / 3 * 4 - val out = ByteArray(length) - var index = 0 - val end = size - size % 3 - var i = 0 - while (i < end) { - val b0 = this[i++].toInt() - val b1 = this[i++].toInt() - val b2 = this[i++].toInt() - out[index++] = map[(b0 and 0xff shr 2)] - out[index++] = map[(b0 and 0x03 shl 4) or (b1 and 0xff shr 4)] - out[index++] = map[(b1 and 0x0f shl 2) or (b2 and 0xff shr 6)] - out[index++] = map[(b2 and 0x3f)] - } - when (size - end) { - 1 -> { - val b0 = this[i].toInt() - out[index++] = map[b0 and 0xff shr 2] - out[index++] = map[b0 and 0x03 shl 4] - out[index++] = '='.code.toByte() - out[index] = '='.code.toByte() - } - 2 -> { - val b0 = this[i++].toInt() - val b1 = this[i].toInt() - out[index++] = map[(b0 and 0xff shr 2)] - out[index++] = map[(b0 and 0x03 shl 4) or (b1 and 0xff shr 4)] - out[index++] = map[(b1 and 0x0f shl 2)] - out[index] = '='.code.toByte() - } - } - return out.toUtf8String() -} diff --git a/core/common/src/-Util.kt b/core/common/src/-Util.kt index ff6510118..2a24024a4 100644 --- a/core/common/src/-Util.kt +++ b/core/common/src/-Util.kt @@ -181,10 +181,6 @@ internal fun resolveDefaultParameter(unsafeCursor: Buffer.UnsafeCursor): Buffer. } internal val DEFAULT__ByteString_size = -1234567890 -internal fun ByteString.resolveDefaultParameter(position: Int): Int { - if (position == DEFAULT__ByteString_size) return size - return position -} internal fun ByteArray.resolveDefaultParameter(sizeParam: Int): Int { if (sizeParam == DEFAULT__ByteString_size) return size diff --git a/core/common/src/Buffer.kt b/core/common/src/Buffer.kt index 1013f41f2..4adced089 100644 --- a/core/common/src/Buffer.kt +++ b/core/common/src/Buffer.kt @@ -81,10 +81,6 @@ expect class Buffer() : Source, Sink { /** Discards `byteCount` bytes from the head of this buffer. */ override fun skip(byteCount: Long) -// override fun write(byteString: ByteString): Buffer - -// override fun write(byteString: ByteString, offset: Int, byteCount: Int): Buffer - override fun writeUtf8(string: String): Buffer override fun writeUtf8(string: String, beginIndex: Int, endIndex: Int): Buffer @@ -99,23 +95,6 @@ expect class Buffer() : Source, Sink { */ internal fun writableSegment(minimumCapacity: Int): Segment -// fun md5(): ByteString -// -// fun sha1(): ByteString -// -// fun sha256(): ByteString -// -// fun sha512(): ByteString -// -// /** Returns the 160-bit SHA-1 HMAC of this buffer. */ -// fun hmacSha1(key: ByteString): ByteString -// -// /** Returns the 256-bit SHA-256 HMAC of this buffer. */ -// fun hmacSha256(key: ByteString): ByteString -// -// /** Returns the 512-bit SHA-512 HMAC of this buffer. */ -// fun hmacSha512(key: ByteString): ByteString - override fun write(source: ByteArray, offset: Int, byteCount: Int): Buffer override fun write(source: RawSource, byteCount: Long): Buffer @@ -141,12 +120,6 @@ expect class Buffer() : Source, Sink { /** Returns a deep copy of this buffer. */ fun copy(): Buffer -// /** Returns an immutable copy of this buffer as a byte string. */ -// fun snapshot(): ByteString -// -// /** Returns an immutable copy of the first `byteCount` bytes of this buffer as a byte string. */ -// fun snapshot(byteCount: Int): ByteString -// // fun readUnsafe(unsafeCursor: UnsafeCursor = DEFAULT__new_UnsafeCursor): UnsafeCursor // // fun readAndWriteUnsafe(unsafeCursor: UnsafeCursor = DEFAULT__new_UnsafeCursor): UnsafeCursor diff --git a/core/common/src/ByteString.kt b/core/common/src/ByteString.kt deleted file mode 100644 index 03d0a25a3..000000000 --- a/core/common/src/ByteString.kt +++ /dev/null @@ -1,218 +0,0 @@ -/* - * Copyright 2017-2023 JetBrains s.r.o. and respective authors and developers. - * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENCE file. - */ - -/* - * 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 kotlinx.io - -import kotlin.jvm.JvmField -import kotlin.jvm.JvmName -import kotlin.jvm.JvmOverloads -import kotlin.jvm.JvmStatic - -/** - * An immutable sequence of bytes. - * - * Byte strings compare lexicographically as a sequence of **unsigned** bytes. That is, the byte - * string `ff` sorts after `00`. This is counter to the sort order of the corresponding bytes, - * where `-1` sorts before `0`. - * - * **Full disclosure:** this class provides untrusted input and output streams with raw access to - * the underlying byte array. A hostile stream implementation could keep a reference to the mutable - * byte string, violating the immutable guarantee of this class. For this reason a byte string's - * immutability guarantee cannot be relied upon for security in applets and other environments that - * run both trusted and untrusted code in the same process. - */ -expect internal open class ByteString -// Trusted internal constructor doesn't clone data. -internal constructor(data: ByteArray) : Comparable { - internal val data: ByteArray - - internal var hashCode: Int - internal var utf8: String? - - /** Constructs a new `String` by decoding the bytes as `UTF-8`. */ - fun utf8(): String - - /** - * Returns this byte string encoded as [Base64](http://www.ietf.org/rfc/rfc2045.txt). In violation - * of the RFC, the returned string does not wrap lines at 76 columns. - */ - fun base64(): String - - /** Returns this byte string encoded as [URL-safe Base64](http://www.ietf.org/rfc/rfc4648.txt). */ - fun base64Url(): String - - /** Returns this byte string encoded in hexadecimal. */ - fun hex(): String - - /** - * Returns the 128-bit MD5 hash of this byte string. - * - * MD5 has been vulnerable to collisions since 2004. It should not be used in new code. - */ -// fun md5(): ByteString - - /** - * Returns the 160-bit SHA-1 hash of this byte string. - * - * SHA-1 has been vulnerable to collisions since 2017. It should not be used in new code. - */ -// fun sha1(): ByteString -// -// /** Returns the 256-bit SHA-256 hash of this byte string. */ -// fun sha256(): ByteString -// -// /** Returns the 512-bit SHA-512 hash of this byte string. */ -// fun sha512(): ByteString -// -// /** Returns the 160-bit SHA-1 HMAC of this byte string. */ -// fun hmacSha1(key: ByteString): ByteString -// -// /** Returns the 256-bit SHA-256 HMAC of this byte string. */ -// fun hmacSha256(key: ByteString): ByteString -// -// /** Returns the 512-bit SHA-512 HMAC of this byte string. */ -// fun hmacSha512(key: ByteString): ByteString - /** - * Returns a byte string equal to this byte string, but with the bytes 'A' through 'Z' replaced - * with the corresponding byte in 'a' through 'z'. Returns this byte string if it contains no - * bytes in 'A' through 'Z'. - */ - fun toAsciiLowercase(): ByteString - - /** - * Returns a byte string that is a substring of this byte string, beginning at the specified - * `beginIndex` and ends at the specified `endIndex`. Returns this byte string if `beginIndex` is - * 0 and `endIndex` is the length of this byte string. - */ - fun substring(beginIndex: Int = 0, endIndex: Int = DEFAULT__ByteString_size): ByteString - - /** - * Returns a byte string equal to this byte string, but with the bytes 'a' through 'z' replaced - * with the corresponding byte in 'A' through 'Z'. Returns this byte string if it contains no - * bytes in 'a' through 'z'. - */ - fun toAsciiUppercase(): ByteString - - /** Returns the byte at `pos`. */ - internal fun internalGet(pos: Int): Byte - - /** Returns the byte at `index`. */ - @JvmName("getByte") - operator fun get(index: Int): Byte - - /** Returns the number of bytes in this ByteString. */ - val size: Int - @JvmName("size") get - - // Hack to work around Kotlin's limitation for using JvmName on open/override vals/funs - internal fun getSize(): Int - - /** Returns a byte array containing a copy of the bytes in this `ByteString`. */ - fun toByteArray(): ByteArray - - /** Writes the contents of this byte string to `buffer`. */ - internal fun write(buffer: Buffer, offset: Int, byteCount: Int) - - /** Returns the bytes of this string without a defensive copy. Do not mutate! */ - internal fun internalArray(): ByteArray - - /** - * Returns true if the bytes of this in `[offset..offset+byteCount)` equal the bytes of `other` in - * `[otherOffset..otherOffset+byteCount)`. Returns false if either range is out of bounds. - */ - fun rangeEquals(offset: Int, other: ByteString, otherOffset: Int, byteCount: Int): Boolean - - /** - * Returns true if the bytes of this in `[offset..offset+byteCount)` equal the bytes of `other` in - * `[otherOffset..otherOffset+byteCount)`. Returns false if either range is out of bounds. - */ - fun rangeEquals(offset: Int, other: ByteArray, otherOffset: Int, byteCount: Int): Boolean - - /** - * Copies bytes of this in `[offset..offset+byteCount]` to other in - * `[targetOffset..targetOffset+byteCount]`. - * - * @throws IndexOutOfBoundsException if either range is out of bounds. - */ - fun copyInto(offset: Int = 0, target: ByteArray, targetOffset: Int = 0, byteCount: Int) - - fun startsWith(prefix: ByteString): Boolean - - fun startsWith(prefix: ByteArray): Boolean - - fun endsWith(suffix: ByteString): Boolean - - fun endsWith(suffix: ByteArray): Boolean - - @JvmOverloads - fun indexOf(other: ByteString, fromIndex: Int = 0): Int - - @JvmOverloads - fun indexOf(other: ByteArray, fromIndex: Int = 0): Int - - fun lastIndexOf(other: ByteString, fromIndex: Int = DEFAULT__ByteString_size): Int - - fun lastIndexOf(other: ByteArray, fromIndex: Int = DEFAULT__ByteString_size): Int - - override fun equals(other: Any?): Boolean - - override fun hashCode(): Int - - override fun compareTo(other: ByteString): Int - - /** - * Returns a human-readable string that describes the contents of this byte string. Typically this - * is a string like `[text=Hello]` or `[hex=0000ffff]`. - */ - override fun toString(): String - - companion object { - /** A singleton empty `ByteString`. */ - @JvmField - val EMPTY: ByteString - - /** Returns a new byte string containing a clone of the bytes of `data`. */ - @JvmStatic - fun of(vararg data: Byte): ByteString - - /** - * Returns a new [ByteString] containing a copy of `byteCount` bytes of this [ByteArray] - * starting at `offset`. - */ - @JvmStatic - fun ByteArray.toByteString(offset: Int = 0, byteCount: Int = DEFAULT__ByteString_size): ByteString - - /** Returns a new byte string containing the `UTF-8` bytes of this [String]. */ - @JvmStatic - fun String.encodeUtf8(): ByteString - - /** - * Decodes the Base64-encoded bytes and returns their value as a byte string. Returns null if - * this is not a Base64-encoded sequence of bytes. - */ - @JvmStatic - fun String.decodeBase64(): ByteString? - - /** Decodes the hex-encoded bytes and returns their value a byte string. */ - @JvmStatic - fun String.decodeHex(): ByteString - } -} diff --git a/core/common/src/SegmentedByteString.kt b/core/common/src/SegmentedByteString.kt deleted file mode 100644 index 9ab6cf43a..000000000 --- a/core/common/src/SegmentedByteString.kt +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2017-2023 JetBrains s.r.o. and respective authors and developers. - * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENCE file. - */ - -/* - * Copyright (C) 2015 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 kotlinx.io - -/** - * An immutable byte string composed of segments of byte arrays. This class exists to implement - * efficient snapshots of buffers. It is implemented as an array of segments, plus a directory in - * two halves that describes how the segments compose this byte string. - * - * The first half of the directory is the cumulative byte count covered by each segment. The - * element at `directory[0]` contains the number of bytes held in `segments[0]`; the - * element at `directory[1]` contains the number of bytes held in `segments[0] + - * segments[1]`, and so on. The element at `directory[segments.length - 1]` contains the total - * size of this byte string. The first half of the directory is always monotonically increasing. - * - * The second half of the directory is the offset in `segments` of the first content byte. - * Bytes preceding this offset are unused, as are bytes beyond the segment's effective size. - * - * Suppose we have a byte string, `[A, B, C, D, E, F, G, H, I, J, K, L, M]` that is stored - * across three byte arrays: `[x, x, x, x, A, B, C, D, E, x, x, x]`, `[x, F, G]`, and `[H, I, J, K, - * L, M, x, x, x, x, x, x]`. The three byte arrays would be stored in `segments` in order. Since the - * arrays contribute 5, 2, and 6 elements respectively, the directory starts with `[5, 7, 13` to - * hold the cumulative total at each position. Since the offsets into the arrays are 4, 1, and 0 - * respectively, the directory ends with `4, 1, 0]`. Concatenating these two halves, the complete - * directory is `[5, 7, 13, 4, 1, 0]`. - * - * This structure is chosen so that the segment holding a particular offset can be found by - * binary search. We use one array rather than two for the directory as a micro-optimization. - */ -internal expect class SegmentedByteString internal constructor( - segments: Array, - directory: IntArray -) : ByteString { - - internal val segments: Array - internal val directory: IntArray -} diff --git a/core/common/src/Source.kt b/core/common/src/Source.kt index e716ccb43..508b9b441 100644 --- a/core/common/src/Source.kt +++ b/core/common/src/Source.kt @@ -240,39 +240,6 @@ expect sealed interface Source : RawSource { */ fun skip(byteCount: Long) -// /** Removes all bytes from this and returns them as a byte string. */ -// fun readByteString(): ByteString - -// /** Removes `byteCount` bytes from this and returns them as a byte string. */ -// fun readByteString(byteCount: Long): ByteString - - /** - * Finds the first string in `options` that is a prefix of this buffer, consumes it from this - * buffer, and returns its index. If no byte string in `options` is a prefix of this buffer this - * returns -1 and no bytes are consumed. - * - * This can be used as an alternative to [readByteString] or even [readUtf8] if the set of - * expected values is known in advance. - * ``` - * Options FIELDS = Options.of( - * ByteString.encodeUtf8("depth="), - * ByteString.encodeUtf8("height="), - * ByteString.encodeUtf8("width=")); - * - * Buffer buffer = new Buffer() - * .writeUtf8("width=640\n") - * .writeUtf8("height=480\n"); - * - * assertEquals(2, buffer.select(FIELDS)); - * assertEquals(640, buffer.readDecimalLong()); - * assertEquals('\n', buffer.readByte()); - * assertEquals(1, buffer.select(FIELDS)); - * assertEquals(480, buffer.readDecimalLong()); - * assertEquals('\n', buffer.readByte()); - * ``` - */ -// fun select(options: Options): Int - /** Removes all bytes from this and returns them as a byte array. */ fun readByteArray(): ByteArray diff --git a/core/common/src/internal/-Buffer.kt b/core/common/src/internal/-Buffer.kt index 0aa71a6cf..2fbaaeb78 100644 --- a/core/common/src/internal/-Buffer.kt +++ b/core/common/src/internal/-Buffer.kt @@ -1458,85 +1458,64 @@ internal inline fun Buffer.commonCopy(): Buffer { return result } -///** Returns an immutable copy of this buffer as a byte string. */ -//internal inline fun Buffer.commonSnapshot(): ByteString { -// check(size <= Int.MAX_VALUE) { "size > Int.MAX_VALUE: $size" } -// return snapshot(size.toInt()) -//} -// -///** Returns an immutable copy of the first `byteCount` bytes of this buffer as a byte string. */ -//internal inline fun Buffer.commonSnapshot(byteCount: Int): ByteString { -// if (byteCount == 0) return ByteString.EMPTY -// checkOffsetAndCount(size, 0, byteCount.toLong()) -// -// // Walk through the buffer to count how many segments we'll need. -// var offset = 0 -// var segmentCount = 0 -// var s = head -// while (offset < byteCount) { -// if (s!!.limit == s.pos) { -// throw AssertionError("s.limit == s.pos") // Empty segment. This should not happen! -// } -// offset += s.limit - s.pos -// segmentCount++ -// s = s.next -// } -// -// // Walk through the buffer again to assign segments and build the directory. -// val segments = arrayOfNulls(segmentCount) -// val directory = IntArray(segmentCount * 2) -// offset = 0 -// segmentCount = 0 -// s = head -// while (offset < byteCount) { -// segments[segmentCount] = s!!.data -// offset += s.limit - s.pos -// // Despite sharing more bytes, only report having up to byteCount. -// directory[segmentCount] = minOf(offset, byteCount) -// directory[segmentCount + segments.size] = s.pos -// s.shared = true -// segmentCount++ -// s = s.next -// } -// @Suppress("UNCHECKED_CAST") -// return SegmentedByteString(segments as Array, directory) -//} - -internal inline fun Buffer.commonString(byteCount: Int): String { - if (byteCount == 0) return "" - checkOffsetAndCount(size, 0, byteCount.toLong()) +// TODO: optimize implementation +internal inline fun Buffer.commonString(): String { + if (size == 0L) return "[size=0]" - // Walk through the buffer to count how many segments we'll need. - var offset = 0 - var segmentCount = 0 - var s = head - while (offset < byteCount) { - if (s!!.limit == s.pos) { - throw AssertionError("s.limit == s.pos") // Empty segment. This should not happen! + val peekSrc = peek() + val data = if (peekSrc.request(128)) { + peekSrc.readByteArray(128) + } else { + peekSrc.readByteArray() + } + val i = codePointIndexToCharIndex(data, 64) + if (i == -1) { + return if (data.size <= 64) { + "[hex=${data.hex()}]" + } else { + "[size=${size} hex=${data.hex(64)}…]" } - offset += s.limit - s.pos - segmentCount++ - s = s.next } - // Walk through the buffer again to assign segments and build the directory. - val segments = arrayOfNulls(segmentCount) - val directory = IntArray(segmentCount * 2) - offset = 0 - segmentCount = 0 - s = head - while (offset < byteCount) { - segments[segmentCount] = s!!.data - offset += s.limit - s.pos - // Despite sharing more bytes, only report having up to byteCount. - directory[segmentCount] = minOf(offset, byteCount) - directory[segmentCount + segments.size] = s.pos - s.shared = true - segmentCount++ - s = s.next + val text = data.decodeToString() + .substring(0, i) + .replace("\\", "\\\\") + .replace("\n", "\\n") + .replace("\r", "\\r") + + return if (i < text.length) { + "[size=${data.size} text=$text…]" + } else { + "[text=$text]" } +} - return SegmentedByteString(segments as Array, directory).toString() +private fun ByteArray.hex(count: Int = this.size): String { + val builder = StringBuilder(count * 2) + forEach { + builder.append(HEX_DIGIT_CHARS[it.shr(4) and 0x0f]) + builder.append(HEX_DIGIT_CHARS[it and 0x0f]) + } + return builder.toString() +} + +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'.code && c != '\r'.code && isIsoControl(c)) || + c == REPLACEMENT_CODE_POINT + ) { + return -1 + } + + charCount += if (c < 0x10000) 1 else 2 + } + return charCount } internal inline fun forEachSegment( diff --git a/core/common/src/internal/-ByteString.kt b/core/common/src/internal/-ByteString.kt deleted file mode 100644 index 88a4c17ac..000000000 --- a/core/common/src/internal/-ByteString.kt +++ /dev/null @@ -1,349 +0,0 @@ -/* - * Copyright 2017-2023 JetBrains s.r.o. and respective authors and developers. - * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENCE file. - */ - -/* - * 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 kotlinx.io.internal - -import kotlinx.io.* -import kotlin.native.concurrent.* - - -@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) - -@SharedImmutable -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 result.concatToString() -} - -@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'.code.toByte() || c > 'Z'.code.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'.code.toByte() || c > 'Z'.code.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'.code.toByte() || c > 'z'.code.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'.code.toByte() || c > 'z'.code.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 { - val endIndex = resolveDefaultParameter(endIndex) - 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.commonCopyInto( - offset: Int, - target: ByteArray, - targetOffset: Int, - byteCount: Int -) { - data.copyInto(target, targetOffset, offset, offset + 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 fromIndex = resolveDefaultParameter(fromIndex) - 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 { - val byteCount = resolveDefaultParameter(byteCount) - 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'.code && c != '\r'.code && isIsoControl(c)) || - c == REPLACEMENT_CODE_POINT - ) { - return -1 - } - - charCount += if (c < 0x10000) 1 else 2 - } - return charCount -} diff --git a/core/common/src/internal/-RealBufferedSink.kt b/core/common/src/internal/-RealBufferedSink.kt index a6913143c..48b09c8ec 100644 --- a/core/common/src/internal/-RealBufferedSink.kt +++ b/core/common/src/internal/-RealBufferedSink.kt @@ -32,22 +32,6 @@ internal inline fun RealSink.commonWrite(source: Buffer, byteCount: Long) { emitCompleteSegments() } -//internal inline fun RealSink.commonWrite(byteString: ByteString): Sink { -// check(!closed) { "closed" } -// buffer.write(byteString) -// return emitCompleteSegments() -//} - -//internal inline fun RealSink.commonWrite( -// byteString: ByteString, -// offset: Int, -// byteCount: Int -//): Sink { -// check(!closed) { "closed" } -// buffer.write(byteString, offset, byteCount) -// return emitCompleteSegments() -//} - internal inline fun RealSink.commonWriteUtf8(string: String): Sink { check(!closed) { "closed" } buffer.writeUtf8(string) diff --git a/core/common/src/internal/-RealBufferedSource.kt b/core/common/src/internal/-RealBufferedSource.kt index 9f61738e2..5a3e9e0fd 100644 --- a/core/common/src/internal/-RealBufferedSource.kt +++ b/core/common/src/internal/-RealBufferedSource.kt @@ -62,39 +62,6 @@ internal inline fun RealSource.commonReadByte(): Byte { return buffer.readByte() } -//internal inline fun RealSource.commonReadByteString(): ByteString { -// buffer.writeAll(source) -// return buffer.readByteString() -//} -// -//internal inline fun RealSource.commonReadByteString(byteCount: Long): ByteString { -// require(byteCount) -// return buffer.readByteString(byteCount) -//} - -//internal inline fun RealSource.commonSelect(options: Options): Int { -// check(!closed) { "closed" } -// -// while (true) { -// val index = buffer.selectPrefix(options, selectTruncated = true) -// when (index) { -// -1 -> { -// return -1 -// } -// -2 -> { -// // We need to grow the buffer. Do that, then try it all again. -// if (source.read(buffer, Segment.SIZE.toLong()) == -1L) return -1 -// } -// else -> { -// // We matched a full byte string: consume it and return it. -// val selectedSize = options.byteStrings[index].size -// buffer.skip(selectedSize.toLong()) -// return index -// } -// } -// } -//} - internal inline fun RealSource.commonReadByteArray(): ByteArray { buffer.writeAll(source) return buffer.readByteArray() diff --git a/core/common/src/internal/-SegmentedByteString.kt b/core/common/src/internal/-SegmentedByteString.kt deleted file mode 100644 index 721350dde..000000000 --- a/core/common/src/internal/-SegmentedByteString.kt +++ /dev/null @@ -1,243 +0,0 @@ -/* - * Copyright 2017-2023 JetBrains s.r.o. and respective authors and developers. - * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENCE file. - */ - -/* - * Copyright (C) 2019 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. - */ - -// TODO move to SegmentedByteString class: https://youtrack.jetbrains.com/issue/KT-20427 -@file:Suppress("NOTHING_TO_INLINE") - -package kotlinx.io.internal - -import kotlinx.io.* - -internal fun IntArray.binarySearch(value: Int, fromIndex: Int, toIndex: Int): Int { - var left = fromIndex - var right = toIndex - 1 - - while (left <= right) { - val mid = (left + right) ushr 1 // protect from overflow - val midVal = this[mid] - - when { - midVal < value -> left = mid + 1 - midVal > value -> right = mid - 1 - else -> return mid - } - } - - // no exact match, return negative of where it should match - return -left - 1 -} - -/** Returns the index of the segment that contains the byte at `pos`. */ -internal fun SegmentedByteString.segment(pos: Int): Int { - // Search for (pos + 1) instead of (pos) because the directory holds sizes, not indexes. - val i = directory.binarySearch(pos + 1, 0, segments.size) - return if (i >= 0) i else i.inv() // If i is negative, bitflip to get the insert position. -} - -/** Processes all segments, invoking `action` with the ByteArray and range of valid data. */ -internal inline fun SegmentedByteString.forEachSegment( - action: (data: ByteArray, offset: Int, byteCount: Int) -> Unit -) { - val segmentCount = segments.size - var s = 0 - var pos = 0 - while (s < segmentCount) { - val segmentPos = directory[segmentCount + s] - val nextSegmentOffset = directory[s] - - action(segments[s], segmentPos, nextSegmentOffset - pos) - pos = nextSegmentOffset - s++ - } -} - -/** - * Processes the segments between `beginIndex` and `endIndex`, invoking `action` with the ByteArray - * and range of the valid data. - */ -private inline fun SegmentedByteString.forEachSegment( - beginIndex: Int, - endIndex: Int, - action: (data: ByteArray, offset: Int, byteCount: Int) -> Unit -) { - var s = segment(beginIndex) - var pos = beginIndex - while (pos < endIndex) { - val segmentOffset = if (s == 0) 0 else directory[s - 1] - val segmentSize = directory[s] - segmentOffset - val segmentPos = directory[segments.size + s] - - val byteCount = minOf(endIndex, segmentOffset + segmentSize) - pos - val offset = segmentPos + (pos - segmentOffset) - action(segments[s], offset, byteCount) - pos += byteCount - s++ - } -} - -// 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. - -internal inline fun SegmentedByteString.commonSubstring(beginIndex: Int, endIndex: Int): ByteString { - val endIndex = resolveDefaultParameter(endIndex) - require(beginIndex >= 0) { "beginIndex=$beginIndex < 0" } - require(endIndex <= size) { "endIndex=$endIndex > length($size)" } - - val subLen = endIndex - beginIndex - require(subLen >= 0) { "endIndex=$endIndex < beginIndex=$beginIndex" } - - when { - beginIndex == 0 && endIndex == size -> return this - beginIndex == endIndex -> return ByteString.EMPTY - } - - val beginSegment = segment(beginIndex) // First segment to include - val endSegment = segment(endIndex - 1) // Last segment to include - - val newSegments = segments.copyOfRange(beginSegment, endSegment + 1) - val newDirectory = IntArray(newSegments.size * 2) - var index = 0 - for (s in beginSegment..endSegment) { - newDirectory[index] = minOf(directory[s] - beginIndex, subLen) - newDirectory[index++ + newSegments.size] = directory[s + segments.size] - } - - // Set the new position of the first segment - val segmentOffset = if (beginSegment == 0) 0 else directory[beginSegment - 1] - newDirectory[newSegments.size] += beginIndex - segmentOffset - - return SegmentedByteString(newSegments, newDirectory) -} - -internal inline fun SegmentedByteString.commonInternalGet(pos: Int): Byte { - checkOffsetAndCount(directory[segments.size - 1].toLong(), pos.toLong(), 1) - val segment = segment(pos) - val segmentOffset = if (segment == 0) 0 else directory[segment - 1] - val segmentPos = directory[segment + segments.size] - return segments[segment][pos - segmentOffset + segmentPos] -} - -internal inline fun SegmentedByteString.commonGetSize() = directory[segments.size - 1] - -internal inline fun SegmentedByteString.commonToByteArray(): ByteArray { - val result = ByteArray(size) - var resultPos = 0 - forEachSegment { data, offset, byteCount -> - data.copyInto( - result, destinationOffset = resultPos, startIndex = offset, - endIndex = offset + byteCount - ) - resultPos += byteCount - } - return result -} - -internal inline fun SegmentedByteString.commonWrite(buffer: Buffer, offset: Int, byteCount: Int) { - forEachSegment(offset, offset + byteCount) { data, offset, byteCount -> - val segment = Segment(data, offset, offset + byteCount, true, false) - if (buffer.head == null) { - segment.prev = segment - segment.next = segment.prev - buffer.head = segment.next - } else { - buffer.head!!.prev!!.push(segment) - } - } - buffer.size += byteCount -} - -internal inline fun SegmentedByteString.commonRangeEquals( - offset: Int, - other: ByteString, - otherOffset: Int, - byteCount: Int -): Boolean { - if (offset < 0 || offset > size - byteCount) return false - // Go segment-by-segment through this, passing arrays to other's rangeEquals(). - var otherOffset = otherOffset - forEachSegment(offset, offset + byteCount) { data, offset, byteCount -> - if (!other.rangeEquals(otherOffset, data, offset, byteCount)) return false - otherOffset += byteCount - } - return true -} - -internal inline fun SegmentedByteString.commonRangeEquals( - offset: Int, - other: ByteArray, - otherOffset: Int, - byteCount: Int -): Boolean { - if (offset < 0 || offset > size - byteCount || - otherOffset < 0 || otherOffset > other.size - byteCount - ) { - return false - } - // Go segment-by-segment through this, comparing ranges of arrays. - var otherOffset = otherOffset - forEachSegment(offset, offset + byteCount) { data, offset, byteCount -> - if (!arrayRangeEquals(data, offset, other, otherOffset, byteCount)) return false - otherOffset += byteCount - } - return true -} - -internal inline fun SegmentedByteString.commonCopyInto( - offset: Int, - target: ByteArray, - targetOffset: Int, - byteCount: Int -) { - checkOffsetAndCount(size.toLong(), offset.toLong(), byteCount.toLong()) - checkOffsetAndCount(target.size.toLong(), targetOffset.toLong(), byteCount.toLong()) - // Go segment-by-segment through this, copying ranges of arrays. - var targetOffset = targetOffset - forEachSegment(offset, offset + byteCount) { data, offset, byteCount -> - data.copyInto(target, targetOffset, offset, offset + byteCount) - targetOffset += byteCount - } -} - -internal inline fun SegmentedByteString.commonEquals(other: Any?): Boolean { - return when { - other === this -> true - other is ByteString -> other.size == size && rangeEquals(0, other, 0, size) - else -> false - } -} - -internal inline fun SegmentedByteString.commonHashCode(): Int { - var result = hashCode - if (result != 0) return result - - // Equivalent to Arrays.hashCode(toByteArray()). - result = 1 - forEachSegment { data, offset, byteCount -> - var i = offset - val limit = offset + byteCount - while (i < limit) { - result = 31 * result + data[i] - i++ - } - } - hashCode = result - return result -} diff --git a/core/jvm/src/Buffer.kt b/core/jvm/src/Buffer.kt index c7f81e31c..3c7ae65d4 100644 --- a/core/jvm/src/Buffer.kt +++ b/core/jvm/src/Buffer.kt @@ -534,7 +534,7 @@ actual class Buffer : Source, Sink, Cloneable, ByteChannel { * Returns a human-readable string that describes the contents of this buffer. Typically this * is a string like `[text=Hello]` or `[hex=0000ffff]`. */ - override fun toString() = commonString(size.toInt()) + override fun toString() = commonString() actual fun copy(): Buffer = commonCopy() diff --git a/core/jvm/src/ByteString.kt b/core/jvm/src/ByteString.kt deleted file mode 100644 index c6f680b44..000000000 --- a/core/jvm/src/ByteString.kt +++ /dev/null @@ -1,341 +0,0 @@ -/* - * Copyright 2017-2023 JetBrains s.r.o. and respective authors and developers. - * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENCE file. - */ - -/* - * Copyright 2014 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 kotlinx.io - -import kotlinx.io.internal.* -import java.io.EOFException -import java.io.IOException -import java.io.InputStream -import java.io.ObjectInputStream -import java.io.ObjectOutputStream -import java.io.OutputStream -import java.io.Serializable -import java.nio.ByteBuffer -import java.nio.charset.Charset -import java.security.InvalidKeyException -import java.security.MessageDigest -import javax.crypto.Mac -import javax.crypto.spec.SecretKeySpec - -internal actual open class ByteString -internal actual constructor( - internal actual val data: ByteArray -) : Serializable, Comparable { - @Transient internal actual var hashCode: Int = 0 // Lazily computed; 0 if unknown. - @Transient internal actual var utf8: String? = null // Lazily computed. - - actual open fun utf8(): String = commonUtf8() - - /** Constructs a new `String` by decoding the bytes using `charset`. */ - open fun string(charset: Charset) = String(data, charset) - - actual open fun base64() = commonBase64() - -// actual fun md5() = digest("MD5") -// -// actual fun sha1() = digest("SHA-1") -// -// actual fun sha256() = digest("SHA-256") -// -// actual fun sha512() = digest("SHA-512") -// -// internal open fun digest(algorithm: String): ByteString { -// val digestBytes = MessageDigest.getInstance(algorithm).run { -// update(data, 0, size) -// digest() -// } -// return ByteString(digestBytes) -// } -// -// /** Returns the 160-bit SHA-1 HMAC of this byte string. */ -// actual open fun hmacSha1(key: ByteString) = hmac("HmacSHA1", key) -// -// /** Returns the 256-bit SHA-256 HMAC of this byte string. */ -// actual open fun hmacSha256(key: ByteString) = hmac("HmacSHA256", key) -// -// /** Returns the 512-bit SHA-512 HMAC of this byte string. */ -// actual open fun hmacSha512(key: ByteString) = hmac("HmacSHA512", key) -// -// internal open fun hmac(algorithm: String, key: ByteString): ByteString { -// try { -// val mac = Mac.getInstance(algorithm) -// mac.init(SecretKeySpec(key.toByteArray(), algorithm)) -// return ByteString(mac.doFinal(data)) -// } catch (e: InvalidKeyException) { -// throw IllegalArgumentException(e) -// } -// } - - actual open fun base64Url() = commonBase64Url() - - actual open fun hex(): String = commonHex() - - actual open fun toAsciiLowercase(): ByteString = commonToAsciiLowercase() - - actual open fun toAsciiUppercase(): ByteString = commonToAsciiUppercase() - - @JvmOverloads - actual open fun substring(beginIndex: Int, endIndex: Int): ByteString = - commonSubstring(beginIndex, endIndex) - - internal actual open fun internalGet(pos: Int) = commonGetByte(pos) - - @JvmName("getByte") - actual operator fun get(index: Int): Byte = internalGet(index) - - actual val size - @JvmName("size") get() = getSize() - - internal actual open fun getSize() = commonGetSize() - - actual open fun toByteArray() = commonToByteArray() - - internal actual open fun internalArray() = commonInternalArray() - - /** Returns a `ByteBuffer` view of the bytes in this `ByteString`. */ - open fun asByteBuffer(): ByteBuffer = ByteBuffer.wrap(data).asReadOnlyBuffer() - - /** Writes the contents of this byte string to `out`. */ - @Throws(IOException::class) - open fun write(out: OutputStream) { - out.write(data) - } - - internal actual open fun write(buffer: Buffer, offset: Int, byteCount: Int) = - commonWrite(buffer, offset, byteCount) - - actual open fun rangeEquals( - offset: Int, - other: ByteString, - otherOffset: Int, - byteCount: Int - ): Boolean = commonRangeEquals(offset, other, otherOffset, byteCount) - - actual open fun rangeEquals( - offset: Int, - other: ByteArray, - otherOffset: Int, - byteCount: Int - ): Boolean = commonRangeEquals(offset, other, otherOffset, byteCount) - - actual open fun copyInto( - offset: Int, - target: ByteArray, - targetOffset: Int, - byteCount: Int - ) = commonCopyInto(offset, target, targetOffset, byteCount) - - actual fun startsWith(prefix: ByteString) = commonStartsWith(prefix) - - actual fun startsWith(prefix: ByteArray) = commonStartsWith(prefix) - - actual fun endsWith(suffix: ByteString) = commonEndsWith(suffix) - - actual fun endsWith(suffix: ByteArray) = commonEndsWith(suffix) - - @JvmOverloads - actual fun indexOf(other: ByteString, fromIndex: Int) = indexOf(other.internalArray(), fromIndex) - - @JvmOverloads - actual open fun indexOf(other: ByteArray, fromIndex: Int) = commonIndexOf(other, fromIndex) - - @JvmOverloads - actual fun lastIndexOf(other: ByteString, fromIndex: Int) = commonLastIndexOf(other, fromIndex) - - @JvmOverloads - actual open fun lastIndexOf(other: ByteArray, fromIndex: Int) = commonLastIndexOf(other, fromIndex) - - actual override fun equals(other: Any?) = commonEquals(other) - - actual override fun hashCode() = commonHashCode() - - actual override fun compareTo(other: ByteString) = commonCompareTo(other) - - actual override fun toString() = commonToString() - - @Throws(IOException::class) - private fun readObject(`in`: ObjectInputStream) { - val dataLength = `in`.readInt() - val byteString = `in`.readByteString(dataLength) - val field = ByteString::class.java.getDeclaredField("data") - field.isAccessible = true - field.set(this, byteString.data) - } - - @Throws(IOException::class) - private fun writeObject(out: ObjectOutputStream) { - out.writeInt(data.size) - out.write(data) - } - - @JvmName("-deprecated_getByte") - @Deprecated( - message = "moved to operator function", - replaceWith = ReplaceWith(expression = "this[index]"), - level = DeprecationLevel.ERROR - ) - fun getByte(index: Int) = this[index] - - @JvmName("-deprecated_size") - @Deprecated( - message = "moved to val", - replaceWith = ReplaceWith(expression = "size"), - level = DeprecationLevel.ERROR - ) - fun size() = size - - actual companion object { - private const val serialVersionUID = 1L - - @JvmField - actual val EMPTY: ByteString = ByteString(byteArrayOf()) - - @JvmStatic - actual fun of(vararg data: Byte) = commonOf(data) - - @JvmStatic - @JvmName("of") - actual fun ByteArray.toByteString(offset: Int, byteCount: Int): ByteString = - commonToByteString(offset, byteCount) - - /** Returns a [ByteString] containing a copy of this [ByteBuffer]. */ - @JvmStatic - @JvmName("of") - fun ByteBuffer.toByteString(): ByteString { - val copy = ByteArray(remaining()) - get(copy) - return ByteString(copy) - } - - @JvmStatic - actual fun String.encodeUtf8(): ByteString = commonEncodeUtf8() - - /** Returns a new [ByteString] containing the `charset`-encoded bytes of this [String]. */ - @JvmStatic - @JvmName("encodeString") - fun String.encode(charset: Charset = Charsets.UTF_8) = ByteString(toByteArray(charset)) - - @JvmStatic - actual fun String.decodeBase64() = commonDecodeBase64() - - @JvmStatic - actual fun String.decodeHex() = commonDecodeHex() - - /** - * Reads `count` bytes from this [InputStream] and returns the result. - * - * @throws java.io.EOFException if `in` has fewer than `count` bytes to read. - */ - @Throws(IOException::class) - @JvmStatic - @JvmName("read") - fun InputStream.readByteString(byteCount: Int): ByteString { - require(byteCount >= 0) { "byteCount < 0: $byteCount" } - - val result = ByteArray(byteCount) - var offset = 0 - var read: Int - while (offset < byteCount) { - read = read(result, offset, byteCount - offset) - if (read == -1) throw EOFException() - offset += read - } - return ByteString(result) - } - - @JvmName("-deprecated_decodeBase64") - @Deprecated( - message = "moved to extension function", - replaceWith = ReplaceWith( - expression = "string.decodeBase64()", - imports = ["okio.ByteString.Companion.decodeBase64"] - ), - level = DeprecationLevel.ERROR - ) - fun decodeBase64(string: String) = string.decodeBase64() - - @JvmName("-deprecated_decodeHex") - @Deprecated( - message = "moved to extension function", - replaceWith = ReplaceWith( - expression = "string.decodeHex()", - imports = ["okio.ByteString.Companion.decodeHex"] - ), - level = DeprecationLevel.ERROR - ) - fun decodeHex(string: String) = string.decodeHex() - - @JvmName("-deprecated_encodeString") - @Deprecated( - message = "moved to extension function", - replaceWith = ReplaceWith( - expression = "string.encode(charset)", - imports = ["okio.ByteString.Companion.encode"] - ), - level = DeprecationLevel.ERROR - ) - fun encodeString(string: String, charset: Charset) = string.encode(charset) - - @JvmName("-deprecated_encodeUtf8") - @Deprecated( - message = "moved to extension function", - replaceWith = ReplaceWith( - expression = "string.encodeUtf8()", - imports = ["okio.ByteString.Companion.encodeUtf8"] - ), - level = DeprecationLevel.ERROR - ) - fun encodeUtf8(string: String) = string.encodeUtf8() - - @JvmName("-deprecated_of") - @Deprecated( - message = "moved to extension function", - replaceWith = ReplaceWith( - expression = "buffer.toByteString()", - imports = ["okio.ByteString.Companion.toByteString"] - ), - level = DeprecationLevel.ERROR - ) - fun of(buffer: ByteBuffer) = buffer.toByteString() - - @JvmName("-deprecated_of") - @Deprecated( - message = "moved to extension function", - replaceWith = ReplaceWith( - expression = "array.toByteString(offset, byteCount)", - imports = ["okio.ByteString.Companion.toByteString"] - ), - level = DeprecationLevel.ERROR - ) - fun of(array: ByteArray, offset: Int, byteCount: Int) = array.toByteString(offset, byteCount) - - @JvmName("-deprecated_read") - @Deprecated( - message = "moved to extension function", - replaceWith = ReplaceWith( - expression = "inputstream.readByteString(byteCount)", - imports = ["okio.ByteString.Companion.readByteString"] - ), - level = DeprecationLevel.ERROR - ) - fun read(inputstream: InputStream, byteCount: Int) = inputstream.readByteString(byteCount) - } -} diff --git a/core/jvm/src/RealSink.kt b/core/jvm/src/RealSink.kt index 3e612236f..c6a951e52 100644 --- a/core/jvm/src/RealSink.kt +++ b/core/jvm/src/RealSink.kt @@ -39,9 +39,6 @@ internal actual class RealSink actual constructor( override fun buffer() = bufferField override fun write(source: Buffer, byteCount: Long) = commonWrite(source, byteCount) -// override fun write(byteString: ByteString) = commonWrite(byteString) -// override fun write(byteString: ByteString, offset: Int, byteCount: Int) = -// commonWrite(byteString, offset, byteCount) override fun writeUtf8(string: String) = commonWriteUtf8(string) override fun writeUtf8(string: String, beginIndex: Int, endIndex: Int) = commonWriteUtf8(string, beginIndex, endIndex) diff --git a/core/jvm/src/RealSource.kt b/core/jvm/src/RealSource.kt index 931e68b67..7d781f719 100644 --- a/core/jvm/src/RealSource.kt +++ b/core/jvm/src/RealSource.kt @@ -43,9 +43,6 @@ internal actual class RealSource actual constructor( override fun require(byteCount: Long): Unit = commonRequire(byteCount) override fun request(byteCount: Long): Boolean = commonRequest(byteCount) override fun readByte(): Byte = commonReadByte() -// override fun readByteString(): ByteString = commonReadByteString() -// override fun readByteString(byteCount: Long): ByteString = commonReadByteString(byteCount) -// override fun select(options: Options): Int = commonSelect(options) override fun readByteArray(): ByteArray = commonReadByteArray() override fun readByteArray(byteCount: Long): ByteArray = commonReadByteArray(byteCount) override fun read(sink: ByteArray): Int = read(sink, 0, sink.size) diff --git a/core/jvm/src/SegmentedByteString.kt b/core/jvm/src/SegmentedByteString.kt deleted file mode 100644 index 1ddd236f6..000000000 --- a/core/jvm/src/SegmentedByteString.kt +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright 2017-2023 JetBrains s.r.o. and respective authors and developers. - * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENCE file. - */ - -/* - * Copyright (C) 2015 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 kotlinx.io - -import kotlinx.io.internal.* -import java.io.IOException -import java.io.OutputStream -import java.nio.ByteBuffer -import java.nio.charset.Charset -import java.security.InvalidKeyException -import java.security.MessageDigest -import javax.crypto.Mac -import javax.crypto.spec.SecretKeySpec - -internal actual class SegmentedByteString internal actual constructor( - @Transient internal actual val segments: Array, - @Transient internal actual val directory: IntArray -) : ByteString(EMPTY.data) { - - override fun string(charset: Charset) = toByteString().string(charset) - - override fun base64() = toByteString().base64() - - override fun hex() = toByteString().hex() - - override fun toAsciiLowercase() = toByteString().toAsciiLowercase() - - override fun toAsciiUppercase() = toByteString().toAsciiUppercase() - -// override fun digest(algorithm: String): ByteString { -// val digestBytes = MessageDigest.getInstance(algorithm).run { -// forEachSegment { data, offset, byteCount -> -// update(data, offset, byteCount) -// } -// digest() -// } -// return ByteString(digestBytes) -// } -// -// override fun hmac(algorithm: String, key: ByteString): ByteString { -// try { -// val mac = Mac.getInstance(algorithm) -// mac.init(SecretKeySpec(key.toByteArray(), algorithm)) -// forEachSegment { data, offset, byteCount -> -// mac.update(data, offset, byteCount) -// } -// return ByteString(mac.doFinal()) -// } catch (e: InvalidKeyException) { -// throw IllegalArgumentException(e) -// } -// } - - override fun base64Url() = toByteString().base64Url() - - override fun substring(beginIndex: Int, endIndex: Int): ByteString = - commonSubstring(beginIndex, endIndex) - - override fun internalGet(pos: Int): Byte = commonInternalGet(pos) - - override fun getSize() = commonGetSize() - - override fun toByteArray(): ByteArray = commonToByteArray() - - override fun asByteBuffer(): ByteBuffer = ByteBuffer.wrap(toByteArray()).asReadOnlyBuffer() - - @Throws(IOException::class) - override fun write(out: OutputStream) { - forEachSegment { data, offset, byteCount -> - out.write(data, offset, byteCount) - } - } - - override fun write(buffer: Buffer, offset: Int, byteCount: Int): Unit = - commonWrite(buffer, offset, byteCount) - - override fun rangeEquals( - offset: Int, - other: ByteString, - otherOffset: Int, - byteCount: Int - ): Boolean = commonRangeEquals(offset, other, otherOffset, byteCount) - - override fun rangeEquals( - offset: Int, - other: ByteArray, - otherOffset: Int, - byteCount: Int - ): Boolean = commonRangeEquals(offset, other, otherOffset, byteCount) - - override fun copyInto( - offset: Int, - target: ByteArray, - targetOffset: Int, - byteCount: Int - ) = commonCopyInto(offset, target, targetOffset, byteCount) - - override fun indexOf(other: ByteArray, fromIndex: Int) = toByteString().indexOf(other, fromIndex) - - override fun lastIndexOf(other: ByteArray, fromIndex: Int) = toByteString().lastIndexOf( - other, - fromIndex - ) - - /** Returns a copy as a non-segmented byte string. */ - private fun toByteString() = ByteString(toByteArray()) - - override fun internalArray() = toByteArray() - - override fun equals(other: Any?): Boolean = commonEquals(other) - - override fun hashCode(): Int = commonHashCode() - - override fun toString() = toByteString().toString() - - @Suppress("unused", "PLATFORM_CLASS_MAPPED_TO_KOTLIN") // For Java Serialization. - private fun writeReplace(): Object = toByteString() as Object -} diff --git a/core/jvm/src/Sink.kt b/core/jvm/src/Sink.kt index 87ca2501d..15abcf7c5 100644 --- a/core/jvm/src/Sink.kt +++ b/core/jvm/src/Sink.kt @@ -36,12 +36,6 @@ actual sealed interface Sink : RawSink, WritableByteChannel { actual val buffer: Buffer -// @Throws(IOException::class) -// actual fun write(byteString: ByteString): Sink -// -// @Throws(IOException::class) -// actual fun write(byteString: ByteString, offset: Int, byteCount: Int): Sink - @Throws(IOException::class) actual fun write(source: ByteArray): Sink diff --git a/core/jvm/src/Source.kt b/core/jvm/src/Source.kt index f5d738f86..7e95d709b 100644 --- a/core/jvm/src/Source.kt +++ b/core/jvm/src/Source.kt @@ -75,15 +75,6 @@ actual sealed interface Source : RawSource, ReadableByteChannel { @Throws(IOException::class) actual fun skip(byteCount: Long) -// @Throws(IOException::class) -// actual fun readByteString(): ByteString -// -// @Throws(IOException::class) -// actual fun readByteString(byteCount: Long): ByteString -// -// @Throws(IOException::class) -// actual fun select(options: Options): Int - @Throws(IOException::class) actual fun readByteArray(): ByteArray diff --git a/core/native/src/Buffer.kt b/core/native/src/Buffer.kt index 1e4d13472..dd189cf53 100644 --- a/core/native/src/Buffer.kt +++ b/core/native/src/Buffer.kt @@ -77,10 +77,6 @@ actual class Buffer : Source, Sink { override fun readHexadecimalUnsignedLong(): Long = commonReadHexadecimalUnsignedLong() -// override fun readByteString(): ByteString = commonReadByteString() -// -// override fun readByteString(byteCount: Long): ByteString = commonReadByteString(byteCount) - override fun readFully(sink: Buffer, byteCount: Long): Unit = commonReadFully(sink, byteCount) override fun readAll(sink: RawSink): Long = commonReadAll(sink) @@ -97,8 +93,6 @@ actual class Buffer : Source, Sink { override fun readUtf8CodePoint(): Int = commonReadUtf8CodePoint() -// override fun select(options: Options): Int = commonSelect(options) - override fun readByteArray(): ByteArray = commonReadByteArray() override fun readByteArray(byteCount: Long): ByteArray = commonReadByteArray(byteCount) @@ -114,11 +108,6 @@ actual class Buffer : Source, Sink { actual override fun skip(byteCount: Long): Unit = commonSkip(byteCount) -// actual override fun write(byteString: ByteString): Buffer = commonWrite(byteString) -// -// actual override fun write(byteString: ByteString, offset: Int, byteCount: Int) = -// commonWrite(byteString, offset, byteCount) - internal actual fun writableSegment(minimumCapacity: Int): Segment = commonWritableSegment(minimumCapacity) @@ -205,49 +194,10 @@ actual class Buffer : Source, Sink { * Returns a human-readable string that describes the contents of this buffer. Typically this * is a string like `[text=Hello]` or `[hex=0000ffff]`. */ - override fun toString() = commonString(size.toInt()) + override fun toString() = commonString() actual fun copy(): Buffer = commonCopy() -// actual fun snapshot(): ByteString = commonSnapshot() -// -// actual fun snapshot(byteCount: Int): ByteString = commonSnapshot(byteCount) -// -// actual fun md5() = digest(Md5()) -// -// actual fun sha1() = digest(Sha1()) -// -// actual fun sha256() = digest(Sha256()) -// -// actual fun sha512() = digest(Sha512()) -// -// /** Returns the 160-bit SHA-1 HMAC of this buffer. */ -// actual fun hmacSha1(key: ByteString) = digest(Hmac.sha1(key)) -// -// /** Returns the 256-bit SHA-256 HMAC of this buffer. */ -// actual fun hmacSha256(key: ByteString) = digest(Hmac.sha256(key)) -// -// /** Returns the 512-bit SHA-512 HMAC of this buffer. */ -// actual fun hmacSha512(key: ByteString) = digest(Hmac.sha512(key)) -// -// private fun digest(hash: HashFunction): ByteString { -// forEachSegment { segment -> -// hash.update(segment.data, segment.pos, segment.limit - segment.pos) -// } -// -// return ByteString(hash.digest()) -// } - - private fun forEachSegment(action: (Segment) -> Unit) { - head?.let { head -> - var segment: Segment? = head - do { - segment?.let(action) - segment = segment?.next - } while (segment !== head) - } - } - // actual fun readUnsafe(unsafeCursor: UnsafeCursor): UnsafeCursor = commonReadUnsafe(unsafeCursor) // // actual fun readAndWriteUnsafe(unsafeCursor: UnsafeCursor): UnsafeCursor = diff --git a/core/native/src/ByteString.kt b/core/native/src/ByteString.kt deleted file mode 100644 index 23863efe6..000000000 --- a/core/native/src/ByteString.kt +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Copyright 2017-2023 JetBrains s.r.o. and respective authors and developers. - * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENCE file. - */ - -/* - * 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 kotlinx.io - -import kotlinx.io.internal.* - -internal actual open class ByteString -internal actual constructor( - internal actual val data: ByteArray -) : Comparable { - @Suppress("SetterBackingFieldAssignment") - internal actual var hashCode: Int = 0 // 0 if unknown. - set(value) { - // Do nothing to avoid IllegalImmutabilityException. - } - @Suppress("SetterBackingFieldAssignment") - internal actual var utf8: String? = null - set(value) { - // Do nothing to avoid IllegalImmutabilityException. - } - - actual open fun utf8(): String = commonUtf8() - - actual open fun base64(): String = commonBase64() - - actual open fun base64Url(): String = commonBase64Url() - - actual open fun hex(): String = commonHex() - -// actual fun md5() = digest(Md5()) -// -// actual fun sha1() = digest(Sha1()) -// -// actual fun sha256() = digest(Sha256()) -// -// actual fun sha512() = digest(Sha512()) -// -// /** Returns the 160-bit SHA-1 HMAC of this byte string. */ -// actual fun hmacSha1(key: ByteString) = digest(Hmac.sha1(key)) -// -// /** Returns the 256-bit SHA-256 HMAC of this byte string. */ -// actual fun hmacSha256(key: ByteString) = digest(Hmac.sha256(key)) -// -// /** Returns the 512-bit SHA-512 HMAC of this byte string. */ -// actual fun hmacSha512(key: ByteString) = digest(Hmac.sha512(key)) -// -// internal open fun digest(hashFunction: HashFunction): ByteString { -// hashFunction.update(data, 0, size) -// val digestBytes = hashFunction.digest() -// return ByteString(digestBytes) -// } - - actual open fun toAsciiLowercase(): ByteString = commonToAsciiLowercase() - - actual open fun toAsciiUppercase(): ByteString = commonToAsciiUppercase() - - actual open fun substring(beginIndex: Int, endIndex: Int): ByteString = - commonSubstring(beginIndex, endIndex) - - internal actual open fun internalGet(pos: Int): Byte { - if (pos >= size || pos < 0) throw ArrayIndexOutOfBoundsException("size=$size pos=$pos") - return commonGetByte(pos) - } - - actual operator fun get(index: Int): Byte = internalGet(index) - - actual val size - get() = getSize() - - internal actual open fun getSize() = commonGetSize() - - actual open fun toByteArray() = commonToByteArray() - - internal actual open fun internalArray() = commonInternalArray() - - internal actual open fun write(buffer: Buffer, offset: Int, byteCount: Int) = - commonWrite(buffer, offset, byteCount) - - actual open fun rangeEquals( - offset: Int, - other: ByteString, - otherOffset: Int, - byteCount: Int - ): Boolean = commonRangeEquals(offset, other, otherOffset, byteCount) - - actual open fun rangeEquals( - offset: Int, - other: ByteArray, - otherOffset: Int, - byteCount: Int - ): Boolean = commonRangeEquals(offset, other, otherOffset, byteCount) - - actual open fun copyInto( - offset: Int, - target: ByteArray, - targetOffset: Int, - byteCount: Int - ) = commonCopyInto(offset, target, targetOffset, byteCount) - - actual fun startsWith(prefix: ByteString) = commonStartsWith(prefix) - - actual fun startsWith(prefix: ByteArray) = commonStartsWith(prefix) - - actual fun endsWith(suffix: ByteString) = commonEndsWith(suffix) - - actual fun endsWith(suffix: ByteArray) = commonEndsWith(suffix) - - actual fun indexOf(other: ByteString, fromIndex: Int) = indexOf(other.internalArray(), fromIndex) - - actual open fun indexOf(other: ByteArray, fromIndex: Int) = commonIndexOf(other, fromIndex) - - actual fun lastIndexOf(other: ByteString, fromIndex: Int) = commonLastIndexOf(other, fromIndex) - - actual open fun lastIndexOf(other: ByteArray, fromIndex: Int) = commonLastIndexOf(other, fromIndex) - - actual override fun equals(other: Any?) = commonEquals(other) - - actual override fun hashCode() = commonHashCode() - - actual override fun compareTo(other: ByteString) = commonCompareTo(other) - - /** - * Returns a human-readable string that describes the contents of this byte string. Typically this - * is a string like `[text=Hello]` or `[hex=0000ffff]`. - */ - actual override fun toString() = commonToString() - - actual companion object { - actual val EMPTY: ByteString = ByteString(byteArrayOf()) - - actual fun of(vararg data: Byte) = commonOf(data) - - actual fun ByteArray.toByteString(offset: Int, byteCount: Int): ByteString = - commonToByteString(offset, byteCount) - - actual fun String.encodeUtf8(): ByteString = commonEncodeUtf8() - - actual fun String.decodeBase64(): ByteString? = commonDecodeBase64() - - actual fun String.decodeHex() = commonDecodeHex() - } -} diff --git a/core/native/src/SegmentedByteString.kt b/core/native/src/SegmentedByteString.kt deleted file mode 100644 index e717d4e26..000000000 --- a/core/native/src/SegmentedByteString.kt +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright 2017-2023 JetBrains s.r.o. and respective authors and developers. - * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENCE file. - */ - -/* - * Copyright (C) 2015 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 kotlinx.io - -import kotlinx.io.internal.* - -internal actual class SegmentedByteString internal actual constructor( - internal actual val segments: Array, - internal actual val directory: IntArray -) : ByteString(EMPTY.data) { - - override fun base64() = toByteString().base64() - - override fun hex() = toByteString().hex() - - override fun toAsciiLowercase() = toByteString().toAsciiLowercase() - - override fun toAsciiUppercase() = toByteString().toAsciiUppercase() - - override fun base64Url() = toByteString().base64Url() - - override fun substring(beginIndex: Int, endIndex: Int): ByteString = - commonSubstring(beginIndex, endIndex) - - override fun internalGet(pos: Int): Byte = commonInternalGet(pos) - - override fun getSize() = commonGetSize() - - override fun toByteArray(): ByteArray = commonToByteArray() - - override fun write(buffer: Buffer, offset: Int, byteCount: Int): Unit = - commonWrite(buffer, offset, byteCount) - - override fun rangeEquals( - offset: Int, - other: ByteString, - otherOffset: Int, - byteCount: Int - ): Boolean = commonRangeEquals(offset, other, otherOffset, byteCount) - - override fun rangeEquals( - offset: Int, - other: ByteArray, - otherOffset: Int, - byteCount: Int - ): Boolean = commonRangeEquals(offset, other, otherOffset, byteCount) - - override fun copyInto( - offset: Int, - target: ByteArray, - targetOffset: Int, - byteCount: Int - ) = commonCopyInto(offset, target, targetOffset, byteCount) - - override fun indexOf(other: ByteArray, fromIndex: Int) = toByteString().indexOf(other, fromIndex) - - override fun lastIndexOf(other: ByteArray, fromIndex: Int) = toByteString().lastIndexOf( - other, - fromIndex - ) - -// override fun digest(hashFunction: HashFunction): ByteString { -// forEachSegment { data, offset, byteCount -> -// hashFunction.update(data, offset, byteCount) -// } -// val digestBytes = hashFunction.digest() -// return ByteString(digestBytes) -// } - - /** Returns a copy as a non-segmented byte string. */ - private fun toByteString() = ByteString(toByteArray()) - - override fun internalArray() = toByteArray() - - override fun equals(other: Any?): Boolean = commonEquals(other) - - override fun hashCode(): Int = commonHashCode() - - override fun toString() = toByteString().toString() -} From 87a44b205890765853a9adc78238dd637d8eb062 Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Fri, 26 May 2023 13:33:12 +0200 Subject: [PATCH 02/83] First round of the refactoring - Removed byte strings and ByteString related APIs - Moved some methods that were previously implemented as member functions to extensions, provided basic implementation - Get rid of hash functions and select API - Get rid of unsafe cursors - Removed a lot of commented out code - Enabled some previously disabled tests - Added tests covering line reading --- core/api/kotlinx-io-core.api | 90 +-- core/common/src/-CommonPlatform.kt | 6 - core/common/src/-Util.kt | 7 - core/common/src/Buffer.kt | 280 --------- core/common/src/Sink.kt | 109 +--- core/common/src/SinkExt.kt | 118 ++++ core/common/src/Source.kt | 280 +-------- core/common/src/SourceExt.kt | 413 +++++++++++++ core/common/src/internal/-Buffer.kt | 408 +------------ .../src/internal/-RealBufferedSource.kt | 56 -- ...ufferedSinkTest.kt => AbstractSinkTest.kt} | 44 +- ...redSourceTest.kt => AbstractSourceTest.kt} | 412 +++---------- core/common/test/BufferCommonTest.kt | 51 -- core/common/test/ByteStringFactory.kt | 56 -- core/common/test/ByteStringMoreTests.kt | 33 - core/common/test/ByteStringTest.kt | 570 ------------------ ...feredSinkTest.kt => CommonRealSinkTest.kt} | 2 +- ...dSourceTest.kt => CommonRealSourceTest.kt} | 2 +- core/common/test/FakeFileSystemTest.kt | 497 --------------- core/common/test/ForwardingFileSystemTest.kt | 173 ------ core/common/test/ForwardingSourceTest.kt | 58 -- core/common/test/HashingSinkTest.kt | 119 ---- core/common/test/HashingSourceTest.kt | 127 ---- core/common/test/HashingTest.kt | 146 ----- ...{BufferedSinkFactory.kt => SinkFactory.kt} | 6 +- ...feredSourceFactory.kt => SourceFactory.kt} | 22 +- core/common/test/TestingSupport.kt | 12 +- core/common/test/UnsafeCursorTest.kt | 92 --- core/common/test/Utf8KotlinTest.kt | 264 ++++---- core/common/test/util.kt | 25 +- core/jvm/src/-JvmPlatform.kt | 17 - core/jvm/src/Buffer.kt | 178 ------ core/jvm/src/RealSink.kt | 7 - core/jvm/src/RealSource.kt | 31 - core/jvm/src/Sink.kt | 21 - core/jvm/src/Source.kt | 54 -- core/native/src/-NonJvmPlatform.kt | 6 - core/native/src/Buffer.kt | 87 --- core/native/src/RealSink.kt | 11 +- core/native/src/RealSource.kt | 34 -- core/native/src/Sink.kt | 18 - core/native/src/Source.kt | 42 -- core/native/src/files/PathsNative.kt | 4 +- 43 files changed, 836 insertions(+), 4152 deletions(-) create mode 100644 core/common/src/SinkExt.kt create mode 100644 core/common/src/SourceExt.kt rename core/common/test/{AbstractBufferedSinkTest.kt => AbstractSinkTest.kt} (85%) rename core/common/test/{AbstractBufferedSourceTest.kt => AbstractSourceTest.kt} (68%) delete mode 100644 core/common/test/ByteStringFactory.kt delete mode 100644 core/common/test/ByteStringMoreTests.kt delete mode 100644 core/common/test/ByteStringTest.kt rename core/common/test/{CommonRealBufferedSinkTest.kt => CommonRealSinkTest.kt} (99%) rename core/common/test/{CommonRealBufferedSourceTest.kt => CommonRealSourceTest.kt} (99%) delete mode 100644 core/common/test/FakeFileSystemTest.kt delete mode 100644 core/common/test/ForwardingFileSystemTest.kt delete mode 100644 core/common/test/ForwardingSourceTest.kt delete mode 100644 core/common/test/HashingSinkTest.kt delete mode 100644 core/common/test/HashingSourceTest.kt delete mode 100644 core/common/test/HashingTest.kt rename core/common/test/{BufferedSinkFactory.kt => SinkFactory.kt} (84%) rename core/common/test/{BufferedSourceFactory.kt => SourceFactory.kt} (86%) delete mode 100644 core/common/test/UnsafeCursorTest.kt diff --git a/core/api/kotlinx-io-core.api b/core/api/kotlinx-io-core.api index bad5921b6..21d6ddde6 100644 --- a/core/api/kotlinx-io-core.api +++ b/core/api/kotlinx-io-core.api @@ -26,41 +26,29 @@ public final class kotlinx/io/Buffer : java/lang/Cloneable, java/nio/channels/By public fun getBuffer ()Lkotlinx/io/Buffer; public final fun getByte (J)B public fun hashCode ()I - public fun indexOf (B)J - public fun indexOf (BJ)J - public fun indexOf (BJJ)J public fun inputStream ()Ljava/io/InputStream; public fun isOpen ()Z public fun outputStream ()Ljava/io/OutputStream; public fun peek ()Lkotlinx/io/Source; public fun read (Ljava/nio/ByteBuffer;)I public fun read (Lkotlinx/io/Buffer;J)J - public fun read ([B)I public fun read ([BII)I public fun readAll (Lkotlinx/io/RawSink;)J public fun readByte ()B public fun readByteArray ()[B public fun readByteArray (J)[B - public fun readDecimalLong ()J public final fun readFrom (Ljava/io/InputStream;)Lkotlinx/io/Buffer; public final fun readFrom (Ljava/io/InputStream;J)Lkotlinx/io/Buffer; public fun readFully (Lkotlinx/io/Buffer;J)V public fun readFully ([B)V - public fun readHexadecimalUnsignedLong ()J public fun readInt ()I - public fun readIntLe ()I public fun readLong ()J - public fun readLongLe ()J public fun readShort ()S - public fun readShortLe ()S public fun readString (JLjava/nio/charset/Charset;)Ljava/lang/String; public fun readString (Ljava/nio/charset/Charset;)Ljava/lang/String; public fun readUtf8 ()Ljava/lang/String; public fun readUtf8 (J)Ljava/lang/String; public fun readUtf8CodePoint ()I - public fun readUtf8Line ()Ljava/lang/String; - public fun readUtf8LineStrict ()Ljava/lang/String; - public fun readUtf8LineStrict (J)Ljava/lang/String; public fun request (J)Z public fun require (J)V public final fun size ()J @@ -70,29 +58,17 @@ public final class kotlinx/io/Buffer : java/lang/Cloneable, java/nio/channels/By public fun write (Lkotlinx/io/Buffer;J)V public fun write (Lkotlinx/io/RawSource;J)Lkotlinx/io/Buffer; public synthetic fun write (Lkotlinx/io/RawSource;J)Lkotlinx/io/Sink; - public fun write ([B)Lkotlinx/io/Buffer; - public synthetic fun write ([B)Lkotlinx/io/Sink; public fun write ([BII)Lkotlinx/io/Buffer; public synthetic fun write ([BII)Lkotlinx/io/Sink; public fun writeAll (Lkotlinx/io/RawSource;)J public fun writeByte (I)Lkotlinx/io/Buffer; public synthetic fun writeByte (I)Lkotlinx/io/Sink; - public fun writeDecimalLong (J)Lkotlinx/io/Buffer; - public synthetic fun writeDecimalLong (J)Lkotlinx/io/Sink; - public fun writeHexadecimalUnsignedLong (J)Lkotlinx/io/Buffer; - public synthetic fun writeHexadecimalUnsignedLong (J)Lkotlinx/io/Sink; public fun writeInt (I)Lkotlinx/io/Buffer; public synthetic fun writeInt (I)Lkotlinx/io/Sink; - public fun writeIntLe (I)Lkotlinx/io/Buffer; - public synthetic fun writeIntLe (I)Lkotlinx/io/Sink; public fun writeLong (J)Lkotlinx/io/Buffer; public synthetic fun writeLong (J)Lkotlinx/io/Sink; - public fun writeLongLe (J)Lkotlinx/io/Buffer; - public synthetic fun writeLongLe (J)Lkotlinx/io/Sink; public fun writeShort (I)Lkotlinx/io/Buffer; public synthetic fun writeShort (I)Lkotlinx/io/Sink; - public fun writeShortLe (I)Lkotlinx/io/Buffer; - public synthetic fun writeShortLe (I)Lkotlinx/io/Sink; public fun writeString (Ljava/lang/String;IILjava/nio/charset/Charset;)Lkotlinx/io/Buffer; public synthetic fun writeString (Ljava/lang/String;IILjava/nio/charset/Charset;)Lkotlinx/io/Sink; public fun writeString (Ljava/lang/String;Ljava/nio/charset/Charset;)Lkotlinx/io/Buffer; @@ -100,29 +76,12 @@ public final class kotlinx/io/Buffer : java/lang/Cloneable, java/nio/channels/By public final fun writeTo (Ljava/io/OutputStream;)Lkotlinx/io/Buffer; public final fun writeTo (Ljava/io/OutputStream;J)Lkotlinx/io/Buffer; public static synthetic fun writeTo$default (Lkotlinx/io/Buffer;Ljava/io/OutputStream;JILjava/lang/Object;)Lkotlinx/io/Buffer; - public fun writeUtf8 (Ljava/lang/String;)Lkotlinx/io/Buffer; - public synthetic fun writeUtf8 (Ljava/lang/String;)Lkotlinx/io/Sink; public fun writeUtf8 (Ljava/lang/String;II)Lkotlinx/io/Buffer; public synthetic fun writeUtf8 (Ljava/lang/String;II)Lkotlinx/io/Sink; public fun writeUtf8CodePoint (I)Lkotlinx/io/Buffer; public synthetic fun writeUtf8CodePoint (I)Lkotlinx/io/Sink; } -public final class kotlinx/io/Buffer$UnsafeCursor : java/io/Closeable { - public field buffer Lkotlinx/io/Buffer; - public field data [B - public field end I - public field offset J - public field readWrite Z - public field start I - public fun ()V - public fun close ()V - public final fun expandBuffer (I)J - public final fun next ()I - public final fun resizeBuffer (J)J - public final fun seek (J)I -} - public final class kotlinx/io/CoreKt { public static final fun blackhole ()Lkotlinx/io/RawSink; public static final fun buffer (Lkotlinx/io/RawSink;)Lkotlinx/io/Sink; @@ -164,63 +123,74 @@ public abstract interface class kotlinx/io/Sink : java/nio/channels/WritableByte public abstract fun getBuffer ()Lkotlinx/io/Buffer; public abstract fun outputStream ()Ljava/io/OutputStream; public abstract fun write (Lkotlinx/io/RawSource;J)Lkotlinx/io/Sink; - public abstract fun write ([B)Lkotlinx/io/Sink; public abstract fun write ([BII)Lkotlinx/io/Sink; public abstract fun writeAll (Lkotlinx/io/RawSource;)J public abstract fun writeByte (I)Lkotlinx/io/Sink; - public abstract fun writeDecimalLong (J)Lkotlinx/io/Sink; - public abstract fun writeHexadecimalUnsignedLong (J)Lkotlinx/io/Sink; public abstract fun writeInt (I)Lkotlinx/io/Sink; - public abstract fun writeIntLe (I)Lkotlinx/io/Sink; public abstract fun writeLong (J)Lkotlinx/io/Sink; - public abstract fun writeLongLe (J)Lkotlinx/io/Sink; public abstract fun writeShort (I)Lkotlinx/io/Sink; - public abstract fun writeShortLe (I)Lkotlinx/io/Sink; public abstract fun writeString (Ljava/lang/String;IILjava/nio/charset/Charset;)Lkotlinx/io/Sink; public abstract fun writeString (Ljava/lang/String;Ljava/nio/charset/Charset;)Lkotlinx/io/Sink; - public abstract fun writeUtf8 (Ljava/lang/String;)Lkotlinx/io/Sink; public abstract fun writeUtf8 (Ljava/lang/String;II)Lkotlinx/io/Sink; public abstract fun writeUtf8CodePoint (I)Lkotlinx/io/Sink; } +public final class kotlinx/io/Sink$DefaultImpls { + public static synthetic fun write$default (Lkotlinx/io/Sink;[BIIILjava/lang/Object;)Lkotlinx/io/Sink; + public static synthetic fun writeUtf8$default (Lkotlinx/io/Sink;Ljava/lang/String;IIILjava/lang/Object;)Lkotlinx/io/Sink; +} + +public final class kotlinx/io/SinkExtKt { + public static final fun writeDecimalLong (Lkotlinx/io/Sink;J)Lkotlinx/io/Sink; + public static final fun writeHexadecimalUnsignedLong (Lkotlinx/io/Sink;J)Lkotlinx/io/Sink; + public static final fun writeIntLe (Lkotlinx/io/Sink;I)Lkotlinx/io/Sink; + public static final fun writeLongLe (Lkotlinx/io/Sink;J)Lkotlinx/io/Sink; + public static final fun writeShortLe (Lkotlinx/io/Sink;I)Lkotlinx/io/Sink; +} + public abstract interface class kotlinx/io/Source : java/nio/channels/ReadableByteChannel, kotlinx/io/RawSource { public abstract fun buffer ()Lkotlinx/io/Buffer; public abstract fun exhausted ()Z public abstract fun getBuffer ()Lkotlinx/io/Buffer; - public abstract fun indexOf (B)J - public abstract fun indexOf (BJ)J - public abstract fun indexOf (BJJ)J public abstract fun inputStream ()Ljava/io/InputStream; public abstract fun peek ()Lkotlinx/io/Source; - public abstract fun read ([B)I public abstract fun read ([BII)I public abstract fun readAll (Lkotlinx/io/RawSink;)J public abstract fun readByte ()B public abstract fun readByteArray ()[B public abstract fun readByteArray (J)[B - public abstract fun readDecimalLong ()J public abstract fun readFully (Lkotlinx/io/Buffer;J)V public abstract fun readFully ([B)V - public abstract fun readHexadecimalUnsignedLong ()J public abstract fun readInt ()I - public abstract fun readIntLe ()I public abstract fun readLong ()J - public abstract fun readLongLe ()J public abstract fun readShort ()S - public abstract fun readShortLe ()S public abstract fun readString (JLjava/nio/charset/Charset;)Ljava/lang/String; public abstract fun readString (Ljava/nio/charset/Charset;)Ljava/lang/String; public abstract fun readUtf8 ()Ljava/lang/String; public abstract fun readUtf8 (J)Ljava/lang/String; public abstract fun readUtf8CodePoint ()I - public abstract fun readUtf8Line ()Ljava/lang/String; - public abstract fun readUtf8LineStrict ()Ljava/lang/String; - public abstract fun readUtf8LineStrict (J)Ljava/lang/String; public abstract fun request (J)Z public abstract fun require (J)V public abstract fun skip (J)V } +public final class kotlinx/io/Source$DefaultImpls { + public static synthetic fun read$default (Lkotlinx/io/Source;[BIIILjava/lang/Object;)I +} + +public final class kotlinx/io/SourceExtKt { + public static final fun indexOf (Lkotlinx/io/Source;BJJ)J + public static synthetic fun indexOf$default (Lkotlinx/io/Source;BJJILjava/lang/Object;)J + public static final fun readDecimalLong (Lkotlinx/io/Source;)J + public static final fun readHexadecimalUnsignedLong (Lkotlinx/io/Source;)J + public static final fun readIntLe (Lkotlinx/io/Source;)I + public static final fun readLongLe (Lkotlinx/io/Source;)J + public static final fun readShortLe (Lkotlinx/io/Source;)S + public static final fun readUtf8Line (Lkotlinx/io/Source;)Ljava/lang/String; + public static final fun readUtf8LineStrict (Lkotlinx/io/Source;J)Ljava/lang/String; + public static synthetic fun readUtf8LineStrict$default (Lkotlinx/io/Source;JILjava/lang/Object;)Ljava/lang/String; +} + public final class kotlinx/io/Utf8 { public static final fun size (Ljava/lang/String;)J public static final fun size (Ljava/lang/String;I)J diff --git a/core/common/src/-CommonPlatform.kt b/core/common/src/-CommonPlatform.kt index 23dbf9f48..b5b73c8a4 100644 --- a/core/common/src/-CommonPlatform.kt +++ b/core/common/src/-CommonPlatform.kt @@ -28,18 +28,12 @@ internal expect fun String.asUtf8ToByteArray(): ByteArray // TODO make internal https://youtrack.jetbrains.com/issue/KT-37316 expect class ArrayIndexOutOfBoundsException(message: String?) : IndexOutOfBoundsException -internal expect inline fun synchronized(lock: Any, block: () -> R): R - expect open class IOException(message: String?, cause: Throwable?) : Exception { constructor(message: String? = null) } -expect class ProtocolException(message: String) : IOException - expect open class EOFException(message: String? = null) : IOException -expect class FileNotFoundException(message: String? = null) : IOException - expect interface Closeable { /** * Closes this object and releases the resources it holds. It is an error to use an object after diff --git a/core/common/src/-Util.kt b/core/common/src/-Util.kt index 2a24024a4..465c553e6 100644 --- a/core/common/src/-Util.kt +++ b/core/common/src/-Util.kt @@ -173,13 +173,6 @@ internal fun Long.toHexString(): String { // for them in the receiving function, then swap in the true default value. // https://youtrack.jetbrains.com/issue/KT-45542 -@SharedImmutable -internal val DEFAULT__new_UnsafeCursor = Buffer.UnsafeCursor() -internal fun resolveDefaultParameter(unsafeCursor: Buffer.UnsafeCursor): Buffer.UnsafeCursor { - if (unsafeCursor === DEFAULT__new_UnsafeCursor) return Buffer.UnsafeCursor() - return unsafeCursor -} - internal val DEFAULT__ByteString_size = -1234567890 internal fun ByteArray.resolveDefaultParameter(sizeParam: Int): Int { diff --git a/core/common/src/Buffer.kt b/core/common/src/Buffer.kt index 4adced089..c56024b23 100644 --- a/core/common/src/Buffer.kt +++ b/core/common/src/Buffer.kt @@ -81,14 +81,10 @@ expect class Buffer() : Source, Sink { /** Discards `byteCount` bytes from the head of this buffer. */ override fun skip(byteCount: Long) - override fun writeUtf8(string: String): Buffer - override fun writeUtf8(string: String, beginIndex: Int, endIndex: Int): Buffer override fun writeUtf8CodePoint(codePoint: Int): Buffer - override fun write(source: ByteArray): Buffer - /** * Returns a tail segment that we can write at least `minimumCapacity` * bytes to, creating it if necessary. @@ -103,286 +99,10 @@ expect class Buffer() : Source, Sink { override fun writeShort(s: Int): Buffer - override fun writeShortLe(s: Int): Buffer - override fun writeInt(i: Int): Buffer - override fun writeIntLe(i: Int): Buffer - override fun writeLong(v: Long): Buffer - override fun writeLongLe(v: Long): Buffer - - override fun writeDecimalLong(v: Long): Buffer - - override fun writeHexadecimalUnsignedLong(v: Long): Buffer - /** Returns a deep copy of this buffer. */ fun copy(): Buffer - -// fun readUnsafe(unsafeCursor: UnsafeCursor = DEFAULT__new_UnsafeCursor): UnsafeCursor -// -// fun readAndWriteUnsafe(unsafeCursor: UnsafeCursor = DEFAULT__new_UnsafeCursor): UnsafeCursor - - /** - * A handle to the underlying data in a buffer. This handle is unsafe because it does not enforce - * its own invariants. Instead, it assumes a careful user who has studied Okio's implementation - * details and their consequences. - * - * Buffer Internals - * ---------------- - * - * Most code should use `Buffer` as a black box: a class that holds 0 or more bytes of - * data with efficient APIs to append data to the end and to consume data from the front. Usually - * this is also the most efficient way to use buffers because it allows Okio to employ several - * optimizations, including: - * - * * **Fast Allocation:** Buffers use a shared pool of memory that is not zero-filled before use. - * * **Fast Resize:** A buffer's capacity can change without copying its contents. - * * **Fast Move:** Memory ownership can be reassigned from one buffer to another. - * * **Fast Copy:** Multiple buffers can share the same underlying memory. - * * **Fast Encoding and Decoding:** Common operations like UTF-8 encoding and decimal decoding - * do not require intermediate objects to be allocated. - * - * These optimizations all leverage the way Okio stores data internally. Okio Buffers are - * implemented using a doubly-linked list of segments. Each segment is a contiguous range within a - * 8 KiB `ByteArray`. Each segment has two indexes, `start`, the offset of the first byte of the - * array containing application data, and `end`, the offset of the first byte beyond `start` whose - * data is undefined. - * - * New buffers are empty and have no segments: - * - * ``` - * val buffer = Buffer() - * ``` - * - * We append 7 bytes of data to the end of our empty buffer. Internally, the buffer allocates a - * segment and writes its new data there. The lone segment has an 8 KiB byte array but only 7 - * bytes of data: - * - * ``` - * buffer.writeUtf8("sealion") - * - * // [ 's', 'e', 'a', 'l', 'i', 'o', 'n', '?', '?', '?', ...] - * // ^ ^ - * // start = 0 end = 7 - * ``` - * - * When we read 4 bytes of data from the buffer, it finds its first segment and returns that data - * to us. As bytes are read the data is consumed. The segment tracks this by adjusting its - * internal indices. - * - * ``` - * buffer.readUtf8(4) // "seal" - * - * // [ 's', 'e', 'a', 'l', 'i', 'o', 'n', '?', '?', '?', ...] - * // ^ ^ - * // start = 4 end = 7 - * ``` - * - * As we write data into a buffer we fill up its internal segments. When a write doesn't fit into - * a buffer's last segment, additional segments are allocated and appended to the linked list of - * segments. Each segment has its own start and end indexes tracking where the user's data begins - * and ends. - * - * ``` - * val xoxo = new Buffer() - * xoxo.writeUtf8("xo".repeat(5_000)) - * - * // [ 'x', 'o', 'x', 'o', 'x', 'o', 'x', 'o', ..., 'x', 'o', 'x', 'o'] - * // ^ ^ - * // start = 0 end = 8192 - * // - * // [ 'x', 'o', 'x', 'o', ..., 'x', 'o', 'x', 'o', '?', '?', '?', ...] - * // ^ ^ - * // start = 0 end = 1808 - * ``` - * - * The start index is always **inclusive** and the end index is always **exclusive**. The data - * preceding the start index is undefined, and the data at and following the end index is - * undefined. - * - * After the last byte of a segment has been read, that segment may be returned to an internal - * segment pool. In addition to reducing the need to do garbage collection, segment pooling also - * saves the JVM from needing to zero-fill byte arrays. Okio doesn't need to zero-fill its arrays - * because it always writes memory before it reads it. But if you look at a segment in a debugger - * you may see its effects. In this example, one of the "xoxo" segments above is reused in an - * unrelated buffer: - * - * ``` - * val abc = new Buffer() - * abc.writeUtf8("abc") - * - * // [ 'a', 'b', 'c', 'o', 'x', 'o', 'x', 'o', ...] - * // ^ ^ - * // start = 0 end = 3 - * ``` - * - * There is an optimization in `Buffer.clone()` and other methods that allows two segments to - * share the same underlying byte array. Clones can't write to the shared byte array; instead they - * allocate a new (private) segment early. - * - * ``` - * val nana = new Buffer() - * nana.writeUtf8("na".repeat(2_500)) - * nana.readUtf8(2) // "na" - * - * // [ 'n', 'a', 'n', 'a', ..., 'n', 'a', 'n', 'a', '?', '?', '?', ...] - * // ^ ^ - * // start = 2 end = 5000 - * - * nana2 = nana.clone() - * nana2.writeUtf8("batman") - * - * // [ 'n', 'a', 'n', 'a', ..., 'n', 'a', 'n', 'a', '?', '?', '?', ...] - * // ^ ^ - * // start = 2 end = 5000 - * // - * // [ 'b', 'a', 't', 'm', 'a', 'n', '?', '?', '?', ...] - * // ^ ^ - * // start = 0 end = 6 - * ``` - * - * Segments are not shared when the shared region is small (ie. less than 1 KiB). This is intended - * to prevent fragmentation in sharing-heavy use cases. - * - * Unsafe Cursor API - * ----------------- - * - * This class exposes privileged access to the internal byte arrays of a buffer. A cursor either - * references the data of a single segment, it is before the first segment (`offset == -1`), or it - * is after the last segment (`offset == buffer.size`). - * - * Call [UnsafeCursor.seek] to move the cursor to the segment that contains a specified offset. - * After seeking, [UnsafeCursor.data] references the segment's internal byte array, - * [UnsafeCursor.start] is the segment's start and [UnsafeCursor.end] is its end. - * - * Call [UnsafeCursor.next] to advance the cursor to the next segment. This returns -1 if there - * are no further segments in the buffer. - * - * Use [Buffer.readUnsafe] to create a cursor to read buffer data and [Buffer.readAndWriteUnsafe] - * to create a cursor to read and write buffer data. In either case, always call - * [UnsafeCursor.close] when done with a cursor. This is convenient with Kotlin's - * [use] extension function. In this example we read all of the bytes in a buffer into a byte - * array: - * - * ``` - * val bufferBytes = ByteArray(buffer.size.toInt()) - * - * buffer.readUnsafe().use { cursor -> - * while (cursor.next() != -1) { - * System.arraycopy(cursor.data, cursor.start, - * bufferBytes, cursor.offset.toInt(), cursor.end - cursor.start); - * } - * } - * ``` - * - * Change the capacity of a buffer with [resizeBuffer]. This is only permitted for read+write - * cursors. The buffer's size always changes from the end: shrinking it removes bytes from the - * end; growing it adds capacity to the end. - * - * Warnings - * -------- - * - * Most application developers should avoid this API. Those that must use this API should - * respect these warnings. - * - * **Don't mutate a cursor.** This class has public, non-final fields because that is convenient - * for low-level I/O frameworks. Never assign values to these fields; instead use the cursor API - * to adjust these. - * - * **Never mutate `data` unless you have read+write access.** You are on the honor system to never - * write the buffer in read-only mode. Read-only mode may be more efficient than read+write mode - * because it does not need to make private copies of shared segments. - * - * **Only access data in `[start..end)`.** Other data in the byte array is undefined! It may - * contain private or sensitive data from other parts of your process. - * - * **Always fill the new capacity when you grow a buffer.** New capacity is not zero-filled and - * may contain data from other parts of your process. Avoid leaking this information by always - * writing something to the newly-allocated capacity. Do not assume that new capacity will be - * filled with `0`; it will not be. - * - * **Do not access a buffer while is being accessed by a cursor.** Even simple read-only - * operations like [Buffer.clone] are unsafe because they mark segments as shared. - * - * **Do not hard-code the segment size in your application.** It is possible that segment sizes - * will change with advances in hardware. Future versions of Okio may even have heterogeneous - * segment sizes. - * - * These warnings are intended to help you to use this API safely. It's here for developers - * that need absolutely the most throughput. Since that's you, here's one final performance tip. - * You can reuse instances of this class if you like. Use the overloads of [Buffer.readUnsafe] and - * [Buffer.readAndWriteUnsafe] that take a cursor and close it after use. - * - * TODO should be internal - */ - public class UnsafeCursor constructor() { - @JvmField var buffer: Buffer? - @JvmField var readWrite: Boolean - - internal var segment: Segment? - @JvmField var offset: Long - @JvmField var data: ByteArray? - @JvmField var start: Int - @JvmField var end: Int - - /** - * Seeks to the next range of bytes, advancing the offset by `end - start`. Returns the size of - * the readable range (at least 1), or -1 if we have reached the end of the buffer and there are - * no more bytes to read. - */ - fun next(): Int - - /** - * Reposition the cursor so that the data at [offset] is readable at `data[start]`. - * Returns the number of bytes readable in [data] (at least 1), or -1 if there are no data - * to read. - */ - fun seek(offset: Long): Int - - /** - * Change the size of the buffer so that it equals [newSize] by either adding new capacity at - * the end or truncating the buffer at the end. Newly added capacity may span multiple segments. - * - * As a side-effect this cursor will [seek][UnsafeCursor.seek]. If the buffer is being enlarged - * it will move [UnsafeCursor.offset] to the first byte of newly-added capacity. This is the - * size of the buffer prior to the `resizeBuffer()` call. If the buffer is being shrunk it will move - * [UnsafeCursor.offset] to the end of the buffer. - * - * Warning: it is the caller’s responsibility to write new data to every byte of the - * newly-allocated capacity. Failure to do so may cause serious security problems as the data - * in the returned buffers is not zero filled. Buffers may contain dirty pooled segments that - * hold very sensitive data from other parts of the current process. - * - * @return the previous size of the buffer. - */ - fun resizeBuffer(newSize: Long): Long - - /** - * Grow the buffer by adding a **contiguous range** of capacity in a single segment. This adds - * at least [minByteCount] bytes but may add up to a full segment of additional capacity. - * - * As a side-effect this cursor will [seek][UnsafeCursor.seek]. It will move - * [offset][UnsafeCursor.offset] to the first byte of newly-added capacity. This is the size of - * the buffer prior to the `expandBuffer()` call. - * - * If [minByteCount] bytes are available in the buffer's current tail segment that will be used; - * otherwise another segment will be allocated and appended. In either case this returns the - * number of bytes of capacity added to this buffer. - * - * Warning: it is the caller’s responsibility to either write new data to every byte of the - * newly-allocated capacity, or to [shrink][UnsafeCursor.resizeBuffer] the buffer to the data - * written. Failure to do so may cause serious security problems as the data in the returned - * buffers is not zero filled. Buffers may contain dirty pooled segments that hold very - * sensitive data from other parts of the current process. - * - * @param minByteCount the size of the contiguous capacity. Must be positive and not greater - * than the capacity size of a single segment (8 KiB). - * @return the number of bytes expanded by. Not less than `minByteCount`. - */ - fun expandBuffer(minByteCount: Int): Long - - fun close() - } } diff --git a/core/common/src/Sink.kt b/core/common/src/Sink.kt index 92128cb98..5eddb55ee 100644 --- a/core/common/src/Sink.kt +++ b/core/common/src/Sink.kt @@ -28,11 +28,10 @@ expect sealed interface Sink : RawSink { /** This sink's internal buffer. */ val buffer: Buffer + // TODO(filipp): fix javadoc /** Like [OutputStream.write], this writes a complete byte array to this sink. */ - fun write(source: ByteArray): Sink - /** Like [OutputStream.write], this writes `byteCount` bytes of `source`, starting at `offset`. */ - fun write(source: ByteArray, offset: Int, byteCount: Int): Sink + fun write(source: ByteArray, offset: Int = 0, byteCount: Int = source.size): Sink /** * Removes all bytes from `source` and appends them to this sink. Returns the number of bytes read @@ -54,9 +53,8 @@ expect sealed interface Sink : RawSink { * assertEquals("Uh uh uh! You didn't say the magic word!", buffer.readUtf8()); * ``` */ - fun writeUtf8(string: String): Sink - - /** + //TODO(filipp): fix javadoc + /* * Encodes the characters at `beginIndex` up to `endIndex` from `string` in UTF-8 and writes it to * this sink. * ``` @@ -70,7 +68,7 @@ expect sealed interface Sink : RawSink { * assertEquals("hacker nerd hacker!", buffer.readUtf8()); * ``` */ - fun writeUtf8(string: String, beginIndex: Int, endIndex: Int): Sink + fun writeUtf8(string: String, beginIndex: Int = 0, endIndex: Int = string.length): Sink /** Encodes `codePoint` in UTF-8 and writes it to this sink. */ fun writeUtf8CodePoint(codePoint: Int): Sink @@ -95,23 +93,6 @@ expect sealed interface Sink : RawSink { */ fun writeShort(s: Int): Sink - /** - * Writes a little-endian short to this sink using two bytes. - * ``` - * Buffer buffer = new Buffer(); - * buffer.writeShortLe(32767); - * buffer.writeShortLe(15); - * - * assertEquals(4, buffer.size()); - * assertEquals((byte) 0xff, buffer.readByte()); - * assertEquals((byte) 0x7f, buffer.readByte()); - * assertEquals((byte) 0x0f, buffer.readByte()); - * assertEquals((byte) 0x00, buffer.readByte()); - * assertEquals(0, buffer.size()); - * ``` - */ - fun writeShortLe(s: Int): Sink - /** * Writes a big-endian int to this sink using four bytes. * ``` @@ -133,27 +114,6 @@ expect sealed interface Sink : RawSink { */ fun writeInt(i: Int): Sink - /** - * Writes a little-endian int to this sink using four bytes. - * ``` - * Buffer buffer = new Buffer(); - * buffer.writeIntLe(2147483647); - * buffer.writeIntLe(15); - * - * assertEquals(8, buffer.size()); - * assertEquals((byte) 0xff, buffer.readByte()); - * assertEquals((byte) 0xff, buffer.readByte()); - * assertEquals((byte) 0xff, buffer.readByte()); - * assertEquals((byte) 0x7f, buffer.readByte()); - * assertEquals((byte) 0x0f, buffer.readByte()); - * assertEquals((byte) 0x00, buffer.readByte()); - * assertEquals((byte) 0x00, buffer.readByte()); - * assertEquals((byte) 0x00, buffer.readByte()); - * assertEquals(0, buffer.size()); - * ``` - */ - fun writeIntLe(i: Int): Sink - /** * Writes a big-endian long to this sink using eight bytes. * ``` @@ -183,65 +143,6 @@ expect sealed interface Sink : RawSink { */ fun writeLong(v: Long): Sink - /** - * Writes a little-endian long to this sink using eight bytes. - * ``` - * Buffer buffer = new Buffer(); - * buffer.writeLongLe(9223372036854775807L); - * buffer.writeLongLe(15); - * - * assertEquals(16, buffer.size()); - * assertEquals((byte) 0xff, buffer.readByte()); - * assertEquals((byte) 0xff, buffer.readByte()); - * assertEquals((byte) 0xff, buffer.readByte()); - * assertEquals((byte) 0xff, buffer.readByte()); - * assertEquals((byte) 0xff, buffer.readByte()); - * assertEquals((byte) 0xff, buffer.readByte()); - * assertEquals((byte) 0xff, buffer.readByte()); - * assertEquals((byte) 0x7f, buffer.readByte()); - * assertEquals((byte) 0x0f, buffer.readByte()); - * assertEquals((byte) 0x00, buffer.readByte()); - * assertEquals((byte) 0x00, buffer.readByte()); - * assertEquals((byte) 0x00, buffer.readByte()); - * assertEquals((byte) 0x00, buffer.readByte()); - * assertEquals((byte) 0x00, buffer.readByte()); - * assertEquals((byte) 0x00, buffer.readByte()); - * assertEquals((byte) 0x00, buffer.readByte()); - * assertEquals(0, buffer.size()); - * ``` - */ - fun writeLongLe(v: Long): Sink - - /** - * Writes a long to this sink in signed decimal form (i.e., as a string in base 10). - * ``` - * Buffer buffer = new Buffer(); - * buffer.writeDecimalLong(8675309L); - * buffer.writeByte(' '); - * buffer.writeDecimalLong(-123L); - * buffer.writeByte(' '); - * buffer.writeDecimalLong(1L); - * - * assertEquals("8675309 -123 1", buffer.readUtf8()); - * ``` - */ - fun writeDecimalLong(v: Long): Sink - - /** - * Writes a long to this sink in hexadecimal form (i.e., as a string in base 16). - * ``` - * Buffer buffer = new Buffer(); - * buffer.writeHexadecimalUnsignedLong(65535L); - * buffer.writeByte(' '); - * buffer.writeHexadecimalUnsignedLong(0xcafebabeL); - * buffer.writeByte(' '); - * buffer.writeHexadecimalUnsignedLong(0x10L); - * - * assertEquals("ffff cafebabe 10", buffer.readUtf8()); - * ``` - */ - fun writeHexadecimalUnsignedLong(v: Long): Sink - /** * Writes all buffered data to the underlying sink, if one exists. Then that sink is recursively * flushed which pushes data as far as possible towards its ultimate destination. Typically that diff --git a/core/common/src/SinkExt.kt b/core/common/src/SinkExt.kt new file mode 100644 index 000000000..3d5a584c7 --- /dev/null +++ b/core/common/src/SinkExt.kt @@ -0,0 +1,118 @@ +/* + * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. + */ + +package kotlinx.io + +/** + * Writes a little-endian short to this sink using two bytes. + * ``` + * Buffer buffer = new Buffer(); + * buffer.writeShortLe(32767); + * buffer.writeShortLe(15); + * + * assertEquals(4, buffer.size()); + * assertEquals((byte) 0xff, buffer.readByte()); + * assertEquals((byte) 0x7f, buffer.readByte()); + * assertEquals((byte) 0x0f, buffer.readByte()); + * assertEquals((byte) 0x00, buffer.readByte()); + * assertEquals(0, buffer.size()); + * ``` + */ +fun T.writeShortLe(s: Int): T { + return this.writeShort(s.toShort().reverseBytes().toInt()) as T +} + +/** + * Writes a little-endian int to this sink using four bytes. + * ``` + * Buffer buffer = new Buffer(); + * buffer.writeIntLe(2147483647); + * buffer.writeIntLe(15); + * + * assertEquals(8, buffer.size()); + * assertEquals((byte) 0xff, buffer.readByte()); + * assertEquals((byte) 0xff, buffer.readByte()); + * assertEquals((byte) 0xff, buffer.readByte()); + * assertEquals((byte) 0x7f, buffer.readByte()); + * assertEquals((byte) 0x0f, buffer.readByte()); + * assertEquals((byte) 0x00, buffer.readByte()); + * assertEquals((byte) 0x00, buffer.readByte()); + * assertEquals((byte) 0x00, buffer.readByte()); + * assertEquals(0, buffer.size()); + * ``` + */ +fun T.writeIntLe(i: Int): T { + return this.writeInt(i.reverseBytes()) as T +} + +/** + * Writes a little-endian long to this sink using eight bytes. + * ``` + * Buffer buffer = new Buffer(); + * buffer.writeLongLe(9223372036854775807L); + * buffer.writeLongLe(15); + * + * assertEquals(16, buffer.size()); + * assertEquals((byte) 0xff, buffer.readByte()); + * assertEquals((byte) 0xff, buffer.readByte()); + * assertEquals((byte) 0xff, buffer.readByte()); + * assertEquals((byte) 0xff, buffer.readByte()); + * assertEquals((byte) 0xff, buffer.readByte()); + * assertEquals((byte) 0xff, buffer.readByte()); + * assertEquals((byte) 0xff, buffer.readByte()); + * assertEquals((byte) 0x7f, buffer.readByte()); + * assertEquals((byte) 0x0f, buffer.readByte()); + * assertEquals((byte) 0x00, buffer.readByte()); + * assertEquals((byte) 0x00, buffer.readByte()); + * assertEquals((byte) 0x00, buffer.readByte()); + * assertEquals((byte) 0x00, buffer.readByte()); + * assertEquals((byte) 0x00, buffer.readByte()); + * assertEquals((byte) 0x00, buffer.readByte()); + * assertEquals((byte) 0x00, buffer.readByte()); + * assertEquals(0, buffer.size()); + * ``` + */ +fun T.writeLongLe(v: Long): T { + return this.writeLong(v.reverseBytes()) as T +} + +/** + * Writes a long to this sink in signed decimal form (i.e., as a string in base 10). + * ``` + * Buffer buffer = new Buffer(); + * buffer.writeDecimalLong(8675309L); + * buffer.writeByte(' '); + * buffer.writeDecimalLong(-123L); + * buffer.writeByte(' '); + * buffer.writeDecimalLong(1L); + * + * assertEquals("8675309 -123 1", buffer.readUtf8()); + * ``` + */ +fun T.writeDecimalLong(v: Long): T { + // TODO: optimize + return writeUtf8(v.toString()) as T +} + +/** + * Writes a long to this sink in hexadecimal form (i.e., as a string in base 16). + * ``` + * Buffer buffer = new Buffer(); + * buffer.writeHexadecimalUnsignedLong(65535L); + * buffer.writeByte(' '); + * buffer.writeHexadecimalUnsignedLong(0xcafebabeL); + * buffer.writeByte(' '); + * buffer.writeHexadecimalUnsignedLong(0x10L); + * + * assertEquals("ffff cafebabe 10", buffer.readUtf8()); + * ``` + */ +fun T.writeHexadecimalUnsignedLong(v: Long): T { + if (v == 0L) { + return writeByte('0'.code) as T + } + // TODO: optimize + return writeUtf8(v.toHexString()) as T +} \ No newline at end of file diff --git a/core/common/src/Source.kt b/core/common/src/Source.kt index 508b9b441..22968874e 100644 --- a/core/common/src/Source.kt +++ b/core/common/src/Source.kt @@ -69,25 +69,6 @@ expect sealed interface Source : RawSource { */ fun readShort(): Short - /** - * Removes two bytes from this source and returns a little-endian short. - * ``` - * Buffer buffer = new Buffer() - * .writeByte(0xff) - * .writeByte(0x7f) - * .writeByte(0x0f) - * .writeByte(0x00); - * assertEquals(4, buffer.size()); - * - * assertEquals(32767, buffer.readShortLe()); - * assertEquals(2, buffer.size()); - * - * assertEquals(15, buffer.readShortLe()); - * assertEquals(0, buffer.size()); - * ``` - */ - fun readShortLe(): Short - /** * Removes four bytes from this source and returns a big-endian int. * ``` @@ -111,29 +92,6 @@ expect sealed interface Source : RawSource { */ fun readInt(): Int - /** - * Removes four bytes from this source and returns a little-endian int. - * ``` - * Buffer buffer = new Buffer() - * .writeByte(0xff) - * .writeByte(0xff) - * .writeByte(0xff) - * .writeByte(0x7f) - * .writeByte(0x0f) - * .writeByte(0x00) - * .writeByte(0x00) - * .writeByte(0x00); - * assertEquals(8, buffer.size()); - * - * assertEquals(2147483647, buffer.readIntLe()); - * assertEquals(4, buffer.size()); - * - * assertEquals(15, buffer.readIntLe()); - * assertEquals(0, buffer.size()); - * ``` - */ - fun readIntLe(): Int - /** * Removes eight bytes from this source and returns a big-endian long. * ``` @@ -165,75 +123,6 @@ expect sealed interface Source : RawSource { */ fun readLong(): Long - /** - * Removes eight bytes from this source and returns a little-endian long. - * ``` - * Buffer buffer = new Buffer() - * .writeByte(0xff) - * .writeByte(0xff) - * .writeByte(0xff) - * .writeByte(0xff) - * .writeByte(0xff) - * .writeByte(0xff) - * .writeByte(0xff) - * .writeByte(0x7f) - * .writeByte(0x0f) - * .writeByte(0x00) - * .writeByte(0x00) - * .writeByte(0x00) - * .writeByte(0x00) - * .writeByte(0x00) - * .writeByte(0x00) - * .writeByte(0x00); - * assertEquals(16, buffer.size()); - * - * assertEquals(9223372036854775807L, buffer.readLongLe()); - * assertEquals(8, buffer.size()); - * - * assertEquals(15, buffer.readLongLe()); - * assertEquals(0, buffer.size()); - * ``` - */ - fun readLongLe(): Long - - /** - * Reads a long from this source in signed decimal form (i.e., as a string in base 10 with - * optional leading '-'). This will iterate until a non-digit character is found. - * ``` - * Buffer buffer = new Buffer() - * .writeUtf8("8675309 -123 00001"); - * - * assertEquals(8675309L, buffer.readDecimalLong()); - * assertEquals(' ', buffer.readByte()); - * assertEquals(-123L, buffer.readDecimalLong()); - * assertEquals(' ', buffer.readByte()); - * assertEquals(1L, buffer.readDecimalLong()); - * ``` - * - * @throws NumberFormatException if the found digits do not fit into a `long` or a decimal - * number was not present. - */ - fun readDecimalLong(): Long - - /** - * Reads a long form this source in hexadecimal form (i.e., as a string in base 16). This will - * iterate until a non-hexadecimal character is found. - * ``` - * Buffer buffer = new Buffer() - * .writeUtf8("ffff CAFEBABE 10"); - * - * assertEquals(65535L, buffer.readHexadecimalUnsignedLong()); - * assertEquals(' ', buffer.readByte()); - * assertEquals(0xcafebabeL, buffer.readHexadecimalUnsignedLong()); - * assertEquals(' ', buffer.readByte()); - * assertEquals(0x10L, buffer.readHexadecimalUnsignedLong()); - * ``` - * - * @throws NumberFormatException if the found hexadecimal does not fit into a `long` or - * hexadecimal was not found. - */ - fun readHexadecimalUnsignedLong(): Long - /** * Reads and discards `byteCount` bytes from this source. Throws an [java.io.EOFException] if the * source is exhausted before the requested bytes can be skipped. @@ -246,23 +135,24 @@ expect sealed interface Source : RawSource { /** Removes `byteCount` bytes from this and returns them as a byte array. */ fun readByteArray(byteCount: Long): ByteArray - /** - * Removes up to `sink.length` bytes from this and copies them into `sink`. Returns the number of - * bytes read, or -1 if this source is exhausted. - */ - fun read(sink: ByteArray): Int - /** * Removes exactly `sink.length` bytes from this and copies them into `sink`. Throws an * [java.io.EOFException] if the requested number of bytes cannot be read. */ fun readFully(sink: ByteArray) + // TODO(filipp): fix javadoc + /** + * Removes up to `sink.length` bytes from this and copies them into `sink`. Returns the number of + * bytes read, or -1 if this source is exhausted. + */ + //fun read(sink: ByteArray): Int + /** * Removes up to `byteCount` bytes from this and copies them into `sink` at `offset`. Returns the * number of bytes read, or -1 if this source is exhausted. */ - fun read(sink: ByteArray, offset: Int, byteCount: Int): Int + fun read(sink: ByteArray, offset: Int = 0, byteCount: Int = sink.size): Int /** * Removes exactly `byteCount` bytes from this and appends them to `sink`. Throws an @@ -315,70 +205,6 @@ expect sealed interface Source : RawSource { */ fun readUtf8(byteCount: Long): String - /** - * Removes and returns characters up to but not including the next line break. A line break is - * either `"\n"` or `"\r\n"`; these characters are not included in the result. - * ``` - * Buffer buffer = new Buffer() - * .writeUtf8("I'm a hacker!\n") - * .writeUtf8("That's what I said: you're a nerd.\n") - * .writeUtf8("I prefer to be called a hacker!\n"); - * assertEquals(81, buffer.size()); - * - * assertEquals("I'm a hacker!", buffer.readUtf8Line()); - * assertEquals(67, buffer.size()); - * - * assertEquals("That's what I said: you're a nerd.", buffer.readUtf8Line()); - * assertEquals(32, buffer.size()); - * - * assertEquals("I prefer to be called a hacker!", buffer.readUtf8Line()); - * assertEquals(0, buffer.size()); - * - * assertEquals(null, buffer.readUtf8Line()); - * assertEquals(0, buffer.size()); - * ``` - * - * **On the end of the stream this method returns null,** just like [java.io.BufferedReader]. If - * the source doesn't end with a line break then an implicit line break is assumed. Null is - * returned once the source is exhausted. Use this for human-generated data, where a trailing - * line break is optional. - */ - fun readUtf8Line(): String? - - /** - * Removes and returns characters up to but not including the next line break. A line break is - * either `"\n"` or `"\r\n"`; these characters are not included in the result. - * - * **On the end of the stream this method throws.** Every call must consume either - * '\r\n' or '\n'. If these characters are absent in the stream, an [java.io.EOFException] - * is thrown. Use this for machine-generated data where a missing line break implies truncated - * input. - */ - fun readUtf8LineStrict(): String - - /** - * Like [readUtf8LineStrict], except this allows the caller to specify the longest allowed match. - * Use this to protect against streams that may not include `"\n"` or `"\r\n"`. - * - * The returned string will have at most `limit` UTF-8 bytes, and the maximum number of bytes - * scanned is `limit + 2`. If `limit == 0` this will always throw an `EOFException` because no - * bytes will be scanned. - * - * This method is safe. No bytes are discarded if the match fails, and the caller is free to try - * another match: - * ``` - * Buffer buffer = new Buffer(); - * buffer.writeUtf8("12345\r\n"); - * - * // This will throw! There must be \r\n or \n at the limit or before it. - * buffer.readUtf8LineStrict(4); - * - * // No bytes have been consumed so the caller can retry. - * assertEquals("12345", buffer.readUtf8LineStrict(5)); - * ``` - */ - fun readUtf8LineStrict(limit: Long): String - /** * Removes and returns a single UTF-8 code point, reading between 1 and 4 bytes as necessary. * @@ -393,96 +219,6 @@ expect sealed interface Source : RawSource { */ fun readUtf8CodePoint(): Int - /** Equivalent to [indexOf(b, 0)][indexOf]. */ - fun indexOf(b: Byte): Long - - /** - * Returns the index of the first `b` in the buffer at or after `fromIndex`. This expands the - * buffer as necessary until `b` is found. This reads an unbounded number of bytes into the - * buffer. Returns -1 if the stream is exhausted before the requested byte is found. - * ``` - * Buffer buffer = new Buffer(); - * buffer.writeUtf8("Don't move! He can't see us if we don't move."); - * - * byte m = 'm'; - * assertEquals(6, buffer.indexOf(m)); - * assertEquals(40, buffer.indexOf(m, 12)); - * ``` - */ - fun indexOf(b: Byte, fromIndex: Long): Long - - /** - * Returns the index of `b` if it is found in the range of `fromIndex` inclusive to `toIndex` - * exclusive. If `b` isn't found, or if `fromIndex == toIndex`, then -1 is returned. - * - * The scan terminates at either `toIndex` or the end of the buffer, whichever comes first. The - * maximum number of bytes scanned is `toIndex-fromIndex`. - */ - fun indexOf(b: Byte, fromIndex: Long, toIndex: Long): Long - -// /** Equivalent to [indexOf(bytes, 0)][indexOf]. */ -// fun indexOf(bytes: ByteString): Long - - /** - * Returns the index of the first match for `bytes` in the buffer at or after `fromIndex`. This - * expands the buffer as necessary until `bytes` is found. This reads an unbounded number of - * bytes into the buffer. Returns -1 if the stream is exhausted before the requested bytes are - * found. - * ``` - * ByteString MOVE = ByteString.encodeUtf8("move"); - * - * Buffer buffer = new Buffer(); - * buffer.writeUtf8("Don't move! He can't see us if we don't move."); - * - * assertEquals(6, buffer.indexOf(MOVE)); - * assertEquals(40, buffer.indexOf(MOVE, 12)); - * ``` - */ -// fun indexOf(bytes: ByteString, fromIndex: Long): Long - - /** Equivalent to [indexOfElement(targetBytes, 0)][indexOfElement]. */ -// fun indexOfElement(targetBytes: ByteString): Long - - /** - * Returns the first index in this buffer that is at or after `fromIndex` and that contains any of - * the bytes in `targetBytes`. This expands the buffer as necessary until a target byte is found. - * This reads an unbounded number of bytes into the buffer. Returns -1 if the stream is exhausted - * before the requested byte is found. - * ``` - * ByteString ANY_VOWEL = ByteString.encodeUtf8("AEOIUaeoiu"); - * - * Buffer buffer = new Buffer(); - * buffer.writeUtf8("Dr. Alan Grant"); - * - * assertEquals(4, buffer.indexOfElement(ANY_VOWEL)); // 'A' in 'Alan'. - * assertEquals(11, buffer.indexOfElement(ANY_VOWEL, 9)); // 'a' in 'Grant'. - * ``` - */ -// fun indexOfElement(targetBytes: ByteString, fromIndex: Long): Long - - /** - * Returns true if the bytes at `offset` in this source equal `bytes`. This expands the buffer as - * necessary until a byte does not match, all bytes are matched, or if the stream is exhausted - * before enough bytes could determine a match. - * ``` - * ByteString simonSays = ByteString.encodeUtf8("Simon says:"); - * - * Buffer standOnOneLeg = new Buffer().writeUtf8("Simon says: Stand on one leg."); - * assertTrue(standOnOneLeg.rangeEquals(0, simonSays)); - * - * Buffer payMeMoney = new Buffer().writeUtf8("Pay me $1,000,000."); - * assertFalse(payMeMoney.rangeEquals(0, simonSays)); - * ``` - */ -// fun rangeEquals(offset: Long, bytes: ByteString): Boolean - - /** - * Returns true if `byteCount` bytes at `offset` in this source equal `bytes` at `bytesOffset`. - * This expands the buffer as necessary until a byte does not match, all bytes are matched, or if - * the stream is exhausted before enough bytes could determine a match. - */ -// fun rangeEquals(offset: Long, bytes: ByteString, bytesOffset: Int, byteCount: Int): Boolean - /** * Returns a new `BufferedSource` that can read data from this `BufferedSource` without consuming * it. The returned source becomes invalid once this source is next read or closed. diff --git a/core/common/src/SourceExt.kt b/core/common/src/SourceExt.kt new file mode 100644 index 000000000..eb179673d --- /dev/null +++ b/core/common/src/SourceExt.kt @@ -0,0 +1,413 @@ +/* + * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. + */ + +package kotlinx.io + +/** + * Removes two bytes from this source and returns a little-endian short. + * ``` + * Buffer buffer = new Buffer() + * .writeByte(0xff) + * .writeByte(0x7f) + * .writeByte(0x0f) + * .writeByte(0x00); + * assertEquals(4, buffer.size()); + * + * assertEquals(32767, buffer.readShortLe()); + * assertEquals(2, buffer.size()); + * + * assertEquals(15, buffer.readShortLe()); + * assertEquals(0, buffer.size()); + * ``` + */ +fun Source.readShortLe(): Short { + return readShort().reverseBytes() +} + +/** + * Removes four bytes from this source and returns a little-endian int. + * ``` + * Buffer buffer = new Buffer() + * .writeByte(0xff) + * .writeByte(0xff) + * .writeByte(0xff) + * .writeByte(0x7f) + * .writeByte(0x0f) + * .writeByte(0x00) + * .writeByte(0x00) + * .writeByte(0x00); + * assertEquals(8, buffer.size()); + * + * assertEquals(2147483647, buffer.readIntLe()); + * assertEquals(4, buffer.size()); + * + * assertEquals(15, buffer.readIntLe()); + * assertEquals(0, buffer.size()); + * ``` + */ +fun Source.readIntLe(): Int { + return readInt().reverseBytes() +} + +/** + * Removes eight bytes from this source and returns a little-endian long. + * ``` + * Buffer buffer = new Buffer() + * .writeByte(0xff) + * .writeByte(0xff) + * .writeByte(0xff) + * .writeByte(0xff) + * .writeByte(0xff) + * .writeByte(0xff) + * .writeByte(0xff) + * .writeByte(0x7f) + * .writeByte(0x0f) + * .writeByte(0x00) + * .writeByte(0x00) + * .writeByte(0x00) + * .writeByte(0x00) + * .writeByte(0x00) + * .writeByte(0x00) + * .writeByte(0x00); + * assertEquals(16, buffer.size()); + * + * assertEquals(9223372036854775807L, buffer.readLongLe()); + * assertEquals(8, buffer.size()); + * + * assertEquals(15, buffer.readLongLe()); + * assertEquals(0, buffer.size()); + * ``` + */ +fun Source.readLongLe(): Long { + return readLong().reverseBytes() +} + +internal const val OVERFLOW_ZONE = Long.MIN_VALUE / 10L +internal const val OVERFLOW_DIGIT_START = Long.MIN_VALUE % 10L + 1 + +/** + * Reads a long from this source in signed decimal form (i.e., as a string in base 10 with + * optional leading '-'). This will iterate until a non-digit character is found. + * ``` + * Buffer buffer = new Buffer() + * .writeUtf8("8675309 -123 00001"); + * + * assertEquals(8675309L, buffer.readDecimalLong()); + * assertEquals(' ', buffer.readByte()); + * assertEquals(-123L, buffer.readDecimalLong()); + * assertEquals(' ', buffer.readByte()); + * assertEquals(1L, buffer.readDecimalLong()); + * ``` + * + * @throws NumberFormatException if the found digits do not fit into a `long` or a decimal + * number was not present. + */ +// TODO: add tests, seems like it may throw exceptions with incorrect messages +// TODO: test overflow detection +fun Source.readDecimalLong(): Long { + require(1) + var b = readByte() + var negative = false + var value = 0L + var seen = 0 + var overflowDigit = OVERFLOW_DIGIT_START + when (b) { + '-'.code.toByte() -> { + negative = true + overflowDigit-- + } + in '0'.code..'9'.code -> { + value = ('0'.code - b).toLong() + seen = 1 + } + else -> { + throw NumberFormatException("Expected a digit or '-' but was 0x${b.toHexString()}") + } + } + + while (request(1)) { + b = buffer[0] + if (b in '0'.code..'9'.code) { + val digit = '0'.code - b + readByte() // consume byte + + // Detect when the digit would cause an overflow. + if (value < OVERFLOW_ZONE || value == OVERFLOW_ZONE && digit < overflowDigit) { + val buffer = Buffer().writeDecimalLong(value).writeByte(b.toInt()) + if (!negative) buffer.readByte() // Skip negative sign. + throw NumberFormatException("Number too large: ${buffer.readUtf8()}") + } + value = value * 10L + digit + seen++ + } else { + break + } + } + + if (seen < 1) { + if (!request(1)) throw EOFException() + val expected = if (negative) "Expected a digit" else "Expected a digit or '-'" + throw NumberFormatException("$expected but was 0x${buffer[0].toHexString()}") + } + + return if (negative) value else -value +} + +/** + * Reads a long form this source in hexadecimal form (i.e., as a string in base 16). This will + * iterate until a non-hexadecimal character is found. + * ``` + * Buffer buffer = new Buffer() + * .writeUtf8("ffff CAFEBABE 10"); + * + * assertEquals(65535L, buffer.readHexadecimalUnsignedLong()); + * assertEquals(' ', buffer.readByte()); + * assertEquals(0xcafebabeL, buffer.readHexadecimalUnsignedLong()); + * assertEquals(' ', buffer.readByte()); + * assertEquals(0x10L, buffer.readHexadecimalUnsignedLong()); + * ``` + * + * @throws NumberFormatException if the found hexadecimal does not fit into a `long` or + * hexadecimal was not found. + */ +fun Source.readHexadecimalUnsignedLong(): Long { + require(1) + var b = readByte() + var result = when (b) { + in '0'.code..'9'.code -> b - '0'.code + in 'a'.code..'f'.code -> b - 'a'.code + 10 + in 'A'.code..'F'.code -> b - 'A'.code + 10 + else -> throw NumberFormatException("Expected leading [0-9a-fA-F] character but was 0x${b.toHexString()}") + }.toLong() + + while (request(1)) { + b = buffer[0] + val bDigit = when (b) { + in '0'.code..'9'.code -> b - '0'.code + in 'a'.code..'f'.code -> b - 'a'.code + 10 + in 'A'.code..'F'.code -> b - 'A'.code + 10 + else -> break + } + if (result and -0x1000000000000000L != 0L) { + val buffer = Buffer().writeHexadecimalUnsignedLong(result).writeByte(b.toInt()) + throw NumberFormatException("Number too large: " + buffer.readUtf8()) + } + readByte() // consume byte + result = result.shl(4) + bDigit + } + return result +} + +/** + * Removes and returns characters up to but not including the next line break. A line break is + * either `"\n"` or `"\r\n"`; these characters are not included in the result. + * ``` + * Buffer buffer = new Buffer() + * .writeUtf8("I'm a hacker!\n") + * .writeUtf8("That's what I said: you're a nerd.\n") + * .writeUtf8("I prefer to be called a hacker!\n"); + * assertEquals(81, buffer.size()); + * + * assertEquals("I'm a hacker!", buffer.readUtf8Line()); + * assertEquals(67, buffer.size()); + * + * assertEquals("That's what I said: you're a nerd.", buffer.readUtf8Line()); + * assertEquals(32, buffer.size()); + * + * assertEquals("I prefer to be called a hacker!", buffer.readUtf8Line()); + * assertEquals(0, buffer.size()); + * + * assertEquals(null, buffer.readUtf8Line()); + * assertEquals(0, buffer.size()); + * ``` + * + * **On the end of the stream this method returns null,** just like [java.io.BufferedReader]. If + * the source doesn't end with a line break then an implicit line break is assumed. Null is + * returned once the source is exhausted. Use this for human-generated data, where a trailing + * line break is optional. + */ +fun Source.readUtf8Line(): String? { + if (!request(1)) return null + + val peekSource = peek() + var offset = 0L + var newlineSize = 0L + while (peekSource.request(1)) { + val b = peekSource.readByte().toInt() + if (b == '\n'.code) { + newlineSize = 1L + break + } else if (b == '\r'.code) { + if (peekSource.request(1) && peekSource.buffer[0].toInt() == '\n'.code) { + newlineSize = 2L + break + } + } + offset++ + } + val line = readUtf8(offset) + skip(newlineSize) + return line +} + +/** + * Like [readUtf8LineStrict], except this allows the caller to specify the longest allowed match. + * Use this to protect against streams that may not include `"\n"` or `"\r\n"`. + * + * The returned string will have at most `limit` UTF-8 bytes, and the maximum number of bytes + * scanned is `limit + 2`. If `limit == 0` this will always throw an `EOFException` because no + * bytes will be scanned. + * + * This method is safe. No bytes are discarded if the match fails, and the caller is free to try + * another match: + * ``` + * Buffer buffer = new Buffer(); + * buffer.writeUtf8("12345\r\n"); + * + * // This will throw! There must be \r\n or \n at the limit or before it. + * buffer.readUtf8LineStrict(4); + * + * // No bytes have been consumed so the caller can retry. + * assertEquals("12345", buffer.readUtf8LineStrict(5)); + * ``` + */ +fun Source.readUtf8LineStrict(limit: Long = Long.MAX_VALUE): String { + if (!request(1)) throw EOFException() + + val peekSource = peek() + var offset = 0L + var newlineSize = 0L + while (offset < limit && peekSource.request(1)) { + val b = peekSource.readByte().toInt() + if (b == '\n'.code) { + newlineSize = 1L + break + } else if (b == '\r'.code) { + if (peekSource.request(1) && peekSource.buffer[0].toInt() == '\n'.code) { + newlineSize = 2L + break + } + } + offset++ + } + if (offset == limit) { + if (!peekSource.request(1)) throw EOFException() + val nlCandidate = peekSource.readByte().toInt() + if (nlCandidate == '\n'.code) { + newlineSize = 1 + } else if (nlCandidate == '\r'.code && peekSource.request(1) && peekSource.readByte().toInt() == '\n'.code) { + newlineSize = 2 + } + } + if (newlineSize == 0L) throw EOFException() + val line = readUtf8(offset) + skip(newlineSize) + return line +} + +/** + * Returns the index of the first `b` in the buffer at or after `fromIndex`. This expands the + * buffer as necessary until `b` is found. This reads an unbounded number of bytes into the + * buffer. Returns -1 if the stream is exhausted before the requested byte is found. + * ``` + * Buffer buffer = new Buffer(); + * buffer.writeUtf8("Don't move! He can't see us if we don't move."); + * + * byte m = 'm'; + * assertEquals(6, buffer.indexOf(m)); + * assertEquals(40, buffer.indexOf(m, 12)); + * ``` + */ +// fun Source.indexOf(b: Byte, fromIndex: Long = 0): Long { +// return 0 +//} + +/** + * Returns the index of `b` if it is found in the range of `fromIndex` inclusive to `toIndex` + * exclusive. If `b` isn't found, or if `fromIndex == toIndex`, then -1 is returned. + * + * The scan terminates at either `toIndex` or the end of the buffer, whichever comes first. The + * maximum number of bytes scanned is `toIndex-fromIndex`. + */ +fun Source.indexOf(b: Byte, fromIndex: Long = 0L, toIndex: Long = Long.MAX_VALUE): Long { + require(fromIndex in 0..toIndex) + if (fromIndex == toIndex) return -1L + + var offset = fromIndex + var peekSource = peek() + + if (!peekSource.request(offset)) { + return -1L + } + peekSource.skip(offset) + while (offset < toIndex && peekSource.request(1)) { + if (peekSource.readByte() == b) return offset + offset++ + } + return -1L +} + +// /** Equivalent to [indexOf(bytes, 0)][indexOf]. */ +// fun indexOf(bytes: ByteString): Long + +/** + * Returns the index of the first match for `bytes` in the buffer at or after `fromIndex`. This + * expands the buffer as necessary until `bytes` is found. This reads an unbounded number of + * bytes into the buffer. Returns -1 if the stream is exhausted before the requested bytes are + * found. + * ``` + * ByteString MOVE = ByteString.encodeUtf8("move"); + * + * Buffer buffer = new Buffer(); + * buffer.writeUtf8("Don't move! He can't see us if we don't move."); + * + * assertEquals(6, buffer.indexOf(MOVE)); + * assertEquals(40, buffer.indexOf(MOVE, 12)); + * ``` + */ +// fun indexOf(bytes: ByteString, fromIndex: Long): Long + +/** Equivalent to [indexOfElement(targetBytes, 0)][indexOfElement]. */ +// fun indexOfElement(targetBytes: ByteString): Long + +/** + * Returns the first index in this buffer that is at or after `fromIndex` and that contains any of + * the bytes in `targetBytes`. This expands the buffer as necessary until a target byte is found. + * This reads an unbounded number of bytes into the buffer. Returns -1 if the stream is exhausted + * before the requested byte is found. + * ``` + * ByteString ANY_VOWEL = ByteString.encodeUtf8("AEOIUaeoiu"); + * + * Buffer buffer = new Buffer(); + * buffer.writeUtf8("Dr. Alan Grant"); + * + * assertEquals(4, buffer.indexOfElement(ANY_VOWEL)); // 'A' in 'Alan'. + * assertEquals(11, buffer.indexOfElement(ANY_VOWEL, 9)); // 'a' in 'Grant'. + * ``` + */ +// fun indexOfElement(targetBytes: ByteString, fromIndex: Long): Long + +/** + * Returns true if the bytes at `offset` in this source equal `bytes`. This expands the buffer as + * necessary until a byte does not match, all bytes are matched, or if the stream is exhausted + * before enough bytes could determine a match. + * ``` + * ByteString simonSays = ByteString.encodeUtf8("Simon says:"); + * + * Buffer standOnOneLeg = new Buffer().writeUtf8("Simon says: Stand on one leg."); + * assertTrue(standOnOneLeg.rangeEquals(0, simonSays)); + * + * Buffer payMeMoney = new Buffer().writeUtf8("Pay me $1,000,000."); + * assertFalse(payMeMoney.rangeEquals(0, simonSays)); + * ``` + */ +// fun rangeEquals(offset: Long, bytes: ByteString): Boolean + +/** + * Returns true if `byteCount` bytes at `offset` in this source equal `bytes` at `bytesOffset`. + * This expands the buffer as necessary until a byte does not match, all bytes are matched, or if + * the stream is exhausted before enough bytes could determine a match. + */ +// fun rangeEquals(offset: Long, bytes: ByteString, bytesOffset: Int, byteCount: Int): Boolean diff --git a/core/common/src/internal/-Buffer.kt b/core/common/src/internal/-Buffer.kt index 2fbaaeb78..3e830c5a7 100644 --- a/core/common/src/internal/-Buffer.kt +++ b/core/common/src/internal/-Buffer.kt @@ -20,21 +20,15 @@ */ // TODO move to Buffer class: https://youtrack.jetbrains.com/issue/KT-20427 -@file:Suppress("NOTHING_TO_INLINE") package kotlinx.io.internal import kotlinx.io.* -import kotlinx.io.Buffer.UnsafeCursor import kotlin.native.concurrent.SharedImmutable @SharedImmutable internal val HEX_DIGIT_BYTES = "0123456789abcdef".asUtf8ToByteArray() -// Threshold determined empirically via ReadByteStringBenchmark -/** Create SegmentedByteString when size is greater than this many bytes. */ -internal const val SEGMENTING_THRESHOLD = 4096 - /** * Returns true if the range within this buffer starting at `segmentPos` in `segment` is equal to * `bytes[bytesOffset..bytesLimit)`. @@ -118,108 +112,6 @@ internal inline fun Buffer.seek( return lambda(s, offset) } } - -/** - * Returns the index of a value in options that is a prefix of this buffer. Returns -1 if no value - * is found. This method does two simultaneous iterations: it iterates the trie and it iterates - * this buffer. It returns when it reaches a result in the trie, when it mismatches in the trie, - * and when the buffer is exhausted. - * - * @param selectTruncated true to return -2 if a possible result is present but truncated. For - * example, this will return -2 if the buffer contains [ab] and the options are [abc, abd]. - * Note that this is made complicated by the fact that options are listed in preference order, - * and one option may be a prefix of another. For example, this returns -2 if the buffer - * contains [ab] and the options are [abc, a]. - */ -//internal fun Buffer.selectPrefix(options: Options, selectTruncated: Boolean = false): Int { -// val head = head ?: return if (selectTruncated) -2 else -1 -// -// var s: Segment? = head -// var data = head.data -// var pos = head.pos -// var limit = head.limit -// -// val trie = options.trie -// var triePos = 0 -// -// var prefixIndex = -1 -// -// navigateTrie@ -// while (true) { -// val scanOrSelect = trie[triePos++] -// -// val possiblePrefixIndex = trie[triePos++] -// if (possiblePrefixIndex != -1) { -// prefixIndex = possiblePrefixIndex -// } -// -// val nextStep: Int -// -// if (s == null) { -// break@navigateTrie -// } else if (scanOrSelect < 0) { -// // Scan: take multiple bytes from the buffer and the trie, looking for any mismatch. -// val scanByteCount = -1 * scanOrSelect -// val trieLimit = triePos + scanByteCount -// while (true) { -// val byte = data[pos++] and 0xff -// if (byte != trie[triePos++]) return prefixIndex // Fail 'cause we found a mismatch. -// val scanComplete = (triePos == trieLimit) -// -// // Advance to the next buffer segment if this one is exhausted. -// if (pos == limit) { -// s = s!!.next!! -// pos = s.pos -// data = s.data -// limit = s.limit -// if (s === head) { -// if (!scanComplete) break@navigateTrie // We were exhausted before the scan completed. -// s = null // We were exhausted at the end of the scan. -// } -// } -// -// if (scanComplete) { -// nextStep = trie[triePos] -// break -// } -// } -// } else { -// // Select: take one byte from the buffer and find a match in the trie. -// val selectChoiceCount = scanOrSelect -// val byte = data[pos++] and 0xff -// val selectLimit = triePos + selectChoiceCount -// while (true) { -// if (triePos == selectLimit) return prefixIndex // Fail 'cause we didn't find a match. -// -// if (byte == trie[triePos]) { -// nextStep = trie[triePos + selectChoiceCount] -// break -// } -// -// triePos++ -// } -// -// // Advance to the next buffer segment if this one is exhausted. -// if (pos == limit) { -// s = s.next!! -// pos = s.pos -// data = s.data -// limit = s.limit -// if (s === head) { -// s = null // No more segments! The next trie node will be our last. -// } -// } -// } -// -// if (nextStep >= 0) return nextStep // Found a matching option. -// triePos = -nextStep // Found another node to continue the search. -// } -// -// // We break out of the loop above when we've exhausted the buffer without exhausting the trie. -// if (selectTruncated) return -2 // The buffer is a prefix of at least one option. -// return prefixIndex // Return any matches we encountered while searching for a deeper match. -//} - // 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. @@ -1272,121 +1164,6 @@ internal inline fun Buffer.commonIndexOf(b: Byte, fromIndex: Long, toIndex: Long } } -//internal inline fun Buffer.commonIndexOf(bytes: ByteString, fromIndex: Long): Long { -// var fromIndex = fromIndex -// require(bytes.size > 0) { "bytes is empty" } -// require(fromIndex >= 0L) { "fromIndex < 0: $fromIndex" } -// -// seek(fromIndex) { s, offset -> -// var s = s ?: return -1L -// var offset = offset -// -// // Scan through the segments, searching for the lead byte. Each time that is found, delegate -// // to rangeEquals() to check for a complete match. -// val targetByteArray = bytes.internalArray() -// val b0 = targetByteArray[0] -// val bytesSize = bytes.size -// val resultLimit = size - bytesSize + 1L -// while (offset < resultLimit) { -// // Scan through the current segment. -// val data = s.data -// val segmentLimit = kotlinx.io.minOf(s.limit, s.pos + resultLimit - offset).toInt() -// for (pos in (s.pos + fromIndex - offset).toInt() until segmentLimit) { -// if (data[pos] == b0 && rangeEquals(s, pos + 1, targetByteArray, 1, bytesSize)) { -// return pos - s.pos + offset -// } -// } -// -// // Not in this segment. Try the next one. -// offset += (s.limit - s.pos).toLong() -// fromIndex = offset -// s = s.next!! -// } -// -// return -1L -// } -//} - -//internal inline fun Buffer.commonIndexOfElement(targetBytes: ByteString, fromIndex: Long): Long { -// var fromIndex = fromIndex -// require(fromIndex >= 0L) { "fromIndex < 0: $fromIndex" } -// -// seek(fromIndex) { s, offset -> -// var s = s ?: return -1L -// var offset = offset -// -// // Special case searching for one of two bytes. This is a common case for tools like Moshi, -// // which search for pairs of chars like `\r` and `\n` or {@code `"` and `\`. The impact of this -// // optimization is a ~5x speedup for this case without a substantial cost to other cases. -// if (targetBytes.size == 2) { -// // Scan through the segments, searching for either of the two bytes. -// val b0 = targetBytes[0] -// val b1 = targetBytes[1] -// while (offset < size) { -// val data = s.data -// var pos = (s.pos + fromIndex - offset).toInt() -// val limit = s.limit -// while (pos < limit) { -// val b = data[pos].toInt() -// if (b == b0.toInt() || b == b1.toInt()) { -// return pos - s.pos + offset -// } -// pos++ -// } -// -// // Not in this segment. Try the next one. -// offset += (s.limit - s.pos).toLong() -// fromIndex = offset -// s = s.next!! -// } -// } else { -// // Scan through the segments, searching for a byte that's also in the array. -// val targetByteArray = targetBytes.internalArray() -// while (offset < size) { -// val data = s.data -// var pos = (s.pos + fromIndex - offset).toInt() -// val limit = s.limit -// while (pos < limit) { -// val b = data[pos].toInt() -// for (t in targetByteArray) { -// if (b == t.toInt()) return pos - s.pos + offset -// } -// pos++ -// } -// -// // Not in this segment. Try the next one. -// offset += (s.limit - s.pos).toLong() -// fromIndex = offset -// s = s.next!! -// } -// } -// -// return -1L -// } -//} - -//internal inline fun Buffer.commonRangeEquals( -// offset: Long, -// bytes: ByteString, -// bytesOffset: Int, -// byteCount: Int -//): Boolean { -// if (offset < 0L || -// bytesOffset < 0 || -// byteCount < 0 || -// size - offset < byteCount || -// bytes.size - bytesOffset < byteCount -// ) { -// return false -// } -// for (i in 0 until byteCount) { -// if (this[offset + i] != bytes[bytesOffset + i]) { -// return false -// } -// } -// return true -//} - internal inline fun Buffer.commonEquals(other: Any?): Boolean { if (this === other) return true if (other !is Buffer) return false @@ -1534,187 +1311,4 @@ internal inline fun forEachSegment( pos = nextSegmentOffset s++ } -} - -internal fun Buffer.commonReadUnsafe(unsafeCursor: UnsafeCursor): UnsafeCursor { - val unsafeCursor = resolveDefaultParameter(unsafeCursor) - check(unsafeCursor.buffer == null) { "already attached to a buffer" } - - unsafeCursor.buffer = this - unsafeCursor.readWrite = false - return unsafeCursor -} - -internal fun Buffer.commonReadAndWriteUnsafe(unsafeCursor: UnsafeCursor): UnsafeCursor { - val unsafeCursor = resolveDefaultParameter(unsafeCursor) - check(unsafeCursor.buffer == null) { "already attached to a buffer" } - - unsafeCursor.buffer = this - unsafeCursor.readWrite = true - return unsafeCursor -} - -internal inline fun UnsafeCursor.commonNext(): Int { - check(offset != buffer!!.size) { "no more bytes" } - return if (offset == -1L) seek(0L) else seek(offset + (end - start)) -} - -internal inline fun UnsafeCursor.commonSeek(offset: Long): Int { - val buffer = checkNotNull(buffer) { "not attached to a buffer" } - if (offset < -1 || offset > buffer.size) { - throw ArrayIndexOutOfBoundsException("offset=$offset > size=${buffer.size}") - } - - if (offset == -1L || offset == buffer.size) { - this.segment = null - this.offset = offset - this.data = null - this.start = -1 - this.end = -1 - return -1 - } - - // Navigate to the segment that contains `offset`. Start from our current segment if possible. - var min = 0L - var max = buffer.size - var head = buffer.head - var tail = buffer.head - if (this.segment != null) { - val segmentOffset = this.offset - (this.start - this.segment!!.pos) - if (segmentOffset > offset) { - // Set the cursor segment to be the 'end' - max = segmentOffset - tail = this.segment - } else { - // Set the cursor segment to be the 'beginning' - min = segmentOffset - head = this.segment - } - } - - var next: Segment? - var nextOffset: Long - if (max - offset > offset - min) { - // Start at the 'beginning' and search forwards - next = head - nextOffset = min - while (offset >= nextOffset + (next!!.limit - next.pos)) { - nextOffset += (next.limit - next.pos).toLong() - next = next.next - } - } else { - // Start at the 'end' and search backwards - next = tail - nextOffset = max - while (nextOffset > offset) { - next = next!!.prev - nextOffset -= (next!!.limit - next.pos).toLong() - } - } - - // If we're going to write and our segment is shared, swap it for a read-write one. - if (readWrite && next!!.shared) { - val unsharedNext = next.unsharedCopy() - if (buffer.head === next) { - buffer.head = unsharedNext - } - next = next.push(unsharedNext) - next.prev!!.pop() - } - - // Update this cursor to the requested offset within the found segment. - this.segment = next - this.offset = offset - this.data = next!!.data - this.start = next.pos + (offset - nextOffset).toInt() - this.end = next.limit - return end - start -} - -internal inline fun UnsafeCursor.commonResizeBuffer(newSize: Long): Long { - val buffer = checkNotNull(buffer) { "not attached to a buffer" } - check(readWrite) { "resizeBuffer() only permitted for read/write buffers" } - - val oldSize = buffer.size - if (newSize <= oldSize) { - require(newSize >= 0L) { "newSize < 0: $newSize" } - // Shrink the buffer by either shrinking segments or removing them. - var bytesToSubtract = oldSize - newSize - while (bytesToSubtract > 0L) { - val tail = buffer.head!!.prev - val tailSize = tail!!.limit - tail.pos - if (tailSize <= bytesToSubtract) { - buffer.head = tail.pop() - SegmentPool.recycle(tail) - bytesToSubtract -= tailSize.toLong() - } else { - tail.limit -= bytesToSubtract.toInt() - break - } - } - // Seek to the end. - this.segment = null - this.offset = newSize - this.data = null - this.start = -1 - this.end = -1 - } else if (newSize > oldSize) { - // Enlarge the buffer by either enlarging segments or adding them. - var needsToSeek = true - var bytesToAdd = newSize - oldSize - while (bytesToAdd > 0L) { - val tail = buffer.writableSegment(1) - val segmentBytesToAdd = minOf(bytesToAdd, Segment.SIZE - tail.limit).toInt() - tail.limit += segmentBytesToAdd - bytesToAdd -= segmentBytesToAdd.toLong() - - // If this is the first segment we're adding, seek to it. - if (needsToSeek) { - this.segment = tail - this.offset = oldSize - this.data = tail.data - this.start = tail.limit - segmentBytesToAdd - this.end = tail.limit - needsToSeek = false - } - } - } - - buffer.size = newSize - - return oldSize -} - -internal inline fun UnsafeCursor.commonExpandBuffer(minByteCount: Int): Long { - require(minByteCount > 0) { "minByteCount <= 0: $minByteCount" } - require(minByteCount <= Segment.SIZE) { "minByteCount > Segment.SIZE: $minByteCount" } - val buffer = checkNotNull(buffer) { "not attached to a buffer" } - check(readWrite) { "expandBuffer() only permitted for read/write buffers" } - - val oldSize = buffer.size - val tail = buffer.writableSegment(minByteCount) - val result = Segment.SIZE - tail.limit - tail.limit = Segment.SIZE - buffer.size = oldSize + result - - // Seek to the old size. - this.segment = tail - this.offset = oldSize - this.data = tail.data - this.start = Segment.SIZE - result - this.end = Segment.SIZE - - return result.toLong() -} - -internal inline fun UnsafeCursor.commonClose() { - // TODO(jwilson): use edit counts or other information to track unexpected changes? - check(buffer != null) { "not attached to a buffer" } - - buffer = null - segment = null - offset = -1L - data = null - start = -1 - end = -1 -} +} \ No newline at end of file diff --git a/core/common/src/internal/-RealBufferedSource.kt b/core/common/src/internal/-RealBufferedSource.kt index 5a3e9e0fd..4fc429593 100644 --- a/core/common/src/internal/-RealBufferedSource.kt +++ b/core/common/src/internal/-RealBufferedSource.kt @@ -20,7 +20,6 @@ */ // TODO move to RealBufferedSource class: https://youtrack.jetbrains.com/issue/KT-20427 -@file:Suppress("NOTHING_TO_INLINE") package kotlinx.io.internal @@ -294,61 +293,6 @@ internal inline fun RealSource.commonIndexOf(b: Byte, fromIndex: Long, toIndex: return -1L } -//internal inline fun RealSource.commonIndexOf(bytes: ByteString, fromIndex: Long): Long { -// var fromIndex = fromIndex -// check(!closed) { "closed" } -// -// while (true) { -// val result = buffer.indexOf(bytes, fromIndex) -// if (result != -1L) return result -// -// val lastBufferSize = buffer.size -// if (source.read(buffer, Segment.SIZE.toLong()) == -1L) return -1L -// -// // Keep searching, picking up from where we left off. -// fromIndex = maxOf(fromIndex, lastBufferSize - bytes.size + 1) -// } -//} -// -//internal inline fun RealSource.commonIndexOfElement(targetBytes: ByteString, fromIndex: Long): Long { -// var fromIndex = fromIndex -// check(!closed) { "closed" } -// -// while (true) { -// val result = buffer.indexOfElement(targetBytes, fromIndex) -// if (result != -1L) return result -// -// val lastBufferSize = buffer.size -// if (source.read(buffer, Segment.SIZE.toLong()) == -1L) return -1L -// -// // Keep searching, picking up from where we left off. -// fromIndex = maxOf(fromIndex, lastBufferSize) -// } -//} - -//internal inline fun RealSource.commonRangeEquals( -// offset: Long, -// bytes: ByteString, -// bytesOffset: Int, -// byteCount: Int -//): Boolean { -// check(!closed) { "closed" } -// -// if (offset < 0L || -// bytesOffset < 0 || -// byteCount < 0 || -// bytes.size - bytesOffset < byteCount -// ) { -// return false -// } -// for (i in 0 until byteCount) { -// val bufferOffset = offset + i -// if (!request(bufferOffset + 1)) return false -// if (buffer[bufferOffset] != bytes[bytesOffset + i]) return false -// } -// return true -//} - internal inline fun RealSource.commonPeek(): Source { return PeekSource(this).buffer() } diff --git a/core/common/test/AbstractBufferedSinkTest.kt b/core/common/test/AbstractSinkTest.kt similarity index 85% rename from core/common/test/AbstractBufferedSinkTest.kt rename to core/common/test/AbstractSinkTest.kt index a17f8b9a6..f1977b389 100644 --- a/core/common/test/AbstractBufferedSinkTest.kt +++ b/core/common/test/AbstractSinkTest.kt @@ -25,11 +25,11 @@ import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFailsWith -class BufferSinkTest : AbstractBufferedSinkTest(BufferedSinkFactory.BUFFER) -class RealBufferedSinkTest : AbstractBufferedSinkTest(BufferedSinkFactory.REAL_BUFFERED_SINK) +class BufferSinkTest : AbstractSinkTest(SinkFactory.BUFFER) +class RealSinkTest : AbstractSinkTest(SinkFactory.REAL_BUFFERED_SINK) -abstract class AbstractBufferedSinkTest internal constructor( - factory: BufferedSinkFactory +abstract class AbstractSinkTest internal constructor( + factory: SinkFactory ) { private val data: Buffer = Buffer() private val sink: Sink = factory.create(data) @@ -119,42 +119,6 @@ abstract class AbstractBufferedSinkTest internal constructor( assertEquals("[hex=2143658701efcdab005cb1b0bebafeca]", data.toString()) } -// @Test fun writeByteString() { -// sink.write("təˈranəˌsôr".encodeUtf8()) -// sink.flush() -// assertEquals("74c999cb8872616ec999cb8c73c3b472".decodeHex(), data.readByteString()) -// } -// -// @Test fun writeByteStringOffset() { -// sink.write("təˈranəˌsôr".encodeUtf8(), 5, 5) -// sink.flush() -// assertEquals("72616ec999".decodeHex(), data.readByteString()) -// } -// -// @Test fun writeSegmentedByteString() { -// sink.write(Buffer().write("təˈranəˌsôr".encodeUtf8()).snapshot()) -// sink.flush() -// assertEquals("74c999cb8872616ec999cb8c73c3b472".decodeHex(), data.readByteString()) -// } -// -// @Test fun writeSegmentedByteStringOffset() { -// sink.write(Buffer().write("təˈranəˌsôr".encodeUtf8()).snapshot(), 5, 5) -// sink.flush() -// assertEquals("72616ec999".decodeHex(), data.readByteString()) -// } -// -// @Test fun writeStringUtf8() { -// sink.writeUtf8("təˈranəˌsôr") -// sink.flush() -// assertEquals("74c999cb8872616ec999cb8c73c3b472".decodeHex(), data.readByteString()) -// } -// -// @Test fun writeSubstringUtf8() { -// sink.writeUtf8("təˈranəˌsôr", 3, 7) -// sink.flush() -// assertEquals("72616ec999".decodeHex(), data.readByteString()) -// } - @Test fun writeAll() { val source = Buffer().writeUtf8("abcdef") diff --git a/core/common/test/AbstractBufferedSourceTest.kt b/core/common/test/AbstractSourceTest.kt similarity index 68% rename from core/common/test/AbstractBufferedSourceTest.kt rename to core/common/test/AbstractSourceTest.kt index 36b92d9d2..6c48f0f44 100644 --- a/core/common/test/AbstractBufferedSourceTest.kt +++ b/core/common/test/AbstractSourceTest.kt @@ -27,15 +27,15 @@ import kotlin.test.assertFailsWith import kotlin.test.assertFalse import kotlin.test.assertTrue -class BufferSourceTest : AbstractBufferedSourceTest(BufferedSourceFactory.BUFFER) -class RealBufferedSourceTest : AbstractBufferedSourceTest(BufferedSourceFactory.REAL_BUFFERED_SOURCE) -class OneByteAtATimeBufferedSourceTest : AbstractBufferedSourceTest(BufferedSourceFactory.ONE_BYTE_AT_A_TIME_BUFFERED_SOURCE) -class OneByteAtATimeBufferTest : AbstractBufferedSourceTest(BufferedSourceFactory.ONE_BYTE_AT_A_TIME_BUFFER) -class PeekBufferTest : AbstractBufferedSourceTest(BufferedSourceFactory.PEEK_BUFFER) -class PeekBufferedSourceTest : AbstractBufferedSourceTest(BufferedSourceFactory.PEEK_BUFFERED_SOURCE) +class BufferSourceTest : AbstractBufferedSourceTest(SourceFactory.BUFFER) +class RealBufferedSourceTest : AbstractBufferedSourceTest(SourceFactory.REAL_BUFFERED_SOURCE) +class OneByteAtATimeBufferedSourceTest : AbstractBufferedSourceTest(SourceFactory.ONE_BYTE_AT_A_TIME_BUFFERED_SOURCE) +class OneByteAtATimeBufferTest : AbstractBufferedSourceTest(SourceFactory.ONE_BYTE_AT_A_TIME_BUFFER) +class PeekBufferTest : AbstractBufferedSourceTest(SourceFactory.PEEK_BUFFER) +class PeekBufferedSourceTest : AbstractBufferedSourceTest(SourceFactory.PEEK_BUFFERED_SOURCE) abstract class AbstractBufferedSourceTest internal constructor( - private val factory: BufferedSourceFactory + private val factory: SourceFactory ) { private val sink: Sink private val source: Source @@ -626,151 +626,6 @@ abstract class AbstractBufferedSourceTest internal constructor( } } -// @Test fun indexOfByteString() { -// assertEquals(-1, source.indexOf("flop".encodeUtf8())) -// -// sink.writeUtf8("flip flop") -// sink.emit() -// assertEquals(5, source.indexOf("flop".encodeUtf8())) -// source.readUtf8() // Clear stream. -// -// // Make sure we backtrack and resume searching after partial match. -// sink.writeUtf8("hi hi hi hey") -// sink.emit() -// assertEquals(3, source.indexOf("hi hi hey".encodeUtf8())) -// } -// -// @Test fun indexOfByteStringAtSegmentBoundary() { -// sink.writeUtf8("a".repeat(Segment.SIZE - 1)) -// sink.writeUtf8("bcd") -// sink.emit() -// assertEquals( -// (Segment.SIZE - 3).toLong(), -// source.indexOf("aabc".encodeUtf8(), (Segment.SIZE - 4).toLong()) -// ) -// assertEquals( -// (Segment.SIZE - 3).toLong(), -// source.indexOf("aabc".encodeUtf8(), (Segment.SIZE - 3).toLong()) -// ) -// assertEquals( -// (Segment.SIZE - 2).toLong(), -// source.indexOf("abcd".encodeUtf8(), (Segment.SIZE - 2).toLong()) -// ) -// assertEquals( -// (Segment.SIZE - 2).toLong(), -// source.indexOf("abc".encodeUtf8(), (Segment.SIZE - 2).toLong()) -// ) -// assertEquals( -// (Segment.SIZE - 2).toLong(), -// source.indexOf("abc".encodeUtf8(), (Segment.SIZE - 2).toLong()) -// ) -// assertEquals( -// (Segment.SIZE - 2).toLong(), -// source.indexOf("ab".encodeUtf8(), (Segment.SIZE - 2).toLong()) -// ) -// assertEquals( -// (Segment.SIZE - 2).toLong(), -// source.indexOf("a".encodeUtf8(), (Segment.SIZE - 2).toLong()) -// ) -// assertEquals( -// (Segment.SIZE - 1).toLong(), -// source.indexOf("bc".encodeUtf8(), (Segment.SIZE - 2).toLong()) -// ) -// assertEquals( -// (Segment.SIZE - 1).toLong(), -// source.indexOf("b".encodeUtf8(), (Segment.SIZE - 2).toLong()) -// ) -// assertEquals( -// Segment.SIZE.toLong(), -// source.indexOf("c".encodeUtf8(), (Segment.SIZE - 2).toLong()) -// ) -// assertEquals( -// Segment.SIZE.toLong(), -// source.indexOf("c".encodeUtf8(), Segment.SIZE.toLong()) -// ) -// assertEquals( -// (Segment.SIZE + 1).toLong(), -// source.indexOf("d".encodeUtf8(), (Segment.SIZE - 2).toLong()) -// ) -// assertEquals( -// (Segment.SIZE + 1).toLong(), -// source.indexOf("d".encodeUtf8(), (Segment.SIZE + 1).toLong()) -// ) -// } -// -// @Test fun indexOfDoesNotWrapAround() { -// sink.writeUtf8("a".repeat(Segment.SIZE - 1)) -// sink.writeUtf8("bcd") -// sink.emit() -// assertEquals(-1, source.indexOf("abcda".encodeUtf8(), (Segment.SIZE - 3).toLong())) -// } -// -// @Test fun indexOfByteStringWithOffset() { -// assertEquals(-1, source.indexOf("flop".encodeUtf8(), 1)) -// -// sink.writeUtf8("flop flip flop") -// sink.emit() -// assertEquals(10, source.indexOf("flop".encodeUtf8(), 1)) -// source.readUtf8() // Clear stream -// -// // Make sure we backtrack and resume searching after partial match. -// sink.writeUtf8("hi hi hi hi hey") -// sink.emit() -// assertEquals(6, source.indexOf("hi hi hey".encodeUtf8(), 1)) -// } -// -// @Test fun indexOfByteStringInvalidArgumentsThrows() { -// var e = assertFailsWith { -// source.indexOf(ByteString.of()) -// } -// assertEquals("bytes is empty", e.message) -// -// e = assertFailsWith { -// source.indexOf("hi".encodeUtf8(), -1) -// } -// assertEquals("fromIndex < 0: -1", e.message) -// } -// -// /** -// * With [BufferedSourceFactory.ONE_BYTE_AT_A_TIME_BUFFERED_SOURCE], this code was extremely slow. -// * https://github.com/square/kotlinx.io/issues/171 -// */ -// @Test fun indexOfByteStringAcrossSegmentBoundaries() { -// sink.writeUtf8("a".repeat(Segment.SIZE * 2 - 3)) -// sink.writeUtf8("bcdefg") -// sink.emit() -// assertEquals((Segment.SIZE * 2 - 4).toLong(), source.indexOf("ab".encodeUtf8())) -// assertEquals((Segment.SIZE * 2 - 4).toLong(), source.indexOf("abc".encodeUtf8())) -// assertEquals((Segment.SIZE * 2 - 4).toLong(), source.indexOf("abcd".encodeUtf8())) -// assertEquals((Segment.SIZE * 2 - 4).toLong(), source.indexOf("abcde".encodeUtf8())) -// assertEquals((Segment.SIZE * 2 - 4).toLong(), source.indexOf("abcdef".encodeUtf8())) -// assertEquals((Segment.SIZE * 2 - 4).toLong(), source.indexOf("abcdefg".encodeUtf8())) -// assertEquals((Segment.SIZE * 2 - 3).toLong(), source.indexOf("bcdefg".encodeUtf8())) -// assertEquals((Segment.SIZE * 2 - 2).toLong(), source.indexOf("cdefg".encodeUtf8())) -// assertEquals((Segment.SIZE * 2 - 1).toLong(), source.indexOf("defg".encodeUtf8())) -// assertEquals((Segment.SIZE * 2).toLong(), source.indexOf("efg".encodeUtf8())) -// assertEquals((Segment.SIZE * 2 + 1).toLong(), source.indexOf("fg".encodeUtf8())) -// assertEquals((Segment.SIZE * 2 + 2).toLong(), source.indexOf("g".encodeUtf8())) -// } -// -// @Test fun indexOfElement() { -// sink.writeUtf8("a").writeUtf8("b".repeat(Segment.SIZE)).writeUtf8("c") -// sink.emit() -// assertEquals(0, source.indexOfElement("DEFGaHIJK".encodeUtf8())) -// assertEquals(1, source.indexOfElement("DEFGHIJKb".encodeUtf8())) -// assertEquals((Segment.SIZE + 1).toLong(), source.indexOfElement("cDEFGHIJK".encodeUtf8())) -// assertEquals(1, source.indexOfElement("DEFbGHIc".encodeUtf8())) -// assertEquals(-1L, source.indexOfElement("DEFGHIJK".encodeUtf8())) -// assertEquals(-1L, source.indexOfElement("".encodeUtf8())) -// } -// -// @Test fun indexOfElementWithOffset() { -// sink.writeUtf8("a").writeUtf8("b".repeat(Segment.SIZE)).writeUtf8("c") -// sink.emit() -// assertEquals(-1, source.indexOfElement("DEFGaHIJK".encodeUtf8(), 1)) -// assertEquals(15, source.indexOfElement("DEFGHIJKb".encodeUtf8(), 15)) -// } - @Test fun indexOfByteWithFromIndex() { sink.writeUtf8("aaa") sink.emit() @@ -780,24 +635,6 @@ abstract class AbstractBufferedSourceTest internal constructor( assertEquals(2, source.indexOf('a'.code.toByte(), 2)) } -// @Test fun indexOfByteStringWithFromIndex() { -// sink.writeUtf8("aaa") -// sink.emit() -// assertEquals(0, source.indexOf("a".encodeUtf8())) -// assertEquals(0, source.indexOf("a".encodeUtf8(), 0)) -// assertEquals(1, source.indexOf("a".encodeUtf8(), 1)) -// assertEquals(2, source.indexOf("a".encodeUtf8(), 2)) -// } -// -// @Test fun indexOfElementWithFromIndex() { -// sink.writeUtf8("aaa") -// sink.emit() -// assertEquals(0, source.indexOfElement("a".encodeUtf8())) -// assertEquals(0, source.indexOfElement("a".encodeUtf8(), 0)) -// assertEquals(1, source.indexOfElement("a".encodeUtf8(), 1)) -// assertEquals(2, source.indexOfElement("a".encodeUtf8(), 2)) -// } - @Test fun request() { sink.writeUtf8("a").writeUtf8("b".repeat(Segment.SIZE)).writeUtf8("c") sink.emit() @@ -968,23 +805,23 @@ abstract class AbstractBufferedSourceTest internal constructor( } } -// @Test fun codePoints() { -// sink.write("7f".decodeHex()) -// sink.emit() -// assertEquals(0x7f, source.readUtf8CodePoint().toLong()) -// -// sink.write("dfbf".decodeHex()) -// sink.emit() -// assertEquals(0x07ff, source.readUtf8CodePoint().toLong()) -// -// sink.write("efbfbf".decodeHex()) -// sink.emit() -// assertEquals(0xffff, source.readUtf8CodePoint().toLong()) -// -// sink.write("f48fbfbf".decodeHex()) -// sink.emit() -// assertEquals(0x10ffff, source.readUtf8CodePoint().toLong()) -// } + @Test fun codePoints() { + sink.writeByte(0x7f) + sink.emit() + assertEquals(0x7f, source.readUtf8CodePoint().toLong()) + + sink.writeByte(0xdf).writeByte(0xbf) + sink.emit() + assertEquals(0x07ff, source.readUtf8CodePoint().toLong()) + + sink.writeByte(0xef).writeByte(0xbf).writeByte(0xbf) + sink.emit() + assertEquals(0xffff, source.readUtf8CodePoint().toLong()) + + sink.writeByte(0xf4).writeByte(0x8f).writeByte(0xbf).writeByte(0xbf) + sink.emit() + assertEquals(0x10ffff, source.readUtf8CodePoint().toLong()) + } @Test fun decimalStringWithManyLeadingZeros() { assertLongDecimalString("00000000000000001", 1) @@ -993,109 +830,6 @@ abstract class AbstractBufferedSourceTest internal constructor( assertLongDecimalString("0".repeat(Segment.SIZE + 1) + "1", 1) } -// @Test fun select() { -// val options = Options.of( -// "ROCK".encodeUtf8(), -// "SCISSORS".encodeUtf8(), -// "PAPER".encodeUtf8() -// ) -// -// sink.writeUtf8("PAPER,SCISSORS,ROCK") -// sink.emit() -// assertEquals(2, source.select(options).toLong()) -// assertEquals(','.code.toLong(), source.readByte().toLong()) -// assertEquals(1, source.select(options).toLong()) -// assertEquals(','.code.toLong(), source.readByte().toLong()) -// assertEquals(0, source.select(options).toLong()) -// assertTrue(source.exhausted()) -// } -// -// /** Note that this test crashes the VM on Android. */ -// @Test fun selectSpanningMultipleSegments() { -// if (factory.isOneByteAtATime && isBrowser()) { -// return // This test times out on browsers. -// } -// val commonPrefix = randomBytes(Segment.SIZE + 10) -// val a = Buffer().write(commonPrefix).writeUtf8("a").readByteString() -// val bc = Buffer().write(commonPrefix).writeUtf8("bc").readByteString() -// val bd = Buffer().write(commonPrefix).writeUtf8("bd").readByteString() -// val options = Options.of(a, bc, bd) -// -// sink.write(bd) -// sink.write(a) -// sink.write(bc) -// sink.emit() -// -// assertEquals(2, source.select(options).toLong()) -// assertEquals(0, source.select(options).toLong()) -// assertEquals(1, source.select(options).toLong()) -// assertTrue(source.exhausted()) -// } -// -// @Test fun selectNotFound() { -// val options = Options.of( -// "ROCK".encodeUtf8(), -// "SCISSORS".encodeUtf8(), -// "PAPER".encodeUtf8() -// ) -// -// sink.writeUtf8("SPOCK") -// sink.emit() -// assertEquals(-1, source.select(options).toLong()) -// assertEquals("SPOCK", source.readUtf8()) -// } -// -// @Test fun selectValuesHaveCommonPrefix() { -// val options = Options.of( -// "abcd".encodeUtf8(), -// "abce".encodeUtf8(), -// "abcc".encodeUtf8() -// ) -// -// sink.writeUtf8("abcc").writeUtf8("abcd").writeUtf8("abce") -// sink.emit() -// assertEquals(2, source.select(options).toLong()) -// assertEquals(0, source.select(options).toLong()) -// assertEquals(1, source.select(options).toLong()) -// } -// -// @Test fun selectLongerThanSource() { -// val options = Options.of( -// "abcd".encodeUtf8(), -// "abce".encodeUtf8(), -// "abcc".encodeUtf8() -// ) -// sink.writeUtf8("abc") -// sink.emit() -// assertEquals(-1, source.select(options).toLong()) -// assertEquals("abc", source.readUtf8()) -// } -// -// @Test fun selectReturnsFirstByteStringThatMatches() { -// val options = Options.of( -// "abcd".encodeUtf8(), -// "abc".encodeUtf8(), -// "abcde".encodeUtf8() -// ) -// sink.writeUtf8("abcdef") -// sink.emit() -// assertEquals(0, source.select(options).toLong()) -// assertEquals("ef", source.readUtf8()) -// } -// -// @Test fun selectFromEmptySource() { -// val options = Options.of( -// "abc".encodeUtf8(), -// "def".encodeUtf8() -// ) -// assertEquals(-1, source.select(options).toLong()) -// } -// -// @Test fun selectNoByteStringsFromEmptySource() { -// val options = Options.of() -// assertEquals(-1, source.select(options).toLong()) -// } - @Test fun peek() { sink.writeUtf8("abcdefghi") sink.emit() @@ -1227,47 +961,6 @@ abstract class AbstractBufferedSourceTest internal constructor( assertEquals("ghi", peek.readUtf8(3L)) } -// @Test fun rangeEquals() { -// sink.writeUtf8("A man, a plan, a canal. Panama.") -// sink.emit() -// assertTrue(source.rangeEquals(7, "a plan".encodeUtf8())) -// assertTrue(source.rangeEquals(0, "A man".encodeUtf8())) -// assertTrue(source.rangeEquals(24, "Panama".encodeUtf8())) -// assertFalse(source.rangeEquals(24, "Panama. Panama. Panama.".encodeUtf8())) -// } -// -// @Test fun rangeEqualsWithOffsetAndCount() { -// sink.writeUtf8("A man, a plan, a canal. Panama.") -// sink.emit() -// assertTrue(source.rangeEquals(7, "aaa plannn".encodeUtf8(), 2, 6)) -// assertTrue(source.rangeEquals(0, "AAA mannn".encodeUtf8(), 2, 5)) -// assertTrue(source.rangeEquals(24, "PPPanamaaa".encodeUtf8(), 2, 6)) -// } -// -// @Test fun rangeEqualsOnlyReadsUntilMismatch() { -// if (factory !== BufferedSourceFactory.ONE_BYTE_AT_A_TIME_BUFFERED_SOURCE) return // Other sources read in chunks anyway. -// -// sink.writeUtf8("A man, a plan, a canal. Panama.") -// sink.emit() -// assertFalse(source.rangeEquals(0, ("A man.").encodeUtf8())) -// assertEquals("A man,", source.buffer.readUtf8()) -// } -// -// @Test fun rangeEqualsArgumentValidation() { -// // Negative source offset. -// assertFalse(source.rangeEquals(-1, "A".encodeUtf8())) -// // Negative bytes offset. -// assertFalse(source.rangeEquals(0, "A".encodeUtf8(), -1, 1)) -// // Bytes offset longer than bytes length. -// assertFalse(source.rangeEquals(0, "A".encodeUtf8(), 2, 1)) -// // Negative byte count. -// assertFalse(source.rangeEquals(0, "A".encodeUtf8(), 0, -1)) -// // Byte count longer than bytes length. -// assertFalse(source.rangeEquals(0, "A".encodeUtf8(), 0, 2)) -// // Bytes offset plus byte count longer than bytes length. -// assertFalse(source.rangeEquals(0, "A".encodeUtf8(), 1, 1)) -// } - @Test fun factorySegmentSizes() { sink.writeUtf8("abc") sink.emit() @@ -1278,4 +971,61 @@ abstract class AbstractBufferedSourceTest internal constructor( assertEquals(listOf(3), segmentSizes(source.buffer)) } } + + @Test fun readUtf8Line() { + val buf = Buffer().writeUtf8("first line\nsecond line\n") + assertEquals("first line", buf.readUtf8Line()) + assertEquals("second line\n", buf.readUtf8()) + assertEquals(null, buf.readUtf8Line()) + + buf.writeUtf8("\nnext line\n") + assertEquals("", buf.readUtf8Line()) + assertEquals("next line", buf.readUtf8Line()) + + buf.writeUtf8("There is no newline!") + assertEquals("There is no newline!", buf.readUtf8Line()) + + buf.writeUtf8("Wot do u call it?\r\nWindows") + assertEquals("Wot do u call it?", buf.readUtf8Line()) + buf.clear() + + buf.writeUtf8("reo\rde\red\n") + assertEquals("reo\rde\red", buf.readUtf8Line()) + } + + @Test fun readUtf8LineStrict() { + val buf = Buffer().writeUtf8("first line\nsecond line\n") + assertEquals("first line", buf.readUtf8LineStrict()) + assertEquals("second line\n", buf.readUtf8()) + assertFailsWith { buf.readUtf8LineStrict() } + + buf.writeUtf8("\nnext line\n") + assertEquals("", buf.readUtf8LineStrict()) + assertEquals("next line", buf.readUtf8LineStrict()) + + buf.writeUtf8("There is no newline!") + assertFailsWith { buf.readUtf8LineStrict() } + assertEquals("There is no newline!", buf.readUtf8()) + + buf.writeUtf8("Wot do u call it?\r\nWindows") + assertEquals("Wot do u call it?", buf.readUtf8LineStrict()) + buf.clear() + + buf.writeUtf8("reo\rde\red\n") + assertEquals("reo\rde\red", buf.readUtf8LineStrict()) + + buf.writeUtf8("line\n") + assertFailsWith { buf.readUtf8LineStrict(3) } + assertEquals("line", buf.readUtf8LineStrict(4)) + assertEquals(0, buf.size) + + buf.writeUtf8("line\r\n") + assertFailsWith { buf.readUtf8LineStrict(3) } + assertEquals("line", buf.readUtf8LineStrict(4)) + assertEquals(0, buf.size) + + buf.writeUtf8("line\n") + assertEquals("line", buf.readUtf8LineStrict(5)) + assertEquals(0, buf.size) + } } diff --git a/core/common/test/BufferCommonTest.kt b/core/common/test/BufferCommonTest.kt index 8a2baffca..8de65980b 100644 --- a/core/common/test/BufferCommonTest.kt +++ b/core/common/test/BufferCommonTest.kt @@ -24,47 +24,6 @@ import kotlin.test.Test import kotlin.test.assertEquals class BufferCommonTest { - -// @Test fun copyToBuffer() { -// val source = Buffer() -// source.write("party".encodeUtf8()) -// -// val target = Buffer() -// source.copyTo(target) -// assertEquals("party", target.readByteString().utf8()) -// assertEquals("party", source.readByteString().utf8()) -// } - -// @Test fun copyToBufferWithOffset() { -// val source = Buffer() -// source.write("party".encodeUtf8()) -// -// val target = Buffer() -// source.copyTo(target, 2) -// assertEquals("rty", target.readByteString().utf8()) -// assertEquals("party", source.readByteString().utf8()) -// } - -// @Test fun copyToBufferWithByteCount() { -// val source = Buffer() -// source.write("party".encodeUtf8()) -// -// val target = Buffer() -// source.copyTo(target, 0, 3) -// assertEquals("par", target.readByteString().utf8()) -// assertEquals("party", source.readByteString().utf8()) -// } -// -// @Test fun copyToBufferWithOffsetAndByteCount() { -// val source = Buffer() -// source.write("party".encodeUtf8()) -// -// val target = Buffer() -// source.copyTo(target, 1, 3) -// assertEquals("art", target.readByteString().utf8()) -// assertEquals("party", source.readByteString().utf8()) -// } - @Test fun completeSegmentByteCountOnEmptyBuffer() { val buffer = Buffer() assertEquals(0, buffer.completeSegmentByteCount()) @@ -81,14 +40,4 @@ class BufferCommonTest { buffer.writeUtf8("a".repeat(Segment.SIZE * 4 - 10)) assertEquals((Segment.SIZE * 3).toLong(), buffer.completeSegmentByteCount()) } - -// @Test fun testHash() { -// val buffer = Buffer().apply { write("Kevin".encodeUtf8()) } -// with(buffer) { -// assertEquals("e043899daa0c7add37bc99792b2c045d6abbc6dc", sha1().hex()) -// assertEquals("f1cd318e412b5f7226e5f377a9544ff7", md5().hex()) -// assertEquals("0e4dd66217fc8d2e298b78c8cd9392870dcd065d0ff675d0edff5bcd227837e9", sha256().hex()) -// assertEquals("483676b93c4417198b465083d196ec6a9fab8d004515874b8ff47e041f5f56303cc08179625030b8b5b721c09149a18f0f59e64e7ae099518cea78d3d83167e1", sha512().hex()) -// } -// } } diff --git a/core/common/test/ByteStringFactory.kt b/core/common/test/ByteStringFactory.kt deleted file mode 100644 index 42fc6f75f..000000000 --- a/core/common/test/ByteStringFactory.kt +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2017-2023 JetBrains s.r.o. and respective authors and developers. - * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENCE file. - */ - -/* - * 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 kotlinx.io - -import kotlinx.io.internal.commonAsUtf8ToByteArray - -//internal interface ByteStringFactory { -// fun decodeHex(hex: String): ByteString -// -// fun encodeUtf8(s: String): ByteString -// -// companion object { -// val BYTE_STRING: ByteStringFactory = object : ByteStringFactory { -// override fun decodeHex(hex: String) = hex.decodeHex() -// override fun encodeUtf8(s: String) = s.encodeUtf8() -// } -// -// val SEGMENTED_BYTE_STRING: ByteStringFactory = object : ByteStringFactory { -// override fun decodeHex(hex: String) = Buffer().apply { write(hex.decodeHex()) }.snapshot() -// override fun encodeUtf8(s: String) = Buffer().apply { writeUtf8(s) }.snapshot() -// } -// -// val ONE_BYTE_PER_SEGMENT: ByteStringFactory = object : ByteStringFactory { -// override fun decodeHex(hex: String) = makeSegments(hex.decodeHex()) -// override fun encodeUtf8(s: String) = makeSegments(s.encodeUtf8()) -// } -// -// // For Kotlin/JVM, the native Java UTF-8 encoder is used. This forces -// // testing of the kotlinx.io encoder used for Kotlin/JS and Kotlin/Native to be -// // tested on JVM as well. -// val kotlinx.io_ENCODER: ByteStringFactory = object : ByteStringFactory { -// override fun decodeHex(hex: String) = hex.decodeHex() -// override fun encodeUtf8(s: String) = -// ByteString.of(*s.commonAsUtf8ToByteArray()) -// } -// } -//} diff --git a/core/common/test/ByteStringMoreTests.kt b/core/common/test/ByteStringMoreTests.kt deleted file mode 100644 index da4c3410a..000000000 --- a/core/common/test/ByteStringMoreTests.kt +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2017-2023 JetBrains s.r.o. and respective authors and developers. - * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENCE file. - */ - -/* - * Copyright (C) 2022 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 kotlinx.io - -import kotlin.test.Test -import kotlin.test.assertEquals - -//class ByteStringMoreTests { -// @Test fun arrayToByteString() { -// val actual = byteArrayOf(1, 2, 3, 4).toByteString() -// val expected = ByteString.of(1, 2, 3, 4) -// assertEquals(actual, expected) -// } -//} diff --git a/core/common/test/ByteStringTest.kt b/core/common/test/ByteStringTest.kt deleted file mode 100644 index 5c649da4c..000000000 --- a/core/common/test/ByteStringTest.kt +++ /dev/null @@ -1,570 +0,0 @@ -/* - * Copyright 2017-2023 JetBrains s.r.o. and respective authors and developers. - * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENCE file. - */ - -/* - * 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 kotlinx.io - -import kotlinx.io.internal.commonAsUtf8ToByteArray -import kotlin.random.Random -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertFailsWith -import kotlin.test.assertFalse -import kotlin.test.assertNotEquals -import kotlin.test.assertSame -import kotlin.test.assertTrue -import kotlin.test.fail - -//class ByteStringTest : AbstractByteStringTest(ByteStringFactory.BYTE_STRING) -//class SegmentedByteStringTest : AbstractByteStringTest(ByteStringFactory.SEGMENTED_BYTE_STRING) -//class ByteStringOneBytePerSegmentTest : AbstractByteStringTest(ByteStringFactory.ONE_BYTE_PER_SEGMENT) -//class kotlinx.ioEncoderTest : AbstractByteStringTest(ByteStringFactory.kotlinx.io_ENCODER) -// -//abstract class AbstractByteStringTest internal constructor( -// private val factory: ByteStringFactory -//) { -// @Test fun get() { -// val actual = factory.encodeUtf8("abc") -// assertEquals(3, actual.size) -// assertEquals(actual[0], 'a'.code.toByte()) -// assertEquals(actual[1], 'b'.code.toByte()) -// assertEquals(actual[2], 'c'.code.toByte()) -// try { -// actual[-1] -// fail("no index out of bounds: -1") -// } catch (expected: IndexOutOfBoundsException) { -// } -// try { -// actual[3] -// fail("no index out of bounds: 3") -// } catch (expected: IndexOutOfBoundsException) { -// } -// } -// -// @Test fun getByte() { -// val byteString = factory.decodeHex("ab12") -// assertEquals(-85, byteString[0].toLong()) -// assertEquals(18, byteString[1].toLong()) -// } -// -// @Test fun startsWithByteString() { -// val byteString = factory.decodeHex("112233") -// assertTrue(byteString.startsWith("".decodeHex())) -// assertTrue(byteString.startsWith("11".decodeHex())) -// assertTrue(byteString.startsWith("1122".decodeHex())) -// assertTrue(byteString.startsWith("112233".decodeHex())) -// assertFalse(byteString.startsWith("2233".decodeHex())) -// assertFalse(byteString.startsWith("11223344".decodeHex())) -// assertFalse(byteString.startsWith("112244".decodeHex())) -// } -// -// @Test fun endsWithByteString() { -// val byteString = factory.decodeHex("112233") -// assertTrue(byteString.endsWith("".decodeHex())) -// assertTrue(byteString.endsWith("33".decodeHex())) -// assertTrue(byteString.endsWith("2233".decodeHex())) -// assertTrue(byteString.endsWith("112233".decodeHex())) -// assertFalse(byteString.endsWith("1122".decodeHex())) -// assertFalse(byteString.endsWith("00112233".decodeHex())) -// assertFalse(byteString.endsWith("002233".decodeHex())) -// } -// -// @Test fun startsWithByteArray() { -// val byteString = factory.decodeHex("112233") -// assertTrue(byteString.startsWith("".decodeHex().toByteArray())) -// assertTrue(byteString.startsWith("11".decodeHex().toByteArray())) -// assertTrue(byteString.startsWith("1122".decodeHex().toByteArray())) -// assertTrue(byteString.startsWith("112233".decodeHex().toByteArray())) -// assertFalse(byteString.startsWith("2233".decodeHex().toByteArray())) -// assertFalse(byteString.startsWith("11223344".decodeHex().toByteArray())) -// assertFalse(byteString.startsWith("112244".decodeHex().toByteArray())) -// } -// -// @Test fun endsWithByteArray() { -// val byteString = factory.decodeHex("112233") -// assertTrue(byteString.endsWith("".decodeHex().toByteArray())) -// assertTrue(byteString.endsWith("33".decodeHex().toByteArray())) -// assertTrue(byteString.endsWith("2233".decodeHex().toByteArray())) -// assertTrue(byteString.endsWith("112233".decodeHex().toByteArray())) -// assertFalse(byteString.endsWith("1122".decodeHex().toByteArray())) -// assertFalse(byteString.endsWith("00112233".decodeHex().toByteArray())) -// assertFalse(byteString.endsWith("002233".decodeHex().toByteArray())) -// } -// -// @Test fun indexOfByteString() { -// val byteString = factory.decodeHex("112233") -// assertEquals(0, byteString.indexOf("112233".decodeHex()).toLong()) -// assertEquals(0, byteString.indexOf("1122".decodeHex()).toLong()) -// assertEquals(0, byteString.indexOf("11".decodeHex()).toLong()) -// assertEquals(0, byteString.indexOf("11".decodeHex(), 0).toLong()) -// assertEquals(0, byteString.indexOf("".decodeHex()).toLong()) -// assertEquals(0, byteString.indexOf("".decodeHex(), 0).toLong()) -// assertEquals(1, byteString.indexOf("2233".decodeHex()).toLong()) -// assertEquals(1, byteString.indexOf("22".decodeHex()).toLong()) -// assertEquals(1, byteString.indexOf("22".decodeHex(), 1).toLong()) -// assertEquals(1, byteString.indexOf("".decodeHex(), 1).toLong()) -// assertEquals(2, byteString.indexOf("33".decodeHex()).toLong()) -// assertEquals(2, byteString.indexOf("33".decodeHex(), 2).toLong()) -// assertEquals(2, byteString.indexOf("".decodeHex(), 2).toLong()) -// assertEquals(3, byteString.indexOf("".decodeHex(), 3).toLong()) -// assertEquals(-1, byteString.indexOf("112233".decodeHex(), 1).toLong()) -// assertEquals(-1, byteString.indexOf("44".decodeHex()).toLong()) -// assertEquals(-1, byteString.indexOf("11223344".decodeHex()).toLong()) -// assertEquals(-1, byteString.indexOf("112244".decodeHex()).toLong()) -// assertEquals(-1, byteString.indexOf("112233".decodeHex(), 1).toLong()) -// assertEquals(-1, byteString.indexOf("2233".decodeHex(), 2).toLong()) -// assertEquals(-1, byteString.indexOf("33".decodeHex(), 3).toLong()) -// assertEquals(-1, byteString.indexOf("".decodeHex(), 4).toLong()) -// } -// -// @Test fun indexOfWithOffset() { -// val byteString = factory.decodeHex("112233112233") -// assertEquals(0, byteString.indexOf("112233".decodeHex(), -1).toLong()) -// assertEquals(0, byteString.indexOf("112233".decodeHex(), 0).toLong()) -// assertEquals(0, byteString.indexOf("112233".decodeHex()).toLong()) -// assertEquals(3, byteString.indexOf("112233".decodeHex(), 1).toLong()) -// assertEquals(3, byteString.indexOf("112233".decodeHex(), 2).toLong()) -// assertEquals(3, byteString.indexOf("112233".decodeHex(), 3).toLong()) -// assertEquals(-1, byteString.indexOf("112233".decodeHex(), 4).toLong()) -// } -// -// @Test fun indexOfByteArray() { -// val byteString = factory.decodeHex("112233") -// assertEquals(0, byteString.indexOf("112233".decodeHex().toByteArray()).toLong()) -// assertEquals(1, byteString.indexOf("2233".decodeHex().toByteArray()).toLong()) -// assertEquals(2, byteString.indexOf("33".decodeHex().toByteArray()).toLong()) -// assertEquals(-1, byteString.indexOf("112244".decodeHex().toByteArray()).toLong()) -// } -// -// @Test fun lastIndexOfByteString() { -// val byteString = factory.decodeHex("112233") -// assertEquals(0, byteString.lastIndexOf("112233".decodeHex()).toLong()) -// assertEquals(0, byteString.lastIndexOf("1122".decodeHex()).toLong()) -// assertEquals(0, byteString.lastIndexOf("11".decodeHex()).toLong()) -// assertEquals(0, byteString.lastIndexOf("11".decodeHex(), 3).toLong()) -// assertEquals(0, byteString.lastIndexOf("11".decodeHex(), 0).toLong()) -// assertEquals(0, byteString.lastIndexOf("".decodeHex(), 0).toLong()) -// assertEquals(1, byteString.lastIndexOf("2233".decodeHex()).toLong()) -// assertEquals(1, byteString.lastIndexOf("22".decodeHex()).toLong()) -// assertEquals(1, byteString.lastIndexOf("22".decodeHex(), 3).toLong()) -// assertEquals(1, byteString.lastIndexOf("22".decodeHex(), 1).toLong()) -// assertEquals(1, byteString.lastIndexOf("".decodeHex(), 1).toLong()) -// assertEquals(2, byteString.lastIndexOf("33".decodeHex()).toLong()) -// assertEquals(2, byteString.lastIndexOf("33".decodeHex(), 3).toLong()) -// assertEquals(2, byteString.lastIndexOf("33".decodeHex(), 2).toLong()) -// assertEquals(2, byteString.lastIndexOf("".decodeHex(), 2).toLong()) -// assertEquals(3, byteString.lastIndexOf("".decodeHex(), 3).toLong()) -// assertEquals(3, byteString.lastIndexOf("".decodeHex()).toLong()) -// assertEquals(-1, byteString.lastIndexOf("112233".decodeHex(), -1).toLong()) -// assertEquals(-1, byteString.lastIndexOf("112233".decodeHex(), -2).toLong()) -// assertEquals(-1, byteString.lastIndexOf("44".decodeHex()).toLong()) -// assertEquals(-1, byteString.lastIndexOf("11223344".decodeHex()).toLong()) -// assertEquals(-1, byteString.lastIndexOf("112244".decodeHex()).toLong()) -// assertEquals(-1, byteString.lastIndexOf("2233".decodeHex(), 0).toLong()) -// assertEquals(-1, byteString.lastIndexOf("33".decodeHex(), 1).toLong()) -// assertEquals(-1, byteString.lastIndexOf("".decodeHex(), -1).toLong()) -// } -// -// @Test fun lastIndexOfByteArray() { -// val byteString = factory.decodeHex("112233") -// assertEquals(0, byteString.lastIndexOf("112233".decodeHex().toByteArray()).toLong()) -// assertEquals(1, byteString.lastIndexOf("2233".decodeHex().toByteArray()).toLong()) -// assertEquals(2, byteString.lastIndexOf("33".decodeHex().toByteArray()).toLong()) -// assertEquals(3, byteString.lastIndexOf("".decodeHex().toByteArray()).toLong()) -// } -// -// @Test fun equalsTest() { -// val byteString = factory.decodeHex("000102") -// assertEquals(byteString, byteString) -// assertEquals(byteString, "000102".decodeHex()) -// assertNotEquals(byteString, Any()) -// assertNotEquals(byteString, "000201".decodeHex()) -// } -// -// @Test fun equalsEmptyTest() { -// assertEquals(factory.decodeHex(""), ByteString.EMPTY) -// assertEquals(factory.decodeHex(""), ByteString.of()) -// assertEquals(ByteString.EMPTY, factory.decodeHex("")) -// assertEquals(ByteString.of(), factory.decodeHex("")) -// } -// -// private val bronzeHorseman = "На берегу пустынных волн" -// -// @Test fun utf8() { -// val byteString = factory.encodeUtf8(bronzeHorseman) -// assertEquals(byteString.toByteArray().toList(), bronzeHorseman.commonAsUtf8ToByteArray().toList()) -// assertTrue(byteString == ByteString.of(*bronzeHorseman.commonAsUtf8ToByteArray())) -// assertEquals( -// byteString, -// ( -// "d09dd0b020d0b1d0b5d180d0b5d0b3d18320d0bfd183d181" + -// "d182d18bd0bdd0bdd18bd18520d0b2d0bed0bbd0bd" -// ).decodeHex() -// ) -// assertEquals(byteString.utf8(), bronzeHorseman) -// } -// -// @Test fun testHashCode() { -// val byteString = factory.decodeHex("0102") -// assertEquals(byteString.hashCode().toLong(), byteString.hashCode().toLong()) -// assertEquals(byteString.hashCode().toLong(), "0102".decodeHex().hashCode().toLong()) -// } -// -// @Test fun toAsciiLowerCaseNoUppercase() { -// val s = factory.encodeUtf8("a1_+") -// assertEquals(s, s.toAsciiLowercase()) -// if (factory === ByteStringFactory.BYTE_STRING) { -// assertSame(s, s.toAsciiLowercase()) -// } -// } -// -// @Test fun toAsciiAllUppercase() { -// assertEquals("ab".encodeUtf8(), factory.encodeUtf8("AB").toAsciiLowercase()) -// } -// -// @Test fun toAsciiStartsLowercaseEndsUppercase() { -// assertEquals("abcd".encodeUtf8(), factory.encodeUtf8("abCD").toAsciiLowercase()) -// } -// -// @Test fun toAsciiStartsUppercaseEndsLowercase() { -// assertEquals("ABCD".encodeUtf8(), factory.encodeUtf8("ABcd").toAsciiUppercase()) -// } -// -// @Test fun substring() { -// val byteString = factory.encodeUtf8("Hello, World!") -// -// assertEquals(byteString.substring(0), byteString) -// assertEquals(byteString.substring(0, 5), "Hello".encodeUtf8()) -// assertEquals(byteString.substring(7), "World!".encodeUtf8()) -// assertEquals(byteString.substring(6, 6), "".encodeUtf8()) -// } -// -// @Test fun substringWithInvalidBounds() { -// val byteString = factory.encodeUtf8("Hello, World!") -// -// assertFailsWith { -// byteString.substring(-1) -// } -// -// assertFailsWith { -// byteString.substring(0, 14) -// } -// -// assertFailsWith { -// byteString.substring(8, 7) -// } -// } -// -// @Test fun encodeBase64() { -// assertEquals("", factory.encodeUtf8("").base64()) -// assertEquals("AA==", factory.encodeUtf8("\u0000").base64()) -// assertEquals("AAA=", factory.encodeUtf8("\u0000\u0000").base64()) -// assertEquals("AAAA", factory.encodeUtf8("\u0000\u0000\u0000").base64()) -// assertEquals( -// "SG93IG1hbnkgbGluZXMgb2YgY29kZSBhcmUgdGhlcmU/ICdib3V0IDIgbWlsbGlvbi4=", -// factory.encodeUtf8("How many lines of code are there? 'bout 2 million.").base64() -// ) -// } -// -// @Test fun encodeBase64Url() { -// assertEquals("", factory.encodeUtf8("").base64Url()) -// assertEquals("AA==", factory.encodeUtf8("\u0000").base64Url()) -// assertEquals("AAA=", factory.encodeUtf8("\u0000\u0000").base64Url()) -// assertEquals("AAAA", factory.encodeUtf8("\u0000\u0000\u0000").base64Url()) -// assertEquals( -// "SG93IG1hbnkgbGluZXMgb2YgY29kZSBhcmUgdGhlcmU_ICdib3V0IDIgbWlsbGlvbi4=", -// factory.encodeUtf8("How many lines of code are there? 'bout 2 million.").base64Url() -// ) -// } -// -// @Test fun ignoreUnnecessaryPadding() { -// assertEquals("", "====".decodeBase64()!!.utf8()) -// assertEquals("\u0000\u0000\u0000", "AAAA====".decodeBase64()!!.utf8()) -// } -// -// @Test fun decodeBase64() { -// assertEquals("", "".decodeBase64()!!.utf8()) -// assertEquals(null, "/===".decodeBase64()) // Can't do anything with 6 bits! -// assertEquals("ff".decodeHex(), "//==".decodeBase64()) -// assertEquals("ff".decodeHex(), "__==".decodeBase64()) -// assertEquals("ffff".decodeHex(), "///=".decodeBase64()) -// assertEquals("ffff".decodeHex(), "___=".decodeBase64()) -// assertEquals("ffffff".decodeHex(), "////".decodeBase64()) -// assertEquals("ffffff".decodeHex(), "____".decodeBase64()) -// assertEquals("ffffffffffff".decodeHex(), "////////".decodeBase64()) -// assertEquals("ffffffffffff".decodeHex(), "________".decodeBase64()) -// assertEquals( -// "What's to be scared about? It's just a little hiccup in the power...", -// ( -// "V2hhdCdzIHRvIGJlIHNjYXJlZCBhYm91dD8gSXQncyBqdXN0IGEgbGl0dGxlIGhpY2" + -// "N1cCBpbiB0aGUgcG93ZXIuLi4=" -// ).decodeBase64()!!.utf8() -// ) -// // Uses two encoding styles. Malformed, but supported as a side-effect. -// assertEquals("ffffff".decodeHex(), "__//".decodeBase64()) -// } -// -// @Test fun decodeBase64WithWhitespace() { -// assertEquals("\u0000\u0000\u0000", " AA AA ".decodeBase64()!!.utf8()) -// assertEquals("\u0000\u0000\u0000", " AA A\r\nA ".decodeBase64()!!.utf8()) -// assertEquals("\u0000\u0000\u0000", "AA AA".decodeBase64()!!.utf8()) -// assertEquals("\u0000\u0000\u0000", " AA AA ".decodeBase64()!!.utf8()) -// assertEquals("\u0000\u0000\u0000", " AA A\r\nA ".decodeBase64()!!.utf8()) -// assertEquals("\u0000\u0000\u0000", "A AAA".decodeBase64()!!.utf8()) -// assertEquals("", " ".decodeBase64()!!.utf8()) -// } -// -// @Test fun encodeHex() { -// assertEquals("000102", ByteString.of(0x0, 0x1, 0x2).hex()) -// } -// -// @Test fun decodeHex() { -// val actual = "CAFEBABE".decodeHex() -// val expected = ByteString.of(-54, -2, -70, -66) -// assertEquals(expected, actual) -// } -// -// @Test fun decodeHexOddNumberOfChars() { -// assertFailsWith { -// "aaa".decodeHex() -// } -// } -// -// @Test fun decodeHexInvalidChar() { -// assertFailsWith { -// "a\u0000".decodeHex() -// } -// } -// -// @Test fun toStringOnEmpty() { -// assertEquals("[size=0]", factory.decodeHex("").toString()) -// } -// -// @Test fun toStringOnShortText() { -// assertEquals( -// "[text=Tyrannosaur]", -// factory.encodeUtf8("Tyrannosaur").toString() -// ) -// assertEquals( -// "[text=təˈranəˌsôr]", -// factory.decodeHex("74c999cb8872616ec999cb8c73c3b472").toString() -// ) -// } -// -// @Test fun toStringOnLongTextIsTruncated() { -// val raw = ( -// "Um, I'll tell you the problem with the scientific power that you're using here, " + -// "it didn't require any discipline to attain it. You read what others had done and you " + -// "took the next step. You didn't earn the knowledge for yourselves, so you don't take any " + -// "responsibility for it. You stood on the shoulders of geniuses to accomplish something " + -// "as fast as you could, and before you even knew what you had, you patented it, and " + -// "packaged it, and slapped it on a plastic lunchbox, and now you're selling it, you wanna " + -// "sell it." -// ) -// assertEquals( -// "[size=517 text=Um, I'll tell you the problem with the scientific power that " + -// "you…]", -// factory.encodeUtf8(raw).toString() -// ) -// val war = ( -// "Սm, I'll 𝓽𝖾ll ᶌօ𝘂 ᴛℎ℮ 𝜚𝕣०bl𝖾m wі𝕥𝒽 𝘵𝘩𝐞 𝓼𝙘𝐢𝔢𝓷𝗍𝜄𝚏𝑖c 𝛠𝝾w𝚎𝑟 𝕥h⍺𝞃 𝛄𝓸𝘂'𝒓𝗲 υ𝖘𝓲𝗇ɡ 𝕙𝚎𝑟e, " + -// "𝛊𝓽 ⅆ𝕚𝐝𝝿'𝗍 𝔯𝙚𝙦ᴜ𝜾𝒓𝘦 𝔞𝘯𝐲 ԁ𝜄𝑠𝚌ι𝘱lι𝒏e 𝑡𝜎 𝕒𝚝𝖙𝓪і𝞹 𝔦𝚝. 𝒀ο𝗎 𝔯𝑒⍺𝖉 w𝐡𝝰𝔱 𝞂𝞽һ𝓮𝓇ƽ հ𝖺𝖉 ⅾ𝛐𝝅ⅇ 𝝰πԁ 𝔂ᴑᴜ 𝓉ﮨ၀𝚔 " + -// "т𝒽𝑒 𝗇𝕖ⅹ𝚝 𝔰𝒕е𝓅. 𝘠ⲟ𝖚 𝖉ⅰԁ𝝕'τ 𝙚𝚊r𝞹 𝘵Ꮒ𝖾 𝝒𝐧هwl𝑒𝖉ƍ𝙚 𝓯૦r 𝔂𝞼𝒖𝕣𝑠𝕖l𝙫𝖊𝓼, 𐑈о y𝘰𝒖 ⅆە𝗇't 𝜏α𝒌𝕖 𝛂𝟉ℽ " + -// "𝐫ⅇ𝗌ⲣ๐ϖ𝖘ꙇᖯ𝓲l𝓲𝒕𝘆 𝐟𝞼𝘳 𝚤𝑡. 𝛶𝛔𝔲 s𝕥σσ𝐝 ﮩ𝕟 𝒕𝗁𝔢 𝘴𝐡𝜎ᴜlⅾ𝓮𝔯𝚜 𝛐𝙛 ᶃ𝚎ᴨᎥս𝚜𝘦𝓈 𝓽𝞸 a𝒄𝚌𝞸mρl𝛊ꜱ𝐡 𝓈𝚘m𝚎𝞃𝔥⍳𝞹𝔤 𝐚𝗌 𝖋a𝐬𝒕 " + -// "αs γ𝛐𝕦 𝔠ﻫ𝛖lԁ, 𝚊π𝑑 Ь𝑒𝙛૦𝓇𝘦 𝓎٥𝖚 ⅇvℯ𝝅 𝜅ո𝒆w w𝗵𝒂𝘁 ᶌ੦𝗎 h𝐚𝗱, 𝜸ﮨ𝒖 𝓹𝝰𝔱𝖾𝗇𝓽𝔢ⅆ і𝕥, 𝚊𝜛𝓭 𝓹𝖺ⅽϰ𝘢ℊеᏧ 𝑖𝞃, " + -// "𝐚𝛑ꓒ 𝙨l𝔞р𝘱𝔢𝓭 ɩ𝗍 ہ𝛑 𝕒 pl𝛂ѕᴛ𝗂𝐜 l𝞄ℼ𝔠𝒽𝑏ﮪ⨯, 𝔞ϖ𝒹 n𝛔w 𝛾𝐨𝞄'𝗿𝔢 ꜱ℮ll𝙞nɡ ɩ𝘁, 𝙮𝕠𝛖 w𝑎ℼ𝚗𝛂 𝕤𝓮ll 𝙞𝓉." -// ) -// assertEquals( -// "[size=1496 text=Սm, I'll 𝓽𝖾ll ᶌօ𝘂 ᴛℎ℮ 𝜚𝕣०bl𝖾m wі𝕥𝒽 𝘵𝘩𝐞 𝓼𝙘𝐢𝔢𝓷𝗍𝜄𝚏𝑖c 𝛠𝝾w𝚎𝑟 𝕥h⍺𝞃 " + -// "𝛄𝓸𝘂…]", -// factory.encodeUtf8(war).toString() -// ) -// } -// -// @Test fun toStringOnTextWithNewlines() { -// // Instead of emitting a literal newline in the toString(), these are escaped as "\n". -// assertEquals( -// "[text=a\\r\\nb\\nc\\rd\\\\e]", -// factory.encodeUtf8("a\r\nb\nc\rd\\e").toString() -// ) -// } -// -// @Test fun toStringOnData() { -// val byteString = factory.decodeHex( -// "" + -// "60b420bb3851d9d47acb933dbe70399bf6c92da33af01d4fb770e98c0325f41d3ebaf8986da712c82bcd4d55" + -// "4bf0b54023c29b624de9ef9c2f931efc580f9afb" -// ) -// assertEquals( -// "[hex=" + -// "60b420bb3851d9d47acb933dbe70399bf6c92da33af01d4fb770e98c0325f41d3ebaf8986da712c82bcd4d55" + -// "4bf0b54023c29b624de9ef9c2f931efc580f9afb]", -// byteString.toString() -// ) -// } -// -// @Test fun toStringOnLongDataIsTruncated() { -// val byteString = factory.decodeHex( -// "" + -// "60b420bb3851d9d47acb933dbe70399bf6c92da33af01d4fb770e98c0325f41d3ebaf8986da712c82bcd4d55" + -// "4bf0b54023c29b624de9ef9c2f931efc580f9afba1" -// ) -// assertEquals( -// "[size=65 hex=" + -// "60b420bb3851d9d47acb933dbe70399bf6c92da33af01d4fb770e98c0325f41d3ebaf8986da712c82bcd4d55" + -// "4bf0b54023c29b624de9ef9c2f931efc580f9afb…]", -// byteString.toString() -// ) -// } -// -// @Test fun compareToSingleBytes() { -// val originalByteStrings = listOf( -// factory.decodeHex("00"), -// factory.decodeHex("01"), -// factory.decodeHex("7e"), -// factory.decodeHex("7f"), -// factory.decodeHex("80"), -// factory.decodeHex("81"), -// factory.decodeHex("fe"), -// factory.decodeHex("ff") -// ) -// -// val sortedByteStrings = originalByteStrings.toMutableList() -// sortedByteStrings.shuffle(Random(0)) -// assertNotEquals(originalByteStrings, sortedByteStrings) -// -// sortedByteStrings.sort() -// assertEquals(originalByteStrings, sortedByteStrings) -// } -// -// @Test fun compareToMultipleBytes() { -// val originalByteStrings = listOf( -// factory.decodeHex(""), -// factory.decodeHex("00"), -// factory.decodeHex("0000"), -// factory.decodeHex("000000"), -// factory.decodeHex("00000000"), -// factory.decodeHex("0000000000"), -// factory.decodeHex("0000000001"), -// factory.decodeHex("000001"), -// factory.decodeHex("00007f"), -// factory.decodeHex("0000ff"), -// factory.decodeHex("000100"), -// factory.decodeHex("000101"), -// factory.decodeHex("007f00"), -// factory.decodeHex("00ff00"), -// factory.decodeHex("010000"), -// factory.decodeHex("010001"), -// factory.decodeHex("01007f"), -// factory.decodeHex("0100ff"), -// factory.decodeHex("010100"), -// factory.decodeHex("01010000"), -// factory.decodeHex("0101000000"), -// factory.decodeHex("0101000001"), -// factory.decodeHex("010101"), -// factory.decodeHex("7f0000"), -// factory.decodeHex("7f0000ffff"), -// factory.decodeHex("ffffff") -// ) -// -// val sortedByteStrings = originalByteStrings.toMutableList() -// sortedByteStrings.shuffle(Random(0)) -// assertNotEquals(originalByteStrings, sortedByteStrings) -// -// sortedByteStrings.sort() -// assertEquals(originalByteStrings, sortedByteStrings) -// } -// -// @Test fun testHash() = with(factory.encodeUtf8("Kevin")) { -// assertEquals("e043899daa0c7add37bc99792b2c045d6abbc6dc", sha1().hex()) -// assertEquals("f1cd318e412b5f7226e5f377a9544ff7", md5().hex()) -// assertEquals("0e4dd66217fc8d2e298b78c8cd9392870dcd065d0ff675d0edff5bcd227837e9", sha256().hex()) -// assertEquals("483676b93c4417198b465083d196ec6a9fab8d004515874b8ff47e041f5f56303cc08179625030b8b5b721c09149a18f0f59e64e7ae099518cea78d3d83167e1", sha512().hex()) -// } -// -// @Test fun copyInto() { -// val byteString = factory.encodeUtf8("abcdefgh") -// val byteArray = "WwwwXxxxYyyyZzzz".encodeToByteArray() -// byteString.copyInto(target = byteArray, byteCount = 5) -// assertEquals("abcdexxxYyyyZzzz", byteArray.decodeToString()) -// } -// -// @Test fun copyIntoFullRange() { -// val byteString = factory.encodeUtf8("abcdefghijklmnop") -// val byteArray = "WwwwXxxxYyyyZzzz".encodeToByteArray() -// byteString.copyInto(target = byteArray, byteCount = 16) -// assertEquals("abcdefghijklmnop", byteArray.decodeToString()) -// } -// -// @Test fun copyIntoWithTargetOffset() { -// val byteString = factory.encodeUtf8("abcdefgh") -// val byteArray = "WwwwXxxxYyyyZzzz".encodeToByteArray() -// byteString.copyInto(target = byteArray, targetOffset = 11, byteCount = 5) -// assertEquals("WwwwXxxxYyyabcde", byteArray.decodeToString()) -// } -// -// @Test fun copyIntoWithSourceOffset() { -// val byteString = factory.encodeUtf8("abcdefgh") -// val byteArray = "WwwwXxxxYyyyZzzz".encodeToByteArray() -// byteString.copyInto(offset = 3, target = byteArray, byteCount = 5) -// assertEquals("defghxxxYyyyZzzz", byteArray.decodeToString()) -// } -// -// @Test fun copyIntoWithAllParameters() { -// val byteString = factory.encodeUtf8("abcdefgh") -// val byteArray = "WwwwXxxxYyyyZzzz".encodeToByteArray() -// byteString.copyInto(offset = 3, target = byteArray, targetOffset = 11, byteCount = 5) -// assertEquals("WwwwXxxxYyydefgh", byteArray.decodeToString()) -// } -// -// @Test fun copyIntoBoundsChecks() { -// val byteString = factory.encodeUtf8("abcdefgh") -// val byteArray = "WwwwXxxxYyyyZzzz".encodeToByteArray() -// assertFailsWith { -// byteString.copyInto(offset = -1, target = byteArray, targetOffset = 1, byteCount = 1) -// } -// assertFailsWith { -// byteString.copyInto(offset = 9, target = byteArray, targetOffset = 0, byteCount = 0) -// } -// assertFailsWith { -// byteString.copyInto(offset = 1, target = byteArray, targetOffset = -1, byteCount = 1) -// } -// assertFailsWith { -// byteString.copyInto(offset = 1, target = byteArray, targetOffset = 17, byteCount = 1) -// } -// assertFailsWith { -// byteString.copyInto(offset = 7, target = byteArray, targetOffset = 1, byteCount = 2) -// } -// assertFailsWith { -// byteString.copyInto(offset = 1, target = byteArray, targetOffset = 15, byteCount = 2) -// } -// } -// -// @Test fun copyEmptyAtBounds() { -// val byteString = factory.encodeUtf8("abcdefgh") -// val byteArray = "WwwwXxxxYyyyZzzz".encodeToByteArray() -// byteString.copyInto(offset = 0, target = byteArray, targetOffset = 0, byteCount = 0) -// assertEquals("WwwwXxxxYyyyZzzz", byteArray.decodeToString()) -// byteString.copyInto(offset = 0, target = byteArray, targetOffset = 16, byteCount = 0) -// assertEquals("WwwwXxxxYyyyZzzz", byteArray.decodeToString()) -// byteString.copyInto(offset = 8, target = byteArray, targetOffset = 0, byteCount = 0) -// assertEquals("WwwwXxxxYyyyZzzz", byteArray.decodeToString()) -// } -//} diff --git a/core/common/test/CommonRealBufferedSinkTest.kt b/core/common/test/CommonRealSinkTest.kt similarity index 99% rename from core/common/test/CommonRealBufferedSinkTest.kt rename to core/common/test/CommonRealSinkTest.kt index ada40c0ee..5fafc685c 100644 --- a/core/common/test/CommonRealBufferedSinkTest.kt +++ b/core/common/test/CommonRealSinkTest.kt @@ -30,7 +30,7 @@ import kotlin.test.fail * Tests solely for the behavior of RealBufferedSink's implementation. For generic * BufferedSink behavior use BufferedSinkTest. */ -class CommonRealBufferedSinkTest { +class CommonRealSinkTest { @Test fun bufferedSinkEmitsTailWhenItIsComplete() { val sink = Buffer() val bufferedSink = (sink as RawSink).buffer() diff --git a/core/common/test/CommonRealBufferedSourceTest.kt b/core/common/test/CommonRealSourceTest.kt similarity index 99% rename from core/common/test/CommonRealBufferedSourceTest.kt rename to core/common/test/CommonRealSourceTest.kt index 4cde5f016..9782a3466 100644 --- a/core/common/test/CommonRealBufferedSourceTest.kt +++ b/core/common/test/CommonRealSourceTest.kt @@ -29,7 +29,7 @@ import kotlin.test.assertFailsWith * Tests solely for the behavior of RealBufferedSource's implementation. For generic * BufferedSource behavior use BufferedSourceTest. */ -class CommonRealBufferedSourceTest { +class CommonRealSourceTest { @Test fun indexOfStopsReadingAtLimit() { val buffer = Buffer().writeUtf8("abcdef") val bufferedSource = ( diff --git a/core/common/test/FakeFileSystemTest.kt b/core/common/test/FakeFileSystemTest.kt deleted file mode 100644 index 196f7cd6d..000000000 --- a/core/common/test/FakeFileSystemTest.kt +++ /dev/null @@ -1,497 +0,0 @@ -/* - * Copyright 2017-2023 JetBrains s.r.o. and respective authors and developers. - * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENCE file. - */ - -/* - * Copyright (C) 2020 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 kotlinx.io - -import kotlin.reflect.KClass -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertFailsWith -import kotlin.test.assertFalse -import kotlin.test.assertNull -import kotlin.test.assertTrue -import kotlin.time.Duration.Companion.minutes - -//class FakeWindowsFileSystemTest : FakeFileSystemTest( -// FakeFileSystem(clock = FakeClock()).also { it.emulateWindows() }, -// temporaryDirectory = "C:\\".toPath() -//) -// -//class FakeUnixFileSystemTest : FakeFileSystemTest( -// FakeFileSystem(clock = FakeClock()).also { it.emulateUnix() }, -// temporaryDirectory = "/".toPath() -//) -// -//class StrictFakeFileSystemTest : FakeFileSystemTest( -// FakeFileSystem(clock = FakeClock()), -// temporaryDirectory = "/".toPath() -//) -// -//abstract class FakeFileSystemTest internal constructor( -// private val fakeFileSystem: FakeFileSystem, -// temporaryDirectory: Path -//) : AbstractFileSystemTest( -// clock = fakeFileSystem.clock, -// fileSystem = fakeFileSystem, -// windowsLimitations = !fakeFileSystem.allowMovingOpenFiles, -// allowClobberingEmptyDirectories = fakeFileSystem.allowClobberingEmptyDirectories, -// temporaryDirectory = temporaryDirectory -//) { -// private val fakeClock: FakeClock = fakeFileSystem.clock as FakeClock -// -// @Test -// fun openPathsIncludesOpenSink() { -// val openPath = base / "open-file" -// val sink = fileSystem.sink(openPath) -// assertEquals(openPath, fakeFileSystem.openPaths.single()) -// sink.close() -// assertTrue(fakeFileSystem.openPaths.isEmpty()) -// } -// -// @Test -// fun openPathsIncludesOpenSource() { -// val openPath = base / "open-file" -// openPath.writeUtf8("hello, world!") -// assertTrue(fakeFileSystem.openPaths.isEmpty()) -// val source = fileSystem.source(openPath) -// assertEquals(openPath, fakeFileSystem.openPaths.single()) -// source.close() -// assertTrue(fakeFileSystem.openPaths.isEmpty()) -// } -// -// @Test -// fun openPathsIsOpenOrder() { -// if (!fakeFileSystem.allowWritesWhileWriting) return -// -// val fileA = base / "a" -// val fileB = base / "b" -// val fileC = base / "c" -// val fileD = base / "d" -// -// assertEquals(fakeFileSystem.openPaths, listOf()) -// val sinkD = fileSystem.sink(fileD) -// assertEquals(fakeFileSystem.openPaths, listOf(fileD)) -// val sinkB = fileSystem.sink(fileB) -// assertEquals(fakeFileSystem.openPaths, listOf(fileD, fileB)) -// val sinkC = fileSystem.sink(fileC) -// assertEquals(fakeFileSystem.openPaths, listOf(fileD, fileB, fileC)) -// val sinkA = fileSystem.sink(fileA) -// assertEquals(fakeFileSystem.openPaths, listOf(fileD, fileB, fileC, fileA)) -// val sinkB2 = fileSystem.sink(fileB) -// assertEquals(fakeFileSystem.openPaths, listOf(fileD, fileB, fileC, fileA, fileB)) -// sinkD.close() -// assertEquals(fakeFileSystem.openPaths, listOf(fileB, fileC, fileA, fileB)) -// sinkB2.close() -// assertEquals(fakeFileSystem.openPaths, listOf(fileB, fileC, fileA)) -// sinkB.close() -// assertEquals(fakeFileSystem.openPaths, listOf(fileC, fileA)) -// sinkC.close() -// assertEquals(fakeFileSystem.openPaths, listOf(fileA)) -// sinkA.close() -// assertEquals(fakeFileSystem.openPaths, listOf()) -// } -// -// @Test -// fun allPathsIncludesFile() { -// val file = base / "all-files-includes-file" -// file.writeUtf8("hello, world!") -// assertEquals(setOf(base, file), fakeFileSystem.allPaths) -// } -// -// @Test -// fun allPathsIsSorted() { -// val fileA = base / "a" -// val fileB = base / "b" -// val fileC = base / "c" -// val fileD = base / "d" -// -// // Create files in a different order than the sorted order, so a file system that returns files -// // in creation-order or reverse-creation order won't pass by accident. -// fileD.writeUtf8("fileD") -// fileB.writeUtf8("fileB") -// fileC.writeUtf8("fileC") -// fileA.writeUtf8("fileA") -// -// assertEquals(listOf(base, fileA, fileB, fileC, fileD), fakeFileSystem.allPaths.toList()) -// } -// -// @Test -// fun allPathsIncludesDirectory() { -// val dir = base / "all-files-includes-directory" -// fileSystem.createDirectory(dir) -// assertEquals(setOf(base, dir), fakeFileSystem.allPaths) -// } -// -// @Test -// fun allPathsDoesNotIncludeDeletedFile() { -// val file = base / "all-files-does-not-include-deleted-file" -// file.writeUtf8("hello, world!") -// fileSystem.delete(file) -// assertEquals(setOf(base), fakeFileSystem.allPaths) -// } -// -// @Test -// fun allPathsDoesNotIncludeDeletedOpenFile() { -// if (windowsLimitations) return // Can't delete open files with Windows' limitations. -// -// val file = base / "all-files-does-not-include-deleted-open-file" -// val sink = fileSystem.sink(file) -// assertEquals(setOf(base, file), fakeFileSystem.allPaths) -// fileSystem.delete(file) -// assertEquals(setOf(base), fakeFileSystem.allPaths) -// sink.close() -// } -// -// @Test -// fun fileLastAccessedTime() { -// val path = base / "file-last-accessed-time" -// -// fakeClock.sleep(1.minutes) -// path.writeUtf8("hello, world!") -// val createdAt = clock.now() -// -// fakeClock.sleep(1.minutes) -// path.writeUtf8("hello again!") -// val modifiedAt = clock.now() -// -// fakeClock.sleep(1.minutes) -// path.readUtf8() -// val accessedAt = clock.now() -// -// val metadata = fileSystem.metadata(path) -// assertEquals(createdAt, metadata.createdAt) -// assertEquals(modifiedAt, metadata.lastModifiedAt) -// assertEquals(accessedAt, metadata.lastAccessedAt) -// } -// -// @Test -// fun directoryLastAccessedTime() { -// val path = base / "directory-last-accessed-time" -// -// fakeClock.sleep(1.minutes) -// fileSystem.createDirectory(path) -// val createdAt = clock.now() -// -// fakeClock.sleep(1.minutes) -// (path / "child").writeUtf8("hello world!") -// val modifiedAt = clock.now() -// -// fakeClock.sleep(1.minutes) -// fileSystem.list(path) -// val accessedAt = clock.now() -// -// val metadata = fileSystem.metadata(path) -// assertEquals(createdAt, metadata.createdAt) -// assertEquals(modifiedAt, metadata.lastModifiedAt) -// assertEquals(accessedAt, metadata.lastAccessedAt) -// } -// -// @Test -// fun checkNoOpenFilesThrowsOnOpenSource() { -// val path = base / "check-no-open-files-open-source" -// path.writeUtf8("hello, world!") -// val exception = fileSystem.source(path).use { source -> -// assertFailsWith { -// fakeFileSystem.checkNoOpenFiles() -// } -// } -// -// assertEquals( -// """ -// |expected 0 open files, but found: -// | $path -// """.trimMargin(), -// exception.message -// ) -// assertEquals("file opened for READ here", exception.cause?.message) -// -// // Now that the source is closed this is safe. -// fakeFileSystem.checkNoOpenFiles() -// } -// -// @Test -// fun checkNoOpenFilesThrowsOnOpenSink() { -// val path = base / "check-no-open-files-open-sink" -// val exception = fileSystem.sink(path).use { source -> -// assertFailsWith { -// fakeFileSystem.checkNoOpenFiles() -// } -// } -// -// assertEquals( -// """ -// |expected 0 open files, but found: -// | $path -// """.trimMargin(), -// exception.message -// ) -// assertEquals("file opened for WRITE here", exception.cause?.message) -// -// // Now that the source is closed this is safe. -// fakeFileSystem.checkNoOpenFiles() -// } -// -// @Test -// fun createDirectoriesForVolumeLetterRoot() { -// val path = "X:\\".toPath() -// fileSystem.createDirectories(path) -// assertTrue(fileSystem.metadata(path).isDirectory) -// } -// -// @Test -// fun createDirectoriesForChildOfVolumeLetterRoot() { -// val path = "X:\\path".toPath() -// fileSystem.createDirectories(path) -// assertTrue(fileSystem.metadata(path).isDirectory) -// } -// -// @Test -// fun createDirectoriesForUnixRoot() { -// val path = "/".toPath() -// fileSystem.createDirectories(path) -// assertTrue(fileSystem.metadata(path).isDirectory) -// } -// -// @Test -// fun createDirectoriesForChildOfUnixRoot() { -// val path = "/path".toPath() -// fileSystem.createDirectories(path) -// assertTrue(fileSystem.metadata(path).isDirectory) -// } -// -// @Test -// fun createDirectoriesForUncRoot() { -// val path = "\\\\server".toPath() -// fileSystem.createDirectories(path) -// assertTrue(fileSystem.metadata(path).isDirectory) -// } -// -// @Test -// fun createDirectoriesForChildOfUncRoot() { -// val path = "\\\\server\\project".toPath() -// fileSystem.createDirectories(path) -// assertTrue(fileSystem.metadata(path).isDirectory) -// } -// -// @Test -// fun workingDirectoryMustBeAbsolute() { -// val exception = assertFailsWith { -// fakeFileSystem.workingDirectory = "some/relative/path".toPath() -// } -// assertEquals("expected an absolute path but was some/relative/path", exception.message) -// } -// -// @Test -// fun metadataForRootsGeneratedOnDemand() { -// assertTrue(fileSystem.metadata("X:\\".toPath()).isDirectory) -// assertTrue(fileSystem.metadata("/".toPath()).isDirectory) -// assertTrue(fileSystem.metadata("\\\\server".toPath()).isDirectory) -// } -// -// @Test -// fun startWriteWhileWritingNotAllowedWhenStrict() { -// val path = base / "write-write" -// path.writeUtf8("hello world!") -// fileSystem.sink(path).use { -// try { -// fileSystem.sink(path).use { -// } -// assertTrue(fakeFileSystem.allowWritesWhileWriting) -// } catch (_: IOException) { -// assertFalse(fakeFileSystem.allowWritesWhileWriting) -// } -// } -// } -// -// @Test -// fun startReadWhileWritingNotAllowedWhenStrict() { -// val path = base / "write-read" -// path.writeUtf8("hello world!") -// fileSystem.sink(path).use { -// try { -// fileSystem.source(path).use { -// } -// assertTrue(fakeFileSystem.allowReadsWhileWriting) -// } catch (_: IOException) { -// assertFalse(fakeFileSystem.allowReadsWhileWriting) -// } -// } -// } -// -// @Test -// fun startWriteWhileReadingNotAllowedWhenStrict() { -// val path = base / "read-write" -// path.writeUtf8("hello world!") -// fileSystem.source(path).use { -// try { -// fileSystem.sink(path).use { -// } -// assertTrue(fakeFileSystem.allowReadsWhileWriting) -// } catch (_: IOException) { -// assertFalse(fakeFileSystem.allowReadsWhileWriting) -// } -// } -// } -// -// @Test -// fun startReadWhileReadingAllowedWhenStrict() { -// val path = base / "read-read" -// path.writeUtf8("hello world!") -// fileSystem.source(path).use { -// fileSystem.source(path).use { -// } -// } -// } -// -// @Test -// fun symlinkCanBeUsedAfterSettingAllowSymlinksToFalse() { -// if (!supportsSymlink()) return -// -// val target = base / "symlink-target" -// val source = base / "symlink-source" -// fileSystem.createSymlink(source, target) -// fakeFileSystem.allowSymlinks = false -// target.writeUtf8("I am the target file") -// assertEquals("I am the target file", source.readUtf8()) -// } -// -// @Test -// fun symlinkCannotBeCreatedAfterSettingAllowSymlinksToFalse() { -// fakeFileSystem.allowSymlinks = false -// val target = base / "symlink-target" -// val source = base / "symlink-source" -// assertFailsWith { -// fileSystem.createSymlink(source, target) -// } -// } -// -// @Test -// fun fileExtras() { -// val path = base / "a.txt" -// path.writeUtf8("hello") -// fakeFileSystem.setExtra(path, ContentTypeExtra::class, ContentTypeExtra("text/plain")) -// val metadata = fileSystem.metadata(path) -// assertEquals(ContentTypeExtra("text/plain"), metadata.extra(ContentTypeExtra::class)) -// } -// -// @Test -// fun directoryExtras() { -// val path = base / "a.txt" -// fileSystem.createDirectory(path) -// fakeFileSystem.setExtra(path, ContentTypeExtra::class, ContentTypeExtra("text/plain")) -// val metadata = fileSystem.metadata(path) -// assertEquals(ContentTypeExtra("text/plain"), metadata.extra(ContentTypeExtra::class)) -// } -// -// @Test -// fun symlinkExtras() { -// if (!supportsSymlink()) return -// -// val pathA = base / "a.txt" -// val pathB = base / "b.txt" -// fileSystem.createSymlink(pathA, pathB) -// fakeFileSystem.setExtra(pathA, ContentTypeExtra::class, ContentTypeExtra("text/plain")) -// val metadata = fileSystem.metadata(pathA) -// assertEquals(ContentTypeExtra("text/plain"), metadata.extra(ContentTypeExtra::class)) -// } -// -// @Test -// fun deleteExtra() { -// val path = base / "a.txt" -// path.writeUtf8("hello") -// fakeFileSystem.setExtra(path, ContentTypeExtra::class, ContentTypeExtra("text/plain")) -// fakeFileSystem.setExtra(path, ContentTypeExtra::class, null) -// val metadata = fileSystem.metadata(path) -// assertNull(metadata.extra(ContentTypeExtra::class)) -// assertEquals(mapOf(), metadata.extras) -// } -// -// @Test -// fun extraIsNotCopiedByFileCopy() { -// val pathA = base / "a.txt" -// val pathB = base / "b.txt" -// pathA.writeUtf8("hello") -// fakeFileSystem.setExtra(pathA, ContentTypeExtra::class, ContentTypeExtra("text/plain")) -// fileSystem.copy(pathA, pathB) -// val metadata = fileSystem.metadata(pathB) -// assertNull(metadata.extra(ContentTypeExtra::class)) -// } -// -// @Test -// fun extraIsMovedByAtomicMove() { -// val pathA = base / "a.txt" -// val pathB = base / "b.txt" -// pathA.writeUtf8("hello") -// fakeFileSystem.setExtra(pathA, ContentTypeExtra::class, ContentTypeExtra("text/plain")) -// fileSystem.atomicMove(pathA, pathB) -// val metadata = fileSystem.metadata(pathB) -// assertEquals(ContentTypeExtra("text/plain"), metadata.extra(ContentTypeExtra::class)) -// } -// -// @Test -// fun extrasHappyPath() { -// val metadata = FileMetadata( -// isRegularFile = true, -// size = 10L, -// extras = mapOf(ContentTypeExtra::class to ContentTypeExtra("text/plain")), -// ) -// assertEquals(ContentTypeExtra("text/plain"), metadata.extra(ContentTypeExtra::class)) -// } -// -// @Test -// fun createExtrasDefensiveCopy() { -// val extras = mutableMapOf, Any>( -// ContentTypeExtra::class to ContentTypeExtra("text/plain") -// ) -// val metadata = FileMetadata( -// isRegularFile = true, -// size = 10L, -// extras = extras, -// ) -// extras.clear() -// assertEquals(ContentTypeExtra("text/plain"), metadata.extra(ContentTypeExtra::class)) -// } -// -// @Test -// fun getExtraAbsent() { -// val metadata = FileMetadata( -// isRegularFile = true, -// size = 10L, -// extras = mapOf(), -// ) -// assertNull(metadata.extra(ContentTypeExtra::class)) -// } -// -// @Test -// fun getExtraWrongType() { -// val metadata = FileMetadata( -// isRegularFile = true, -// size = 10L, -// extras = mapOf(ContentTypeExtra::class to "hello"), -// ) -// assertFailsWith { -// metadata.extra(ContentTypeExtra::class) -// } -// } -// -// internal data class ContentTypeExtra( -// val contentType: String -// ) -//} diff --git a/core/common/test/ForwardingFileSystemTest.kt b/core/common/test/ForwardingFileSystemTest.kt deleted file mode 100644 index a54154ab3..000000000 --- a/core/common/test/ForwardingFileSystemTest.kt +++ /dev/null @@ -1,173 +0,0 @@ -/* - * Copyright 2017-2023 JetBrains s.r.o. and respective authors and developers. - * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENCE file. - */ - -/* - * Copyright (C) 2020 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 kotlinx.io - -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertFailsWith -import kotlin.test.assertTrue - -//class ForwardingFileSystemTest : AbstractFileSystemTest( -// clock = Clock.System, -// fileSystem = object : ForwardingFileSystem(FakeFileSystem().apply { emulateUnix() }) {}, -// windowsLimitations = false, -// allowClobberingEmptyDirectories = false, -// temporaryDirectory = "/".toPath() -//) { -// @Test -// fun pathBlocking() { -// val forwardingFileSystem = object : ForwardingFileSystem(fileSystem) { -// override fun delete(path: Path, mustExist: Boolean) { -// throw IOException("synthetic failure!") -// } -// -// override fun onPathParameter(path: Path, functionName: String, parameterName: String): Path { -// if (path.name.contains("blocked")) throw IOException("blocked path!") -// return path -// } -// } -// -// forwardingFileSystem.createDirectory(base / "okay") -// assertFailsWith { -// forwardingFileSystem.createDirectory(base / "blocked") -// } -// } -// -// @Test -// fun operationBlocking() { -// val forwardingFileSystem = object : ForwardingFileSystem(fileSystem) { -// override fun onPathParameter(path: Path, functionName: String, parameterName: String): Path { -// if (functionName == "delete") throw IOException("blocked operation!") -// return path -// } -// } -// -// forwardingFileSystem.createDirectory(base / "operation-blocking") -// assertFailsWith { -// forwardingFileSystem.delete(base / "operation-blocking") -// } -// } -// -// @Test -// fun pathMapping() { -// val prefix = "/mapped" -// val source = base / "source" -// val mappedSource = (prefix + source).toPath() -// val target = base / "target" -// val mappedTarget = (prefix + target).toPath() -// -// source.writeUtf8("hello, world!") -// -// val forwardingFileSystem = object : ForwardingFileSystem(fileSystem) { -// override fun onPathParameter(path: Path, functionName: String, parameterName: String): Path { -// return path.toString().removePrefix(prefix).toPath() -// } -// -// override fun onPathResult(path: Path, functionName: String): Path { -// return (prefix + path).toPath() -// } -// } -// -// forwardingFileSystem.copy(mappedSource, mappedTarget) -// assertTrue(target in fileSystem.list(base)) -// assertTrue(mappedTarget in forwardingFileSystem.list(base)) -// assertEquals("hello, world!", source.readUtf8()) -// assertEquals("hello, world!", target.readUtf8()) -// } -// -// /** -// * Path mapping might impact the sort order. Confirm that list() returns elements in sorted order -// * even if that order is different in the delegate file system. -// */ -// @Test -// fun pathMappingImpactedBySorting() { -// val az = base / "az" -// val by = base / "by" -// val cx = base / "cx" -// az.writeUtf8("az") -// by.writeUtf8("by") -// cx.writeUtf8("cx") -// -// val forwardingFileSystem = object : ForwardingFileSystem(fileSystem) { -// override fun onPathResult(path: Path, functionName: String): Path { -// return path.parent!! / path.name.reversed() -// } -// } -// -// assertEquals(listOf(base / "az", base / "by", base / "cx"), fileSystem.list(base)) -// assertEquals(listOf(base / "xc", base / "yb", base / "za"), forwardingFileSystem.list(base)) -// } -// -// @Test -// fun copyIsNotForwarded() { -// val log = mutableListOf() -// -// val delegate = object : ForwardingFileSystem(fileSystem) { -// override fun copy(source: Path, target: Path) { -// throw AssertionError("unexpected call to copy()") -// } -// } -// -// val forwardingFileSystem = object : ForwardingFileSystem(delegate) { -// override fun onPathParameter(path: Path, functionName: String, parameterName: String): Path { -// log += "$functionName($parameterName=$path)" -// return path -// } -// } -// -// val source = base / "source" -// source.writeUtf8("hello, world!") -// val target = base / "target" -// forwardingFileSystem.copy(source, target) -// assertTrue(target in fileSystem.list(base)) -// assertEquals("hello, world!", source.readUtf8()) -// assertEquals("hello, world!", target.readUtf8()) -// -// assertEquals(listOf("source(file=$source)", "sink(file=$target)"), log) -// } -// -// @Test -// fun metadataForwardsParameterAndSymlinkTarget() { -// val log = mutableListOf() -// -// val forwardingFileSystem = object : ForwardingFileSystem(fileSystem) { -// override fun onPathParameter(path: Path, functionName: String, parameterName: String): Path { -// log += "$functionName($parameterName=$path)" -// return path -// } -// -// override fun onPathResult(path: Path, functionName: String): Path { -// log += "$functionName($path)" -// return path -// } -// } -// -// val target = base / "symlink-target" -// val source = base / "symlink-source" -// -// fileSystem.createSymlink(source, target) -// -// val sourceMetadata = forwardingFileSystem.metadata(source) -// assertEquals(target, sourceMetadata.symlinkTarget) -// -// assertEquals(listOf("metadataOrNull(path=$source)", "metadataOrNull($target)"), log) -// } -//} diff --git a/core/common/test/ForwardingSourceTest.kt b/core/common/test/ForwardingSourceTest.kt deleted file mode 100644 index dc2cfb4f6..000000000 --- a/core/common/test/ForwardingSourceTest.kt +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2017-2023 JetBrains s.r.o. and respective authors and developers. - * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENCE file. - */ - -/* - * Copyright (C) 2022 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 kotlinx.io - -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertTrue - -//class ForwardingSourceTest { -// val source = Buffer().writeUtf8("Delegate") -// -// @Test -// fun testForwardingSourceOverrides() { -// val forwarder = "Forwarder" -// val newSource = Buffer().writeUtf8(forwarder) -// val forwardingSource = object : ForwardingSource(source) { -// override fun read(sink: Buffer, byteCount: Long): Long { -// return newSource.read(sink, byteCount) -// } -// } -// -// assertEquals("Forwarder", forwardingSource.buffer().readUtf8()) -// } -// -// @Test -// fun testForwardingSourceDelegates() { -// val forwardingSource = object : ForwardingSource(source) { -// } -// -// assertEquals("Delegate", forwardingSource.buffer().readUtf8()) -// } -// -// @Test -// fun testToString() { -// val forwardingSource = object : ForwardingSource(source) { -// } -// -// assertTrue(forwardingSource.toString().endsWith("([text=Delegate])")) -// } -//} diff --git a/core/common/test/HashingSinkTest.kt b/core/common/test/HashingSinkTest.kt deleted file mode 100644 index 77419b204..000000000 --- a/core/common/test/HashingSinkTest.kt +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright 2017-2023 JetBrains s.r.o. and respective authors and developers. - * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENCE file. - */ - -/* - * Copyright (C) 2016 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 kotlinx.io - - -import kotlin.test.Test -import kotlin.test.assertEquals - -class HashingSinkTest { - private val source = Buffer() - private val sink = Buffer() - -// @Test fun md5() { -// val hashingSink: HashingSink = HashingSink.md5(sink) -// source.writeUtf8("abc") -// hashingSink.write(source, 3L) -// assertEquals(HashingTest.MD5_abc, hashingSink.hash) -// } -// -// @Test fun sha1() { -// val hashingSink = sha1(sink) -// source.writeUtf8("abc") -// hashingSink.write(source, 3L) -// assertEquals(HashingTest.SHA1_abc, hashingSink.hash) -// } -// -// @Test fun sha256() { -// val hashingSink = sha256(sink) -// source.writeUtf8("abc") -// hashingSink.write(source, 3L) -// assertEquals(HashingTest.SHA256_abc, hashingSink.hash) -// } -// -// @Test fun sha512() { -// val hashingSink = sha512(sink) -// source.writeUtf8("abc") -// hashingSink.write(source, 3L) -// assertEquals(HashingTest.SHA512_abc, hashingSink.hash) -// } -// -// @Test fun hmacSha1() { -// val hashingSink = hmacSha1(sink, HashingTest.HMAC_KEY) -// source.writeUtf8("abc") -// hashingSink.write(source, 3L) -// assertEquals(HashingTest.HMAC_SHA1_abc, hashingSink.hash) -// } -// -// @Test fun hmacSha256() { -// val hashingSink = hmacSha256(sink, HashingTest.HMAC_KEY) -// source.writeUtf8("abc") -// hashingSink.write(source, 3L) -// assertEquals(HashingTest.HMAC_SHA256_abc, hashingSink.hash) -// } -// -// @Test fun hmacSha512() { -// val hashingSink = hmacSha512(sink, HashingTest.HMAC_KEY) -// source.writeUtf8("abc") -// hashingSink.write(source, 3L) -// assertEquals(HashingTest.HMAC_SHA512_abc, hashingSink.hash) -// } -// -// @Test fun multipleWrites() { -// val hashingSink = sha256(sink) -// source.writeUtf8("a") -// hashingSink.write(source, 1L) -// source.writeUtf8("b") -// hashingSink.write(source, 1L) -// source.writeUtf8("c") -// hashingSink.write(source, 1L) -// assertEquals(HashingTest.SHA256_abc, hashingSink.hash) -// } -// -// @Test fun multipleHashes() { -// val hashingSink = sha256(sink) -// source.writeUtf8("abc") -// hashingSink.write(source, 3L) -// val hash_abc = hashingSink.hash -// assertEquals(HashingTest.SHA256_abc, hash_abc) -// source.writeUtf8("def") -// hashingSink.write(source, 3L) -// assertEquals(HashingTest.SHA256_def, hashingSink.hash) -// assertEquals(HashingTest.SHA256_abc, hash_abc) -// } -// -// @Test fun multipleSegments() { -// val hashingSink = sha256(sink) -// source.write(HashingTest.r32k) -// hashingSink.write(source, HashingTest.r32k.size.toLong()) -// assertEquals(HashingTest.SHA256_r32k, hashingSink.hash) -// } -// -// @Test fun readFromPrefixOfBuffer() { -// source.writeUtf8("z") -// source.write(HashingTest.r32k) -// source.skip(1) -// source.writeUtf8("z".repeat(Segment.SIZE * 2 - 1)) -// val hashingSink = sha256(sink) -// hashingSink.write(source, HashingTest.r32k.size.toLong()) -// assertEquals(HashingTest.SHA256_r32k, hashingSink.hash) -// } -} diff --git a/core/common/test/HashingSourceTest.kt b/core/common/test/HashingSourceTest.kt deleted file mode 100644 index a6065fdac..000000000 --- a/core/common/test/HashingSourceTest.kt +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright 2017-2023 JetBrains s.r.o. and respective authors and developers. - * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENCE file. - */ - -/* - * Copyright (C) 2016 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 kotlinx.io - -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.fail - -//class HashingSourceTest { -// private val source = Buffer() -// private val sink = Buffer() -// -// @Test fun md5() { -// val hashingSource = md5(source) -// source.writeUtf8("abc") -// assertEquals(3L, hashingSource.read(sink, Long.MAX_VALUE)) -// assertEquals(HashingTest.MD5_abc, hashingSource.hash) -// } -// -// @Test fun sha1() { -// val hashingSource = sha1(source) -// source.writeUtf8("abc") -// assertEquals(3L, hashingSource.read(sink, Long.MAX_VALUE)) -// assertEquals(HashingTest.SHA1_abc, hashingSource.hash) -// } -// -// @Test fun sha256() { -// val hashingSource = sha256(source) -// source.writeUtf8("abc") -// assertEquals(3L, hashingSource.read(sink, Long.MAX_VALUE)) -// assertEquals(HashingTest.SHA256_abc, hashingSource.hash) -// } -// -// @Test fun sha512() { -// val hashingSource: HashingSource = HashingSource.sha512(source) -// source.writeUtf8("abc") -// assertEquals(3L, hashingSource.read(sink, Long.MAX_VALUE)) -// assertEquals(HashingTest.SHA512_abc, hashingSource.hash) -// } -// -// @Test fun hmacSha1() { -// val hashingSource = hmacSha1(source, HashingTest.HMAC_KEY) -// source.writeUtf8("abc") -// assertEquals(3L, hashingSource.read(sink, Long.MAX_VALUE)) -// assertEquals(HashingTest.HMAC_SHA1_abc, hashingSource.hash) -// } -// -// @Test fun hmacSha256() { -// val hashingSource = hmacSha256(source, HashingTest.HMAC_KEY) -// source.writeUtf8("abc") -// assertEquals(3L, hashingSource.read(sink, Long.MAX_VALUE)) -// assertEquals(HashingTest.HMAC_SHA256_abc, hashingSource.hash) -// } -// -// @Test fun hmacSha512() { -// val hashingSource = hmacSha512(source, HashingTest.HMAC_KEY) -// source.writeUtf8("abc") -// assertEquals(3L, hashingSource.read(sink, Long.MAX_VALUE)) -// assertEquals(HashingTest.HMAC_SHA512_abc, hashingSource.hash) -// } -// -// @Test fun multipleReads() { -// val hashingSource = sha256(source) -// val bufferedSource = hashingSource.buffer() -// source.writeUtf8("a") -// assertEquals('a'.code.toLong(), bufferedSource.readUtf8CodePoint().toLong()) -// source.writeUtf8("b") -// assertEquals('b'.code.toLong(), bufferedSource.readUtf8CodePoint().toLong()) -// source.writeUtf8("c") -// assertEquals('c'.code.toLong(), bufferedSource.readUtf8CodePoint().toLong()) -// assertEquals(HashingTest.SHA256_abc, hashingSource.hash) -// } -// -// @Test fun multipleHashes() { -// val hashingSource = sha256(source) -// source.writeUtf8("abc") -// assertEquals(3L, hashingSource.read(sink, Long.MAX_VALUE)) -// val hash_abc = hashingSource.hash -// assertEquals(HashingTest.SHA256_abc, hash_abc) -// source.writeUtf8("def") -// assertEquals(3L, hashingSource.read(sink, Long.MAX_VALUE)) -// assertEquals(HashingTest.SHA256_def, hashingSource.hash) -// assertEquals(HashingTest.SHA256_abc, hash_abc) -// } -// -// @Test fun multipleSegments() { -// val hashingSource = sha256(source) -// val bufferedSource = hashingSource.buffer() -// source.write(HashingTest.r32k) -// assertEquals(HashingTest.r32k, bufferedSource.readByteString()) -// assertEquals(HashingTest.SHA256_r32k, hashingSource.hash) -// } -// -// @Test fun readIntoSuffixOfBuffer() { -// val hashingSource = sha256(source) -// source.write(HashingTest.r32k) -// sink.writeUtf8("z".repeat(Segment.SIZE * 2 - 1)) -// assertEquals(HashingTest.r32k.size.toLong(), hashingSource.read(sink, Long.MAX_VALUE)) -// assertEquals(HashingTest.SHA256_r32k, hashingSource.hash) -// } -// -// @Test fun hmacEmptyKey() { -// try { -// hmacSha256(source, ByteString.EMPTY) -// fail() -// } catch (expected: IllegalArgumentException) { -// } -// } -//} diff --git a/core/common/test/HashingTest.kt b/core/common/test/HashingTest.kt deleted file mode 100644 index ff5247761..000000000 --- a/core/common/test/HashingTest.kt +++ /dev/null @@ -1,146 +0,0 @@ -/* - * Copyright 2017-2023 JetBrains s.r.o. and respective authors and developers. - * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENCE file. - */ - -/* - * Copyright 2014 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 kotlinx.io - -import kotlin.test.Test -import kotlin.test.assertEquals - -//class HashingTest { -// @Test fun byteStringMd5() { -// assertEquals(MD5_abc, "abc".encodeUtf8().md5()) -// } -// -// @Test fun byteStringSha1() { -// assertEquals(SHA1_abc, "abc".encodeUtf8().sha1()) -// } -// -// @Test fun byteStringSha256() { -// assertEquals(SHA256_abc, "abc".encodeUtf8().sha256()) -// } -// -// @Test fun byteStringSha512() { -// assertEquals(SHA512_abc, "abc".encodeUtf8().sha512()) -// } -// -// @Test fun byteStringHmacSha1() { -// assertEquals(HMAC_SHA1_abc, "abc".encodeUtf8().hmacSha1(HMAC_KEY)) -// } -// -// @Test fun byteStringHmacSha256() { -// assertEquals(HMAC_SHA256_abc, "abc".encodeUtf8().hmacSha256(HMAC_KEY)) -// } -// -// @Test fun byteStringHmacSha512() { -// assertEquals(HMAC_SHA512_abc, "abc".encodeUtf8().hmacSha512(HMAC_KEY)) -// } -// -// @Test fun bufferMd5() { -// assertEquals(MD5_abc, Buffer().writeUtf8("abc").md5()) -// } -// -// @Test fun bufferSha1() { -// assertEquals(SHA1_abc, Buffer().writeUtf8("abc").sha1()) -// } -// -// @Test fun bufferSha256() { -// assertEquals(SHA256_abc, Buffer().writeUtf8("abc").sha256()) -// } -// -// @Test fun bufferSha512() { -// assertEquals(SHA512_abc, Buffer().writeUtf8("abc").sha512()) -// } -// -// @Test fun hashEmptySha256Buffer() { -// assertEquals(SHA256_empty, Buffer().sha256()) -// } -// -// @Test fun hashEmptySha512Buffer() { -// assertEquals(SHA512_empty, Buffer().sha512()) -// } -// -// @Test fun bufferHmacSha1() { -// assertEquals(HMAC_SHA1_abc, Buffer().writeUtf8("abc").hmacSha1(HMAC_KEY)) -// } -// -// @Test fun bufferHmacSha256() { -// assertEquals(HMAC_SHA256_abc, Buffer().writeUtf8("abc").hmacSha256(HMAC_KEY)) -// } -// -// @Test fun bufferHmacSha512() { -// assertEquals(HMAC_SHA512_abc, Buffer().writeUtf8("abc").hmacSha512(HMAC_KEY)) -// } -// -// @Test fun hmacSha256EmptyBuffer() { -// assertEquals(HMAC_SHA256_empty, Buffer().sha256()) -// } -// -// @Test fun hmacSha512EmptyBuffer() { -// assertEquals(HMAC_SHA512_empty, Buffer().sha512()) -// } -// -// @Test fun bufferHashIsNotDestructive() { -// val buffer = Buffer() -// -// buffer.writeUtf8("abc") -// assertEquals(SHA256_abc, buffer.sha256()) -// assertEquals("abc", buffer.readUtf8()) -// -// buffer.writeUtf8("def") -// assertEquals(SHA256_def, buffer.sha256()) -// assertEquals("def", buffer.readUtf8()) -// -// buffer.write(r32k) -// assertEquals(SHA256_r32k, buffer.sha256()) -// assertEquals(r32k, buffer.readByteString()) -// } -// -// companion object { -// val HMAC_KEY = -// "0102030405060708".decodeHex() -// val MD5_abc = -// "900150983cd24fb0d6963f7d28e17f72".decodeHex() -// val SHA1_abc = -// "a9993e364706816aba3e25717850c26c9cd0d89d".decodeHex() -// val HMAC_SHA1_abc = -// "987af8649982ff7d9fbb1b8aa35099146997af51".decodeHex() -// val SHA256_abc = -// "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad".decodeHex() -// val SHA256_empty = -// "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855".decodeHex() -// val SHA256_def = -// "cb8379ac2098aa165029e3938a51da0bcecfc008fd6795f401178647f96c5b34".decodeHex() -// val SHA256_r32k = -// "dadec7297f49bdf219895bd9942454047d394e1f20f247fbdc591080b4e8731e".decodeHex() -// val SHA512_abc = -// "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f".decodeHex() -// val SHA512_empty = -// "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e".decodeHex() -// val HMAC_SHA256_empty = -// "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855".decodeHex() -// val HMAC_SHA256_abc = -// "446d1715583cf1c30dfffbec0df4ff1f9d39d493211ab4c97ed6f3f0eb579b47".decodeHex() -// val HMAC_SHA512_empty = -// "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e".decodeHex() -// val HMAC_SHA512_abc = -// "24391790e7131050b05b606f2079a8983313894a1642a5ed97d094e7cabd00cfaa857d92c1f320ca3b6aaabb84c7155d6f1b10940dc133ded1b40baee8900be6".decodeHex() -// val r32k = randomBytes(32768) -// } -//} diff --git a/core/common/test/BufferedSinkFactory.kt b/core/common/test/SinkFactory.kt similarity index 84% rename from core/common/test/BufferedSinkFactory.kt rename to core/common/test/SinkFactory.kt index cd844f902..78764e5e4 100644 --- a/core/common/test/BufferedSinkFactory.kt +++ b/core/common/test/SinkFactory.kt @@ -21,18 +21,18 @@ package kotlinx.io -internal interface BufferedSinkFactory { +internal interface SinkFactory { fun create(data: Buffer): Sink companion object { - val BUFFER: BufferedSinkFactory = object : BufferedSinkFactory { + val BUFFER: SinkFactory = object : SinkFactory { override fun create(data: Buffer): Sink { return data } } - val REAL_BUFFERED_SINK: BufferedSinkFactory = object : BufferedSinkFactory { + val REAL_BUFFERED_SINK: SinkFactory = object : SinkFactory { override fun create(data: Buffer): Sink { return (data as RawSink).buffer() } diff --git a/core/common/test/BufferedSourceFactory.kt b/core/common/test/SourceFactory.kt similarity index 86% rename from core/common/test/BufferedSourceFactory.kt rename to core/common/test/SourceFactory.kt index 839327d1c..095c683a0 100644 --- a/core/common/test/BufferedSourceFactory.kt +++ b/core/common/test/SourceFactory.kt @@ -21,7 +21,7 @@ package kotlinx.io -interface BufferedSourceFactory { +interface SourceFactory { class Pipe( var sink: Sink, var source: Source @@ -32,7 +32,7 @@ interface BufferedSourceFactory { fun pipe(): Pipe companion object { - val BUFFER: BufferedSourceFactory = object : BufferedSourceFactory { + val BUFFER: SourceFactory = object : SourceFactory { override val isOneByteAtATime: Boolean get() = false @@ -46,8 +46,8 @@ interface BufferedSourceFactory { } } - val REAL_BUFFERED_SOURCE: BufferedSourceFactory = object : - BufferedSourceFactory { + val REAL_BUFFERED_SOURCE: SourceFactory = object : + SourceFactory { override val isOneByteAtATime: Boolean get() = false @@ -65,8 +65,8 @@ interface BufferedSourceFactory { * A factory deliberately written to create buffers whose internal segments are always 1 byte * long. We like testing with these segments because are likely to trigger bugs! */ - val ONE_BYTE_AT_A_TIME_BUFFERED_SOURCE: BufferedSourceFactory = object : - BufferedSourceFactory { + val ONE_BYTE_AT_A_TIME_BUFFERED_SOURCE: SourceFactory = object : + SourceFactory { override val isOneByteAtATime: Boolean get() = true @@ -89,8 +89,8 @@ interface BufferedSourceFactory { } } - val ONE_BYTE_AT_A_TIME_BUFFER: BufferedSourceFactory = object : - BufferedSourceFactory { + val ONE_BYTE_AT_A_TIME_BUFFER: SourceFactory = object : + SourceFactory { override val isOneByteAtATime: Boolean get() = true @@ -114,7 +114,7 @@ interface BufferedSourceFactory { } } - val PEEK_BUFFER: BufferedSourceFactory = object : BufferedSourceFactory { + val PEEK_BUFFER: SourceFactory = object : SourceFactory { override val isOneByteAtATime: Boolean get() = false @@ -128,8 +128,8 @@ interface BufferedSourceFactory { } } - val PEEK_BUFFERED_SOURCE: BufferedSourceFactory = object : - BufferedSourceFactory { + val PEEK_BUFFERED_SOURCE: SourceFactory = object : + SourceFactory { override val isOneByteAtATime: Boolean get() = false diff --git a/core/common/test/TestingSupport.kt b/core/common/test/TestingSupport.kt index 9a021481e..416998113 100644 --- a/core/common/test/TestingSupport.kt +++ b/core/common/test/TestingSupport.kt @@ -28,14 +28,4 @@ fun Char.repeat(count: Int): String { fun assertArrayEquals(a: ByteArray, b: ByteArray) { assertEquals(a.contentToString(), b.contentToString()) -} -// -//fun randomBytes(length: Int): ByteString { -// val random = Random(0) -// val randomBytes = ByteArray(length) -// random.nextBytes(randomBytes) -// return ByteString.of(*randomBytes) -//} -// -//fun randomToken(length: Int) = Random.nextBytes(length).toByteString(0, length).hex() - +} \ No newline at end of file diff --git a/core/common/test/UnsafeCursorTest.kt b/core/common/test/UnsafeCursorTest.kt deleted file mode 100644 index 44b353f2c..000000000 --- a/core/common/test/UnsafeCursorTest.kt +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright 2017-2023 JetBrains s.r.o. and respective authors and developers. - * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENCE file. - */ - -/* - * Copyright (C) 2020 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 kotlinx.io - -import kotlin.test.Test -import kotlin.test.assertEquals - -//class UnsafeCursorTest { -// @Test fun acquireForRead() { -// val buffer = Buffer() -// buffer.writeUtf8("xo".repeat(5000)) -// -// val cursor = buffer.readAndWriteUnsafe() -// try { -// val copy = Buffer() -// while (cursor.next() != -1) { -// copy.write(cursor.data!!, cursor.start, cursor.end - cursor.start) -// } -// } finally { -// cursor.close() -// } -// -// assertEquals("xo".repeat(5000), buffer.readUtf8()) -// } -// -// @Test fun acquireForWrite() { -// val buffer = Buffer() -// buffer.writeUtf8("xo".repeat(5000)) -// -// val cursor = buffer.readAndWriteUnsafe() -// try { -// while (cursor.next() != -1) { -// cursor.data!!.fill('z'.code.toByte(), cursor.start, cursor.end) -// } -// } finally { -// cursor.close() -// } -// -// assertEquals("zz".repeat(5000), buffer.readUtf8()) -// } -// -// @Test fun expand() { -// val buffer = Buffer() -// -// val cursor = buffer.readAndWriteUnsafe() -// try { -// cursor.expandBuffer(100) -// cursor.data!!.fill('z'.code.toByte(), cursor.start, cursor.start + 100) -// cursor.resizeBuffer(100L) -// } finally { -// cursor.close() -// } -// -// val expected = "z".repeat(100) -// val actual = buffer.readUtf8() -// println(actual) -// println(expected) -// assertEquals(expected, actual) -// } -// -// @Test fun resizeBuffer() { -// val buffer = Buffer() -// -// val cursor = buffer.readAndWriteUnsafe() -// try { -// cursor.resizeBuffer(100L) -// cursor.data!!.fill('z'.code.toByte(), cursor.start, cursor.end) -// } finally { -// cursor.close() -// } -// -// assertEquals("z".repeat(100), buffer.readUtf8()) -// } -//} diff --git a/core/common/test/Utf8KotlinTest.kt b/core/common/test/Utf8KotlinTest.kt index d54dbdaf1..0d8e4bd7e 100644 --- a/core/common/test/Utf8KotlinTest.kt +++ b/core/common/test/Utf8KotlinTest.kt @@ -27,107 +27,107 @@ import kotlin.test.assertEquals import kotlin.test.assertFailsWith class Utf8KotlinTest { -// @Test fun oneByteCharacters() { -// assertEncoded("00", 0x00) // Smallest 1-byte character. -// assertEncoded("20", ' '.code) -// assertEncoded("7e", '~'.code) -// assertEncoded("7f", 0x7f) // Largest 1-byte character. -// } -// -// @Test fun twoByteCharacters() { -// assertEncoded("c280", 0x0080) // Smallest 2-byte character. -// assertEncoded("c3bf", 0x00ff) -// assertEncoded("c480", 0x0100) -// assertEncoded("dfbf", 0x07ff) // Largest 2-byte character. -// } -// -// @Test fun threeByteCharacters() { -// assertEncoded("e0a080", 0x0800) // Smallest 3-byte character. -// assertEncoded("e0bfbf", 0x0fff) -// assertEncoded("e18080", 0x1000) -// assertEncoded("e1bfbf", 0x1fff) -// assertEncoded("ed8080", 0xd000) -// assertEncoded("ed9fbf", 0xd7ff) // Largest character lower than the min surrogate. -// assertEncoded("ee8080", 0xe000) // Smallest character greater than the max surrogate. -// assertEncoded("eebfbf", 0xefff) -// assertEncoded("ef8080", 0xf000) -// assertEncoded("efbfbf", 0xffff) // Largest 3-byte character. -// } -// -// @Test fun fourByteCharacters() { -// assertEncoded("f0908080", 0x010000) // Smallest surrogate pair. -// assertEncoded("f48fbfbf", 0x10ffff) // Largest code point expressible by UTF-16. -// } -// -// @Test fun unknownBytes() { -// assertCodePointDecoded("f8", REPLACEMENT_CODE_POINT) // Too large -// assertCodePointDecoded("f0f8", REPLACEMENT_CODE_POINT, REPLACEMENT_CODE_POINT) -// assertCodePointDecoded("ff", REPLACEMENT_CODE_POINT) // Largest -// assertCodePointDecoded("f0ff", REPLACEMENT_CODE_POINT, REPLACEMENT_CODE_POINT) -// -// // Lone continuation -// assertCodePointDecoded("80", REPLACEMENT_CODE_POINT) // Smallest -// assertCodePointDecoded("bf", REPLACEMENT_CODE_POINT) // Largest -// } -// -// @Test fun overlongSequences() { -// // Overlong representation of the NUL character -// assertCodePointDecoded("c080", REPLACEMENT_CODE_POINT) -// assertCodePointDecoded("e08080", REPLACEMENT_CODE_POINT) -// assertCodePointDecoded("f0808080", REPLACEMENT_CODE_POINT) -// -// // Maximum overlong sequences -// assertCodePointDecoded("c1bf", REPLACEMENT_CODE_POINT) -// assertCodePointDecoded("e09fbf", REPLACEMENT_CODE_POINT) -// assertCodePointDecoded("f08fbfbf", REPLACEMENT_CODE_POINT) -// } -// -// @Test fun danglingHighSurrogate() { -// assertStringEncoded("3f", "\ud800") // "?" -// assertCodePointDecoded("eda080", REPLACEMENT_CODE_POINT) -// } -// -// @Test fun lowSurrogateWithoutHighSurrogate() { -// assertStringEncoded("3f", "\udc00") // "?" -// assertCodePointDecoded("edb080", REPLACEMENT_CODE_POINT) -// } -// -// @Test fun highSurrogateFollowedByNonSurrogate() { -// assertStringEncoded("3fee8080", "\ud800\ue000") // "?\ue000": Following character is too high. -// assertCodePointDecoded("f090ee8080", REPLACEMENT_CODE_POINT, '\ue000'.code) -// -// assertStringEncoded("3f61", "\ud800\u0061") // "?a": Following character is too low. -// assertCodePointDecoded("f09061", REPLACEMENT_CODE_POINT, 'a'.code) -// } -// -// @Test fun doubleLowSurrogate() { -// assertStringEncoded("3f3f", "\udc00\udc00") // "??" -// assertCodePointDecoded("edb080edb080", REPLACEMENT_CODE_POINT, REPLACEMENT_CODE_POINT) -// } -// -// @Test fun doubleHighSurrogate() { -// assertStringEncoded("3f3f", "\ud800\ud800") // "??" -// assertCodePointDecoded("eda080eda080", REPLACEMENT_CODE_POINT, REPLACEMENT_CODE_POINT) -// } -// -// @Test fun lowSurrogateHighSurrogate() { -// assertStringEncoded("3f3f", "\udc00\ud800") // "??" -// assertCodePointDecoded("edb080eda080", REPLACEMENT_CODE_POINT, REPLACEMENT_CODE_POINT) -// } -// -// @Test fun writeSurrogateCodePoint() { -// assertStringEncoded("ed9fbf", "\ud7ff") // Below lowest surrogate is okay. -// assertCodePointDecoded("ed9fbf", '\ud7ff'.code) -// -// assertStringEncoded("3f", "\ud800") // Lowest surrogate gets '?'. -// assertCodePointDecoded("eda080", REPLACEMENT_CODE_POINT) -// -// assertStringEncoded("3f", "\udfff") // Highest surrogate gets '?'. -// assertCodePointDecoded("edbfbf", REPLACEMENT_CODE_POINT) -// -// assertStringEncoded("ee8080", "\ue000") // Above highest surrogate is okay. -// assertCodePointDecoded("ee8080", '\ue000'.code) -// } + @Test fun oneByteCharacters() { + assertEncoded("00", 0x00) // Smallest 1-byte character. + assertEncoded("20", ' '.code) + assertEncoded("7e", '~'.code) + assertEncoded("7f", 0x7f) // Largest 1-byte character. + } + + @Test fun twoByteCharacters() { + assertEncoded("c280", 0x0080) // Smallest 2-byte character. + assertEncoded("c3bf", 0x00ff) + assertEncoded("c480", 0x0100) + assertEncoded("dfbf", 0x07ff) // Largest 2-byte character. + } + + @Test fun threeByteCharacters() { + assertEncoded("e0a080", 0x0800) // Smallest 3-byte character. + assertEncoded("e0bfbf", 0x0fff) + assertEncoded("e18080", 0x1000) + assertEncoded("e1bfbf", 0x1fff) + assertEncoded("ed8080", 0xd000) + assertEncoded("ed9fbf", 0xd7ff) // Largest character lower than the min surrogate. + assertEncoded("ee8080", 0xe000) // Smallest character greater than the max surrogate. + assertEncoded("eebfbf", 0xefff) + assertEncoded("ef8080", 0xf000) + assertEncoded("efbfbf", 0xffff) // Largest 3-byte character. + } + + @Test fun fourByteCharacters() { + assertEncoded("f0908080", 0x010000) // Smallest surrogate pair. + assertEncoded("f48fbfbf", 0x10ffff) // Largest code point expressible by UTF-16. + } + + @Test fun unknownBytes() { + assertCodePointDecoded("f8", REPLACEMENT_CODE_POINT) // Too large + assertCodePointDecoded("f0f8", REPLACEMENT_CODE_POINT, REPLACEMENT_CODE_POINT) + assertCodePointDecoded("ff", REPLACEMENT_CODE_POINT) // Largest + assertCodePointDecoded("f0ff", REPLACEMENT_CODE_POINT, REPLACEMENT_CODE_POINT) + + // Lone continuation + assertCodePointDecoded("80", REPLACEMENT_CODE_POINT) // Smallest + assertCodePointDecoded("bf", REPLACEMENT_CODE_POINT) // Largest + } + + @Test fun overlongSequences() { + // Overlong representation of the NUL character + assertCodePointDecoded("c080", REPLACEMENT_CODE_POINT) + assertCodePointDecoded("e08080", REPLACEMENT_CODE_POINT) + assertCodePointDecoded("f0808080", REPLACEMENT_CODE_POINT) + + // Maximum overlong sequences + assertCodePointDecoded("c1bf", REPLACEMENT_CODE_POINT) + assertCodePointDecoded("e09fbf", REPLACEMENT_CODE_POINT) + assertCodePointDecoded("f08fbfbf", REPLACEMENT_CODE_POINT) + } + + @Test fun danglingHighSurrogate() { + assertStringEncoded("3f", "\ud800") // "?" + assertCodePointDecoded("eda080", REPLACEMENT_CODE_POINT) + } + + @Test fun lowSurrogateWithoutHighSurrogate() { + assertStringEncoded("3f", "\udc00") // "?" + assertCodePointDecoded("edb080", REPLACEMENT_CODE_POINT) + } + + @Test fun highSurrogateFollowedByNonSurrogate() { + assertStringEncoded("3fee8080", "\ud800\ue000") // "?\ue000": Following character is too high. + assertCodePointDecoded("f090ee8080", REPLACEMENT_CODE_POINT, '\ue000'.code) + + assertStringEncoded("3f61", "\ud800\u0061") // "?a": Following character is too low. + assertCodePointDecoded("f09061", REPLACEMENT_CODE_POINT, 'a'.code) + } + + @Test fun doubleLowSurrogate() { + assertStringEncoded("3f3f", "\udc00\udc00") // "??" + assertCodePointDecoded("edb080edb080", REPLACEMENT_CODE_POINT, REPLACEMENT_CODE_POINT) + } + + @Test fun doubleHighSurrogate() { + assertStringEncoded("3f3f", "\ud800\ud800") // "??" + assertCodePointDecoded("eda080eda080", REPLACEMENT_CODE_POINT, REPLACEMENT_CODE_POINT) + } + + @Test fun lowSurrogateHighSurrogate() { + assertStringEncoded("3f3f", "\udc00\ud800") // "??" + assertCodePointDecoded("edb080eda080", REPLACEMENT_CODE_POINT, REPLACEMENT_CODE_POINT) + } + + @Test fun writeSurrogateCodePoint() { + assertStringEncoded("ed9fbf", "\ud7ff") // Below lowest surrogate is okay. + assertCodePointDecoded("ed9fbf", '\ud7ff'.code) + + assertStringEncoded("3f", "\ud800") // Lowest surrogate gets '?'. + assertCodePointDecoded("eda080", REPLACEMENT_CODE_POINT) + + assertStringEncoded("3f", "\udfff") // Highest surrogate gets '?'. + assertCodePointDecoded("edbfbf", REPLACEMENT_CODE_POINT) + + assertStringEncoded("ee8080", "\ue000") // Above highest surrogate is okay. + assertCodePointDecoded("ee8080", '\ue000'.code) + } @Test fun size() { assertEquals(0, "".utf8Size()) @@ -159,35 +159,35 @@ class Utf8KotlinTest { } } -// private fun assertEncoded(hex: String, vararg codePoints: Int) { -// assertCodePointDecoded(hex, *codePoints) -// } - -// private fun assertCodePointDecoded(hex: String, vararg codePoints: Int) { -// val bytes = hex.decodeHex().toByteArray() -// var i = 0 -// bytes.processUtf8CodePoints(0, bytes.size) { codePoint -> -// if (i < codePoints.size) assertEquals(codePoints[i], codePoint, "index=$i") -// i++ -// } -// assertEquals(i, codePoints.size) // Checked them all -// } -// -// private fun assertStringEncoded(hex: String, string: String) { -// val expectedUtf8 = hex.decodeHex() -// -// // Confirm our expectations are consistent with the platform. -// val platformUtf8 = ByteString.of(*string.asUtf8ToByteArray()) -// assertEquals(expectedUtf8, platformUtf8) -// -// // Confirm our implementations matches those expectations. -// val actualUtf8 = ByteString.of(*string.commonAsUtf8ToByteArray()) -// assertEquals(expectedUtf8, actualUtf8) -// -// // TODO Confirm we are consistent when writing one code point at a time. -// -// // Confirm we are consistent when measuring lengths. -// assertEquals(expectedUtf8.size.toLong(), string.utf8Size()) -// assertEquals(expectedUtf8.size.toLong(), string.utf8Size(0, string.length)) -// } + private fun assertEncoded(hex: String, vararg codePoints: Int) { + assertCodePointDecoded(hex, *codePoints) + } + + private fun assertCodePointDecoded(hex: String, vararg codePoints: Int) { + val bytes = hex.decodeHex() + var i = 0 + bytes.processUtf8CodePoints(0, bytes.size) { codePoint -> + if (i < codePoints.size) assertEquals(codePoints[i], codePoint, "index=$i") + i++ + } + assertEquals(i, codePoints.size) // Checked them all + } + + private fun assertStringEncoded(hex: String, string: String) { + val expectedUtf8 = hex.decodeHex() + + // Confirm our expectations are consistent with the platform. + val platformUtf8 = string.asUtf8ToByteArray() + assertArrayEquals(expectedUtf8, platformUtf8) + + // Confirm our implementations matches those expectations. + val actualUtf8 = string.commonAsUtf8ToByteArray() + assertArrayEquals(expectedUtf8, actualUtf8) + + // TODO Confirm we are consistent when writing one code point at a time. + + // Confirm we are consistent when measuring lengths. + assertEquals(expectedUtf8.size.toLong(), string.utf8Size()) + assertEquals(expectedUtf8.size.toLong(), string.utf8Size(0, string.length)) + } } diff --git a/core/common/test/util.kt b/core/common/test/util.kt index 2efde353b..1ed7ab5d2 100644 --- a/core/common/test/util.kt +++ b/core/common/test/util.kt @@ -104,4 +104,27 @@ fun bufferWithSegments(vararg segments: String): Buffer { //): IllegalArgumentException expect fun createTempFile(): String -expect fun deleteFile(path: String) \ No newline at end of file +expect fun deleteFile(path: String) + +private fun fromHexChar(char: Char): Int { + val code = char.code + return when (code) { + in '0'.code..'9'.code -> code - '0'.code + in 'a'.code..'f'.code -> code - 'a'.code + 10 + in 'A'.code..'F'.code -> code - 'A'.code + 10 + else -> throw NumberFormatException("Not a hexadecimal digit: $char") + } +} + +fun String.decodeHex(): ByteArray { + if (length % 2 != 0) throw IllegalArgumentException("Even number of bytes is expected.") + + val result = ByteArray(length / 2) + + for (idx in result.indices) { + val byte = fromHexChar(this[idx * 2]).shl(4).or(fromHexChar(this[idx * 2 + 1])) + result[idx] = byte.toByte() + } + + return result +} \ No newline at end of file diff --git a/core/jvm/src/-JvmPlatform.kt b/core/jvm/src/-JvmPlatform.kt index 285b93cbd..94fb82260 100644 --- a/core/jvm/src/-JvmPlatform.kt +++ b/core/jvm/src/-JvmPlatform.kt @@ -28,25 +28,8 @@ internal actual fun String.asUtf8ToByteArray(): ByteArray = toByteArray(Charsets // TODO remove if https://youtrack.jetbrains.com/issue/KT-20641 provides a better solution actual typealias ArrayIndexOutOfBoundsException = java.lang.ArrayIndexOutOfBoundsException -internal actual inline fun synchronized(lock: Any, block: () -> R): R { - return kotlin.synchronized(lock, block) -} - actual typealias IOException = java.io.IOException -actual typealias ProtocolException = java.net.ProtocolException - actual typealias EOFException = java.io.EOFException -actual typealias FileNotFoundException = java.io.FileNotFoundException - actual typealias Closeable = java.io.Closeable - -@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN", "NOTHING_TO_INLINE") -internal inline fun Any.wait() = (this as Object).wait() - -@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN", "NOTHING_TO_INLINE") -internal inline fun Any.notify() = (this as Object).notify() - -@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN", "NOTHING_TO_INLINE") -internal inline fun Any.notifyAll() = (this as Object).notifyAll() diff --git a/core/jvm/src/Buffer.kt b/core/jvm/src/Buffer.kt index 3c7ae65d4..b8e4c6127 100644 --- a/core/jvm/src/Buffer.kt +++ b/core/jvm/src/Buffer.kt @@ -227,28 +227,6 @@ actual class Buffer : Source, Sink, Cloneable, ByteChannel { @Throws(EOFException::class) override fun readLong(): Long = commonReadLong() - @Throws(EOFException::class) - override fun readShortLe() = readShort().reverseBytes() - - @Throws(EOFException::class) - override fun readIntLe() = readInt().reverseBytes() - - @Throws(EOFException::class) - override fun readLongLe() = readLong().reverseBytes() - - @Throws(EOFException::class) - override fun readDecimalLong(): Long = commonReadDecimalLong() - - @Throws(EOFException::class) - override fun readHexadecimalUnsignedLong(): Long = commonReadHexadecimalUnsignedLong() - -// override fun readByteString(): ByteString = commonReadByteString() -// -// @Throws(EOFException::class) -// override fun readByteString(byteCount: Long) = commonReadByteString(byteCount) -// -// override fun select(options: Options): Int = commonSelect(options) - @Throws(EOFException::class) override fun readFully(sink: Buffer, byteCount: Long): Unit = commonReadFully(sink, byteCount) @@ -286,15 +264,6 @@ actual class Buffer : Source, Sink, Cloneable, ByteChannel { return result } - @Throws(EOFException::class) - override fun readUtf8Line(): String? = commonReadUtf8Line() - - @Throws(EOFException::class) - override fun readUtf8LineStrict() = readUtf8LineStrict(Long.MAX_VALUE) - - @Throws(EOFException::class) - override fun readUtf8LineStrict(limit: Long): String = commonReadUtf8LineStrict(limit) - @Throws(EOFException::class) override fun readUtf8CodePoint(): Int = commonReadUtf8CodePoint() @@ -303,8 +272,6 @@ actual class Buffer : Source, Sink, Cloneable, ByteChannel { @Throws(EOFException::class) override fun readByteArray(byteCount: Long): ByteArray = commonReadByteArray(byteCount) - override fun read(sink: ByteArray) = commonRead(sink) - @Throws(EOFException::class) override fun readFully(sink: ByteArray) = commonReadFully(sink) @@ -334,13 +301,6 @@ actual class Buffer : Source, Sink, Cloneable, ByteChannel { @Throws(EOFException::class) actual override fun skip(byteCount: Long) = commonSkip(byteCount) -// actual override fun write(byteString: ByteString): Buffer = commonWrite(byteString) -// -// actual override fun write(byteString: ByteString, offset: Int, byteCount: Int) = -// commonWrite(byteString, offset, byteCount) - - actual override fun writeUtf8(string: String): Buffer = writeUtf8(string, 0, string.length) - actual override fun writeUtf8(string: String, beginIndex: Int, endIndex: Int): Buffer = commonWriteUtf8(string, beginIndex, endIndex) @@ -366,8 +326,6 @@ actual class Buffer : Source, Sink, Cloneable, ByteChannel { return write(data, 0, data.size) } - actual override fun write(source: ByteArray): Buffer = commonWrite(source) - actual override fun write( source: ByteArray, offset: Int, @@ -403,21 +361,10 @@ actual class Buffer : Source, Sink, Cloneable, ByteChannel { actual override fun writeShort(s: Int): Buffer = commonWriteShort(s) - actual override fun writeShortLe(s: Int) = writeShort(s.toShort().reverseBytes().toInt()) - actual override fun writeInt(i: Int): Buffer = commonWriteInt(i) - actual override fun writeIntLe(i: Int) = writeInt(i.reverseBytes()) - actual override fun writeLong(v: Long): Buffer = commonWriteLong(v) - actual override fun writeLongLe(v: Long) = writeLong(v.reverseBytes()) - - actual override fun writeDecimalLong(v: Long): Buffer = commonWriteDecimalLong(v) - - actual override fun writeHexadecimalUnsignedLong(v: Long): Buffer = - commonWriteHexadecimalUnsignedLong(v) - internal actual fun writableSegment(minimumCapacity: Int): Segment = commonWritableSegment(minimumCapacity) @@ -425,37 +372,6 @@ actual class Buffer : Source, Sink, Cloneable, ByteChannel { override fun read(sink: Buffer, byteCount: Long): Long = commonRead(sink, byteCount) - override fun indexOf(b: Byte) = indexOf(b, 0, Long.MAX_VALUE) - - /** - * Returns the index of `b` in this at or beyond `fromIndex`, or -1 if this buffer does not - * contain `b` in that range. - */ - override fun indexOf(b: Byte, fromIndex: Long) = indexOf(b, fromIndex, Long.MAX_VALUE) - - override fun indexOf(b: Byte, fromIndex: Long, toIndex: Long): Long = commonIndexOf(b, fromIndex, toIndex) - -// @Throws(IOException::class) -// override fun indexOf(bytes: ByteString): Long = indexOf(bytes, 0) -// -// @Throws(IOException::class) -// override fun indexOf(bytes: ByteString, fromIndex: Long): Long = commonIndexOf(bytes, fromIndex) -// -// override fun indexOfElement(targetBytes: ByteString) = indexOfElement(targetBytes, 0L) -// -// override fun indexOfElement(targetBytes: ByteString, fromIndex: Long): Long = -// commonIndexOfElement(targetBytes, fromIndex) -// -// override fun rangeEquals(offset: Long, bytes: ByteString) = -// rangeEquals(offset, bytes, 0, bytes.size) -// -// override fun rangeEquals( -// offset: Long, -// bytes: ByteString, -// bytesOffset: Int, -// byteCount: Int -// ): Boolean = commonRangeEquals(offset, bytes, bytesOffset, byteCount) - override fun flush() {} override fun isOpen() = true @@ -466,66 +382,6 @@ actual class Buffer : Source, Sink, Cloneable, ByteChannel { // Not cancelable. } -// /** -// * Returns the 128-bit MD5 hash of this buffer. -// * -// * MD5 has been vulnerable to collisions since 2004. It should not be used in new code. -// */ -// actual fun md5() = digest("MD5") -// -// /** -// * Returns the 160-bit SHA-1 hash of this buffer. -// * -// * SHA-1 has been vulnerable to collisions since 2017. It should not be used in new code. -// */ -// actual fun sha1() = digest("SHA-1") -// -// /** Returns the 256-bit SHA-256 hash of this buffer. */ -// actual fun sha256() = digest("SHA-256") -// -// /** Returns the 512-bit SHA-512 hash of this buffer. */ -// actual fun sha512() = digest("SHA-512") -// -// private fun digest(algorithm: String): ByteString { -// val messageDigest = MessageDigest.getInstance(algorithm) -// head?.let { head -> -// messageDigest.update(head.data, head.pos, head.limit - head.pos) -// var s = head.next!! -// while (s !== head) { -// messageDigest.update(s.data, s.pos, s.limit - s.pos) -// s = s.next!! -// } -// } -// return ByteString(messageDigest.digest()) -// } -// -// /** Returns the 160-bit SHA-1 HMAC of this buffer. */ -// actual fun hmacSha1(key: ByteString) = hmac("HmacSHA1", key) -// -// /** Returns the 256-bit SHA-256 HMAC of this buffer. */ -// actual fun hmacSha256(key: ByteString) = hmac("HmacSHA256", key) -// -// /** Returns the 512-bit SHA-512 HMAC of this buffer. */ -// actual fun hmacSha512(key: ByteString) = hmac("HmacSHA512", key) -// -// private fun hmac(algorithm: String, key: ByteString): ByteString { -// try { -// val mac = Mac.getInstance(algorithm) -// mac.init(SecretKeySpec(key.internalArray(), algorithm)) -// head?.let { head -> -// mac.update(head.data, head.pos, head.limit - head.pos) -// var s = head.next!! -// while (s !== head) { -// mac.update(s.data, s.pos, s.limit - s.pos) -// s = s.next!! -// } -// } -// return ByteString(mac.doFinal()) -// } catch (e: InvalidKeyException) { -// throw IllegalArgumentException(e) -// } -// } - override fun equals(other: Any?): Boolean = commonEquals(other) override fun hashCode(): Int = commonHashCode() @@ -540,38 +396,4 @@ actual class Buffer : Source, Sink, Cloneable, ByteChannel { /** Returns a deep copy of this buffer. */ public override fun clone(): Buffer = copy() - -// actual fun snapshot(): ByteString = commonSnapshot() -// -// actual fun snapshot(byteCount: Int): ByteString = commonSnapshot(byteCount) -// -// @JvmOverloads -// actual fun readUnsafe(unsafeCursor: UnsafeCursor): UnsafeCursor = commonReadUnsafe(unsafeCursor) -// -// @JvmOverloads -// actual fun readAndWriteUnsafe(unsafeCursor: UnsafeCursor): UnsafeCursor = -// commonReadAndWriteUnsafe(unsafeCursor) - - actual class UnsafeCursor : Closeable { - @JvmField actual var buffer: Buffer? = null - @JvmField actual var readWrite: Boolean = false - - internal actual var segment: Segment? = null - @JvmField actual var offset = -1L - @JvmField actual var data: ByteArray? = null - @JvmField actual var start = -1 - @JvmField actual var end = -1 - - actual fun next(): Int = commonNext() - - actual fun seek(offset: Long): Int = commonSeek(offset) - - actual fun resizeBuffer(newSize: Long): Long = commonResizeBuffer(newSize) - - actual fun expandBuffer(minByteCount: Int): Long = commonExpandBuffer(minByteCount) - - actual override fun close() { - commonClose() - } - } } diff --git a/core/jvm/src/RealSink.kt b/core/jvm/src/RealSink.kt index c6a951e52..5673c4f5b 100644 --- a/core/jvm/src/RealSink.kt +++ b/core/jvm/src/RealSink.kt @@ -39,7 +39,6 @@ internal actual class RealSink actual constructor( override fun buffer() = bufferField override fun write(source: Buffer, byteCount: Long) = commonWrite(source, byteCount) - override fun writeUtf8(string: String) = commonWriteUtf8(string) override fun writeUtf8(string: String, beginIndex: Int, endIndex: Int) = commonWriteUtf8(string, beginIndex, endIndex) @@ -62,7 +61,6 @@ internal actual class RealSink actual constructor( return emitCompleteSegments() } - override fun write(source: ByteArray) = commonWrite(source) override fun write(source: ByteArray, offset: Int, byteCount: Int) = commonWrite(source, offset, byteCount) @@ -77,13 +75,8 @@ internal actual class RealSink actual constructor( override fun write(source: RawSource, byteCount: Long): Sink = commonWrite(source, byteCount) override fun writeByte(b: Int) = commonWriteByte(b) override fun writeShort(s: Int) = commonWriteShort(s) - override fun writeShortLe(s: Int) = commonWriteShortLe(s) override fun writeInt(i: Int) = commonWriteInt(i) - override fun writeIntLe(i: Int) = commonWriteIntLe(i) override fun writeLong(v: Long) = commonWriteLong(v) - override fun writeLongLe(v: Long) = commonWriteLongLe(v) - override fun writeDecimalLong(v: Long) = commonWriteDecimalLong(v) - override fun writeHexadecimalUnsignedLong(v: Long) = commonWriteHexadecimalUnsignedLong(v) override fun emitCompleteSegments() = commonEmitCompleteSegments() override fun emit() = commonEmit() diff --git a/core/jvm/src/RealSource.kt b/core/jvm/src/RealSource.kt index 7d781f719..0c4680ad8 100644 --- a/core/jvm/src/RealSource.kt +++ b/core/jvm/src/RealSource.kt @@ -45,7 +45,6 @@ internal actual class RealSource actual constructor( override fun readByte(): Byte = commonReadByte() override fun readByteArray(): ByteArray = commonReadByteArray() override fun readByteArray(byteCount: Long): ByteArray = commonReadByteArray(byteCount) - override fun read(sink: ByteArray): Int = read(sink, 0, sink.size) override fun readFully(sink: ByteArray): Unit = commonReadFully(sink) override fun read(sink: ByteArray, offset: Int, byteCount: Int): Int = commonRead(sink, offset, byteCount) @@ -74,41 +73,11 @@ internal actual class RealSource actual constructor( return buffer.readString(byteCount, charset) } - override fun readUtf8Line(): String? = commonReadUtf8Line() - override fun readUtf8LineStrict() = readUtf8LineStrict(Long.MAX_VALUE) - override fun readUtf8LineStrict(limit: Long): String = commonReadUtf8LineStrict(limit) override fun readUtf8CodePoint(): Int = commonReadUtf8CodePoint() override fun readShort(): Short = commonReadShort() - override fun readShortLe(): Short = commonReadShortLe() override fun readInt(): Int = commonReadInt() - override fun readIntLe(): Int = commonReadIntLe() override fun readLong(): Long = commonReadLong() - override fun readLongLe(): Long = commonReadLongLe() - override fun readDecimalLong(): Long = commonReadDecimalLong() - override fun readHexadecimalUnsignedLong(): Long = commonReadHexadecimalUnsignedLong() override fun skip(byteCount: Long): Unit = commonSkip(byteCount) - override fun indexOf(b: Byte): Long = indexOf(b, 0L, Long.MAX_VALUE) - override fun indexOf(b: Byte, fromIndex: Long): Long = indexOf(b, fromIndex, Long.MAX_VALUE) - override fun indexOf(b: Byte, fromIndex: Long, toIndex: Long): Long = - commonIndexOf(b, fromIndex, toIndex) - -// override fun indexOf(bytes: ByteString): Long = indexOf(bytes, 0L) -// override fun indexOf(bytes: ByteString, fromIndex: Long): Long = commonIndexOf(bytes, fromIndex) -// override fun indexOfElement(targetBytes: ByteString): Long = indexOfElement(targetBytes, 0L) -// override fun indexOfElement(targetBytes: ByteString, fromIndex: Long): Long = -// commonIndexOfElement(targetBytes, fromIndex) -// -// override fun rangeEquals(offset: Long, bytes: ByteString) = rangeEquals( -// offset, bytes, 0, -// bytes.size -// ) - -// override fun rangeEquals( -// offset: Long, -// bytes: ByteString, -// bytesOffset: Int, -// byteCount: Int -// ): Boolean = commonRangeEquals(offset, bytes, bytesOffset, byteCount) override fun peek(): Source = commonPeek() diff --git a/core/jvm/src/Sink.kt b/core/jvm/src/Sink.kt index 15abcf7c5..d44e0668f 100644 --- a/core/jvm/src/Sink.kt +++ b/core/jvm/src/Sink.kt @@ -36,9 +36,6 @@ actual sealed interface Sink : RawSink, WritableByteChannel { actual val buffer: Buffer - @Throws(IOException::class) - actual fun write(source: ByteArray): Sink - @Throws(IOException::class) actual fun write(source: ByteArray, offset: Int, byteCount: Int): Sink @@ -48,9 +45,6 @@ actual sealed interface Sink : RawSink, WritableByteChannel { @Throws(IOException::class) actual fun write(source: RawSource, byteCount: Long): Sink - @Throws(IOException::class) - actual fun writeUtf8(string: String): Sink - @Throws(IOException::class) actual fun writeUtf8(string: String, beginIndex: Int, endIndex: Int): Sink @@ -69,27 +63,12 @@ actual sealed interface Sink : RawSink, WritableByteChannel { @Throws(IOException::class) actual fun writeShort(s: Int): Sink - @Throws(IOException::class) - actual fun writeShortLe(s: Int): Sink - @Throws(IOException::class) actual fun writeInt(i: Int): Sink - @Throws(IOException::class) - actual fun writeIntLe(i: Int): Sink - @Throws(IOException::class) actual fun writeLong(v: Long): Sink - @Throws(IOException::class) - actual fun writeLongLe(v: Long): Sink - - @Throws(IOException::class) - actual fun writeDecimalLong(v: Long): Sink - - @Throws(IOException::class) - actual fun writeHexadecimalUnsignedLong(v: Long): Sink - @Throws(IOException::class) actual override fun flush() diff --git a/core/jvm/src/Source.kt b/core/jvm/src/Source.kt index 7e95d709b..79fd94bee 100644 --- a/core/jvm/src/Source.kt +++ b/core/jvm/src/Source.kt @@ -51,27 +51,12 @@ actual sealed interface Source : RawSource, ReadableByteChannel { @Throws(IOException::class) actual fun readShort(): Short - @Throws(IOException::class) - actual fun readShortLe(): Short - @Throws(IOException::class) actual fun readInt(): Int - @Throws(IOException::class) - actual fun readIntLe(): Int - @Throws(IOException::class) actual fun readLong(): Long - @Throws(IOException::class) - actual fun readLongLe(): Long - - @Throws(IOException::class) - actual fun readDecimalLong(): Long - - @Throws(IOException::class) - actual fun readHexadecimalUnsignedLong(): Long - @Throws(IOException::class) actual fun skip(byteCount: Long) @@ -81,9 +66,6 @@ actual sealed interface Source : RawSource, ReadableByteChannel { @Throws(IOException::class) actual fun readByteArray(byteCount: Long): ByteArray - @Throws(IOException::class) - actual fun read(sink: ByteArray): Int - @Throws(IOException::class) actual fun readFully(sink: ByteArray) @@ -102,15 +84,6 @@ actual sealed interface Source : RawSource, ReadableByteChannel { @Throws(IOException::class) actual fun readUtf8(byteCount: Long): String - @Throws(IOException::class) - actual fun readUtf8Line(): String? - - @Throws(IOException::class) - actual fun readUtf8LineStrict(): String - - @Throws(IOException::class) - actual fun readUtf8LineStrict(limit: Long): String - @Throws(IOException::class) actual fun readUtf8CodePoint(): Int @@ -125,33 +98,6 @@ actual sealed interface Source : RawSource, ReadableByteChannel { @Throws(IOException::class) fun readString(byteCount: Long, charset: Charset): String - @Throws(IOException::class) - actual fun indexOf(b: Byte): Long - - @Throws(IOException::class) - actual fun indexOf(b: Byte, fromIndex: Long): Long - - @Throws(IOException::class) - actual fun indexOf(b: Byte, fromIndex: Long, toIndex: Long): Long - -// @Throws(IOException::class) -// actual fun indexOf(bytes: ByteString): Long - -// @Throws(IOException::class) -// actual fun indexOf(bytes: ByteString, fromIndex: Long): Long -// -// @Throws(IOException::class) -// actual fun indexOfElement(targetBytes: ByteString): Long -// -// @Throws(IOException::class) -// actual fun indexOfElement(targetBytes: ByteString, fromIndex: Long): Long -// -// @Throws(IOException::class) -// actual fun rangeEquals(offset: Long, bytes: ByteString): Boolean -// -// @Throws(IOException::class) -// actual fun rangeEquals(offset: Long, bytes: ByteString, bytesOffset: Int, byteCount: Int): Boolean - actual fun peek(): Source /** Returns an input stream that reads from this source. */ diff --git a/core/native/src/-NonJvmPlatform.kt b/core/native/src/-NonJvmPlatform.kt index fe2423113..4ee335ef7 100644 --- a/core/native/src/-NonJvmPlatform.kt +++ b/core/native/src/-NonJvmPlatform.kt @@ -30,8 +30,6 @@ actual open class ArrayIndexOutOfBoundsException actual constructor( message: String? ) : IndexOutOfBoundsException(message) -internal actual inline fun synchronized(lock: Any, block: () -> R): R = block() - actual open class IOException actual constructor( message: String?, cause: Throwable? @@ -39,12 +37,8 @@ actual open class IOException actual constructor( actual constructor(message: String?) : this(message, null) } -actual class ProtocolException actual constructor(message: String) : IOException(message) - actual open class EOFException actual constructor(message: String?) : IOException(message) -actual open class FileNotFoundException actual constructor(message: String?) : IOException(message) - actual interface Closeable { @Throws(IOException::class) actual fun close() diff --git a/core/native/src/Buffer.kt b/core/native/src/Buffer.kt index dd189cf53..7ddb3c202 100644 --- a/core/native/src/Buffer.kt +++ b/core/native/src/Buffer.kt @@ -67,16 +67,6 @@ actual class Buffer : Source, Sink { override fun readLong(): Long = commonReadLong() - override fun readShortLe(): Short = readShort().reverseBytes() - - override fun readIntLe(): Int = readInt().reverseBytes() - - override fun readLongLe(): Long = readLong().reverseBytes() - - override fun readDecimalLong(): Long = commonReadDecimalLong() - - override fun readHexadecimalUnsignedLong(): Long = commonReadHexadecimalUnsignedLong() - override fun readFully(sink: Buffer, byteCount: Long): Unit = commonReadFully(sink, byteCount) override fun readAll(sink: RawSink): Long = commonReadAll(sink) @@ -85,20 +75,12 @@ actual class Buffer : Source, Sink { override fun readUtf8(byteCount: Long): String = commonReadUtf8(byteCount) - override fun readUtf8Line(): String? = commonReadUtf8Line() - - override fun readUtf8LineStrict(): String = readUtf8LineStrict(Long.MAX_VALUE) - - override fun readUtf8LineStrict(limit: Long): String = commonReadUtf8LineStrict(limit) - override fun readUtf8CodePoint(): Int = commonReadUtf8CodePoint() override fun readByteArray(): ByteArray = commonReadByteArray() override fun readByteArray(byteCount: Long): ByteArray = commonReadByteArray(byteCount) - override fun read(sink: ByteArray): Int = commonRead(sink) - override fun readFully(sink: ByteArray): Unit = commonReadFully(sink) override fun read(sink: ByteArray, offset: Int, byteCount: Int): Int = @@ -111,16 +93,12 @@ actual class Buffer : Source, Sink { internal actual fun writableSegment(minimumCapacity: Int): Segment = commonWritableSegment(minimumCapacity) - actual override fun writeUtf8(string: String): Buffer = writeUtf8(string, 0, string.length) - actual override fun writeUtf8(string: String, beginIndex: Int, endIndex: Int): Buffer = commonWriteUtf8(string, beginIndex, endIndex) actual override fun writeUtf8CodePoint(codePoint: Int): Buffer = commonWriteUtf8CodePoint(codePoint) - actual override fun write(source: ByteArray): Buffer = commonWrite(source) - actual override fun write(source: ByteArray, offset: Int, byteCount: Int): Buffer = commonWrite(source, offset, byteCount) @@ -133,51 +111,14 @@ actual class Buffer : Source, Sink { actual override fun writeShort(s: Int): Buffer = commonWriteShort(s) - actual override fun writeShortLe(s: Int): Buffer = writeShort(s.toShort().reverseBytes().toInt()) - actual override fun writeInt(i: Int): Buffer = commonWriteInt(i) - actual override fun writeIntLe(i: Int): Buffer = writeInt(i.reverseBytes()) - actual override fun writeLong(v: Long): Buffer = commonWriteLong(v) - actual override fun writeLongLe(v: Long): Buffer = writeLong(v.reverseBytes()) - - actual override fun writeDecimalLong(v: Long): Buffer = commonWriteDecimalLong(v) - - actual override fun writeHexadecimalUnsignedLong(v: Long): Buffer = - commonWriteHexadecimalUnsignedLong(v) - override fun write(source: Buffer, byteCount: Long): Unit = commonWrite(source, byteCount) override fun read(sink: Buffer, byteCount: Long): Long = commonRead(sink, byteCount) - override fun indexOf(b: Byte): Long = indexOf(b, 0, Long.MAX_VALUE) - - override fun indexOf(b: Byte, fromIndex: Long): Long = indexOf(b, fromIndex, Long.MAX_VALUE) - - override fun indexOf(b: Byte, fromIndex: Long, toIndex: Long): Long = - commonIndexOf(b, fromIndex, toIndex) - -// override fun indexOf(bytes: ByteString): Long = indexOf(bytes, 0) -// -// override fun indexOf(bytes: ByteString, fromIndex: Long): Long = commonIndexOf(bytes, fromIndex) -// -// override fun indexOfElement(targetBytes: ByteString): Long = indexOfElement(targetBytes, 0L) -// -// override fun indexOfElement(targetBytes: ByteString, fromIndex: Long): Long = -// commonIndexOfElement(targetBytes, fromIndex) -// -// override fun rangeEquals(offset: Long, bytes: ByteString): Boolean = -// rangeEquals(offset, bytes, 0, bytes.size) -// -// override fun rangeEquals( -// offset: Long, -// bytes: ByteString, -// bytesOffset: Int, -// byteCount: Int -// ): Boolean = commonRangeEquals(offset, bytes, bytesOffset, byteCount) - override fun flush() = Unit override fun close() = Unit @@ -197,32 +138,4 @@ actual class Buffer : Source, Sink { override fun toString() = commonString() actual fun copy(): Buffer = commonCopy() - -// actual fun readUnsafe(unsafeCursor: UnsafeCursor): UnsafeCursor = commonReadUnsafe(unsafeCursor) -// -// actual fun readAndWriteUnsafe(unsafeCursor: UnsafeCursor): UnsafeCursor = -// commonReadAndWriteUnsafe(unsafeCursor) - - actual class UnsafeCursor { - actual var buffer: Buffer? = null - actual var readWrite: Boolean = false - - internal actual var segment: Segment? = null - actual var offset = -1L - actual var data: ByteArray? = null - actual var start = -1 - actual var end = -1 - - actual fun next(): Int = commonNext() - - actual fun seek(offset: Long): Int = commonSeek(offset) - - actual fun resizeBuffer(newSize: Long): Long = commonResizeBuffer(newSize) - - actual fun expandBuffer(minByteCount: Int): Long = commonExpandBuffer(minByteCount) - - actual fun close() { - commonClose() - } - } } diff --git a/core/native/src/RealSink.kt b/core/native/src/RealSink.kt index 505561a34..b59657e26 100644 --- a/core/native/src/RealSink.kt +++ b/core/native/src/RealSink.kt @@ -30,15 +30,11 @@ internal actual class RealSink actual constructor( override val buffer = Buffer() override fun write(source: Buffer, byteCount: Long) = commonWrite(source, byteCount) -// override fun write(byteString: ByteString) = commonWrite(byteString) -// override fun write(byteString: ByteString, offset: Int, byteCount: Int) = -// commonWrite(byteString, offset, byteCount) - override fun writeUtf8(string: String) = commonWriteUtf8(string) + override fun writeUtf8(string: String, beginIndex: Int, endIndex: Int) = commonWriteUtf8(string, beginIndex, endIndex) override fun writeUtf8CodePoint(codePoint: Int) = commonWriteUtf8CodePoint(codePoint) - override fun write(source: ByteArray) = commonWrite(source) override fun write(source: ByteArray, offset: Int, byteCount: Int) = commonWrite(source, offset, byteCount) @@ -46,13 +42,8 @@ internal actual class RealSink actual constructor( override fun write(source: RawSource, byteCount: Long): Sink = commonWrite(source, byteCount) override fun writeByte(b: Int) = commonWriteByte(b) override fun writeShort(s: Int) = commonWriteShort(s) - override fun writeShortLe(s: Int) = commonWriteShortLe(s) override fun writeInt(i: Int) = commonWriteInt(i) - override fun writeIntLe(i: Int) = commonWriteIntLe(i) override fun writeLong(v: Long) = commonWriteLong(v) - override fun writeLongLe(v: Long) = commonWriteLongLe(v) - override fun writeDecimalLong(v: Long) = commonWriteDecimalLong(v) - override fun writeHexadecimalUnsignedLong(v: Long) = commonWriteHexadecimalUnsignedLong(v) override fun emitCompleteSegments() = commonEmitCompleteSegments() override fun emit() = commonEmit() override fun flush() = commonFlush() diff --git a/core/native/src/RealSource.kt b/core/native/src/RealSource.kt index 3b924970e..6fd608038 100644 --- a/core/native/src/RealSource.kt +++ b/core/native/src/RealSource.kt @@ -33,12 +33,8 @@ internal actual class RealSource actual constructor( override fun require(byteCount: Long): Unit = commonRequire(byteCount) override fun request(byteCount: Long): Boolean = commonRequest(byteCount) override fun readByte(): Byte = commonReadByte() -// override fun readByteString(): ByteString = commonReadByteString() -// override fun readByteString(byteCount: Long): ByteString = commonReadByteString(byteCount) -// override fun select(options: Options): Int = commonSelect(options) override fun readByteArray(): ByteArray = commonReadByteArray() override fun readByteArray(byteCount: Long): ByteArray = commonReadByteArray(byteCount) - override fun read(sink: ByteArray): Int = read(sink, 0, sink.size) override fun readFully(sink: ByteArray): Unit = commonReadFully(sink) override fun read(sink: ByteArray, offset: Int, byteCount: Int): Int = commonRead(sink, offset, byteCount) @@ -47,41 +43,11 @@ internal actual class RealSource actual constructor( override fun readAll(sink: RawSink): Long = commonReadAll(sink) override fun readUtf8(): String = commonReadUtf8() override fun readUtf8(byteCount: Long): String = commonReadUtf8(byteCount) - override fun readUtf8Line(): String? = commonReadUtf8Line() - override fun readUtf8LineStrict() = readUtf8LineStrict(Long.MAX_VALUE) - override fun readUtf8LineStrict(limit: Long): String = commonReadUtf8LineStrict(limit) override fun readUtf8CodePoint(): Int = commonReadUtf8CodePoint() override fun readShort(): Short = commonReadShort() - override fun readShortLe(): Short = commonReadShortLe() override fun readInt(): Int = commonReadInt() - override fun readIntLe(): Int = commonReadIntLe() override fun readLong(): Long = commonReadLong() - override fun readLongLe(): Long = commonReadLongLe() - override fun readDecimalLong(): Long = commonReadDecimalLong() - override fun readHexadecimalUnsignedLong(): Long = commonReadHexadecimalUnsignedLong() override fun skip(byteCount: Long): Unit = commonSkip(byteCount) - override fun indexOf(b: Byte): Long = indexOf(b, 0L, Long.MAX_VALUE) - override fun indexOf(b: Byte, fromIndex: Long): Long = indexOf(b, fromIndex, Long.MAX_VALUE) - override fun indexOf(b: Byte, fromIndex: Long, toIndex: Long): Long = - commonIndexOf(b, fromIndex, toIndex) - -// override fun indexOf(bytes: ByteString): Long = indexOf(bytes, 0L) -// override fun indexOf(bytes: ByteString, fromIndex: Long): Long = commonIndexOf(bytes, fromIndex) -// override fun indexOfElement(targetBytes: ByteString): Long = indexOfElement(targetBytes, 0L) -// override fun indexOfElement(targetBytes: ByteString, fromIndex: Long): Long = -// commonIndexOfElement(targetBytes, fromIndex) -// -// override fun rangeEquals(offset: Long, bytes: ByteString) = rangeEquals( -// offset, bytes, 0, -// bytes.size -// ) -// -// override fun rangeEquals( -// offset: Long, -// bytes: ByteString, -// bytesOffset: Int, -// byteCount: Int -// ): Boolean = commonRangeEquals(offset, bytes, bytesOffset, byteCount) override fun peek(): Source = commonPeek() override fun close(): Unit = commonClose() diff --git a/core/native/src/Sink.kt b/core/native/src/Sink.kt index e69206ff5..b07bf9b61 100644 --- a/core/native/src/Sink.kt +++ b/core/native/src/Sink.kt @@ -23,20 +23,12 @@ package kotlinx.io actual sealed interface Sink : RawSink { actual val buffer: Buffer -// actual fun write(byteString: ByteString): Sink -// -// actual fun write(byteString: ByteString, offset: Int, byteCount: Int): Sink - - actual fun write(source: ByteArray): Sink - actual fun write(source: ByteArray, offset: Int, byteCount: Int): Sink actual fun writeAll(source: RawSource): Long actual fun write(source: RawSource, byteCount: Long): Sink - actual fun writeUtf8(string: String): Sink - actual fun writeUtf8(string: String, beginIndex: Int, endIndex: Int): Sink actual fun writeUtf8CodePoint(codePoint: Int): Sink @@ -45,20 +37,10 @@ actual sealed interface Sink : RawSink { actual fun writeShort(s: Int): Sink - actual fun writeShortLe(s: Int): Sink - actual fun writeInt(i: Int): Sink - actual fun writeIntLe(i: Int): Sink - actual fun writeLong(v: Long): Sink - actual fun writeLongLe(v: Long): Sink - - actual fun writeDecimalLong(v: Long): Sink - - actual fun writeHexadecimalUnsignedLong(v: Long): Sink - actual fun emit(): Sink actual fun emitCompleteSegments(): Sink diff --git a/core/native/src/Source.kt b/core/native/src/Source.kt index 471b4db0a..e33b7589f 100644 --- a/core/native/src/Source.kt +++ b/core/native/src/Source.kt @@ -33,34 +33,16 @@ actual sealed interface Source : RawSource { actual fun readShort(): Short - actual fun readShortLe(): Short - actual fun readInt(): Int - actual fun readIntLe(): Int - actual fun readLong(): Long - actual fun readLongLe(): Long - - actual fun readDecimalLong(): Long - - actual fun readHexadecimalUnsignedLong(): Long - actual fun skip(byteCount: Long) -// actual fun readByteString(): ByteString -// -// actual fun readByteString(byteCount: Long): ByteString -// -// actual fun select(options: Options): Int - actual fun readByteArray(): ByteArray actual fun readByteArray(byteCount: Long): ByteArray - actual fun read(sink: ByteArray): Int - actual fun readFully(sink: ByteArray) actual fun read(sink: ByteArray, offset: Int, byteCount: Int): Int @@ -73,31 +55,7 @@ actual sealed interface Source : RawSource { actual fun readUtf8(byteCount: Long): String - actual fun readUtf8Line(): String? - - actual fun readUtf8LineStrict(): String - - actual fun readUtf8LineStrict(limit: Long): String - actual fun readUtf8CodePoint(): Int - actual fun indexOf(b: Byte): Long - - actual fun indexOf(b: Byte, fromIndex: Long): Long - - actual fun indexOf(b: Byte, fromIndex: Long, toIndex: Long): Long - -// actual fun indexOf(bytes: ByteString): Long -// -// actual fun indexOf(bytes: ByteString, fromIndex: Long): Long -// -// actual fun indexOfElement(targetBytes: ByteString): Long -// -// actual fun indexOfElement(targetBytes: ByteString, fromIndex: Long): Long -// -// actual fun rangeEquals(offset: Long, bytes: ByteString): Boolean -// -// actual fun rangeEquals(offset: Long, bytes: ByteString, bytesOffset: Int, byteCount: Int): Boolean - actual fun peek(): Source } diff --git a/core/native/src/files/PathsNative.kt b/core/native/src/files/PathsNative.kt index 24ea48926..62be6a6fc 100644 --- a/core/native/src/files/PathsNative.kt +++ b/core/native/src/files/PathsNative.kt @@ -73,14 +73,14 @@ internal fun variantFread( target: CPointer>, byteCount: UInt, file: CPointer -): UInt = fread(target, 1, byteCount.convert(), file).convert() +): UInt = fread(target, 1u, byteCount.convert(), file).convert() @OptIn(UnsafeNumber::class) internal fun variantFwrite( source: CPointer, byteCount: UInt, file: CPointer -): UInt = fwrite(source, 1, byteCount.convert(), file).convert() +): UInt = fwrite(source, 1u, byteCount.convert(), file).convert() internal class FileSink( private val file: CPointer From ee9c3958bb17ee4bfcb82bce8a07782b5a9c3c84 Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Fri, 26 May 2023 17:51:23 +0200 Subject: [PATCH 03/83] Enable Dokka --- build.gradle.kts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/build.gradle.kts b/build.gradle.kts index 0927dceaa..b425bf4ac 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -7,6 +7,7 @@ import org.jetbrains.kotlin.gradle.tasks.* plugins { id("org.jetbrains.kotlinx.binary-compatibility-validator") version "0.11.1" + id("org.jetbrains.dokka") version "1.8.10" `maven-publish` signing } @@ -33,12 +34,17 @@ apply(plugin = "maven-publish") apply(plugin = "signing") subprojects { + if (name.contains("benchmark")) { + return@subprojects + } + repositories { mavenCentral() } apply(plugin = "maven-publish") apply(plugin = "signing") + apply(plugin = "org.jetbrains.dokka") publishing { repositories { From 8847c346a1bce022a0ce9e788a12d766a1398d12 Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Tue, 30 May 2023 10:27:28 +0200 Subject: [PATCH 04/83] Enable Kover for core-io --- core/build.gradle.kts | 1 + 1 file changed, 1 insertion(+) diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 98cc12166..f5e159bcf 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -7,6 +7,7 @@ import org.jetbrains.kotlin.gradle.plugin.* plugins { kotlin("multiplatform") + id("org.jetbrains.kotlinx.kover") version "0.7.0" } kotlin { From 597bb41df639c88f83e808fd7b21a82524bafe36 Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Tue, 30 May 2023 11:13:51 +0200 Subject: [PATCH 05/83] Suppressed/fixed warnings --- build.gradle.kts | 1 + buildSrc/src/main/kotlin/Platforms.kt | 2 - core/common/src/-Util.kt | 18 ++-- core/common/src/SinkExt.kt | 20 +++-- core/common/src/internal/-Buffer.kt | 88 +++++++++++-------- core/common/src/internal/-RealBufferedSink.kt | 8 +- .../src/internal/-RealBufferedSource.kt | 12 ++- core/jvm/src/Buffer.kt | 37 ++++---- core/jvm/src/RealSink.kt | 2 - core/jvm/src/RealSource.kt | 2 - core/jvm/src/SegmentPool.kt | 1 + core/jvm/src/Sink.kt | 8 -- core/jvm/src/Source.kt | 8 -- core/native/src/files/PathsNative.kt | 5 +- 14 files changed, 106 insertions(+), 106 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index b425bf4ac..89dcac85d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -64,6 +64,7 @@ subprojects { tasks.withType().configureEach { kotlinOptions { jvmTarget = JavaVersion.VERSION_1_8.toString() + allWarningsAsErrors = true } } } diff --git a/buildSrc/src/main/kotlin/Platforms.kt b/buildSrc/src/main/kotlin/Platforms.kt index 3a6e30f56..a23bbbd9e 100644 --- a/buildSrc/src/main/kotlin/Platforms.kt +++ b/buildSrc/src/main/kotlin/Platforms.kt @@ -17,7 +17,6 @@ fun KotlinMultiplatformExtension.configureNativePlatforms() { tvosSimulatorArm64() watchosArm32() watchosArm64() - watchosX86() watchosX64() watchosSimulatorArm64() // Required to generate tests tasks: https://youtrack.jetbrains.com/issue/KT-26547 @@ -38,7 +37,6 @@ private val appleTargets = listOf( "tvosSimulatorArm64", "watchosArm32", "watchosArm64", - "watchosX86", "watchosX64", "watchosSimulatorArm64" ) diff --git a/core/common/src/-Util.kt b/core/common/src/-Util.kt index 465c553e6..3eaf209aa 100644 --- a/core/common/src/-Util.kt +++ b/core/common/src/-Util.kt @@ -19,6 +19,8 @@ * limitations under the License. */ +@file:Suppress("NOTHING_TO_INLINE") + package kotlinx.io import kotlin.native.concurrent.SharedImmutable @@ -70,28 +72,28 @@ internal inline infix fun Long.rightRotate(bitCount: Int): Long { return (this ushr bitCount) or (this shl (64 - bitCount)) } -@Suppress("NOTHING_TO_INLINE") // Syntactic sugar. +// Syntactic sugar. internal inline infix fun Byte.shr(other: Int): Int = toInt() shr other -@Suppress("NOTHING_TO_INLINE") // Syntactic sugar. +// Syntactic sugar. internal inline infix fun Byte.shl(other: Int): Int = toInt() shl other -@Suppress("NOTHING_TO_INLINE") // Syntactic sugar. +// Syntactic sugar. internal inline infix fun Byte.and(other: Int): Int = toInt() and other -@Suppress("NOTHING_TO_INLINE") // Syntactic sugar. +// Syntactic sugar. internal inline infix fun Byte.and(other: Long): Long = toLong() and other -@Suppress("NOTHING_TO_INLINE") // Pending `kotlin.experimental.xor` becoming stable +// Pending `kotlin.experimental.xor` becoming stable internal inline infix fun Byte.xor(other: Byte): Byte = (toInt() xor other.toInt()).toByte() -@Suppress("NOTHING_TO_INLINE") // Syntactic sugar. +// Syntactic sugar. internal inline infix fun Int.and(other: Long): Long = toLong() and other -@Suppress("NOTHING_TO_INLINE") // Syntactic sugar. +// Syntactic sugar. internal inline fun minOf(a: Long, b: Int): Long = minOf(a, b.toLong()) -@Suppress("NOTHING_TO_INLINE") // Syntactic sugar. +// Syntactic sugar. internal inline fun minOf(a: Int, b: Long): Long = minOf(a.toLong(), b) internal fun arrayRangeEquals( diff --git a/core/common/src/SinkExt.kt b/core/common/src/SinkExt.kt index 3d5a584c7..1d316637d 100644 --- a/core/common/src/SinkExt.kt +++ b/core/common/src/SinkExt.kt @@ -21,7 +21,8 @@ package kotlinx.io * ``` */ fun T.writeShortLe(s: Int): T { - return this.writeShort(s.toShort().reverseBytes().toInt()) as T + this.writeShort(s.toShort().reverseBytes().toInt()) + return this } /** @@ -44,7 +45,8 @@ fun T.writeShortLe(s: Int): T { * ``` */ fun T.writeIntLe(i: Int): T { - return this.writeInt(i.reverseBytes()) as T + this.writeInt(i.reverseBytes()) + return this } /** @@ -75,7 +77,8 @@ fun T.writeIntLe(i: Int): T { * ``` */ fun T.writeLongLe(v: Long): T { - return this.writeLong(v.reverseBytes()) as T + this.writeLong(v.reverseBytes()) + return this } /** @@ -93,7 +96,8 @@ fun T.writeLongLe(v: Long): T { */ fun T.writeDecimalLong(v: Long): T { // TODO: optimize - return writeUtf8(v.toString()) as T + writeUtf8(v.toString()) + return this } /** @@ -111,8 +115,10 @@ fun T.writeDecimalLong(v: Long): T { */ fun T.writeHexadecimalUnsignedLong(v: Long): T { if (v == 0L) { - return writeByte('0'.code) as T + writeByte('0'.code) + } else { + // TODO: optimize + writeUtf8(v.toHexString()) } - // TODO: optimize - return writeUtf8(v.toHexString()) as T + return this } \ No newline at end of file diff --git a/core/common/src/internal/-Buffer.kt b/core/common/src/internal/-Buffer.kt index 3e830c5a7..3a27a1598 100644 --- a/core/common/src/internal/-Buffer.kt +++ b/core/common/src/internal/-Buffer.kt @@ -21,6 +21,8 @@ // TODO move to Buffer class: https://youtrack.jetbrains.com/issue/KT-20427 +@file:Suppress("NOTHING_TO_INLINE") + package kotlinx.io.internal import kotlinx.io.* @@ -33,6 +35,8 @@ internal val HEX_DIGIT_BYTES = "0123456789abcdef".asUtf8ToByteArray() * Returns true if the range within this buffer starting at `segmentPos` in `segment` is equal to * `bytes[bytesOffset..bytesLimit)`. */ +// TODO: remove? +/* internal fun rangeEquals( segment: Segment, segmentPos: Int, @@ -64,6 +68,7 @@ internal fun rangeEquals( return true } +*/ internal fun Buffer.readUtf8Line(newline: Long): String { return when { @@ -120,25 +125,26 @@ internal inline fun Buffer.commonCopyTo( offset: Long, byteCount: Long ): Buffer { - var offset = offset - var byteCount = byteCount checkOffsetAndCount(size, offset, byteCount) if (byteCount == 0L) return this - out.size += byteCount + var currentOffset = offset + var remainingByteCount = byteCount + + out.size += remainingByteCount // Skip segments that we aren't copying from. var s = head - while (offset >= s!!.limit - s.pos) { - offset -= (s.limit - s.pos).toLong() + while (currentOffset >= s!!.limit - s.pos) { + currentOffset -= (s.limit - s.pos).toLong() s = s.next } // Copy one segment at a time. - while (byteCount > 0L) { + while (remainingByteCount > 0L) { val copy = s!!.sharedCopy() - copy.pos += offset.toInt() - copy.limit = minOf(copy.pos + byteCount.toInt(), copy.limit) + copy.pos += currentOffset.toInt() + copy.limit = minOf(copy.pos + remainingByteCount.toInt(), copy.limit) if (out.head == null) { copy.prev = copy copy.next = copy.prev @@ -146,8 +152,8 @@ internal inline fun Buffer.commonCopyTo( } else { out.head!!.prev!!.push(copy) } - byteCount -= (copy.limit - copy.pos).toLong() - offset = 0L + remainingByteCount -= (copy.limit - copy.pos).toLong() + currentOffset = 0L s = s.next } @@ -299,13 +305,13 @@ internal inline fun Buffer.commonGet(pos: Long): Byte { internal inline fun Buffer.commonClear() = skip(size) internal inline fun Buffer.commonSkip(byteCount: Long) { - var byteCount = byteCount - while (byteCount > 0) { + var remainingByteCount = byteCount + while (remainingByteCount > 0) { val head = this.head ?: throw EOFException() - val toSkip = minOf(byteCount, head.limit - head.pos).toInt() + val toSkip = minOf(remainingByteCount, head.limit - head.pos).toInt() size -= toSkip.toLong() - byteCount -= toSkip.toLong() + remainingByteCount -= toSkip.toLong() head.pos += toSkip if (head.pos == head.limit) { @@ -324,6 +330,7 @@ internal inline fun Buffer.commonSkip(byteCount: Long) { // return this //} +/* internal inline fun Buffer.commonWriteDecimalLong(v: Long): Buffer { var v = v if (v == 0L) { @@ -432,6 +439,7 @@ internal inline fun Buffer.commonWriteHexadecimalUnsignedLong(v: Long): Buffer { size += width.toLong() return this } + */ internal inline fun Buffer.commonWritableSegment(minimumCapacity: Int): Segment { require(minimumCapacity >= 1 && minimumCapacity <= Segment.SIZE) { "unexpected capacity" } @@ -458,22 +466,22 @@ internal inline fun Buffer.commonWrite( offset: Int, byteCount: Int ): Buffer { - var offset = offset - checkOffsetAndCount(source.size.toLong(), offset.toLong(), byteCount.toLong()) + var currentOffset = offset + checkOffsetAndCount(source.size.toLong(), currentOffset.toLong(), byteCount.toLong()) - val limit = offset + byteCount - while (offset < limit) { + val limit = currentOffset + byteCount + while (currentOffset < limit) { val tail = writableSegment(1) - val toCopy = minOf(limit - offset, Segment.SIZE - tail.limit) + val toCopy = minOf(limit - currentOffset, Segment.SIZE - tail.limit) source.copyInto( destination = tail.data, destinationOffset = tail.limit, - startIndex = offset, - endIndex = offset + toCopy + startIndex = currentOffset, + endIndex = currentOffset + toCopy ) - offset += toCopy + currentOffset += toCopy tail.limit += toCopy } @@ -971,11 +979,11 @@ internal inline fun Buffer.commonWriteAll(source: RawSource): Long { } internal inline fun Buffer.commonWrite(source: RawSource, byteCount: Long): Buffer { - var byteCount = byteCount - while (byteCount > 0L) { - val read = source.read(this, byteCount) + var remainingByteCount = byteCount + while (remainingByteCount > 0L) { + val read = source.read(this, remainingByteCount) if (read == -1L) throw EOFException() - byteCount -= read + remainingByteCount -= read } return this } @@ -1029,7 +1037,6 @@ internal inline fun Buffer.commonWriteLong(v: Long): Buffer { } internal inline fun Buffer.commonWrite(source: Buffer, byteCount: Long) { - var byteCount = byteCount // Move bytes from the head of the source buffer to the tail of this buffer // while balancing two conflicting goals: don't waste CPU and don't waste // memory. @@ -1083,22 +1090,24 @@ internal inline fun Buffer.commonWrite(source: Buffer, byteCount: Long) { require(source !== this) { "source == this" } checkOffsetAndCount(source.size, 0, byteCount) - while (byteCount > 0L) { + var remainingByteCount = byteCount + + while (remainingByteCount > 0L) { // Is a prefix of the source's head segment all that we need to move? - if (byteCount < source.head!!.limit - source.head!!.pos) { + if (remainingByteCount < source.head!!.limit - source.head!!.pos) { val tail = if (head != null) head!!.prev else null if (tail != null && tail.owner && - byteCount + tail.limit - (if (tail.shared) 0 else tail.pos) <= Segment.SIZE + remainingByteCount + tail.limit - (if (tail.shared) 0 else tail.pos) <= Segment.SIZE ) { // Our existing segments are sufficient. Move bytes from source's head to our tail. - source.head!!.writeTo(tail, byteCount.toInt()) - source.size -= byteCount - size += byteCount + source.head!!.writeTo(tail, remainingByteCount.toInt()) + source.size -= remainingByteCount + size += remainingByteCount return } else { // We're going to need another segment. Split the source's head // segment in two, then move the first of those two to this buffer. - source.head = source.head!!.split(byteCount.toInt()) + source.head = source.head!!.split(remainingByteCount.toInt()) } } @@ -1117,19 +1126,19 @@ internal inline fun Buffer.commonWrite(source: Buffer, byteCount: Long) { } source.size -= movedByteCount size += movedByteCount - byteCount -= movedByteCount + remainingByteCount -= movedByteCount } } internal inline fun Buffer.commonRead(sink: Buffer, byteCount: Long): Long { - var byteCount = byteCount require(byteCount >= 0L) { "byteCount < 0: $byteCount" } if (size == 0L) return -1L - if (byteCount > size) byteCount = size - sink.write(this, byteCount) - return byteCount + val bytesWritten = if (byteCount > size) size else byteCount + sink.write(this, bytesWritten) + return bytesWritten } +/* internal inline fun Buffer.commonIndexOf(b: Byte, fromIndex: Long, toIndex: Long): Long { var fromIndex = fromIndex var toIndex = toIndex @@ -1163,6 +1172,7 @@ internal inline fun Buffer.commonIndexOf(b: Byte, fromIndex: Long, toIndex: Long return -1L } } + */ internal inline fun Buffer.commonEquals(other: Any?): Boolean { if (this === other) return true diff --git a/core/common/src/internal/-RealBufferedSink.kt b/core/common/src/internal/-RealBufferedSink.kt index 48b09c8ec..7b188fd5f 100644 --- a/core/common/src/internal/-RealBufferedSink.kt +++ b/core/common/src/internal/-RealBufferedSink.kt @@ -82,11 +82,11 @@ internal inline fun RealSink.commonWriteAll(source: RawSource): Long { } internal inline fun RealSink.commonWrite(source: RawSource, byteCount: Long): Sink { - var byteCount = byteCount - while (byteCount > 0L) { - val read = source.read(buffer, byteCount) + var remainingByteCount = byteCount + while (remainingByteCount > 0L) { + val read = source.read(buffer, remainingByteCount) if (read == -1L) throw EOFException() - byteCount -= read + remainingByteCount -= read emitCompleteSegments() } return this diff --git a/core/common/src/internal/-RealBufferedSource.kt b/core/common/src/internal/-RealBufferedSource.kt index 4fc429593..89de6ae5c 100644 --- a/core/common/src/internal/-RealBufferedSource.kt +++ b/core/common/src/internal/-RealBufferedSource.kt @@ -21,6 +21,8 @@ // TODO move to RealBufferedSource class: https://youtrack.jetbrains.com/issue/KT-20427 +@file:Suppress("NOTHING_TO_INLINE") + package kotlinx.io.internal import kotlinx.io.* @@ -261,18 +263,19 @@ internal inline fun RealSource.commonReadHexadecimalUnsignedLong(): Long { } internal inline fun RealSource.commonSkip(byteCount: Long) { - var byteCount = byteCount + var remainingByteCount = byteCount check(!closed) { "closed" } - while (byteCount > 0) { + while (remainingByteCount > 0) { if (buffer.size == 0L && source.read(buffer, Segment.SIZE.toLong()) == -1L) { throw EOFException() } - val toSkip = minOf(byteCount, buffer.size) + val toSkip = minOf(remainingByteCount, buffer.size) buffer.skip(toSkip) - byteCount -= toSkip + remainingByteCount -= toSkip } } +/* internal inline fun RealSource.commonIndexOf(b: Byte, fromIndex: Long, toIndex: Long): Long { var fromIndex = fromIndex check(!closed) { "closed" } @@ -292,6 +295,7 @@ internal inline fun RealSource.commonIndexOf(b: Byte, fromIndex: Long, toIndex: } return -1L } + */ internal inline fun RealSource.commonPeek(): Source { return PeekSource(this).buffer() diff --git a/core/jvm/src/Buffer.kt b/core/jvm/src/Buffer.kt index b8e4c6127..caf191c89 100644 --- a/core/jvm/src/Buffer.kt +++ b/core/jvm/src/Buffer.kt @@ -41,8 +41,6 @@ actual class Buffer : Source, Sink, Cloneable, ByteChannel { actual var size: Long = 0L internal set - override fun buffer() = this - actual override val buffer get() = this override fun outputStream(): OutputStream { @@ -110,25 +108,26 @@ actual class Buffer : Source, Sink, Cloneable, ByteChannel { offset: Long = 0L, byteCount: Long = size - offset ): Buffer { - var offset = offset - var byteCount = byteCount checkOffsetAndCount(size, offset, byteCount) if (byteCount == 0L) return this + var currentOffset = offset + var remainingByteCount = byteCount + // Skip segments that we aren't copying from. var s = head - while (offset >= s!!.limit - s.pos) { - offset -= (s.limit - s.pos).toLong() + while (currentOffset >= s!!.limit - s.pos) { + currentOffset -= (s.limit - s.pos).toLong() s = s.next } // Copy from one segment at a time. - while (byteCount > 0L) { - val pos = (s!!.pos + offset).toInt() - val toCopy = minOf(s.limit - pos, byteCount).toInt() + while (remainingByteCount > 0L) { + val pos = (s!!.pos + currentOffset).toInt() + val toCopy = minOf(s.limit - pos, remainingByteCount).toInt() out.write(s.data, pos, toCopy) - byteCount -= toCopy.toLong() - offset = 0L + remainingByteCount -= toCopy.toLong() + currentOffset = 0L s = s.next } @@ -150,17 +149,17 @@ actual class Buffer : Source, Sink, Cloneable, ByteChannel { @Throws(IOException::class) @JvmOverloads fun writeTo(out: OutputStream, byteCount: Long = size): Buffer { - var byteCount = byteCount checkOffsetAndCount(size, 0, byteCount) + var remainingByteCount = byteCount var s = head - while (byteCount > 0L) { - val toCopy = minOf(byteCount, s!!.limit - s.pos).toInt() + while (remainingByteCount > 0L) { + val toCopy = minOf(remainingByteCount, s!!.limit - s.pos).toInt() out.write(s.data, s.pos, toCopy) s.pos += toCopy size -= toCopy.toLong() - byteCount -= toCopy.toLong() + remainingByteCount -= toCopy.toLong() if (s.pos == s.limit) { val toRecycle = s @@ -190,10 +189,10 @@ actual class Buffer : Source, Sink, Cloneable, ByteChannel { @Throws(IOException::class) private fun readFrom(input: InputStream, byteCount: Long, forever: Boolean) { - var byteCount = byteCount - while (byteCount > 0L || forever) { + var remainingByteCount = byteCount + while (remainingByteCount > 0L || forever) { val tail = writableSegment(1) - val maxToCopy = minOf(byteCount, Segment.SIZE - tail.limit).toInt() + val maxToCopy = minOf(remainingByteCount, Segment.SIZE - tail.limit).toInt() val bytesRead = input.read(tail.data, tail.limit, maxToCopy) if (bytesRead == -1) { if (tail.pos == tail.limit) { @@ -206,7 +205,7 @@ actual class Buffer : Source, Sink, Cloneable, ByteChannel { } tail.limit += bytesRead size += bytesRead.toLong() - byteCount -= bytesRead.toLong() + remainingByteCount -= bytesRead.toLong() } } diff --git a/core/jvm/src/RealSink.kt b/core/jvm/src/RealSink.kt index 5673c4f5b..e28a7b82a 100644 --- a/core/jvm/src/RealSink.kt +++ b/core/jvm/src/RealSink.kt @@ -36,8 +36,6 @@ internal actual class RealSink actual constructor( override val buffer: Buffer inline get() = bufferField - override fun buffer() = bufferField - override fun write(source: Buffer, byteCount: Long) = commonWrite(source, byteCount) override fun writeUtf8(string: String, beginIndex: Int, endIndex: Int) = commonWriteUtf8(string, beginIndex, endIndex) diff --git a/core/jvm/src/RealSource.kt b/core/jvm/src/RealSource.kt index 0c4680ad8..bfbc896a8 100644 --- a/core/jvm/src/RealSource.kt +++ b/core/jvm/src/RealSource.kt @@ -36,8 +36,6 @@ internal actual class RealSource actual constructor( override val buffer: Buffer inline get() = bufferField - override fun buffer() = bufferField - override fun read(sink: Buffer, byteCount: Long): Long = commonRead(sink, byteCount) override fun exhausted(): Boolean = commonExhausted() override fun require(byteCount: Long): Unit = commonRequire(byteCount) diff --git a/core/jvm/src/SegmentPool.kt b/core/jvm/src/SegmentPool.kt index 11cec0dc6..518d701a7 100644 --- a/core/jvm/src/SegmentPool.kt +++ b/core/jvm/src/SegmentPool.kt @@ -126,6 +126,7 @@ internal actual object SegmentPool { private fun firstRef(): AtomicReference { // Get a value in [0..HASH_BUCKET_COUNT) based on the current thread. + @Suppress("DEPRECATION") // TODO: switch to threadId after JDK19 val hashBucket = (Thread.currentThread().id and (HASH_BUCKET_COUNT - 1L)).toInt() return hashBuckets[hashBucket] } diff --git a/core/jvm/src/Sink.kt b/core/jvm/src/Sink.kt index d44e0668f..311703199 100644 --- a/core/jvm/src/Sink.kt +++ b/core/jvm/src/Sink.kt @@ -26,14 +26,6 @@ import java.nio.channels.WritableByteChannel import java.nio.charset.Charset actual sealed interface Sink : RawSink, WritableByteChannel { - /** Returns this sink's internal buffer. */ - @Deprecated( - message = "moved to val: use getBuffer() instead", - replaceWith = ReplaceWith(expression = "buffer"), - level = DeprecationLevel.WARNING - ) - fun buffer(): Buffer - actual val buffer: Buffer @Throws(IOException::class) diff --git a/core/jvm/src/Source.kt b/core/jvm/src/Source.kt index 79fd94bee..e503ce3b0 100644 --- a/core/jvm/src/Source.kt +++ b/core/jvm/src/Source.kt @@ -26,14 +26,6 @@ import java.nio.channels.ReadableByteChannel import java.nio.charset.Charset actual sealed interface Source : RawSource, ReadableByteChannel { - /** Returns this source's internal buffer. */ - @Deprecated( - message = "moved to val: use getBuffer() instead", - replaceWith = ReplaceWith(expression = "buffer"), - level = DeprecationLevel.WARNING - ) - fun buffer(): Buffer - actual val buffer: Buffer @Throws(IOException::class) diff --git a/core/native/src/files/PathsNative.kt b/core/native/src/files/PathsNative.kt index 62be6a6fc..80b58c7ad 100644 --- a/core/native/src/files/PathsNative.kt +++ b/core/native/src/files/PathsNative.kt @@ -13,9 +13,8 @@ import platform.posix.* * The very base skeleton just to play around */ -public actual class Path internal constructor(internal val path: String, any: Any?) { - -} +public actual class Path internal constructor(internal val path: String, + @Suppress("UNUSED_PARAMETER") any: Any?) public actual fun Path(path: String): Path = Path(path, null) From fb7b533f17fc01244d51596126c8f7ce0b7a292b Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Tue, 30 May 2023 11:44:32 +0200 Subject: [PATCH 06/83] Update API dump --- core/api/kotlinx-io-core.api | 3 --- 1 file changed, 3 deletions(-) diff --git a/core/api/kotlinx-io-core.api b/core/api/kotlinx-io-core.api index 21d6ddde6..90ab1c4af 100644 --- a/core/api/kotlinx-io-core.api +++ b/core/api/kotlinx-io-core.api @@ -1,6 +1,5 @@ public final class kotlinx/io/Buffer : java/lang/Cloneable, java/nio/channels/ByteChannel, kotlinx/io/Sink, kotlinx/io/Source { public fun ()V - public fun buffer ()Lkotlinx/io/Buffer; public fun cancel ()V public final fun clear ()V public synthetic fun clone ()Ljava/lang/Object; @@ -116,7 +115,6 @@ public abstract interface class kotlinx/io/RawSource : java/io/Closeable { } public abstract interface class kotlinx/io/Sink : java/nio/channels/WritableByteChannel, kotlinx/io/RawSink { - public abstract fun buffer ()Lkotlinx/io/Buffer; public abstract fun emit ()Lkotlinx/io/Sink; public abstract fun emitCompleteSegments ()Lkotlinx/io/Sink; public abstract fun flush ()V @@ -149,7 +147,6 @@ public final class kotlinx/io/SinkExtKt { } public abstract interface class kotlinx/io/Source : java/nio/channels/ReadableByteChannel, kotlinx/io/RawSource { - public abstract fun buffer ()Lkotlinx/io/Buffer; public abstract fun exhausted ()Z public abstract fun getBuffer ()Lkotlinx/io/Buffer; public abstract fun inputStream ()Ljava/io/InputStream; From 5efafef654049b71c167bce68dd2bec00340116f Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Tue, 30 May 2023 17:11:15 +0200 Subject: [PATCH 07/83] Extract utf8-related methods from Sink/Source into ext functions --- core/api/kotlinx-io-core.api | 47 +++++----- core/common/src/Buffer.kt | 6 -- core/common/src/Sink.kt | 31 ------- core/common/src/Source.kt | 53 ------------ core/common/src/SourceExt.kt | 107 ----------------------- core/common/src/Utf8.kt | 157 ++++++++++++++++++++++++++++++++++ core/jvm/src/Buffer.kt | 59 ------------- core/jvm/src/RealSink.kt | 21 ----- core/jvm/src/RealSource.kt | 13 --- core/jvm/src/Sink.kt | 23 +++-- core/jvm/src/Source.kt | 55 ++++++++---- core/native/src/Buffer.kt | 12 --- core/native/src/RealSink.kt | 4 - core/native/src/RealSource.kt | 3 - core/native/src/Sink.kt | 4 - core/native/src/Source.kt | 6 -- 16 files changed, 226 insertions(+), 375 deletions(-) diff --git a/core/api/kotlinx-io-core.api b/core/api/kotlinx-io-core.api index 90ab1c4af..acbcec2a5 100644 --- a/core/api/kotlinx-io-core.api +++ b/core/api/kotlinx-io-core.api @@ -43,11 +43,6 @@ public final class kotlinx/io/Buffer : java/lang/Cloneable, java/nio/channels/By public fun readInt ()I public fun readLong ()J public fun readShort ()S - public fun readString (JLjava/nio/charset/Charset;)Ljava/lang/String; - public fun readString (Ljava/nio/charset/Charset;)Ljava/lang/String; - public fun readUtf8 ()Ljava/lang/String; - public fun readUtf8 (J)Ljava/lang/String; - public fun readUtf8CodePoint ()I public fun request (J)Z public fun require (J)V public final fun size ()J @@ -68,17 +63,9 @@ public final class kotlinx/io/Buffer : java/lang/Cloneable, java/nio/channels/By public synthetic fun writeLong (J)Lkotlinx/io/Sink; public fun writeShort (I)Lkotlinx/io/Buffer; public synthetic fun writeShort (I)Lkotlinx/io/Sink; - public fun writeString (Ljava/lang/String;IILjava/nio/charset/Charset;)Lkotlinx/io/Buffer; - public synthetic fun writeString (Ljava/lang/String;IILjava/nio/charset/Charset;)Lkotlinx/io/Sink; - public fun writeString (Ljava/lang/String;Ljava/nio/charset/Charset;)Lkotlinx/io/Buffer; - public synthetic fun writeString (Ljava/lang/String;Ljava/nio/charset/Charset;)Lkotlinx/io/Sink; public final fun writeTo (Ljava/io/OutputStream;)Lkotlinx/io/Buffer; public final fun writeTo (Ljava/io/OutputStream;J)Lkotlinx/io/Buffer; public static synthetic fun writeTo$default (Lkotlinx/io/Buffer;Ljava/io/OutputStream;JILjava/lang/Object;)Lkotlinx/io/Buffer; - public fun writeUtf8 (Ljava/lang/String;II)Lkotlinx/io/Buffer; - public synthetic fun writeUtf8 (Ljava/lang/String;II)Lkotlinx/io/Sink; - public fun writeUtf8CodePoint (I)Lkotlinx/io/Buffer; - public synthetic fun writeUtf8CodePoint (I)Lkotlinx/io/Sink; } public final class kotlinx/io/CoreKt { @@ -127,15 +114,10 @@ public abstract interface class kotlinx/io/Sink : java/nio/channels/WritableByte public abstract fun writeInt (I)Lkotlinx/io/Sink; public abstract fun writeLong (J)Lkotlinx/io/Sink; public abstract fun writeShort (I)Lkotlinx/io/Sink; - public abstract fun writeString (Ljava/lang/String;IILjava/nio/charset/Charset;)Lkotlinx/io/Sink; - public abstract fun writeString (Ljava/lang/String;Ljava/nio/charset/Charset;)Lkotlinx/io/Sink; - public abstract fun writeUtf8 (Ljava/lang/String;II)Lkotlinx/io/Sink; - public abstract fun writeUtf8CodePoint (I)Lkotlinx/io/Sink; } public final class kotlinx/io/Sink$DefaultImpls { public static synthetic fun write$default (Lkotlinx/io/Sink;[BIIILjava/lang/Object;)Lkotlinx/io/Sink; - public static synthetic fun writeUtf8$default (Lkotlinx/io/Sink;Ljava/lang/String;IIILjava/lang/Object;)Lkotlinx/io/Sink; } public final class kotlinx/io/SinkExtKt { @@ -146,6 +128,11 @@ public final class kotlinx/io/SinkExtKt { public static final fun writeShortLe (Lkotlinx/io/Sink;I)Lkotlinx/io/Sink; } +public final class kotlinx/io/SinkKt { + public static final fun writeString (Lkotlinx/io/Sink;Ljava/lang/String;Ljava/nio/charset/Charset;II)Lkotlinx/io/Sink; + public static synthetic fun writeString$default (Lkotlinx/io/Sink;Ljava/lang/String;Ljava/nio/charset/Charset;IIILjava/lang/Object;)Lkotlinx/io/Sink; +} + public abstract interface class kotlinx/io/Source : java/nio/channels/ReadableByteChannel, kotlinx/io/RawSource { public abstract fun exhausted ()Z public abstract fun getBuffer ()Lkotlinx/io/Buffer; @@ -161,11 +148,6 @@ public abstract interface class kotlinx/io/Source : java/nio/channels/ReadableBy public abstract fun readInt ()I public abstract fun readLong ()J public abstract fun readShort ()S - public abstract fun readString (JLjava/nio/charset/Charset;)Ljava/lang/String; - public abstract fun readString (Ljava/nio/charset/Charset;)Ljava/lang/String; - public abstract fun readUtf8 ()Ljava/lang/String; - public abstract fun readUtf8 (J)Ljava/lang/String; - public abstract fun readUtf8CodePoint ()I public abstract fun request (J)Z public abstract fun require (J)V public abstract fun skip (J)V @@ -183,16 +165,29 @@ public final class kotlinx/io/SourceExtKt { public static final fun readIntLe (Lkotlinx/io/Source;)I public static final fun readLongLe (Lkotlinx/io/Source;)J public static final fun readShortLe (Lkotlinx/io/Source;)S - public static final fun readUtf8Line (Lkotlinx/io/Source;)Ljava/lang/String; - public static final fun readUtf8LineStrict (Lkotlinx/io/Source;J)Ljava/lang/String; - public static synthetic fun readUtf8LineStrict$default (Lkotlinx/io/Source;JILjava/lang/Object;)Ljava/lang/String; +} + +public final class kotlinx/io/SourceKt { + public static final fun readString (Lkotlinx/io/Source;JLjava/nio/charset/Charset;)Ljava/lang/String; + public static final fun readString (Lkotlinx/io/Source;Ljava/nio/charset/Charset;)Ljava/lang/String; } public final class kotlinx/io/Utf8 { + public static final fun readUtf8 (Lkotlinx/io/Buffer;)Ljava/lang/String; + public static final fun readUtf8 (Lkotlinx/io/Source;)Ljava/lang/String; + public static final fun readUtf8 (Lkotlinx/io/Source;J)Ljava/lang/String; + public static final fun readUtf8CodePoint (Lkotlinx/io/Buffer;)I + public static final fun readUtf8CodePoint (Lkotlinx/io/Source;)I + public static final fun readUtf8Line (Lkotlinx/io/Source;)Ljava/lang/String; + public static final fun readUtf8LineStrict (Lkotlinx/io/Source;J)Ljava/lang/String; + public static synthetic fun readUtf8LineStrict$default (Lkotlinx/io/Source;JILjava/lang/Object;)Ljava/lang/String; public static final fun size (Ljava/lang/String;)J public static final fun size (Ljava/lang/String;I)J public static final fun size (Ljava/lang/String;II)J public static synthetic fun size$default (Ljava/lang/String;IIILjava/lang/Object;)J + public static final fun writeUtf8 (Lkotlinx/io/Sink;Ljava/lang/String;II)Lkotlinx/io/Sink; + public static synthetic fun writeUtf8$default (Lkotlinx/io/Sink;Ljava/lang/String;IIILjava/lang/Object;)Lkotlinx/io/Sink; + public static final fun writeUtf8CodePoint (Lkotlinx/io/Sink;I)Lkotlinx/io/Sink; } public final class kotlinx/io/files/Path { diff --git a/core/common/src/Buffer.kt b/core/common/src/Buffer.kt index c56024b23..35bb384df 100644 --- a/core/common/src/Buffer.kt +++ b/core/common/src/Buffer.kt @@ -20,8 +20,6 @@ */ package kotlinx.io -import kotlin.jvm.JvmField - /** * A collection of bytes in memory. * @@ -81,10 +79,6 @@ expect class Buffer() : Source, Sink { /** Discards `byteCount` bytes from the head of this buffer. */ override fun skip(byteCount: Long) - override fun writeUtf8(string: String, beginIndex: Int, endIndex: Int): Buffer - - override fun writeUtf8CodePoint(codePoint: Int): Buffer - /** * Returns a tail segment that we can write at least `minimumCapacity` * bytes to, creating it if necessary. diff --git a/core/common/src/Sink.kt b/core/common/src/Sink.kt index 5eddb55ee..f308c4b4a 100644 --- a/core/common/src/Sink.kt +++ b/core/common/src/Sink.kt @@ -42,37 +42,6 @@ expect sealed interface Sink : RawSink { /** Removes `byteCount` bytes from `source` and appends them to this sink. */ fun write(source: RawSource, byteCount: Long): Sink - /** - * Encodes `string` in UTF-8 and writes it to this sink. - * ``` - * Buffer buffer = new Buffer(); - * buffer.writeUtf8("Uh uh uh!"); - * buffer.writeByte(' '); - * buffer.writeUtf8("You didn't say the magic word!"); - * - * assertEquals("Uh uh uh! You didn't say the magic word!", buffer.readUtf8()); - * ``` - */ - //TODO(filipp): fix javadoc - /* - * Encodes the characters at `beginIndex` up to `endIndex` from `string` in UTF-8 and writes it to - * this sink. - * ``` - * Buffer buffer = new Buffer(); - * buffer.writeUtf8("I'm a hacker!\n", 6, 12); - * buffer.writeByte(' '); - * buffer.writeUtf8("That's what I said: you're a nerd.\n", 29, 33); - * buffer.writeByte(' '); - * buffer.writeUtf8("I prefer to be called a hacker!\n", 24, 31); - * - * assertEquals("hacker nerd hacker!", buffer.readUtf8()); - * ``` - */ - fun writeUtf8(string: String, beginIndex: Int = 0, endIndex: Int = string.length): Sink - - /** Encodes `codePoint` in UTF-8 and writes it to this sink. */ - fun writeUtf8CodePoint(codePoint: Int): Sink - /** Writes a byte to this sink. */ fun writeByte(b: Int): Sink diff --git a/core/common/src/Source.kt b/core/common/src/Source.kt index 22968874e..e1fc930b3 100644 --- a/core/common/src/Source.kt +++ b/core/common/src/Source.kt @@ -166,59 +166,6 @@ expect sealed interface Source : RawSource { */ fun readAll(sink: RawSink): Long - /** - * Removes all bytes from this, decodes them as UTF-8, and returns the string. Returns the empty - * string if this source is empty. - * ``` - * Buffer buffer = new Buffer() - * .writeUtf8("Uh uh uh!") - * .writeByte(' ') - * .writeUtf8("You didn't say the magic word!"); - * - * assertEquals("Uh uh uh! You didn't say the magic word!", buffer.readUtf8()); - * assertEquals(0, buffer.size()); - * - * assertEquals("", buffer.readUtf8()); - * assertEquals(0, buffer.size()); - * ``` - */ - fun readUtf8(): String - - /** - * Removes `byteCount` bytes from this, decodes them as UTF-8, and returns the string. - * ``` - * Buffer buffer = new Buffer() - * .writeUtf8("Uh uh uh!") - * .writeByte(' ') - * .writeUtf8("You didn't say the magic word!"); - * assertEquals(40, buffer.size()); - * - * assertEquals("Uh uh uh! You ", buffer.readUtf8(14)); - * assertEquals(26, buffer.size()); - * - * assertEquals("didn't say the", buffer.readUtf8(14)); - * assertEquals(12, buffer.size()); - * - * assertEquals(" magic word!", buffer.readUtf8(12)); - * assertEquals(0, buffer.size()); - * ``` - */ - fun readUtf8(byteCount: Long): String - - /** - * Removes and returns a single UTF-8 code point, reading between 1 and 4 bytes as necessary. - * - * If this source is exhausted before a complete code point can be read, this throws an - * [java.io.EOFException] and consumes no input. - * - * If this source doesn't start with a properly-encoded UTF-8 code point, this method will remove - * 1 or more non-UTF-8 bytes and return the replacement character (`U+FFFD`). This covers encoding - * problems (the input is not properly-encoded UTF-8), characters out of range (beyond the - * 0x10ffff limit of Unicode), code points for UTF-16 surrogates (U+d800..U+dfff) and overlong - * encodings (such as `0xc080` for the NUL character in modified UTF-8). - */ - fun readUtf8CodePoint(): Int - /** * Returns a new `BufferedSource` that can read data from this `BufferedSource` without consuming * it. The returned source becomes invalid once this source is next read or closed. diff --git a/core/common/src/SourceExt.kt b/core/common/src/SourceExt.kt index eb179673d..0677d509d 100644 --- a/core/common/src/SourceExt.kt +++ b/core/common/src/SourceExt.kt @@ -200,113 +200,6 @@ fun Source.readHexadecimalUnsignedLong(): Long { return result } -/** - * Removes and returns characters up to but not including the next line break. A line break is - * either `"\n"` or `"\r\n"`; these characters are not included in the result. - * ``` - * Buffer buffer = new Buffer() - * .writeUtf8("I'm a hacker!\n") - * .writeUtf8("That's what I said: you're a nerd.\n") - * .writeUtf8("I prefer to be called a hacker!\n"); - * assertEquals(81, buffer.size()); - * - * assertEquals("I'm a hacker!", buffer.readUtf8Line()); - * assertEquals(67, buffer.size()); - * - * assertEquals("That's what I said: you're a nerd.", buffer.readUtf8Line()); - * assertEquals(32, buffer.size()); - * - * assertEquals("I prefer to be called a hacker!", buffer.readUtf8Line()); - * assertEquals(0, buffer.size()); - * - * assertEquals(null, buffer.readUtf8Line()); - * assertEquals(0, buffer.size()); - * ``` - * - * **On the end of the stream this method returns null,** just like [java.io.BufferedReader]. If - * the source doesn't end with a line break then an implicit line break is assumed. Null is - * returned once the source is exhausted. Use this for human-generated data, where a trailing - * line break is optional. - */ -fun Source.readUtf8Line(): String? { - if (!request(1)) return null - - val peekSource = peek() - var offset = 0L - var newlineSize = 0L - while (peekSource.request(1)) { - val b = peekSource.readByte().toInt() - if (b == '\n'.code) { - newlineSize = 1L - break - } else if (b == '\r'.code) { - if (peekSource.request(1) && peekSource.buffer[0].toInt() == '\n'.code) { - newlineSize = 2L - break - } - } - offset++ - } - val line = readUtf8(offset) - skip(newlineSize) - return line -} - -/** - * Like [readUtf8LineStrict], except this allows the caller to specify the longest allowed match. - * Use this to protect against streams that may not include `"\n"` or `"\r\n"`. - * - * The returned string will have at most `limit` UTF-8 bytes, and the maximum number of bytes - * scanned is `limit + 2`. If `limit == 0` this will always throw an `EOFException` because no - * bytes will be scanned. - * - * This method is safe. No bytes are discarded if the match fails, and the caller is free to try - * another match: - * ``` - * Buffer buffer = new Buffer(); - * buffer.writeUtf8("12345\r\n"); - * - * // This will throw! There must be \r\n or \n at the limit or before it. - * buffer.readUtf8LineStrict(4); - * - * // No bytes have been consumed so the caller can retry. - * assertEquals("12345", buffer.readUtf8LineStrict(5)); - * ``` - */ -fun Source.readUtf8LineStrict(limit: Long = Long.MAX_VALUE): String { - if (!request(1)) throw EOFException() - - val peekSource = peek() - var offset = 0L - var newlineSize = 0L - while (offset < limit && peekSource.request(1)) { - val b = peekSource.readByte().toInt() - if (b == '\n'.code) { - newlineSize = 1L - break - } else if (b == '\r'.code) { - if (peekSource.request(1) && peekSource.buffer[0].toInt() == '\n'.code) { - newlineSize = 2L - break - } - } - offset++ - } - if (offset == limit) { - if (!peekSource.request(1)) throw EOFException() - val nlCandidate = peekSource.readByte().toInt() - if (nlCandidate == '\n'.code) { - newlineSize = 1 - } else if (nlCandidate == '\r'.code && peekSource.request(1) && peekSource.readByte().toInt() == '\n'.code) { - newlineSize = 2 - } - } - if (newlineSize == 0L) throw EOFException() - val line = readUtf8(offset) - skip(newlineSize) - return line -} - /** * Returns the index of the first `b` in the buffer at or after `fromIndex`. This expands the * buffer as necessary until `b` is found. This reads an unbounded number of bytes into the diff --git a/core/common/src/Utf8.kt b/core/common/src/Utf8.kt index 29aad9d36..729083dbc 100644 --- a/core/common/src/Utf8.kt +++ b/core/common/src/Utf8.kt @@ -70,6 +70,10 @@ package kotlinx.io +import kotlinx.io.internal.commonReadUtf8 +import kotlinx.io.internal.commonReadUtf8CodePoint +import kotlinx.io.internal.commonWriteUtf8 +import kotlinx.io.internal.commonWriteUtf8CodePoint import kotlin.jvm.JvmName import kotlin.jvm.JvmOverloads @@ -560,3 +564,156 @@ internal inline fun ByteArray.process4Utf8Bytes( } return 4 } + +fun T.writeUtf8CodePoint(codePoint: Int): T { + buffer.commonWriteUtf8CodePoint(codePoint) + emitCompleteSegments() + return this +} + +fun T.writeUtf8(string: String, beginIndex: Int = 0, endIndex: Int = string.length): T { + buffer.commonWriteUtf8(string, beginIndex, endIndex) + emitCompleteSegments() + return this +} + +fun Source.readUtf8(): String { + var req: Long = 1 + while (request(req)) { + req *= 2 + } + return buffer.commonReadUtf8(buffer.size) +} + +fun Buffer.readUtf8(): String { + return commonReadUtf8(buffer.size) +} + +fun Source.readUtf8(byteCount: Long): String { + require(byteCount) + return buffer.commonReadUtf8(byteCount) +} + +fun Source.readUtf8CodePoint(): Int { + require(1) + + val b0 = buffer[0].toInt() + when { + b0 and 0xe0 == 0xc0 -> require(2) + b0 and 0xf0 == 0xe0 -> require(3) + b0 and 0xf8 == 0xf0 -> require(4) + } + + return buffer.commonReadUtf8CodePoint() +} + +fun Buffer.readUtf8CodePoint(): Int { + return buffer.commonReadUtf8CodePoint() +} + +/** + * Removes and returns characters up to but not including the next line break. A line break is + * either `"\n"` or `"\r\n"`; these characters are not included in the result. + * ``` + * Buffer buffer = new Buffer() + * .writeUtf8("I'm a hacker!\n") + * .writeUtf8("That's what I said: you're a nerd.\n") + * .writeUtf8("I prefer to be called a hacker!\n"); + * assertEquals(81, buffer.size()); + * + * assertEquals("I'm a hacker!", buffer.readUtf8Line()); + * assertEquals(67, buffer.size()); + * + * assertEquals("That's what I said: you're a nerd.", buffer.readUtf8Line()); + * assertEquals(32, buffer.size()); + * + * assertEquals("I prefer to be called a hacker!", buffer.readUtf8Line()); + * assertEquals(0, buffer.size()); + * + * assertEquals(null, buffer.readUtf8Line()); + * assertEquals(0, buffer.size()); + * ``` + * + * **On the end of the stream this method returns null,** just like [java.io.BufferedReader]. If + * the source doesn't end with a line break then an implicit line break is assumed. Null is + * returned once the source is exhausted. Use this for human-generated data, where a trailing + * line break is optional. + */ +fun Source.readUtf8Line(): String? { + if (!request(1)) return null + + val peekSource = peek() + var offset = 0L + var newlineSize = 0L + while (peekSource.request(1)) { + val b = peekSource.readByte().toInt() + if (b == '\n'.code) { + newlineSize = 1L + break + } else if (b == '\r'.code) { + if (peekSource.request(1) && peekSource.buffer[0].toInt() == '\n'.code) { + newlineSize = 2L + break + } + } + offset++ + } + val line = readUtf8(offset) + skip(newlineSize) + return line +} + +/** + * Like [readUtf8LineStrict], except this allows the caller to specify the longest allowed match. + * Use this to protect against streams that may not include `"\n"` or `"\r\n"`. + * + * The returned string will have at most `limit` UTF-8 bytes, and the maximum number of bytes + * scanned is `limit + 2`. If `limit == 0` this will always throw an `EOFException` because no + * bytes will be scanned. + * + * This method is safe. No bytes are discarded if the match fails, and the caller is free to try + * another match: + * ``` + * Buffer buffer = new Buffer(); + * buffer.writeUtf8("12345\r\n"); + * + * // This will throw! There must be \r\n or \n at the limit or before it. + * buffer.readUtf8LineStrict(4); + * + * // No bytes have been consumed so the caller can retry. + * assertEquals("12345", buffer.readUtf8LineStrict(5)); + * ``` + */ +fun Source.readUtf8LineStrict(limit: Long = Long.MAX_VALUE): String { + if (!request(1)) throw EOFException() + + val peekSource = peek() + var offset = 0L + var newlineSize = 0L + while (offset < limit && peekSource.request(1)) { + val b = peekSource.readByte().toInt() + if (b == '\n'.code) { + newlineSize = 1L + break + } else if (b == '\r'.code) { + if (peekSource.request(1) && peekSource.buffer[0].toInt() == '\n'.code) { + newlineSize = 2L + break + } + } + offset++ + } + if (offset == limit) { + if (!peekSource.request(1)) throw EOFException() + val nlCandidate = peekSource.readByte().toInt() + if (nlCandidate == '\n'.code) { + newlineSize = 1 + } else if (nlCandidate == '\r'.code && peekSource.request(1) && peekSource.readByte().toInt() == '\n'.code) { + newlineSize = 2 + } + } + if (newlineSize == 0L) throw EOFException() + val line = readUtf8(offset) + skip(newlineSize) + return line +} \ No newline at end of file diff --git a/core/jvm/src/Buffer.kt b/core/jvm/src/Buffer.kt index caf191c89..ca7be2a6b 100644 --- a/core/jvm/src/Buffer.kt +++ b/core/jvm/src/Buffer.kt @@ -232,40 +232,6 @@ actual class Buffer : Source, Sink, Cloneable, ByteChannel { @Throws(IOException::class) override fun readAll(sink: RawSink): Long = commonReadAll(sink) - override fun readUtf8() = readString(size, Charsets.UTF_8) - - @Throws(EOFException::class) - override fun readUtf8(byteCount: Long) = readString(byteCount, Charsets.UTF_8) - - override fun readString(charset: Charset) = readString(size, charset) - - @Throws(EOFException::class) - override fun readString(byteCount: Long, charset: Charset): String { - require(byteCount >= 0 && byteCount <= Integer.MAX_VALUE) { "byteCount: $byteCount" } - if (size < byteCount) throw EOFException() - if (byteCount == 0L) return "" - - val s = head!! - if (s.pos + byteCount > s.limit) { - // If the string spans multiple segments, delegate to readBytes(). - return String(readByteArray(byteCount), charset) - } - - val result = String(s.data, s.pos, byteCount.toInt(), charset) - s.pos += byteCount.toInt() - size -= byteCount - - if (s.pos == s.limit) { - head = s.pop() - SegmentPool.recycle(s) - } - - return result - } - - @Throws(EOFException::class) - override fun readUtf8CodePoint(): Int = commonReadUtf8CodePoint() - override fun readByteArray() = commonReadByteArray() @Throws(EOFException::class) @@ -300,31 +266,6 @@ actual class Buffer : Source, Sink, Cloneable, ByteChannel { @Throws(EOFException::class) actual override fun skip(byteCount: Long) = commonSkip(byteCount) - actual override fun writeUtf8(string: String, beginIndex: Int, endIndex: Int): Buffer = - commonWriteUtf8(string, beginIndex, endIndex) - - actual override fun writeUtf8CodePoint(codePoint: Int): Buffer = - commonWriteUtf8CodePoint(codePoint) - - override fun writeString(string: String, charset: Charset) = writeString( - string, 0, string.length, - charset - ) - - override fun writeString( - string: String, - beginIndex: Int, - endIndex: Int, - charset: Charset - ): Buffer { - require(beginIndex >= 0) { "beginIndex < 0: $beginIndex" } - require(endIndex >= beginIndex) { "endIndex < beginIndex: $endIndex < $beginIndex" } - require(endIndex <= string.length) { "endIndex > string.length: $endIndex > ${string.length}" } - if (charset == Charsets.UTF_8) return writeUtf8(string, beginIndex, endIndex) - val data = string.substring(beginIndex, endIndex).toByteArray(charset) - return write(data, 0, data.size) - } - actual override fun write( source: ByteArray, offset: Int, diff --git a/core/jvm/src/RealSink.kt b/core/jvm/src/RealSink.kt index e28a7b82a..35b553591 100644 --- a/core/jvm/src/RealSink.kt +++ b/core/jvm/src/RealSink.kt @@ -37,27 +37,6 @@ internal actual class RealSink actual constructor( inline get() = bufferField override fun write(source: Buffer, byteCount: Long) = commonWrite(source, byteCount) - override fun writeUtf8(string: String, beginIndex: Int, endIndex: Int) = - commonWriteUtf8(string, beginIndex, endIndex) - - override fun writeUtf8CodePoint(codePoint: Int) = commonWriteUtf8CodePoint(codePoint) - - override fun writeString(string: String, charset: Charset): Sink { - check(!closed) { "closed" } - buffer.writeString(string, charset) - return emitCompleteSegments() - } - - override fun writeString( - string: String, - beginIndex: Int, - endIndex: Int, - charset: Charset - ): Sink { - check(!closed) { "closed" } - buffer.writeString(string, beginIndex, endIndex, charset) - return emitCompleteSegments() - } override fun write(source: ByteArray, offset: Int, byteCount: Int) = commonWrite(source, offset, byteCount) diff --git a/core/jvm/src/RealSource.kt b/core/jvm/src/RealSource.kt index bfbc896a8..3ce3a97e0 100644 --- a/core/jvm/src/RealSource.kt +++ b/core/jvm/src/RealSource.kt @@ -58,20 +58,7 @@ internal actual class RealSource actual constructor( override fun readFully(sink: Buffer, byteCount: Long): Unit = commonReadFully(sink, byteCount) override fun readAll(sink: RawSink): Long = commonReadAll(sink) - override fun readUtf8(): String = commonReadUtf8() - override fun readUtf8(byteCount: Long): String = commonReadUtf8(byteCount) - override fun readString(charset: Charset): String { - buffer.writeAll(source) - return buffer.readString(charset) - } - - override fun readString(byteCount: Long, charset: Charset): String { - require(byteCount) - return buffer.readString(byteCount, charset) - } - - override fun readUtf8CodePoint(): Int = commonReadUtf8CodePoint() override fun readShort(): Short = commonReadShort() override fun readInt(): Int = commonReadInt() override fun readLong(): Long = commonReadLong() diff --git a/core/jvm/src/Sink.kt b/core/jvm/src/Sink.kt index 311703199..4fc9335fa 100644 --- a/core/jvm/src/Sink.kt +++ b/core/jvm/src/Sink.kt @@ -37,18 +37,6 @@ actual sealed interface Sink : RawSink, WritableByteChannel { @Throws(IOException::class) actual fun write(source: RawSource, byteCount: Long): Sink - @Throws(IOException::class) - actual fun writeUtf8(string: String, beginIndex: Int, endIndex: Int): Sink - - @Throws(IOException::class) - actual fun writeUtf8CodePoint(codePoint: Int): Sink - - @Throws(IOException::class) - fun writeString(string: String, charset: Charset): Sink - - @Throws(IOException::class) - fun writeString(string: String, beginIndex: Int, endIndex: Int, charset: Charset): Sink - @Throws(IOException::class) actual fun writeByte(b: Int): Sink @@ -73,3 +61,14 @@ actual sealed interface Sink : RawSink, WritableByteChannel { /** Returns an output stream that writes to this sink. */ fun outputStream(): OutputStream } + + +fun T.writeString(string: String, charset: Charset, beginIndex: Int = 0, endIndex: Int = string.length): T { + require(beginIndex >= 0) { "beginIndex < 0: $beginIndex" } + require(endIndex >= beginIndex) { "endIndex < beginIndex: $endIndex < $beginIndex" } + require(endIndex <= string.length) { "endIndex > string.length: $endIndex > ${string.length}" } + if (charset == Charsets.UTF_8) return writeUtf8(string, beginIndex, endIndex) + val data = string.substring(beginIndex, endIndex).toByteArray(charset) + write(data, 0, data.size) + return this +} \ No newline at end of file diff --git a/core/jvm/src/Source.kt b/core/jvm/src/Source.kt index e503ce3b0..d2ceb4c8d 100644 --- a/core/jvm/src/Source.kt +++ b/core/jvm/src/Source.kt @@ -20,6 +20,7 @@ */ package kotlinx.io +import java.io.EOFException import java.io.IOException import java.io.InputStream import java.nio.channels.ReadableByteChannel @@ -70,28 +71,46 @@ actual sealed interface Source : RawSource, ReadableByteChannel { @Throws(IOException::class) actual fun readAll(sink: RawSink): Long - @Throws(IOException::class) - actual fun readUtf8(): String + actual fun peek(): Source - @Throws(IOException::class) - actual fun readUtf8(byteCount: Long): String + /** Returns an input stream that reads from this source. */ + fun inputStream(): InputStream +} - @Throws(IOException::class) - actual fun readUtf8CodePoint(): Int +private fun Buffer.readStringImpl(byteCount: Long, charset: Charset): String { + require(byteCount >= 0 && byteCount <= Integer.MAX_VALUE) { "byteCount: $byteCount" } + if (size < byteCount) throw EOFException() + if (byteCount == 0L) return "" - /** Removes all bytes from this, decodes them as `charset`, and returns the string. */ - @Throws(IOException::class) - fun readString(charset: Charset): String + val s = head!! + if (s.pos + byteCount > s.limit) { + // If the string spans multiple segments, delegate to readBytes(). + return String(readByteArray(byteCount), charset) + } - /** - * Removes `byteCount` bytes from this, decodes them as `charset`, and returns the - * string. - */ - @Throws(IOException::class) - fun readString(byteCount: Long, charset: Charset): String + val result = String(s.data, s.pos, byteCount.toInt(), charset) + s.pos += byteCount.toInt() + size -= byteCount - actual fun peek(): Source + if (s.pos == s.limit) { + head = s.pop() + SegmentPool.recycle(s) + } - /** Returns an input stream that reads from this source. */ - fun inputStream(): InputStream + return result } + +@Throws(IOException::class) +fun Source.readString(charset: Charset): String { + var req = 1L + while (request(req)) { + req *= 2 + } + return buffer.readStringImpl(buffer.size, charset) +} + +@Throws(IOException::class) +fun Source.readString(byteCount: Long, charset: Charset): String { + require(byteCount) + return buffer.readStringImpl(byteCount, charset) +} \ No newline at end of file diff --git a/core/native/src/Buffer.kt b/core/native/src/Buffer.kt index 7ddb3c202..2f8bdcb04 100644 --- a/core/native/src/Buffer.kt +++ b/core/native/src/Buffer.kt @@ -71,12 +71,6 @@ actual class Buffer : Source, Sink { override fun readAll(sink: RawSink): Long = commonReadAll(sink) - override fun readUtf8(): String = readUtf8(size) - - override fun readUtf8(byteCount: Long): String = commonReadUtf8(byteCount) - - override fun readUtf8CodePoint(): Int = commonReadUtf8CodePoint() - override fun readByteArray(): ByteArray = commonReadByteArray() override fun readByteArray(byteCount: Long): ByteArray = commonReadByteArray(byteCount) @@ -93,12 +87,6 @@ actual class Buffer : Source, Sink { internal actual fun writableSegment(minimumCapacity: Int): Segment = commonWritableSegment(minimumCapacity) - actual override fun writeUtf8(string: String, beginIndex: Int, endIndex: Int): Buffer = - commonWriteUtf8(string, beginIndex, endIndex) - - actual override fun writeUtf8CodePoint(codePoint: Int): Buffer = - commonWriteUtf8CodePoint(codePoint) - actual override fun write(source: ByteArray, offset: Int, byteCount: Int): Buffer = commonWrite(source, offset, byteCount) diff --git a/core/native/src/RealSink.kt b/core/native/src/RealSink.kt index b59657e26..2174a41f6 100644 --- a/core/native/src/RealSink.kt +++ b/core/native/src/RealSink.kt @@ -31,10 +31,6 @@ internal actual class RealSink actual constructor( override fun write(source: Buffer, byteCount: Long) = commonWrite(source, byteCount) - override fun writeUtf8(string: String, beginIndex: Int, endIndex: Int) = - commonWriteUtf8(string, beginIndex, endIndex) - - override fun writeUtf8CodePoint(codePoint: Int) = commonWriteUtf8CodePoint(codePoint) override fun write(source: ByteArray, offset: Int, byteCount: Int) = commonWrite(source, offset, byteCount) diff --git a/core/native/src/RealSource.kt b/core/native/src/RealSource.kt index 6fd608038..29858e7de 100644 --- a/core/native/src/RealSource.kt +++ b/core/native/src/RealSource.kt @@ -41,9 +41,6 @@ internal actual class RealSource actual constructor( override fun readFully(sink: Buffer, byteCount: Long): Unit = commonReadFully(sink, byteCount) override fun readAll(sink: RawSink): Long = commonReadAll(sink) - override fun readUtf8(): String = commonReadUtf8() - override fun readUtf8(byteCount: Long): String = commonReadUtf8(byteCount) - override fun readUtf8CodePoint(): Int = commonReadUtf8CodePoint() override fun readShort(): Short = commonReadShort() override fun readInt(): Int = commonReadInt() override fun readLong(): Long = commonReadLong() diff --git a/core/native/src/Sink.kt b/core/native/src/Sink.kt index b07bf9b61..96ca2fde2 100644 --- a/core/native/src/Sink.kt +++ b/core/native/src/Sink.kt @@ -29,10 +29,6 @@ actual sealed interface Sink : RawSink { actual fun write(source: RawSource, byteCount: Long): Sink - actual fun writeUtf8(string: String, beginIndex: Int, endIndex: Int): Sink - - actual fun writeUtf8CodePoint(codePoint: Int): Sink - actual fun writeByte(b: Int): Sink actual fun writeShort(s: Int): Sink diff --git a/core/native/src/Source.kt b/core/native/src/Source.kt index e33b7589f..af543ea2d 100644 --- a/core/native/src/Source.kt +++ b/core/native/src/Source.kt @@ -51,11 +51,5 @@ actual sealed interface Source : RawSource { actual fun readAll(sink: RawSink): Long - actual fun readUtf8(): String - - actual fun readUtf8(byteCount: Long): String - - actual fun readUtf8CodePoint(): Int - actual fun peek(): Source } From 800bccb3e4f5141f52ef26eb90f82211a6082384 Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Wed, 31 May 2023 17:31:23 +0200 Subject: [PATCH 08/83] Updated documentation, added additional tests --- core/api/kotlinx-io-core.api | 2 +- core/common/src/Buffer.kt | 8 +- core/common/src/Core.kt | 15 +- core/common/src/RawSink.kt | 36 ++-- core/common/src/RawSource.kt | 28 +-- core/common/src/Sink.kt | 202 +++++++----------- core/common/src/SinkExt.kt | 109 +++------- core/common/src/Source.kt | 181 +++++++--------- core/common/src/SourceExt.kt | 187 ++-------------- core/common/src/Utf8.kt | 106 +++++---- core/common/src/internal/-Buffer.kt | 8 +- core/common/src/internal/-RealBufferedSink.kt | 3 + core/common/test/AbstractSinkTest.kt | 107 +++++++++- core/common/test/AbstractSourceTest.kt | 94 ++++++-- core/jvm/src/Buffer.kt | 8 +- core/jvm/src/RealSink.kt | 8 +- core/jvm/src/Sink.kt | 8 +- core/native/src/Buffer.kt | 8 +- core/native/src/RealSink.kt | 8 +- core/native/src/Sink.kt | 8 +- 20 files changed, 498 insertions(+), 636 deletions(-) diff --git a/core/api/kotlinx-io-core.api b/core/api/kotlinx-io-core.api index acbcec2a5..fb63f3c43 100644 --- a/core/api/kotlinx-io-core.api +++ b/core/api/kotlinx-io-core.api @@ -69,7 +69,7 @@ public final class kotlinx/io/Buffer : java/lang/Cloneable, java/nio/channels/By } public final class kotlinx/io/CoreKt { - public static final fun blackhole ()Lkotlinx/io/RawSink; + public static final fun blackholeSink ()Lkotlinx/io/RawSink; public static final fun buffer (Lkotlinx/io/RawSink;)Lkotlinx/io/Sink; public static final fun buffer (Lkotlinx/io/RawSource;)Lkotlinx/io/Source; public static final fun use (Ljava/io/Closeable;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; diff --git a/core/common/src/Buffer.kt b/core/common/src/Buffer.kt index 35bb384df..99de4fbdc 100644 --- a/core/common/src/Buffer.kt +++ b/core/common/src/Buffer.kt @@ -89,13 +89,13 @@ expect class Buffer() : Source, Sink { override fun write(source: RawSource, byteCount: Long): Buffer - override fun writeByte(b: Int): Buffer + override fun writeByte(byte: Int): Buffer - override fun writeShort(s: Int): Buffer + override fun writeShort(short: Int): Buffer - override fun writeInt(i: Int): Buffer + override fun writeInt(int: Int): Buffer - override fun writeLong(v: Long): Buffer + override fun writeLong(long: Long): Buffer /** Returns a deep copy of this buffer. */ fun copy(): Buffer diff --git a/core/common/src/Core.kt b/core/common/src/Core.kt index 4142fb176..59e0f5d0a 100644 --- a/core/common/src/Core.kt +++ b/core/common/src/Core.kt @@ -20,23 +20,22 @@ */ package kotlinx.io -import kotlin.jvm.JvmName - /** - * Returns a new source that buffers reads from `source`. The returned source will perform bulk + * Returns a new source that buffers reads from the source. The returned source will perform bulk * reads into its in-memory buffer. Use this wherever you read a source to get an ergonomic and * efficient access to data. */ fun RawSource.buffer(): Source = RealSource(this) /** - * Returns a new sink that buffers writes to `sink`. The returned sink will batch writes to `sink`. + * Returns a new sink that buffers writes to the sink. The returned sink will batch writes to the sink. * Use this wherever you write to a sink to get an ergonomic and efficient access to data. */ fun RawSink.buffer(): Sink = RealSink(this) -/** Returns a sink that writes nowhere. */ -@JvmName("blackhole") +/** + * Returns a sink that writes nowhere. + */ fun blackholeSink(): RawSink = BlackholeSink() private class BlackholeSink : RawSink { @@ -46,7 +45,9 @@ private class BlackholeSink : RawSink { override fun close() {} } -/** Execute [block] then close this. This will be closed even if [block] throws. */ +/** + * Execute [block] then close this. This will be closed even if [block] throws. + */ inline fun T.use(block: (T) -> R): R { var result: R? = null var thrown: Throwable? = null diff --git a/core/common/src/RawSink.kt b/core/common/src/RawSink.kt index 04fef8bfe..34ae768e8 100644 --- a/core/common/src/RawSink.kt +++ b/core/common/src/RawSink.kt @@ -25,39 +25,33 @@ package kotlinx.io * network, storage, or a buffer in memory. Sinks may be layered to transform received data, such as * to compress, encrypt, throttle, or add protocol framing. * - * Most application code shouldn't operate on a sink directly, but rather on a [Sink] which - * is both more efficient and more convenient. Use [buffer] to wrap any sink with a buffer. + * Most application code shouldn't operate on a raw sink directly, but rather on a [Sink] which + * is both more efficient and more convenient. Use [buffer] to wrap any raw sink with a buffer. * * Sinks are easy to test: just use a [Buffer] in your tests, and read from it to confirm it * received the data that was expected. - * - * ### Comparison with OutputStream - * - * This interface is functionally equivalent to [java.io.OutputStream]. - * - * `OutputStream` requires multiple layers when emitted data is heterogeneous: a `DataOutputStream` - * for primitive values, a `BufferedOutputStream` for buffering, and `OutputStreamWriter` for - * charset encoding. This library uses `BufferedSink` for all of the above. - * - * RawSink is also easier to layer: there is no [write()][java.io.OutputStream.write] method that is - * awkward to implement efficiently. - * - * ### Interop with OutputStream - * - * Use [sink] to adapt an `OutputStream` to a sink. Use [outputStream()][Sink.outputStream] - * to adapt a sink to an `OutputStream`. */ expect interface RawSink : Closeable { - /** Removes `byteCount` bytes from `source` and appends them to this. */ + // TODO: should it throw EOFException instead? + /** + * Removes [byteCount] bytes from [source] and appends them to this. + * + * @param source the source to read data from. + * @param byteCount amount of bytes to write. + * + * @throws IndexOutOfBoundsException when the [source]'s size is below [byteCount]. + */ @Throws(IOException::class) fun write(source: Buffer, byteCount: Long) - /** Pushes all buffered bytes to their final destination. */ + /** + * Pushes all buffered bytes to their final destination. + */ @Throws(IOException::class) fun flush() /** - * Asynchronously cancel this source. Any [write] or [flush] in flight should immediately fail + * Asynchronously cancel this sink. Any [write] or [flush] in flight should immediately fail * with an [IOException], and any future writes and flushes should also immediately fail with an * [IOException]. * diff --git a/core/common/src/RawSource.kt b/core/common/src/RawSource.kt index 4ae59c508..b49d6dfad 100644 --- a/core/common/src/RawSource.kt +++ b/core/common/src/RawSource.kt @@ -25,35 +25,11 @@ package kotlinx.io * network, storage, or a buffer in memory. Sources may be layered to transform supplied data, such * as to decompress, decrypt, or remove protocol framing. * - * Most applications shouldn't operate on a source directly, but rather on a [Source] which - * is both more efficient and more convenient. Use [buffer] to wrap any source with a buffer. + * Most applications shouldn't operate on a raw source directly, but rather on a [Source] which + * is both more efficient and more convenient. Use [buffer] to wrap any raw source with a buffer. * * Sources are easy to test: just use a [Buffer] in your tests, and fill it with the data your * application is to read. - * - * ### Comparison with InputStream - - * This interface is functionally equivalent to [java.io.InputStream]. - * - * `InputStream` requires multiple layers when consumed data is heterogeneous: a `DataInputStream` - * for primitive values, a `BufferedInputStream` for buffering, and `InputStreamReader` for strings. - * This library uses `BufferedSource` for all of the above. - * - * RawSource avoids the impossible-to-implement [available()][java.io.InputStream.available] method. - * Instead callers specify how many bytes they [require][Source.require]. - * - * RawSource omits the unsafe-to-compose [mark and reset][java.io.InputStream.mark] state that's - * tracked by `InputStream`; instead, callers just buffer what they need. - * - * When implementing a source, you don't need to worry about the [read()][java.io.InputStream.read] - * method that is awkward to implement efficiently and returns one of 257 possible values. - * - * And source has a stronger `skip` method: [Source.skip] won't return prematurely. - * - * ### Interop with InputStream - * - * Use [source] to adapt an `InputStream` to a source. Use [Source.inputStream] to adapt a - * source to an `InputStream`. */ interface RawSource : Closeable { /** diff --git a/core/common/src/Sink.kt b/core/common/src/Sink.kt index f308c4b4a..e4546c682 100644 --- a/core/common/src/Sink.kt +++ b/core/common/src/Sink.kt @@ -25,161 +25,109 @@ package kotlinx.io * penalty. */ expect sealed interface Sink : RawSink { - /** This sink's internal buffer. */ + /** + * This sink's internal buffer. + */ val buffer: Buffer - // TODO(filipp): fix javadoc - /** Like [OutputStream.write], this writes a complete byte array to this sink. */ - /** Like [OutputStream.write], this writes `byteCount` bytes of `source`, starting at `offset`. */ - fun write(source: ByteArray, offset: Int = 0, byteCount: Int = source.size): Sink + /** + * Writes bytes from [source] array or its subrange to this sink. + * + * @param source the array from which bytes will be written into this sink. + * @param offset the beginning of data within the [source], 0 by default. + * @param byteCount amount of bytes to write, size of the [source] subarray starting at [offset] by default. + * + * @throws IndexOutOfBoundsException when a range specified by [offset] and [byteCount] + * is out of range of [source] array indices. + * + * @sample kotlinx.io.AbstractSinkTest.writeByteArray + */ + fun write(source: ByteArray, offset: Int = 0, byteCount: Int = source.size - offset): Sink /** - * Removes all bytes from `source` and appends them to this sink. Returns the number of bytes read - * which will be 0 if `source` is exhausted. + * Removes all bytes from [source] and write them to this sink. + * Returns the number of bytes read which will be 0 if [source] is exhausted. + * + * @param source the source to consume data from. + * + * @sample kotlinx.io.AbstractSinkTest.writeAll + * @sample kotlinx.io.AbstractSinkTest.writeAllExhausted */ fun writeAll(source: RawSource): Long - /** Removes `byteCount` bytes from `source` and appends them to this sink. */ + /** + * Removes [byteCount] bytes from [source] and write them to this sink. + * + * If [source] will be exhausted before reading [byteCount] from it then an exception throws on + * attempt to read remaining bytes will be propagated to a caller of this method. + * + * @param source the source to consume data from. + * @param byteCount amount of bytes to read from [source] and to write into this sink. + * + * @throws IllegalArgumentException when [byteCount] is negative. + * + * @sample kotlinx.io.AbstractSinkTest.writeSource + */ fun write(source: RawSource, byteCount: Long): Sink - /** Writes a byte to this sink. */ - fun writeByte(b: Int): Sink + /** + * Writes a byte to this sink. + * + * @param byte the byte to be written. + * + * @sample kotlinx.io.AbstractSinkTest.writeByte + */ + fun writeByte(byte: Int): Sink /** - * Writes a big-endian short to this sink using two bytes. - * ``` - * Buffer buffer = new Buffer(); - * buffer.writeShort(32767); - * buffer.writeShort(15); - * - * assertEquals(4, buffer.size()); - * assertEquals((byte) 0x7f, buffer.readByte()); - * assertEquals((byte) 0xff, buffer.readByte()); - * assertEquals((byte) 0x00, buffer.readByte()); - * assertEquals((byte) 0x0f, buffer.readByte()); - * assertEquals(0, buffer.size()); - * ``` + * Writes two bytes containing [short], in the big-endian order, to this sink. + * + * @param short the short integer to be written. + * + * @sample kotlinx.io.AbstractSinkTest.writeShort */ - fun writeShort(s: Int): Sink + fun writeShort(short: Int): Sink /** - * Writes a big-endian int to this sink using four bytes. - * ``` - * Buffer buffer = new Buffer(); - * buffer.writeInt(2147483647); - * buffer.writeInt(15); - * - * assertEquals(8, buffer.size()); - * assertEquals((byte) 0x7f, buffer.readByte()); - * assertEquals((byte) 0xff, buffer.readByte()); - * assertEquals((byte) 0xff, buffer.readByte()); - * assertEquals((byte) 0xff, buffer.readByte()); - * assertEquals((byte) 0x00, buffer.readByte()); - * assertEquals((byte) 0x00, buffer.readByte()); - * assertEquals((byte) 0x00, buffer.readByte()); - * assertEquals((byte) 0x0f, buffer.readByte()); - * assertEquals(0, buffer.size()); - * ``` + * Writes four bytes containing [int], in the big-endian order, to this sink. + * + * @param int the integer to be written. + * + * @sample kotlinx.io.AbstractSinkTest.writeInt */ - fun writeInt(i: Int): Sink + fun writeInt(int: Int): Sink /** - * Writes a big-endian long to this sink using eight bytes. - * ``` - * Buffer buffer = new Buffer(); - * buffer.writeLong(9223372036854775807L); - * buffer.writeLong(15); - * - * assertEquals(16, buffer.size()); - * assertEquals((byte) 0x7f, buffer.readByte()); - * assertEquals((byte) 0xff, buffer.readByte()); - * assertEquals((byte) 0xff, buffer.readByte()); - * assertEquals((byte) 0xff, buffer.readByte()); - * assertEquals((byte) 0xff, buffer.readByte()); - * assertEquals((byte) 0xff, buffer.readByte()); - * assertEquals((byte) 0xff, buffer.readByte()); - * assertEquals((byte) 0xff, buffer.readByte()); - * assertEquals((byte) 0x00, buffer.readByte()); - * assertEquals((byte) 0x00, buffer.readByte()); - * assertEquals((byte) 0x00, buffer.readByte()); - * assertEquals((byte) 0x00, buffer.readByte()); - * assertEquals((byte) 0x00, buffer.readByte()); - * assertEquals((byte) 0x00, buffer.readByte()); - * assertEquals((byte) 0x00, buffer.readByte()); - * assertEquals((byte) 0x0f, buffer.readByte()); - * assertEquals(0, buffer.size()); - * ``` + * Writes eight bytes containing [long], in the big-endian order, to this sink. + * + * @param long the long integer to be written. + * + * @sample kotlinx.io.AbstractSinkTest.writeLong */ - fun writeLong(v: Long): Sink + fun writeLong(long: Long): Sink /** - * Writes all buffered data to the underlying sink, if one exists. Then that sink is recursively - * flushed which pushes data as far as possible towards its ultimate destination. Typically that - * destination is a network socket or file. - * ``` - * BufferedSink b0 = new Buffer(); - * BufferedSink b1 = Okio.buffer(b0); - * BufferedSink b2 = Okio.buffer(b1); - * - * b2.writeUtf8("hello"); - * assertEquals(5, b2.buffer().size()); - * assertEquals(0, b1.buffer().size()); - * assertEquals(0, b0.buffer().size()); - * - * b2.flush(); - * assertEquals(0, b2.buffer().size()); - * assertEquals(0, b1.buffer().size()); - * assertEquals(5, b0.buffer().size()); - * ``` + * Writes all buffered data to the underlying sink, if one exists. + * Then the underlying sink is explicitly flushed. */ override fun flush() /** - * Writes all buffered data to the underlying sink, if one exists. Like [flush], but weaker. Call - * this before this buffered sink goes out of scope so that its data can reach its destination. - * ``` - * BufferedSink b0 = new Buffer(); - * BufferedSink b1 = Okio.buffer(b0); - * BufferedSink b2 = Okio.buffer(b1); - * - * b2.writeUtf8("hello"); - * assertEquals(5, b2.buffer().size()); - * assertEquals(0, b1.buffer().size()); - * assertEquals(0, b0.buffer().size()); - * - * b2.emit(); - * assertEquals(0, b2.buffer().size()); - * assertEquals(5, b1.buffer().size()); - * assertEquals(0, b0.buffer().size()); - * - * b1.emit(); - * assertEquals(0, b2.buffer().size()); - * assertEquals(0, b1.buffer().size()); - * assertEquals(5, b0.buffer().size()); - * ``` + * Writes all buffered data to the underlying sink, if one exists. + * The underlying sink will not be explicitly flushed. + * + * This method behaves like [flush], but has weaker guarantees. + * Call this method before a buffered sink goes out of scope so that its data can reach its destination. */ fun emit(): Sink /** - * Writes complete segments to the underlying sink, if one exists. Like [flush], but weaker. Use - * this to limit the memory held in the buffer to a single segment. Typically application code - * will not need to call this: it is only necessary when application code writes directly to this - * [sink's buffer][buffer]. - * ``` - * BufferedSink b0 = new Buffer(); - * BufferedSink b1 = Okio.buffer(b0); - * BufferedSink b2 = Okio.buffer(b1); - * - * b2.buffer().write(new byte[20_000]); - * assertEquals(20_000, b2.buffer().size()); - * assertEquals( 0, b1.buffer().size()); - * assertEquals( 0, b0.buffer().size()); - * - * b2.emitCompleteSegments(); - * assertEquals( 3_616, b2.buffer().size()); - * assertEquals( 0, b1.buffer().size()); - * assertEquals(16_384, b0.buffer().size()); // This example assumes 8192 byte segments. - * ``` + * Writes complete segments to the underlying sink, if one exists. + * The underlying sink will not be explicitly flushed. + * + * Use this to limit the memory held in the buffer to a single segment. + * Typically, application code will not need to call this: it is only necessary when + * application code writes directly to this [buffer]. */ fun emitCompleteSegments(): Sink } diff --git a/core/common/src/SinkExt.kt b/core/common/src/SinkExt.kt index 1d316637d..c52470923 100644 --- a/core/common/src/SinkExt.kt +++ b/core/common/src/SinkExt.kt @@ -6,119 +6,62 @@ package kotlinx.io /** - * Writes a little-endian short to this sink using two bytes. - * ``` - * Buffer buffer = new Buffer(); - * buffer.writeShortLe(32767); - * buffer.writeShortLe(15); + * Writes two bytes containing [short], in the little-endian order, to this sink. * - * assertEquals(4, buffer.size()); - * assertEquals((byte) 0xff, buffer.readByte()); - * assertEquals((byte) 0x7f, buffer.readByte()); - * assertEquals((byte) 0x0f, buffer.readByte()); - * assertEquals((byte) 0x00, buffer.readByte()); - * assertEquals(0, buffer.size()); - * ``` + * @param short the short integer to be written. */ -fun T.writeShortLe(s: Int): T { - this.writeShort(s.toShort().reverseBytes().toInt()) +fun T.writeShortLe(short: Int): T { + this.writeShort(short.toShort().reverseBytes().toInt()) return this } /** - * Writes a little-endian int to this sink using four bytes. - * ``` - * Buffer buffer = new Buffer(); - * buffer.writeIntLe(2147483647); - * buffer.writeIntLe(15); + * Writes four bytes containing [int], in the little-endian order, to this sink. + * + * @param int the integer to be written. * - * assertEquals(8, buffer.size()); - * assertEquals((byte) 0xff, buffer.readByte()); - * assertEquals((byte) 0xff, buffer.readByte()); - * assertEquals((byte) 0xff, buffer.readByte()); - * assertEquals((byte) 0x7f, buffer.readByte()); - * assertEquals((byte) 0x0f, buffer.readByte()); - * assertEquals((byte) 0x00, buffer.readByte()); - * assertEquals((byte) 0x00, buffer.readByte()); - * assertEquals((byte) 0x00, buffer.readByte()); - * assertEquals(0, buffer.size()); - * ``` */ -fun T.writeIntLe(i: Int): T { - this.writeInt(i.reverseBytes()) +fun T.writeIntLe(int: Int): T { + this.writeInt(int.reverseBytes()) return this } /** - * Writes a little-endian long to this sink using eight bytes. - * ``` - * Buffer buffer = new Buffer(); - * buffer.writeLongLe(9223372036854775807L); - * buffer.writeLongLe(15); + * Writes eight bytes containing [long], in the little-endian order, to this sink. * - * assertEquals(16, buffer.size()); - * assertEquals((byte) 0xff, buffer.readByte()); - * assertEquals((byte) 0xff, buffer.readByte()); - * assertEquals((byte) 0xff, buffer.readByte()); - * assertEquals((byte) 0xff, buffer.readByte()); - * assertEquals((byte) 0xff, buffer.readByte()); - * assertEquals((byte) 0xff, buffer.readByte()); - * assertEquals((byte) 0xff, buffer.readByte()); - * assertEquals((byte) 0x7f, buffer.readByte()); - * assertEquals((byte) 0x0f, buffer.readByte()); - * assertEquals((byte) 0x00, buffer.readByte()); - * assertEquals((byte) 0x00, buffer.readByte()); - * assertEquals((byte) 0x00, buffer.readByte()); - * assertEquals((byte) 0x00, buffer.readByte()); - * assertEquals((byte) 0x00, buffer.readByte()); - * assertEquals((byte) 0x00, buffer.readByte()); - * assertEquals((byte) 0x00, buffer.readByte()); - * assertEquals(0, buffer.size()); - * ``` + * @param long the long integer to be written. */ -fun T.writeLongLe(v: Long): T { - this.writeLong(v.reverseBytes()) +fun T.writeLongLe(long: Long): T { + this.writeLong(long.reverseBytes()) return this } /** - * Writes a long to this sink in signed decimal form (i.e., as a string in base 10). - * ``` - * Buffer buffer = new Buffer(); - * buffer.writeDecimalLong(8675309L); - * buffer.writeByte(' '); - * buffer.writeDecimalLong(-123L); - * buffer.writeByte(' '); - * buffer.writeDecimalLong(1L); + * Writes [long] to this sink in signed decimal form (i.e., as a string in base 10). + * + * Resulting string will not contain leading zeros, except the `0` value itself. * - * assertEquals("8675309 -123 1", buffer.readUtf8()); - * ``` + * @param long the long to be written. */ -fun T.writeDecimalLong(v: Long): T { +fun T.writeDecimalLong(long: Long): T { // TODO: optimize - writeUtf8(v.toString()) + writeUtf8(long.toString()) return this } /** - * Writes a long to this sink in hexadecimal form (i.e., as a string in base 16). - * ``` - * Buffer buffer = new Buffer(); - * buffer.writeHexadecimalUnsignedLong(65535L); - * buffer.writeByte(' '); - * buffer.writeHexadecimalUnsignedLong(0xcafebabeL); - * buffer.writeByte(' '); - * buffer.writeHexadecimalUnsignedLong(0x10L); + * Writes [long] to this sink in hexadecimal form (i.e., as a string in base 16). + * + * Resulting string will not contain leading zeros, except the `0` value itself. * - * assertEquals("ffff cafebabe 10", buffer.readUtf8()); - * ``` + * @param long the long to be written. */ -fun T.writeHexadecimalUnsignedLong(v: Long): T { - if (v == 0L) { +fun T.writeHexadecimalUnsignedLong(long: Long): T { + if (long == 0L) { writeByte('0'.code) } else { // TODO: optimize - writeUtf8(v.toHexString()) + writeUtf8(long.toHexString()) } return this } \ No newline at end of file diff --git a/core/common/src/Source.kt b/core/common/src/Source.kt index e1fc930b3..e4f1fe14f 100644 --- a/core/common/src/Source.kt +++ b/core/common/src/Source.kt @@ -26,164 +26,141 @@ package kotlinx.io * input. */ expect sealed interface Source : RawSource { - /** This source's internal buffer. */ + /** + * This source's internal buffer. + */ val buffer: Buffer /** - * Returns true if there are no more bytes in this source. This will block until there are bytes - * to read or the source is definitely exhausted. + * Returns true if there are no more bytes in this source. + * + * The call of this method will block until there are bytes to read or the source is definitely exhausted. */ fun exhausted(): Boolean /** - * Returns when the buffer contains at least `byteCount` bytes. Throws an - * [java.io.EOFException] if the source is exhausted before the required bytes can be read. + * Attempts to fill the buffer with at least [byteCount] bytes of data from the underlying source + * and throw [EOFException] when the source is exhausted before fulfilling the requirement. + * + * If the buffer already contains required amount of bytes then there will be no requests to + * the underlying source. + * + * @param byteCount amount of bytes that the buffer should contain. + * + * @throws EOFException when the source is exhausted before the required bytes count could be read. */ fun require(byteCount: Long) /** - * Returns true when the buffer contains at least `byteCount` bytes, expanding it as - * necessary. Returns false if the source is exhausted before the requested bytes can be read. + * Attempts to fill the buffer with at least [byteCount] bytes of data from the underlying source + * and returns a value indicating if the requirement was successfully fulfilled. + * + * `false` value returned by this method indicates that the underlying source was exhausted before + * filling the buffer with [byteCount] bytes of data. + * + * @param byteCount amount of bytes that the buffer should contain. */ fun request(byteCount: Long): Boolean - /** Removes a byte from this source and returns it. */ + /** + * Removes a byte from this source and returns it. + * + * @throws EOFException when there are no more bytes to read. + */ fun readByte(): Byte /** - * Removes two bytes from this source and returns a big-endian short. - * ``` - * Buffer buffer = new Buffer() - * .writeByte(0x7f) - * .writeByte(0xff) - * .writeByte(0x00) - * .writeByte(0x0f); - * assertEquals(4, buffer.size()); - * - * assertEquals(32767, buffer.readShort()); - * assertEquals(2, buffer.size()); + * Removes two bytes from this source and returns a short integer composed of it according to the big-endian order. * - * assertEquals(15, buffer.readShort()); - * assertEquals(0, buffer.size()); - * ``` + * @throws EOFException when there are not enough data to read a short value. */ fun readShort(): Short /** - * Removes four bytes from this source and returns a big-endian int. - * ``` - * Buffer buffer = new Buffer() - * .writeByte(0x7f) - * .writeByte(0xff) - * .writeByte(0xff) - * .writeByte(0xff) - * .writeByte(0x00) - * .writeByte(0x00) - * .writeByte(0x00) - * .writeByte(0x0f); - * assertEquals(8, buffer.size()); + * Removes four bytes from this source and returns an integer composed of it according to the big-endian order. * - * assertEquals(2147483647, buffer.readInt()); - * assertEquals(4, buffer.size()); - * - * assertEquals(15, buffer.readInt()); - * assertEquals(0, buffer.size()); - * ``` + * @throws EOFException when there are not enough data to read an int value. */ fun readInt(): Int /** - * Removes eight bytes from this source and returns a big-endian long. - * ``` - * Buffer buffer = new Buffer() - * .writeByte(0x7f) - * .writeByte(0xff) - * .writeByte(0xff) - * .writeByte(0xff) - * .writeByte(0xff) - * .writeByte(0xff) - * .writeByte(0xff) - * .writeByte(0xff) - * .writeByte(0x00) - * .writeByte(0x00) - * .writeByte(0x00) - * .writeByte(0x00) - * .writeByte(0x00) - * .writeByte(0x00) - * .writeByte(0x00) - * .writeByte(0x0f); - * assertEquals(16, buffer.size()); - * - * assertEquals(9223372036854775807L, buffer.readLong()); - * assertEquals(8, buffer.size()); - * - * assertEquals(15, buffer.readLong()); - * assertEquals(0, buffer.size()); - * ``` + * Removes eight bytes from this source and returns a long integer composed of it according to the big-endian order. + * + * @throws EOFException when there are not enough data to read a long value. */ fun readLong(): Long /** - * Reads and discards `byteCount` bytes from this source. Throws an [java.io.EOFException] if the - * source is exhausted before the requested bytes can be skipped. + * Reads and discards [byteCount] bytes from this source. + * + * @param byteCount amount of bytes to be skipped. + * + * @throws EOFException when the source is exhausted before the requested amount of bytes can be skipped. */ fun skip(byteCount: Long) - /** Removes all bytes from this and returns them as a byte array. */ + /** + * Removes all bytes from this source and returns them as a byte array. + */ fun readByteArray(): ByteArray - /** Removes `byteCount` bytes from this and returns them as a byte array. */ - fun readByteArray(byteCount: Long): ByteArray - /** - * Removes exactly `sink.length` bytes from this and copies them into `sink`. Throws an - * [java.io.EOFException] if the requested number of bytes cannot be read. + * Removes [byteCount] bytes from this source and returns them as a byte array. + * + * @param byteCount amount of bytes that should be read from the source. + * + * @throws IllegalArgumentException when byteCount is negative. + * @throws EOFException when the underlying source is exhausted before [byteCount] bytes of data could be read. */ - fun readFully(sink: ByteArray) + fun readByteArray(byteCount: Long): ByteArray - // TODO(filipp): fix javadoc /** - * Removes up to `sink.length` bytes from this and copies them into `sink`. Returns the number of - * bytes read, or -1 if this source is exhausted. + * Removes exactly `sink.length` bytes from this source and copies them into [sink]. + * + * @throws EOFException when the requested number of bytes cannot be read. */ - //fun read(sink: ByteArray): Int + fun readFully(sink: ByteArray) /** - * Removes up to `byteCount` bytes from this and copies them into `sink` at `offset`. Returns the + * Removes up to [byteCount] bytes from this source, copies them into [sink] starting at [offset] and returns the * number of bytes read, or -1 if this source is exhausted. + * + * @param sink the array to which data will be written from this source. + * @param offset the offset to start writing data into [sink] at, 0 by default. + * @param byteCount amount of bytes that should be written into [sink], + * size of the [sink] subarray starting at [offset] by default. + * + * @throws IndexOutOfBoundsException when a range specified by [offset] and [byteCount] + * is out of range of [sink] array indices. */ - fun read(sink: ByteArray, offset: Int = 0, byteCount: Int = sink.size): Int + fun read(sink: ByteArray, offset: Int = 0, byteCount: Int = sink.size - offset): Int /** - * Removes exactly `byteCount` bytes from this and appends them to `sink`. Throws an - * [java.io.EOFException] if the requested number of bytes cannot be read. + * Removes exactly [byteCount] bytes from this source and writes them to [sink]. + * + * @param sink the sink to which data will be written from this source. + * @param byteCount amount of bytes that should be written into [sink] + * + * @throws IllegalArgumentException when [byteCount] is negative. + * @throws EOFException when the requested number of bytes cannot be read. */ fun readFully(sink: Buffer, byteCount: Long) /** - * Removes all bytes from this and appends them to `sink`. Returns the total number of bytes - * written to `sink` which will be 0 if this is exhausted. + * Removes all bytes from this source, writes them to [sink], and returns the total number of bytes + * written to [sink]. + * + * Return 0 if this source is exhausted. + * + * @param sink the sink to which data will be written from this source. */ fun readAll(sink: RawSink): Long /** - * Returns a new `BufferedSource` that can read data from this `BufferedSource` without consuming - * it. The returned source becomes invalid once this source is next read or closed. - * - * For example, we can use `peek()` to lookahead and read the same data multiple times. - * - * ``` - * val buffer = Buffer() - * buffer.writeUtf8("abcdefghi") - * - * buffer.readUtf8(3) // returns "abc", buffer contains "defghi" - * - * val peek = buffer.peek() - * peek.readUtf8(3) // returns "def", buffer contains "defghi" - * peek.readUtf8(3) // returns "ghi", buffer contains "defghi" + * Returns a new [Source] that can read data from this source without consuming it. + * The returned source becomes invalid once this source is next read or closed. * - * buffer.readUtf8(3) // returns "def", buffer contains "ghi" - * ``` + * Peek could be used to lookahead and read the same data multiple times. */ fun peek(): Source } diff --git a/core/common/src/SourceExt.kt b/core/common/src/SourceExt.kt index 0677d509d..288a6647c 100644 --- a/core/common/src/SourceExt.kt +++ b/core/common/src/SourceExt.kt @@ -6,79 +6,27 @@ package kotlinx.io /** - * Removes two bytes from this source and returns a little-endian short. - * ``` - * Buffer buffer = new Buffer() - * .writeByte(0xff) - * .writeByte(0x7f) - * .writeByte(0x0f) - * .writeByte(0x00); - * assertEquals(4, buffer.size()); + * Removes two bytes from this source and returns a short integer composed of it according to the little-endian order. * - * assertEquals(32767, buffer.readShortLe()); - * assertEquals(2, buffer.size()); - * - * assertEquals(15, buffer.readShortLe()); - * assertEquals(0, buffer.size()); - * ``` + * @throws EOFException when there are not enough data to read a short value. */ fun Source.readShortLe(): Short { return readShort().reverseBytes() } /** - * Removes four bytes from this source and returns a little-endian int. - * ``` - * Buffer buffer = new Buffer() - * .writeByte(0xff) - * .writeByte(0xff) - * .writeByte(0xff) - * .writeByte(0x7f) - * .writeByte(0x0f) - * .writeByte(0x00) - * .writeByte(0x00) - * .writeByte(0x00); - * assertEquals(8, buffer.size()); - * - * assertEquals(2147483647, buffer.readIntLe()); - * assertEquals(4, buffer.size()); + * Removes four bytes from this source and returns an integer composed of it according to the little-endian order. * - * assertEquals(15, buffer.readIntLe()); - * assertEquals(0, buffer.size()); - * ``` + * @throws EOFException when there are not enough data to read an int value. */ fun Source.readIntLe(): Int { return readInt().reverseBytes() } /** - * Removes eight bytes from this source and returns a little-endian long. - * ``` - * Buffer buffer = new Buffer() - * .writeByte(0xff) - * .writeByte(0xff) - * .writeByte(0xff) - * .writeByte(0xff) - * .writeByte(0xff) - * .writeByte(0xff) - * .writeByte(0xff) - * .writeByte(0x7f) - * .writeByte(0x0f) - * .writeByte(0x00) - * .writeByte(0x00) - * .writeByte(0x00) - * .writeByte(0x00) - * .writeByte(0x00) - * .writeByte(0x00) - * .writeByte(0x00); - * assertEquals(16, buffer.size()); + * Removes eight bytes from this source and returns a long integer composed of it according to the little-endian order. * - * assertEquals(9223372036854775807L, buffer.readLongLe()); - * assertEquals(8, buffer.size()); - * - * assertEquals(15, buffer.readLongLe()); - * assertEquals(0, buffer.size()); - * ``` + * @throws EOFException when there are not enough data to read a long value. */ fun Source.readLongLe(): Long { return readLong().reverseBytes() @@ -89,23 +37,15 @@ internal const val OVERFLOW_DIGIT_START = Long.MIN_VALUE % 10L + 1 /** * Reads a long from this source in signed decimal form (i.e., as a string in base 10 with - * optional leading '-'). This will iterate until a non-digit character is found. - * ``` - * Buffer buffer = new Buffer() - * .writeUtf8("8675309 -123 00001"); + * optional leading `-`). * - * assertEquals(8675309L, buffer.readDecimalLong()); - * assertEquals(' ', buffer.readByte()); - * assertEquals(-123L, buffer.readDecimalLong()); - * assertEquals(' ', buffer.readByte()); - * assertEquals(1L, buffer.readDecimalLong()); - * ``` + * Source data will be consumed until the source is exhausted, the first occurrence of non-digit byte, + * or overflow happened during resulting value construction. * * @throws NumberFormatException if the found digits do not fit into a `long` or a decimal * number was not present. + * @throws EOFException if the source is exhausted before a call of this method. */ -// TODO: add tests, seems like it may throw exceptions with incorrect messages -// TODO: test overflow detection fun Source.readDecimalLong(): Long { require(1) var b = readByte() @@ -156,21 +96,14 @@ fun Source.readDecimalLong(): Long { } /** - * Reads a long form this source in hexadecimal form (i.e., as a string in base 16). This will - * iterate until a non-hexadecimal character is found. - * ``` - * Buffer buffer = new Buffer() - * .writeUtf8("ffff CAFEBABE 10"); + * Reads a long form this source in hexadecimal form (i.e., as a string in base 16). * - * assertEquals(65535L, buffer.readHexadecimalUnsignedLong()); - * assertEquals(' ', buffer.readByte()); - * assertEquals(0xcafebabeL, buffer.readHexadecimalUnsignedLong()); - * assertEquals(' ', buffer.readByte()); - * assertEquals(0x10L, buffer.readHexadecimalUnsignedLong()); - * ``` + * Source data will be consumed until the source is exhausted, the first occurrence of non-digit byte, + * or overflow happened during resulting value construction. * * @throws NumberFormatException if the found hexadecimal does not fit into a `long` or * hexadecimal was not found. + * @throws EOFException if the source is exhausted before a call of this method. */ fun Source.readHexadecimalUnsignedLong(): Long { require(1) @@ -201,35 +134,20 @@ fun Source.readHexadecimalUnsignedLong(): Long { } /** - * Returns the index of the first `b` in the buffer at or after `fromIndex`. This expands the - * buffer as necessary until `b` is found. This reads an unbounded number of bytes into the - * buffer. Returns -1 if the stream is exhausted before the requested byte is found. - * ``` - * Buffer buffer = new Buffer(); - * buffer.writeUtf8("Don't move! He can't see us if we don't move."); - * - * byte m = 'm'; - * assertEquals(6, buffer.indexOf(m)); - * assertEquals(40, buffer.indexOf(m, 12)); - * ``` - */ -// fun Source.indexOf(b: Byte, fromIndex: Long = 0): Long { -// return 0 -//} - -/** - * Returns the index of `b` if it is found in the range of `fromIndex` inclusive to `toIndex` - * exclusive. If `b` isn't found, or if `fromIndex == toIndex`, then -1 is returned. + * Returns an index of [b] first occurrence in the range of [fromIndex] inclusive to [toIndex] + * exclusive, or `-1` if [b]. * - * The scan terminates at either `toIndex` or the end of the buffer, whichever comes first. The + * The scan terminates at either [toIndex] or source's exhaustion, whichever comes first. The * maximum number of bytes scanned is `toIndex-fromIndex`. + * If [b] not found in buffered data, [toIndex] is yet to be reached and the underlying source is not yet exhausted + * then new data will be read from the underlying source into the buffer. */ fun Source.indexOf(b: Byte, fromIndex: Long = 0L, toIndex: Long = Long.MAX_VALUE): Long { require(fromIndex in 0..toIndex) if (fromIndex == toIndex) return -1L var offset = fromIndex - var peekSource = peek() + val peekSource = peek() if (!peekSource.request(offset)) { return -1L @@ -240,67 +158,4 @@ fun Source.indexOf(b: Byte, fromIndex: Long = 0L, toIndex: Long = Long.MAX_VALUE offset++ } return -1L -} - -// /** Equivalent to [indexOf(bytes, 0)][indexOf]. */ -// fun indexOf(bytes: ByteString): Long - -/** - * Returns the index of the first match for `bytes` in the buffer at or after `fromIndex`. This - * expands the buffer as necessary until `bytes` is found. This reads an unbounded number of - * bytes into the buffer. Returns -1 if the stream is exhausted before the requested bytes are - * found. - * ``` - * ByteString MOVE = ByteString.encodeUtf8("move"); - * - * Buffer buffer = new Buffer(); - * buffer.writeUtf8("Don't move! He can't see us if we don't move."); - * - * assertEquals(6, buffer.indexOf(MOVE)); - * assertEquals(40, buffer.indexOf(MOVE, 12)); - * ``` - */ -// fun indexOf(bytes: ByteString, fromIndex: Long): Long - -/** Equivalent to [indexOfElement(targetBytes, 0)][indexOfElement]. */ -// fun indexOfElement(targetBytes: ByteString): Long - -/** - * Returns the first index in this buffer that is at or after `fromIndex` and that contains any of - * the bytes in `targetBytes`. This expands the buffer as necessary until a target byte is found. - * This reads an unbounded number of bytes into the buffer. Returns -1 if the stream is exhausted - * before the requested byte is found. - * ``` - * ByteString ANY_VOWEL = ByteString.encodeUtf8("AEOIUaeoiu"); - * - * Buffer buffer = new Buffer(); - * buffer.writeUtf8("Dr. Alan Grant"); - * - * assertEquals(4, buffer.indexOfElement(ANY_VOWEL)); // 'A' in 'Alan'. - * assertEquals(11, buffer.indexOfElement(ANY_VOWEL, 9)); // 'a' in 'Grant'. - * ``` - */ -// fun indexOfElement(targetBytes: ByteString, fromIndex: Long): Long - -/** - * Returns true if the bytes at `offset` in this source equal `bytes`. This expands the buffer as - * necessary until a byte does not match, all bytes are matched, or if the stream is exhausted - * before enough bytes could determine a match. - * ``` - * ByteString simonSays = ByteString.encodeUtf8("Simon says:"); - * - * Buffer standOnOneLeg = new Buffer().writeUtf8("Simon says: Stand on one leg."); - * assertTrue(standOnOneLeg.rangeEquals(0, simonSays)); - * - * Buffer payMeMoney = new Buffer().writeUtf8("Pay me $1,000,000."); - * assertFalse(payMeMoney.rangeEquals(0, simonSays)); - * ``` - */ -// fun rangeEquals(offset: Long, bytes: ByteString): Boolean - -/** - * Returns true if `byteCount` bytes at `offset` in this source equal `bytes` at `bytesOffset`. - * This expands the buffer as necessary until a byte does not match, all bytes are matched, or if - * the stream is exhausted before enough bytes could determine a match. - */ -// fun rangeEquals(offset: Long, bytes: ByteString, bytesOffset: Int, byteCount: Int): Boolean +} \ No newline at end of file diff --git a/core/common/src/Utf8.kt b/core/common/src/Utf8.kt index 729083dbc..af0439942 100644 --- a/core/common/src/Utf8.kt +++ b/core/common/src/Utf8.kt @@ -20,7 +20,7 @@ */ /** - * Okio assumes most applications use UTF-8 exclusively, and offers optimized implementations of + * kotlinx-io assumes most applications use UTF-8 exclusively, and offers optimized implementations of * common operations on UTF-8 strings. * * @@ -78,8 +78,13 @@ import kotlin.jvm.JvmName import kotlin.jvm.JvmOverloads /** - * Returns the number of bytes used to encode the slice of `string` as UTF-8 when using - * [Sink.writeUtf8]. + * Returns the number of bytes used to encode the slice of `string` as UTF-8 when using [Sink.writeUtf8]. + * + * @param beginIndex the index of the first character to encode. + * @param endIndex the index of the character past the last character to encode. + * + * @throws IndexOutOfBoundsException when [beginIndex] or [endIndex] correspond to a range + * out of the current string bounds. */ @JvmOverloads @JvmName("size") @@ -565,18 +570,38 @@ internal inline fun ByteArray.process4Utf8Bytes( return 4 } +/** + * Encodes [codePoint] in UTF-8 and writes it to this sink. + * + * @param codePoint the codePoint to be written. + */ fun T.writeUtf8CodePoint(codePoint: Int): T { buffer.commonWriteUtf8CodePoint(codePoint) emitCompleteSegments() return this } +/** + * Encodes the characters at [beginIndex] up to [endIndex] from [string] in UTF-8 and writes it to this sink. + * + * @param string the string to be encoded. + * @param beginIndex the index of a first character to encode, 0 by default. + * @param endIndex the index of a character past to a last character to encode, `string.length` by default. + * + * @throws IndexOutOfBoundsException when [beginIndex] or [endIndex] correspond to a range + * out of the current string bounds. + */ fun T.writeUtf8(string: String, beginIndex: Int = 0, endIndex: Int = string.length): T { buffer.commonWriteUtf8(string, beginIndex, endIndex) emitCompleteSegments() return this } +/** + * Removes all bytes from this source, decodes them as UTF-8, and returns the string. + * + * Returns the empty string if this source is empty. + */ fun Source.readUtf8(): String { var req: Long = 1 while (request(req)) { @@ -585,15 +610,39 @@ fun Source.readUtf8(): String { return buffer.commonReadUtf8(buffer.size) } +/** + * Removes all bytes from this buffer, decodes them as UTF-8, and returns the string. + * + * Returns the empty string if this buffer is empty. + */ fun Buffer.readUtf8(): String { return commonReadUtf8(buffer.size) } +/** + * Removes [byteCount] bytes from this source, decodes them as UTF-8, and returns the string. + * + * @throws EOFException when the source is exhausted before reading [byteCount] bytes from it. + */ fun Source.readUtf8(byteCount: Long): String { require(byteCount) return buffer.commonReadUtf8(byteCount) } +/** + * Removes and returns a single UTF-8 code point, reading between 1 and 4 bytes as necessary. + * + * If this source is exhausted before a complete code point can be read, this throws an + * [EOFException] and consumes no input. + * + * If this source doesn't start with a properly-encoded UTF-8 code point, this method will remove + * 1 or more non-UTF-8 bytes and return the replacement character (`U+FFFD`). This covers encoding + * problems (the input is not properly-encoded UTF-8), characters out of range (beyond the + * 0x10ffff limit of Unicode), code points for UTF-16 surrogates (U+d800..U+dfff) and overlong + * encodings (such as `0xc080` for the NUL character in modified UTF-8). + * + * @throws EOFException when the source is exhausted before a complete code point can be read. + */ fun Source.readUtf8CodePoint(): Int { require(1) @@ -607,6 +656,9 @@ fun Source.readUtf8CodePoint(): Int { return buffer.commonReadUtf8CodePoint() } +/** + * @see Source.readUtf8CodePoint + */ fun Buffer.readUtf8CodePoint(): Int { return buffer.commonReadUtf8CodePoint() } @@ -614,30 +666,9 @@ fun Buffer.readUtf8CodePoint(): Int { /** * Removes and returns characters up to but not including the next line break. A line break is * either `"\n"` or `"\r\n"`; these characters are not included in the result. - * ``` - * Buffer buffer = new Buffer() - * .writeUtf8("I'm a hacker!\n") - * .writeUtf8("That's what I said: you're a nerd.\n") - * .writeUtf8("I prefer to be called a hacker!\n"); - * assertEquals(81, buffer.size()); - * - * assertEquals("I'm a hacker!", buffer.readUtf8Line()); - * assertEquals(67, buffer.size()); - * - * assertEquals("That's what I said: you're a nerd.", buffer.readUtf8Line()); - * assertEquals(32, buffer.size()); - * - * assertEquals("I prefer to be called a hacker!", buffer.readUtf8Line()); - * assertEquals(0, buffer.size()); - * - * assertEquals(null, buffer.readUtf8Line()); - * assertEquals(0, buffer.size()); - * ``` * - * **On the end of the stream this method returns null,** just like [java.io.BufferedReader]. If - * the source doesn't end with a line break then an implicit line break is assumed. Null is - * returned once the source is exhausted. Use this for human-generated data, where a trailing - * line break is optional. + * On the end of the stream this method returns null. If the source doesn't end with a line break then + * an implicit line break is assumed. Null is returned once the source is exhausted. */ fun Source.readUtf8Line(): String? { if (!request(1)) return null @@ -664,25 +695,20 @@ fun Source.readUtf8Line(): String? { } /** - * Like [readUtf8LineStrict], except this allows the caller to specify the longest allowed match. - * Use this to protect against streams that may not include `"\n"` or `"\r\n"`. + * Removes and returns characters up to but not including the next line break, throwing + * [EOFException] if a line break was not encountered. A line break is either `"\n"` or `"\r\n"`; + * these characters are not included in the result. * - * The returned string will have at most `limit` UTF-8 bytes, and the maximum number of bytes - * scanned is `limit + 2`. If `limit == 0` this will always throw an `EOFException` because no + * The returned string will have at most [limit] UTF-8 bytes, and the maximum number of bytes + * scanned is `limit + 2`. If `limit == 0` this will always throw an [EOFException] because no * bytes will be scanned. * - * This method is safe. No bytes are discarded if the match fails, and the caller is free to try - * another match: - * ``` - * Buffer buffer = new Buffer(); - * buffer.writeUtf8("12345\r\n"); + * No bytes are discarded if the match fails. * - * // This will throw! There must be \r\n or \n at the limit or before it. - * buffer.readUtf8LineStrict(4); + * @param limit the maximum UTF-8 bytes constituting a returned string. * - * // No bytes have been consumed so the caller can retry. - * assertEquals("12345", buffer.readUtf8LineStrict(5)); - * ``` + * @throws EOFException when the source does not contain a string consisting with at most [limit] bytes followed by + * line break characters. */ fun Source.readUtf8LineStrict(limit: Long = Long.MAX_VALUE): String { if (!request(1)) throw EOFException() diff --git a/core/common/src/internal/-Buffer.kt b/core/common/src/internal/-Buffer.kt index 3a27a1598..63518fbc6 100644 --- a/core/common/src/internal/-Buffer.kt +++ b/core/common/src/internal/-Buffer.kt @@ -827,9 +827,10 @@ internal inline fun Buffer.commonReadUtf8CodePoint(): Int { } internal inline fun Buffer.commonWriteUtf8(string: String, beginIndex: Int, endIndex: Int): Buffer { - require(beginIndex >= 0) { "beginIndex < 0: $beginIndex" } - require(endIndex >= beginIndex) { "endIndex < beginIndex: $endIndex < $beginIndex" } - require(endIndex <= string.length) { "endIndex > string.length: $endIndex > ${string.length}" } + checkOffsetAndCount(string.length.toLong(), beginIndex.toLong(), (endIndex - beginIndex).toLong()) + //require(beginIndex >= 0) { "beginIndex < 0: $beginIndex" } + //require(endIndex >= beginIndex) { "endIndex < beginIndex: $endIndex < $beginIndex" } + //require(endIndex <= string.length) { "endIndex > string.length: $endIndex > ${string.length}" } // Transcode a UTF-16 Java String to UTF-8 bytes. var i = beginIndex @@ -1088,6 +1089,7 @@ internal inline fun Buffer.commonWrite(source: Buffer, byteCount: Long) { // yielding sink [51%, 91%, 30%] and source [62%, 82%]. require(source !== this) { "source == this" } + require(byteCount >= 0) { "byteCount ($byteCount) should not be negative." } checkOffsetAndCount(source.size, 0, byteCount) var remainingByteCount = byteCount diff --git a/core/common/src/internal/-RealBufferedSink.kt b/core/common/src/internal/-RealBufferedSink.kt index 7b188fd5f..06ac09c8b 100644 --- a/core/common/src/internal/-RealBufferedSink.kt +++ b/core/common/src/internal/-RealBufferedSink.kt @@ -27,6 +27,7 @@ package kotlinx.io.internal import kotlinx.io.* internal inline fun RealSink.commonWrite(source: Buffer, byteCount: Long) { + require(byteCount >= 0) { "byteCount ($byteCount) should not be negative." } check(!closed) { "closed" } buffer.write(source, byteCount) emitCompleteSegments() @@ -65,6 +66,7 @@ internal inline fun RealSink.commonWrite( offset: Int, byteCount: Int ): Sink { + checkOffsetAndCount(source.size.toLong(), offset.toLong(), byteCount.toLong()) check(!closed) { "closed" } buffer.write(source, offset, byteCount) return emitCompleteSegments() @@ -82,6 +84,7 @@ internal inline fun RealSink.commonWriteAll(source: RawSource): Long { } internal inline fun RealSink.commonWrite(source: RawSource, byteCount: Long): Sink { + require(byteCount >= 0) { "byteCount ($byteCount) should not be negative."} var remainingByteCount = byteCount while (remainingByteCount > 0L) { val read = source.read(buffer, remainingByteCount) diff --git a/core/common/test/AbstractSinkTest.kt b/core/common/test/AbstractSinkTest.kt index f1977b389..68e262af8 100644 --- a/core/common/test/AbstractSinkTest.kt +++ b/core/common/test/AbstractSinkTest.kt @@ -21,9 +21,7 @@ package kotlinx.io -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertFailsWith +import kotlin.test.* class BufferSinkTest : AbstractSinkTest(SinkFactory.BUFFER) class RealSinkTest : AbstractSinkTest(SinkFactory.REAL_BUFFERED_SINK) @@ -34,12 +32,46 @@ abstract class AbstractSinkTest internal constructor( private val data: Buffer = Buffer() private val sink: Sink = factory.create(data) + @Test fun writeByteArray() { + val source = byteArrayOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9) + sink.write(source) + sink.flush() + assertEquals("[hex=00010203040506070809]", data.toString()) + data.clear() + + sink.write(source, 3) + sink.flush() + assertEquals("[hex=03040506070809]", data.toString()) + data.clear() + + sink.write(source, 0, 3) + sink.flush() + assertEquals("[hex=000102]", data.toString()) + data.clear() + + assertFailsWith { + sink.write(source, -1, 1) + } + assertEquals(0, data.size) + + assertFailsWith { + sink.write(source, 1, source.size) + } + assertEquals(0, data.size) + } + @Test fun writeNothing() { sink.writeUtf8("") sink.flush() assertEquals(0, data.size) } + @Test fun writeByte() { + sink.writeByte(0xba) + sink.flush() + assertEquals("[hex=ba]", data.toString()) + } + @Test fun writeBytes() { sink.writeByte(0xab) sink.writeByte(0xcd) @@ -58,6 +90,12 @@ abstract class AbstractSinkTest internal constructor( } @Test fun writeShort() { + sink.writeShort(0xab01) + sink.flush() + assertEquals("[hex=ab01]", data.toString()) + } + + @Test fun writeShorts() { sink.writeShort(0xabcd) sink.writeShort(0x4321) sink.flush() @@ -72,6 +110,12 @@ abstract class AbstractSinkTest internal constructor( } @Test fun writeInt() { + sink.writeInt(0x197760) + sink.flush() + assertEquals("[hex=00197760]", data.toString()) + } + + @Test fun writeInts() { sink.writeInt(-0x543210ff) sink.writeInt(-0x789abcdf) sink.flush() @@ -106,6 +150,12 @@ abstract class AbstractSinkTest internal constructor( } @Test fun writeLong() { + sink.writeLong(0x123456789abcdef0L) + sink.flush() + assertEquals("[hex=123456789abcdef0]", data.toString()) + } + + @Test fun writeLongs() { sink.writeLong(-0x543210fe789abcdfL) sink.writeLong(-0x350145414f4ea400L) sink.flush() @@ -128,6 +178,12 @@ abstract class AbstractSinkTest internal constructor( assertEquals("abcdef", data.readUtf8()) } + @Test fun writeAllExhausted() { + val source = Buffer() + assertEquals(0, sink.writeAll(source)) + assertEquals(0, source.size) + } + @Test fun writeSource() { val source = Buffer().writeUtf8("abcdef") @@ -163,6 +219,27 @@ abstract class AbstractSinkTest internal constructor( assertEquals("abcd", data.readUtf8()) } + @Ignore // TODO: its unclear how write should behave + @Test fun writeBufferThrowsIOOBE() { + val source: Buffer = Buffer().writeUtf8("abcd") + + assertFailsWith { + sink.write(source, 8) + } + + // Ensure that whatever was available was correctly written. + sink.flush() + assertEquals("abcd", data.readUtf8()) + } + + @Test fun writeSourceWithNegativeBytesCount() { + val source = Buffer().writeByte(0) + + assertFailsWith { + sink.write(source, -1L) + } + } + @Test fun writeSourceWithZeroIsNoOp() { // This test ensures that a zero byte count never calls through to read the source. It may be // tied to something like a socket which will potentially block trying to read a segment when @@ -176,12 +253,6 @@ abstract class AbstractSinkTest internal constructor( assertEquals(0, data.size) } - @Test fun writeAllExhausted() { - val source = Buffer() - assertEquals(0, sink.writeAll(source)) - assertEquals(0, source.size) - } - @Test fun closeEmitsBufferedBytes() { sink.writeByte('a'.code) sink.close() @@ -258,4 +329,22 @@ abstract class AbstractSinkTest internal constructor( val actual = data.readUtf8() assertEquals(expected, actual, "$value expected $expected but was $actual") } + + @Test fun writeUtf8FromIndex() { + sink.writeUtf8("12345", 3) + sink.emit() + assertEquals("45", data.readUtf8()) + } + + @Test fun writeUtf8FromRange() { + sink.writeUtf8("0123456789", 4, 7) + sink.emit() + assertEquals("456", data.readUtf8()) + } + + @Test fun writeUtf8WithInvalidIndexes() { + assertFailsWith { sink.writeUtf8("hello", -1) } + assertFailsWith { sink.writeUtf8("hello", 0, 6) } + assertFailsWith { sink.writeUtf8("hello", 6) } + } } diff --git a/core/common/test/AbstractSourceTest.kt b/core/common/test/AbstractSourceTest.kt index 6c48f0f44..60065bd53 100644 --- a/core/common/test/AbstractSourceTest.kt +++ b/core/common/test/AbstractSourceTest.kt @@ -46,6 +46,10 @@ abstract class AbstractBufferedSourceTest internal constructor( source = pipe.source } + @Test fun exhausted() { + assertTrue(source.exhausted()) + } + @Test fun readBytes() { sink.write(byteArrayOf(0xab.toByte(), 0xcd.toByte())) sink.emit() @@ -318,6 +322,19 @@ abstract class AbstractBufferedSourceTest internal constructor( assertEquals("Hi", sink.readUtf8()) } + @Test fun readFullyWithNegativeByteCount() { + val sink = Buffer() + assertFailsWith { + source.readFully(sink, -1) + } + } + + @Test fun readFullyZeroBytes() { + val sink = Buffer() + source.readFully(sink, 0) + assertEquals(0, sink.size) + } + @Test fun readFullyByteArray() { val data = Buffer() data.writeUtf8("Hello").writeUtf8("e".repeat(Segment.SIZE)) @@ -407,6 +424,43 @@ abstract class AbstractBufferedSourceTest internal constructor( } } + @Test fun readIntoByteArrayOffset() { + sink.writeUtf8("abcd") + sink.emit() + + val sink = ByteArray(7) + val read = source.read(sink, 4) + if (factory.isOneByteAtATime) { + assertEquals(1, read.toLong()) + val expected = byteArrayOf(0, 0, 0, 0, 'a'.code.toByte(), 0, 0) + assertArrayEquals(expected, sink) + } else { + assertEquals(3, read.toLong()) + val expected = + byteArrayOf(0, 0, 0, 0, 'a'.code.toByte(), 'b'.code.toByte(), 'c'.code.toByte()) + assertArrayEquals(expected, sink) + } + } + + @Test fun readIntoByteArrayWithInvalidArguments() { + sink.write(ByteArray(10)) + sink.emit() + + val sink = ByteArray(4) + + assertFailsWith { + source.read(sink, 4, 1) + } + + assertFailsWith { + source.read(sink, 1, 4) + } + + assertFailsWith { + source.read(sink, -1, 2) + } + } + @Test fun readByteArray() { val string = "abcd" + "e".repeat(Segment.SIZE) sink.writeUtf8(string) @@ -431,29 +485,6 @@ abstract class AbstractBufferedSourceTest internal constructor( assertEquals("abc", source.readUtf8()) // The read shouldn't consume any data. } -// @Test fun readByteString() { -// sink.writeUtf8("abcd").writeUtf8("e".repeat(Segment.SIZE)) -// sink.emit() -// assertEquals("abcd" + "e".repeat(Segment.SIZE), source.readByteString().utf8()) -// } -// -// @Test fun readByteStringPartial() { -// sink.writeUtf8("abcd").writeUtf8("e".repeat(Segment.SIZE)) -// sink.emit() -// assertEquals("abc", source.readByteString(3).utf8()) -// assertEquals("d", source.readUtf8(1)) -// } -// -// @Test fun readByteStringTooShortThrows() { -// sink.writeUtf8("abc") -// sink.emit() -// assertFailsWith { -// source.readByteString(4) -// } -// -// assertEquals("abc", source.readUtf8()) // The read shouldn't consume any data. -// } - @Test fun readUtf8SpansSegments() { sink.writeUtf8("a".repeat(Segment.SIZE * 2)) sink.emit() @@ -823,6 +854,23 @@ abstract class AbstractBufferedSourceTest internal constructor( assertEquals(0x10ffff, source.readUtf8CodePoint().toLong()) } + @Test fun codePointsFromExhaustedSource() { + sink.writeByte(0xdf) // a second byte is missing + sink.emit() + assertFailsWith { source.readUtf8CodePoint() } + assertEquals(1, source.readByteArray().size) + + sink.writeByte(0xe2).writeByte(0x98) // a third byte is missing + sink.emit() + assertFailsWith { source.readUtf8CodePoint() } + assertEquals(2, source.readByteArray().size) + + sink.writeByte(0xf0).writeByte(0x9f).writeByte(0x92) // a forth byte is missing + sink.emit() + assertFailsWith { source.readUtf8CodePoint() } + assertEquals(3, source.readByteArray().size) + } + @Test fun decimalStringWithManyLeadingZeros() { assertLongDecimalString("00000000000000001", 1) assertLongDecimalString("00000000000000009223372036854775807", Long.MAX_VALUE) diff --git a/core/jvm/src/Buffer.kt b/core/jvm/src/Buffer.kt index ca7be2a6b..db866c2dc 100644 --- a/core/jvm/src/Buffer.kt +++ b/core/jvm/src/Buffer.kt @@ -297,13 +297,13 @@ actual class Buffer : Source, Sink, Cloneable, ByteChannel { actual override fun write(source: RawSource, byteCount: Long): Buffer = commonWrite(source, byteCount) - actual override fun writeByte(b: Int): Buffer = commonWriteByte(b) + actual override fun writeByte(byte: Int): Buffer = commonWriteByte(byte) - actual override fun writeShort(s: Int): Buffer = commonWriteShort(s) + actual override fun writeShort(short: Int): Buffer = commonWriteShort(short) - actual override fun writeInt(i: Int): Buffer = commonWriteInt(i) + actual override fun writeInt(int: Int): Buffer = commonWriteInt(int) - actual override fun writeLong(v: Long): Buffer = commonWriteLong(v) + actual override fun writeLong(long: Long): Buffer = commonWriteLong(long) internal actual fun writableSegment(minimumCapacity: Int): Segment = commonWritableSegment(minimumCapacity) diff --git a/core/jvm/src/RealSink.kt b/core/jvm/src/RealSink.kt index 35b553591..a4e34e8d2 100644 --- a/core/jvm/src/RealSink.kt +++ b/core/jvm/src/RealSink.kt @@ -50,10 +50,10 @@ internal actual class RealSink actual constructor( override fun writeAll(source: RawSource) = commonWriteAll(source) override fun write(source: RawSource, byteCount: Long): Sink = commonWrite(source, byteCount) - override fun writeByte(b: Int) = commonWriteByte(b) - override fun writeShort(s: Int) = commonWriteShort(s) - override fun writeInt(i: Int) = commonWriteInt(i) - override fun writeLong(v: Long) = commonWriteLong(v) + override fun writeByte(byte: Int) = commonWriteByte(byte) + override fun writeShort(short: Int) = commonWriteShort(short) + override fun writeInt(int: Int) = commonWriteInt(int) + override fun writeLong(long: Long) = commonWriteLong(long) override fun emitCompleteSegments() = commonEmitCompleteSegments() override fun emit() = commonEmit() diff --git a/core/jvm/src/Sink.kt b/core/jvm/src/Sink.kt index 4fc9335fa..bedf9edcb 100644 --- a/core/jvm/src/Sink.kt +++ b/core/jvm/src/Sink.kt @@ -38,16 +38,16 @@ actual sealed interface Sink : RawSink, WritableByteChannel { actual fun write(source: RawSource, byteCount: Long): Sink @Throws(IOException::class) - actual fun writeByte(b: Int): Sink + actual fun writeByte(byte: Int): Sink @Throws(IOException::class) - actual fun writeShort(s: Int): Sink + actual fun writeShort(short: Int): Sink @Throws(IOException::class) - actual fun writeInt(i: Int): Sink + actual fun writeInt(int: Int): Sink @Throws(IOException::class) - actual fun writeLong(v: Long): Sink + actual fun writeLong(long: Long): Sink @Throws(IOException::class) actual override fun flush() diff --git a/core/native/src/Buffer.kt b/core/native/src/Buffer.kt index 2f8bdcb04..dd1528eda 100644 --- a/core/native/src/Buffer.kt +++ b/core/native/src/Buffer.kt @@ -95,13 +95,13 @@ actual class Buffer : Source, Sink { actual override fun write(source: RawSource, byteCount: Long): Buffer = commonWrite(source, byteCount) - actual override fun writeByte(b: Int): Buffer = commonWriteByte(b) + actual override fun writeByte(byte: Int): Buffer = commonWriteByte(byte) - actual override fun writeShort(s: Int): Buffer = commonWriteShort(s) + actual override fun writeShort(short: Int): Buffer = commonWriteShort(short) - actual override fun writeInt(i: Int): Buffer = commonWriteInt(i) + actual override fun writeInt(int: Int): Buffer = commonWriteInt(int) - actual override fun writeLong(v: Long): Buffer = commonWriteLong(v) + actual override fun writeLong(long: Long): Buffer = commonWriteLong(long) override fun write(source: Buffer, byteCount: Long): Unit = commonWrite(source, byteCount) diff --git a/core/native/src/RealSink.kt b/core/native/src/RealSink.kt index 2174a41f6..b90b2121b 100644 --- a/core/native/src/RealSink.kt +++ b/core/native/src/RealSink.kt @@ -36,10 +36,10 @@ internal actual class RealSink actual constructor( override fun writeAll(source: RawSource) = commonWriteAll(source) override fun write(source: RawSource, byteCount: Long): Sink = commonWrite(source, byteCount) - override fun writeByte(b: Int) = commonWriteByte(b) - override fun writeShort(s: Int) = commonWriteShort(s) - override fun writeInt(i: Int) = commonWriteInt(i) - override fun writeLong(v: Long) = commonWriteLong(v) + override fun writeByte(byte: Int) = commonWriteByte(byte) + override fun writeShort(short: Int) = commonWriteShort(short) + override fun writeInt(int: Int) = commonWriteInt(int) + override fun writeLong(long: Long) = commonWriteLong(long) override fun emitCompleteSegments() = commonEmitCompleteSegments() override fun emit() = commonEmit() override fun flush() = commonFlush() diff --git a/core/native/src/Sink.kt b/core/native/src/Sink.kt index 96ca2fde2..6bf593a53 100644 --- a/core/native/src/Sink.kt +++ b/core/native/src/Sink.kt @@ -29,13 +29,13 @@ actual sealed interface Sink : RawSink { actual fun write(source: RawSource, byteCount: Long): Sink - actual fun writeByte(b: Int): Sink + actual fun writeByte(byte: Int): Sink - actual fun writeShort(s: Int): Sink + actual fun writeShort(short: Int): Sink - actual fun writeInt(i: Int): Sink + actual fun writeInt(int: Int): Sink - actual fun writeLong(v: Long): Sink + actual fun writeLong(long: Long): Sink actual fun emit(): Sink From 78bc776a57d8f8b6a1d8d0b31b27eccf08cd585b Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Thu, 1 Jun 2023 14:37:19 +0200 Subject: [PATCH 09/83] Add more JVM-specific tests --- core/api/kotlinx-io-core.api | 3 - core/common/src/Buffer.kt | 61 +++++--- core/common/test/BufferCommonTest.kt | 52 +++++++ core/jvm/src/Buffer.kt | 5 - core/jvm/src/JvmCore.kt | 3 - core/jvm/test/AbstractSinkTestJVM.kt | 114 ++++++++++++++ core/jvm/test/AbstractSourceTestJVM.kt | 191 +++++++++++++++++++++++ core/jvm/test/BufferTest.kt | 208 +++++++++++++++++++++++++ core/jvm/test/JvmPlatformTest.kt | 130 ++++++++++++++++ core/jvm/test/NioTest.kt | 157 +++++++++++++++++++ core/jvm/test/{ultil.kt => utilJVM.kt} | 10 ++ core/native/src/Buffer.kt | 8 +- 12 files changed, 905 insertions(+), 37 deletions(-) create mode 100644 core/jvm/test/AbstractSinkTestJVM.kt create mode 100644 core/jvm/test/AbstractSourceTestJVM.kt create mode 100644 core/jvm/test/BufferTest.kt create mode 100644 core/jvm/test/JvmPlatformTest.kt create mode 100644 core/jvm/test/NioTest.kt rename core/jvm/test/{ultil.kt => utilJVM.kt} (60%) diff --git a/core/api/kotlinx-io-core.api b/core/api/kotlinx-io-core.api index fb63f3c43..a52b8bcfe 100644 --- a/core/api/kotlinx-io-core.api +++ b/core/api/kotlinx-io-core.api @@ -10,10 +10,8 @@ public final class kotlinx/io/Buffer : java/lang/Cloneable, java/nio/channels/By public final fun copyTo (Ljava/io/OutputStream;)Lkotlinx/io/Buffer; public final fun copyTo (Ljava/io/OutputStream;J)Lkotlinx/io/Buffer; public final fun copyTo (Ljava/io/OutputStream;JJ)Lkotlinx/io/Buffer; - public final fun copyTo (Lkotlinx/io/Buffer;J)Lkotlinx/io/Buffer; public final fun copyTo (Lkotlinx/io/Buffer;JJ)Lkotlinx/io/Buffer; public static synthetic fun copyTo$default (Lkotlinx/io/Buffer;Ljava/io/OutputStream;JJILjava/lang/Object;)Lkotlinx/io/Buffer; - public static synthetic fun copyTo$default (Lkotlinx/io/Buffer;Lkotlinx/io/Buffer;JILjava/lang/Object;)Lkotlinx/io/Buffer; public static synthetic fun copyTo$default (Lkotlinx/io/Buffer;Lkotlinx/io/Buffer;JJILjava/lang/Object;)Lkotlinx/io/Buffer; public fun emit ()Lkotlinx/io/Buffer; public synthetic fun emit ()Lkotlinx/io/Sink; @@ -76,7 +74,6 @@ public final class kotlinx/io/CoreKt { } public final class kotlinx/io/JvmCoreKt { - public static final fun appendingSink (Ljava/io/File;)Lkotlinx/io/RawSink; public static final fun sink (Ljava/io/File;Z)Lkotlinx/io/RawSink; public static final fun sink (Ljava/io/OutputStream;)Lkotlinx/io/RawSink; public static final fun sink (Ljava/net/Socket;)Lkotlinx/io/RawSink; diff --git a/core/common/src/Buffer.kt b/core/common/src/Buffer.kt index 99de4fbdc..065001b43 100644 --- a/core/common/src/Buffer.kt +++ b/core/common/src/Buffer.kt @@ -23,19 +23,21 @@ package kotlinx.io /** * A collection of bytes in memory. * - * **Moving data from one buffer to another is fast.** Instead of copying bytes from one place in - * memory to another, this class just changes ownership of the underlying byte arrays. + * The buffer can be viewed as an unbound queue whose size grows with the data being written + * and shrinks with data being consumed. Internally, the buffer consists of data segments and buffer's capacity + * grows and shrinks in units of data segments instead of individual bytes. * - * **This buffer grows with your data.** Just like ArrayList, each buffer starts small. It consumes - * only the memory it needs to. + * The buffer was designed to reduce memory allocations when possible. Instead of copying bytes + * from one place in memory to another, this class just changes ownership of the underlying data segments. * - * **This buffer pools its byte arrays.** When you allocate a byte array in Java, the runtime must - * zero-fill the requested array before returning it to you. Even if you're going to write over that - * space anyway. This class avoids zero-fill and GC churn by pooling byte arrays. + * To reduce allocations and speed up buffer's extension, it may use data segments pooling. */ expect class Buffer() : Source, Sink { internal var head: Segment? + /** + * Amount of bytes accessible for read from this buffer. + */ var size: Long internal set @@ -45,20 +47,19 @@ expect class Buffer() : Source, Sink { override fun emit(): Buffer - /** Copy `byteCount` bytes from this, starting at `offset`, to `out`. */ - fun copyTo( - out: Buffer, - offset: Long = 0L, - byteCount: Long - ): Buffer - /** - * Overload of [copyTo] with byteCount = size - offset, work around for - * https://youtrack.jetbrains.com/issue/KT-30847 + * Copy [byteCount] bytes from this buffer, starting at [offset], to [out] buffer. + * + * @param out the destination buffer to copy data into. + * @param offset the offset to the first byte of data in this buffer to start copying from. + * @param byteCount amount of bytes to copy. + * + * @throws IndexOutOfBoundsException when [offset] and [byteCount] correspond to a range out of this buffer bounds. */ fun copyTo( out: Buffer, - offset: Long = 0L + offset: Long = 0L, + byteCount: Long = size - offset ): Buffer /** @@ -67,16 +68,30 @@ expect class Buffer() : Source, Sink { */ fun completeSegmentByteCount(): Long - /** Returns the byte at `pos`. */ + /** + * Returns the byte at [pos]. + * + * Use of this method may expose significant performance penalties and it's not recommended to use it + * for sequential access to a range of bytes within the buffer. + * + * @throws IndexOutOfBoundsException when [pos] is out of this buffer's bounds. + */ operator fun get(pos: Long): Byte /** - * Discards all bytes in this buffer. Calling this method when you're done with a buffer will - * return its segments to the pool. + * Discards all bytes in this buffer. + * + * Call to this method is equivalent to [skip] with `byteCount = size`. */ fun clear() - /** Discards `byteCount` bytes from the head of this buffer. */ + // TODO: figure out what this method may actually throw + /** + * Discards [byteCount]` bytes from the head of this buffer. + * + * @throws IllegalArgumentException when [byteCount] is negative. + * @throws ??? when [byteCount] exceeds buffer's [size]. + */ override fun skip(byteCount: Long) /** @@ -97,6 +112,8 @@ expect class Buffer() : Source, Sink { override fun writeLong(long: Long): Buffer - /** Returns a deep copy of this buffer. */ + /** + * Returns a deep copy of this buffer. + */ fun copy(): Buffer } diff --git a/core/common/test/BufferCommonTest.kt b/core/common/test/BufferCommonTest.kt index 8de65980b..6936cf134 100644 --- a/core/common/test/BufferCommonTest.kt +++ b/core/common/test/BufferCommonTest.kt @@ -23,6 +23,8 @@ package kotlinx.io import kotlin.test.Test import kotlin.test.assertEquals +private const val SEGMENT_SIZE = Segment.SIZE + class BufferCommonTest { @Test fun completeSegmentByteCountOnEmptyBuffer() { val buffer = Buffer() @@ -40,4 +42,54 @@ class BufferCommonTest { buffer.writeUtf8("a".repeat(Segment.SIZE * 4 - 10)) assertEquals((Segment.SIZE * 3).toLong(), buffer.completeSegmentByteCount()) } + + @Test + fun cloneDoesNotObserveWritesToOriginal() { + val original = Buffer() + val clone: Buffer = original.copy() + original.writeUtf8("abc") + assertEquals(0, clone.size) + } + + @Test + fun cloneDoesNotObserveReadsFromOriginal() { + val original = Buffer() + original.writeUtf8("abc") + val clone: Buffer = original.copy() + assertEquals("abc", original.readUtf8(3)) + assertEquals(3, clone.size) + assertEquals("ab", clone.readUtf8(2)) + } + + @Test + fun originalDoesNotObserveWritesToClone() { + val original = Buffer() + val clone: Buffer = original.copy() + clone.writeUtf8("abc") + assertEquals(0, original.size) + } + + @Test + fun originalDoesNotObserveReadsFromClone() { + val original = Buffer() + original.writeUtf8("abc") + val clone: Buffer = original.copy() + assertEquals("abc", clone.readUtf8(3)) + assertEquals(3, original.size) + assertEquals("ab", original.readUtf8(2)) + } + + @Test + fun cloneMultipleSegments() { + val original = Buffer() + original.writeUtf8("a".repeat(SEGMENT_SIZE * 3)) + val clone: Buffer = original.copy() + original.writeUtf8("b".repeat(SEGMENT_SIZE * 3)) + clone.writeUtf8("c".repeat(SEGMENT_SIZE * 3)) + + assertEquals("a".repeat(SEGMENT_SIZE * 3) + "b".repeat(SEGMENT_SIZE * 3), + original.readUtf8((SEGMENT_SIZE * 6).toLong())) + assertEquals("a".repeat( SEGMENT_SIZE * 3) + "c".repeat(SEGMENT_SIZE * 3), + clone.readUtf8((SEGMENT_SIZE * 6).toLong())) + } } diff --git a/core/jvm/src/Buffer.kt b/core/jvm/src/Buffer.kt index db866c2dc..dea417f3c 100644 --- a/core/jvm/src/Buffer.kt +++ b/core/jvm/src/Buffer.kt @@ -140,11 +140,6 @@ actual class Buffer : Source, Sink, Cloneable, ByteChannel { byteCount: Long ): Buffer = commonCopyTo(out, offset, byteCount) - actual fun copyTo( - out: Buffer, - offset: Long - ): Buffer = copyTo(out, offset, size - offset) - /** Write `byteCount` bytes from this to `out`. */ @Throws(IOException::class) @JvmOverloads diff --git a/core/jvm/src/JvmCore.kt b/core/jvm/src/JvmCore.kt index 179965f0d..bf2a80939 100644 --- a/core/jvm/src/JvmCore.kt +++ b/core/jvm/src/JvmCore.kt @@ -145,9 +145,6 @@ fun Socket.source(): RawSource { /** Returns a sink that writes to `file`. */ fun File.sink(append: Boolean = false): RawSink = FileOutputStream(this, append).sink() -/** Returns a sink that writes to `file`. */ -fun File.appendingSink(): RawSink = FileOutputStream(this, true).sink() - /** Returns a source that reads from `file`. */ fun File.source(): RawSource = InputStreamSource(inputStream()) diff --git a/core/jvm/test/AbstractSinkTestJVM.kt b/core/jvm/test/AbstractSinkTestJVM.kt new file mode 100644 index 000000000..0d97a28d8 --- /dev/null +++ b/core/jvm/test/AbstractSinkTestJVM.kt @@ -0,0 +1,114 @@ +/* + * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. + */ + +/* + * Copyright (C) 2014 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 kotlinx.io + +import org.junit.jupiter.api.Test +import java.io.OutputStream +import java.nio.ByteBuffer +import java.nio.charset.Charset +import kotlin.test.* +import kotlin.text.Charsets.UTF_8 + +private const val SEGMENT_SIZE = Segment.SIZE + +class BufferSinkTestJVM : AbstractSinkTestJVM(SinkFactory.BUFFER) +class RealSinkTestJVM : AbstractSinkTestJVM(SinkFactory.REAL_BUFFERED_SINK) + +abstract class AbstractSinkTestJVM internal constructor(factory: SinkFactory) { + private val data: Buffer = Buffer() + private val sink: Sink = factory.create(data) + + @Test + @Throws(Exception::class) + fun outputStream() { + val out: OutputStream = sink.outputStream() + out.write('a'.code) + out.write("b".repeat(9998).toByteArray(UTF_8)) + out.write('c'.code) + out.flush() + assertEquals(("a" + "b".repeat(9998)) + "c", data.readUtf8()) + } + + @Test + @Throws(Exception::class) + fun outputStreamBounds() { + val out: OutputStream = sink.outputStream() + assertFailsWith { + out.write(ByteArray(100), 50, 51) + } + } + + @Test + @Throws(java.lang.Exception::class) + fun writeNioBuffer() { + val expected = "abcdefg" + val nioByteBuffer: ByteBuffer = ByteBuffer.allocate(1024) + nioByteBuffer.put("abcdefg".toByteArray(UTF_8)) + nioByteBuffer.flip() // Cast necessary for Java 8. + val byteCount: Int = sink.write(nioByteBuffer) + assertEquals(expected.length, byteCount) + assertEquals(expected.length, nioByteBuffer.position()) + assertEquals(expected.length, nioByteBuffer.limit()) + sink.flush() + assertEquals(expected, data.readUtf8()) + } + + @Test + @Throws(java.lang.Exception::class) + fun writeLargeNioBufferWritesAllData() { + val expected: String = "a".repeat(SEGMENT_SIZE * 3) + val nioByteBuffer: ByteBuffer = ByteBuffer.allocate(SEGMENT_SIZE * 4) + nioByteBuffer.put("a".repeat(SEGMENT_SIZE * 3).toByteArray(UTF_8)) + nioByteBuffer.flip() // Cast necessary for Java 8. + val byteCount: Int = sink.write(nioByteBuffer) + assertEquals(expected.length, byteCount) + assertEquals(expected.length, nioByteBuffer.position()) + assertEquals(expected.length, nioByteBuffer.limit()) + sink.flush() + assertEquals(expected, data.readUtf8()) + } + + @Test + @Throws(IOException::class) + fun writeStringWithCharset() { + sink.writeString("təˈranəˌsôr", Charset.forName("utf-32be")) + sink.flush() + val expected = "0000007400000259000002c800000072000000610000006e00000259000002cc00000073000000f400000072" + assertArrayEquals(expected.decodeHex(), data.readByteArray()) + } + + @Test + @Throws(IOException::class) + fun writeSubstringWithCharset() { + sink.writeString("təˈranəˌsôr", Charset.forName("utf-32be"), 3, 7) + sink.flush() + assertArrayEquals("00000072000000610000006e00000259".decodeHex(), data.readByteArray()) + } + + @Test + @Throws(IOException::class) + fun writeUtf8SubstringWithCharset() { + sink.writeString("təˈranəˌsôr", Charset.forName("utf-8"), 3, 7) + sink.flush() + assertArrayEquals("ranə".toByteArray(UTF_8), data.readByteArray()) + } +} \ No newline at end of file diff --git a/core/jvm/test/AbstractSourceTestJVM.kt b/core/jvm/test/AbstractSourceTestJVM.kt new file mode 100644 index 000000000..e43d16c3f --- /dev/null +++ b/core/jvm/test/AbstractSourceTestJVM.kt @@ -0,0 +1,191 @@ +/* + * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. + */ + +/* + * Copyright (C) 2014 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 kotlinx.io + +import org.junit.jupiter.api.Test +import java.io.InputStream +import java.nio.Buffer +import java.nio.ByteBuffer +import java.nio.charset.Charset +import kotlin.test.* + +private const val SEGMENT_SIZE = Segment.SIZE + +class BufferSourceTestJVM : AbstractSourceTestJVM(SourceFactory.BUFFER) +class RealBufferedSourceTestJVM : AbstractSourceTestJVM(SourceFactory.REAL_BUFFERED_SOURCE) +class OneByteAtATimeBufferedSourceTestJVM : AbstractSourceTestJVM(SourceFactory.ONE_BYTE_AT_A_TIME_BUFFERED_SOURCE) +class OneByteAtATimeBufferTestJVM : AbstractSourceTestJVM(SourceFactory.ONE_BYTE_AT_A_TIME_BUFFER) +class PeekBufferTestJVM : AbstractSourceTestJVM(SourceFactory.PEEK_BUFFER) +class PeekBufferedSourceTestJVM : AbstractSourceTestJVM(SourceFactory.PEEK_BUFFERED_SOURCE) +abstract class AbstractSourceTestJVM(private val factory: SourceFactory) { + private var sink: Sink + private var source: Source + + init { + val pipe: SourceFactory.Pipe = factory.pipe() + sink = pipe.sink + source = pipe.source + } + @Test + @Throws(Exception::class) + fun inputStream() { + sink.writeUtf8("abc") + sink.emit() + val input: InputStream = source.inputStream() + val bytes = byteArrayOf('z'.code.toByte(), 'z'.code.toByte(), 'z'.code.toByte()) + var read: Int = input.read(bytes) + if (factory.isOneByteAtATime) { + assertEquals(1, read) + assertByteArrayEquals("azz", bytes) + read = input.read(bytes) + assertEquals(1, read) + assertByteArrayEquals("bzz", bytes) + read = input.read(bytes) + assertEquals(1, read) + assertByteArrayEquals("czz", bytes) + } else { + assertEquals(3, read) + assertByteArrayEquals("abc", bytes) + } + assertEquals(-1, input.read()) + } + + @Test + @Throws(Exception::class) + fun inputStreamOffsetCount() { + sink.writeUtf8("abcde") + sink.emit() + val input: InputStream = source.inputStream() + val bytes = + byteArrayOf('z'.code.toByte(), 'z'.code.toByte(), 'z'.code.toByte(), 'z'.code.toByte(), 'z'.code.toByte()) + val read: Int = input.read(bytes, 1, 3) + if (factory.isOneByteAtATime) { + assertEquals(1, read) + assertByteArrayEquals("zazzz", bytes) + } else { + assertEquals(3, read) + assertByteArrayEquals("zabcz", bytes) + } + } + + @Test + @Throws(Exception::class) + fun inputStreamSkip() { + sink.writeUtf8("abcde") + sink.emit() + val input: InputStream = source.inputStream() + assertEquals(4, input.skip(4)) + assertEquals('e'.code, input.read()) + sink.writeUtf8("abcde") + sink.emit() + assertEquals(5, input.skip(10)) // Try to skip too much. + assertEquals(0, input.skip(1)) // Try to skip when exhausted. + } + + @Test + @Throws(Exception::class) + fun inputStreamCharByChar() { + sink.writeUtf8("abc") + sink.emit() + val input: InputStream = source.inputStream() + assertEquals('a'.code, input.read()) + assertEquals('b'.code, input.read()) + assertEquals('c'.code, input.read()) + assertEquals(-1, input.read()) + } + + @Test + @Throws(IOException::class) + fun inputStreamBounds() { + sink.writeUtf8("a".repeat(100)) + sink.emit() + val input: InputStream = source.inputStream() + assertFailsWith { + input.read(ByteArray(100), 50, 51) + } + } + + @Test + @Throws(java.lang.Exception::class) + fun readNioBuffer() { + val expected = if (factory.isOneByteAtATime) "a" else "abcdefg" + sink.writeUtf8("abcdefg") + sink.emit() + val nioByteBuffer: ByteBuffer = ByteBuffer.allocate(1024) + val byteCount: Int = source.read(nioByteBuffer) + assertEquals(expected.length, byteCount) + assertEquals(expected.length, nioByteBuffer.position()) + assertEquals(nioByteBuffer.capacity(), nioByteBuffer.limit()) + (nioByteBuffer as Buffer).flip() // Cast necessary for Java 8. + val data = ByteArray(expected.length) + nioByteBuffer.get(data) + assertEquals(expected, String(data)) + } + + /** Note that this test crashes the VM on Android. */ + @Test + @Throws(java.lang.Exception::class) + fun readLargeNioBufferOnlyReadsOneSegment() { + val expected: String = if (factory.isOneByteAtATime) "a" else "a".repeat(SEGMENT_SIZE) + sink.writeUtf8("a".repeat(SEGMENT_SIZE * 4)) + sink.emit() + val nioByteBuffer: ByteBuffer = ByteBuffer.allocate(SEGMENT_SIZE * 3) + val byteCount: Int = source.read(nioByteBuffer) + assertEquals(expected.length, byteCount) + assertEquals(expected.length, nioByteBuffer.position()) + assertEquals(nioByteBuffer.capacity(), nioByteBuffer.limit()) + (nioByteBuffer as Buffer).flip() // Cast necessary for Java 8. + val data = ByteArray(expected.length) + nioByteBuffer.get(data) + assertEquals(expected, String(data)) + } + + @Test + @Throws(java.lang.Exception::class) + fun readSpecificCharsetPartial() { + sink.write(("0000007600000259000002c80000006c000000e40000007300000259000002" + + "cc000000720000006100000070000000740000025900000072").decodeHex()) + sink.emit() + assertEquals("vəˈläsə", source.readString(7 * 4, Charset.forName("utf-32"))) + } + + @Test + @Throws(java.lang.Exception::class) + fun readSpecificCharset() { + sink.write(("0000007600000259000002c80000006c000000e40000007300000259000002" + + "cc000000720000006100000070000000740000025900000072").decodeHex()) + + sink.emit() + assertEquals("vəˈläsəˌraptər", source.readString(Charset.forName("utf-32"))) + } + + @Test + @Throws(IOException::class) + fun readStringTooShortThrows() { + sink.writeString("abc", Charsets.US_ASCII) + sink.emit() + assertFailsWith { + source.readString(4, Charsets.US_ASCII) + } + assertEquals("abc", source.readUtf8()) // The read shouldn't consume any data. + } +} \ No newline at end of file diff --git a/core/jvm/test/BufferTest.kt b/core/jvm/test/BufferTest.kt new file mode 100644 index 000000000..b8aee0122 --- /dev/null +++ b/core/jvm/test/BufferTest.kt @@ -0,0 +1,208 @@ +/* + * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. + */ + +/* + * Copyright (C) 2014 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 kotlinx.io + +import kotlin.test.* +import java.io.ByteArrayInputStream +import java.io.ByteArrayOutputStream +import java.io.InputStream +import java.util.* +import kotlin.text.Charsets.UTF_8 + +private const val SEGMENT_SIZE = Segment.SIZE + +class BufferTest { + @Test + @Throws(Exception::class) + fun copyToSpanningSegments() { + val source = Buffer() + source.writeUtf8("a".repeat(SEGMENT_SIZE * 2)) + source.writeUtf8("b".repeat( SEGMENT_SIZE * 2)) + val out = ByteArrayOutputStream() + source.copyTo(out, 10L, SEGMENT_SIZE * 3L) + assertEquals("a".repeat( SEGMENT_SIZE * 2 - 10) + "b".repeat( SEGMENT_SIZE + 10), + out.toString()) + assertEquals("a".repeat(SEGMENT_SIZE * 2) + "b".repeat( SEGMENT_SIZE * 2), + source.readUtf8(SEGMENT_SIZE * 4L)) + } + + @Test + @Throws(Exception::class) + fun copyToStream() { + val buffer = Buffer().writeUtf8("hello, world!") + val out = ByteArrayOutputStream() + buffer.copyTo(out) + val outString = String(out.toByteArray(), UTF_8) + assertEquals("hello, world!", outString) + assertEquals("hello, world!", buffer.readUtf8()) + } + + @Test + @Throws(java.lang.Exception::class) + fun writeToSpanningSegments() { + val buffer = Buffer() + buffer.writeUtf8("a".repeat(SEGMENT_SIZE * 2)) + buffer.writeUtf8("b".repeat(SEGMENT_SIZE * 2)) + val out = ByteArrayOutputStream() + buffer.skip(10) + buffer.writeTo(out, SEGMENT_SIZE * 3L) + assertEquals("a".repeat(SEGMENT_SIZE * 2 - 10) + "b".repeat(SEGMENT_SIZE + 10), out.toString()) + assertEquals("b".repeat(SEGMENT_SIZE - 10), buffer.readUtf8(buffer.size)) + } + + @Test + @Throws(java.lang.Exception::class) + fun writeToStream() { + val buffer = Buffer().writeUtf8("hello, world!") + val out = ByteArrayOutputStream() + buffer.writeTo(out) + val outString = String(out.toByteArray(), UTF_8) + assertEquals("hello, world!", outString) + assertEquals(0, buffer.size) + } + + @Test + @Throws(java.lang.Exception::class) + fun readFromStream() { + val `in`: InputStream = ByteArrayInputStream("hello, world!".toByteArray(UTF_8)) + val buffer = Buffer() + buffer.readFrom(`in`) + val out = buffer.readUtf8() + assertEquals("hello, world!", out) + } + + @Test + @Throws(java.lang.Exception::class) + fun readFromSpanningSegments() { + val `in`: InputStream = ByteArrayInputStream("hello, world!".toByteArray(UTF_8)) + val buffer = Buffer().writeUtf8("a".repeat(SEGMENT_SIZE - 10)) + buffer.readFrom(`in`) + val out = buffer.readUtf8() + assertEquals("a".repeat(SEGMENT_SIZE - 10) + "hello, world!", out) + } + + @Test + @Throws(java.lang.Exception::class) + fun readFromStreamWithCount() { + val `in`: InputStream = ByteArrayInputStream("hello, world!".toByteArray(UTF_8)) + val buffer = Buffer() + buffer.readFrom(`in`, 10) + val out = buffer.readUtf8() + assertEquals("hello, wor", out) + } + + @Test + @Throws(IOException::class) + fun readFromDoesNotLeaveEmptyTailSegment() { + val buffer = Buffer() + buffer.readFrom(ByteArrayInputStream(ByteArray(SEGMENT_SIZE))) + assertNoEmptySegments(buffer) + } + + @Test + @Throws(java.lang.Exception::class) + fun bufferInputStreamByteByByte() { + val source = Buffer() + source.writeUtf8("abc") + val `in`: InputStream = source.inputStream() + assertEquals(3, `in`.available()) + assertEquals('a'.code, `in`.read()) + assertEquals('b'.code, `in`.read()) + assertEquals('c'.code, `in`.read()) + assertEquals(-1, `in`.read()) + assertEquals(0, `in`.available()) + } + + @Test + @Throws(java.lang.Exception::class) + fun bufferInputStreamBulkReads() { + val source = Buffer() + source.writeUtf8("abc") + val byteArray = ByteArray(4) + Arrays.fill(byteArray, (-5).toByte()) + val `in`: InputStream = source.inputStream() + assertEquals(3, `in`.read(byteArray)) + assertEquals("[97, 98, 99, -5]", byteArray.contentToString()) + Arrays.fill(byteArray, (-7).toByte()) + assertEquals(-1, `in`.read(byteArray)) + assertEquals("[-7, -7, -7, -7]", byteArray.contentToString()) + } + + @Test fun copyToOutputStream() { + val source = Buffer() + source.writeUtf8("party") + + val target = Buffer() + source.copyTo(target.outputStream()) + assertEquals("party", target.readUtf8()) + assertEquals("party", source.readUtf8()) + } + + @Test fun copyToOutputStreamWithOffset() { + val source = Buffer() + source.writeUtf8("party") + + val target = Buffer() + source.copyTo(target.outputStream(), offset = 2) + assertEquals("rty", target.readUtf8()) + assertEquals("party", source.readUtf8()) + } + + @Test fun copyToOutputStreamWithByteCount() { + val source = Buffer() + source.writeUtf8("party") + + val target = Buffer() + source.copyTo(target.outputStream(), byteCount = 3) + assertEquals("par", target.readUtf8()) + assertEquals("party", source.readUtf8()) + } + + @Test fun copyToOutputStreamWithOffsetAndByteCount() { + val source = Buffer() + source.writeUtf8("party") + + val target = Buffer() + source.copyTo(target.outputStream(), offset = 1, byteCount = 3) + assertEquals("art", target.readUtf8()) + assertEquals("party", source.readUtf8()) + } + + @Test fun writeToOutputStream() { + val source = Buffer() + source.writeUtf8("party") + + val target = Buffer() + source.writeTo(target.outputStream()) + assertEquals("party", target.readUtf8()) + assertEquals("", source.readUtf8()) + } + + @Test fun writeToOutputStreamWithByteCount() { + val source = Buffer() + source.writeUtf8("party") + + val target = Buffer() + source.writeTo(target.outputStream(), byteCount = 3) + assertEquals("par", target.readUtf8()) + assertEquals("ty", source.readUtf8()) + } +} \ No newline at end of file diff --git a/core/jvm/test/JvmPlatformTest.kt b/core/jvm/test/JvmPlatformTest.kt new file mode 100644 index 000000000..03e7e3289 --- /dev/null +++ b/core/jvm/test/JvmPlatformTest.kt @@ -0,0 +1,130 @@ +/* + * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. + */ + +/* + * 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 kotlinx.io + +import org.junit.jupiter.api.io.TempDir +import java.io.ByteArrayInputStream +import java.io.ByteArrayOutputStream +import java.io.File +import java.net.Socket +import java.nio.file.StandardOpenOption +import kotlin.test.* + +class JvmPlatformTest { + @TempDir + lateinit var tempDir: File + + @Test fun outputStreamSink() { + val baos = ByteArrayOutputStream() + val sink = baos.sink() + sink.write(Buffer().writeUtf8("a"), 1L) + assertArrayEquals(baos.toByteArray(), byteArrayOf(0x61)) + } + + @Test fun inputStreamSource() { + val bais = ByteArrayInputStream(byteArrayOf(0x61)) + val source = bais.source() + val buffer = Buffer() + source.read(buffer, 1) + assertEquals(buffer.readUtf8(), "a") + } + + @Test fun fileSink() { + val file = File(tempDir, "test") + val sink = file.sink() + sink.write(Buffer().writeUtf8("a"), 1L) + assertEquals(file.readText(), "a") + } + + @Test fun fileAppendingSink() { + val file = File(tempDir, "test") + file.writeText("a") + val sink = file.sink(append = true) + sink.write(Buffer().writeUtf8("b"), 1L) + sink.close() + assertEquals(file.readText(), "ab") + } + + @Test fun fileSource() { + val file = File(tempDir, "test") + file.writeText("a") + val source = file.source() + val buffer = Buffer() + source.read(buffer, 1L) + assertEquals(buffer.readUtf8(), "a") + } + + @Test fun pathSink() { + val file = File(tempDir, "test") + val sink = file.toPath().sink() + sink.write(Buffer().writeUtf8("a"), 1L) + assertEquals(file.readText(), "a") + } + + @Test fun pathSinkWithOptions() { + val file = File(tempDir, "test") + file.writeText("a") + val sink = file.toPath().sink(StandardOpenOption.APPEND) + sink.write(Buffer().writeUtf8("b"), 1L) + assertEquals(file.readText(), "ab") + } + + @Test fun pathSource() { + val file = File(tempDir, "test") + file.writeText("a") + val source = file.toPath().source() + val buffer = Buffer() + source.read(buffer, 1L) + assertEquals(buffer.readUtf8(), "a") + } + + @Ignore("Not sure how to test this") + @Test + fun pathSourceWithOptions() { + val folder = File(tempDir, "folder") + folder.mkdir() + val file = File(folder, "new.txt") + file.toPath().source(StandardOpenOption.CREATE_NEW) + // This still throws NoSuchFileException... + } + + @Test fun socketSink() { + val baos = ByteArrayOutputStream() + val socket = object : Socket() { + override fun getOutputStream() = baos + } + val sink = socket.sink() + sink.write(Buffer().writeUtf8("a"), 1L) + assertArrayEquals(baos.toByteArray(), byteArrayOf(0x61)) + } + + @Test fun socketSource() { + val bais = ByteArrayInputStream(byteArrayOf(0x61)) + val socket = object : Socket() { + override fun getInputStream() = bais + } + val source = socket.source() + val buffer = Buffer() + source.read(buffer, 1L) + assertEquals(buffer.readUtf8(), "a") + } +} \ No newline at end of file diff --git a/core/jvm/test/NioTest.kt b/core/jvm/test/NioTest.kt new file mode 100644 index 000000000..331a264c2 --- /dev/null +++ b/core/jvm/test/NioTest.kt @@ -0,0 +1,157 @@ +/* + * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. + */ + +/* + * 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 kotlinx.io + +import org.junit.jupiter.api.io.TempDir +import kotlin.test.* +import java.io.File +import java.nio.ByteBuffer +import java.nio.channels.FileChannel +import java.nio.channels.ReadableByteChannel +import java.nio.channels.WritableByteChannel +import java.nio.file.Path +import java.nio.file.Paths +import java.nio.file.StandardOpenOption +import kotlin.io.path.createFile +import kotlin.text.Charsets.UTF_8 + +/** Test interop with java.nio. */ +class NioTest { + @TempDir + lateinit var temporaryFolder: Path + @Test + @Throws(Exception::class) + fun sourceIsOpen() { + val source: Source = RealSource(Buffer()) + assertTrue(source.isOpen()) + source.close() + assertFalse(source.isOpen()) + } + + @Test + @Throws(Exception::class) + fun sinkIsOpen() { + val sink: Sink = RealSink(Buffer()) + assertTrue(sink.isOpen()) + sink.close() + assertFalse(sink.isOpen()) + } + + @Test + @Throws(Exception::class) + fun writableChannelNioFile() { + val file = Paths.get(temporaryFolder.toString(), "test").createFile() + val fileChannel: FileChannel = FileChannel.open(file, StandardOpenOption.WRITE) + testWritableByteChannel(fileChannel) + val emitted: Source = file.source().buffer() + assertEquals("defghijklmnopqrstuvw", emitted.readUtf8()) + emitted.close() + } + + @Test + @Throws(Exception::class) + fun writableChannelBuffer() { + val buffer = Buffer() + testWritableByteChannel(buffer) + assertEquals("defghijklmnopqrstuvw", buffer.readUtf8()) + } + + @Test + @Throws(Exception::class) + fun writableChannelBufferedSink() { + val buffer = Buffer() + val bufferedSink: Sink = buffer + testWritableByteChannel(bufferedSink) + assertEquals("defghijklmnopqrstuvw", buffer.readUtf8()) + } + + @Test + @Throws(Exception::class) + fun readableChannelNioFile() { + val file: File = Paths.get(temporaryFolder.toString(), "test").toFile() + val initialData: Sink = file.sink().buffer() + initialData.writeUtf8("abcdefghijklmnopqrstuvwxyz") + initialData.close() + val fileChannel: FileChannel = FileChannel.open(file.toPath(), StandardOpenOption.READ) + testReadableByteChannel(fileChannel) + } + + @Test + @Throws(Exception::class) + fun readableChannelBuffer() { + val buffer = Buffer() + buffer.writeUtf8("abcdefghijklmnopqrstuvwxyz") + testReadableByteChannel(buffer) + } + + @Test + @Throws(Exception::class) + fun readableChannelBufferedSource() { + val buffer = Buffer() + val bufferedSource: Source = buffer + buffer.writeUtf8("abcdefghijklmnopqrstuvwxyz") + testReadableByteChannel(bufferedSource) + } + + /** + * Does some basic writes to `channel`. We execute this against both Okio's channels and + * also a standard implementation from the JDK to confirm that their behavior is consistent. + */ + @Throws(Exception::class) + private fun testWritableByteChannel(channel: WritableByteChannel) { + assertTrue(channel.isOpen()) + val byteBuffer = ByteBuffer.allocate(1024) + byteBuffer.put("abcdefghijklmnopqrstuvwxyz".toByteArray(UTF_8)) + byteBuffer.flip() // Cast necessary for Java 8. + byteBuffer.position(3) // Cast necessary for Java 8. + byteBuffer.limit(23) // Cast necessary for Java 8. + val byteCount: Int = channel.write(byteBuffer) + assertEquals(20, byteCount) + assertEquals(23, byteBuffer.position()) + assertEquals(23, byteBuffer.limit()) + channel.close() + assertEquals(channel is Buffer, channel.isOpen) // Buffer.close() does nothing. + } + + /** + * Does some basic reads from `channel`. We execute this against both Okio's channels and + * also a standard implementation from the JDK to confirm that their behavior is consistent. + */ + @Throws(Exception::class) + private fun testReadableByteChannel(channel: ReadableByteChannel) { + assertTrue(channel.isOpen) + val byteBuffer = ByteBuffer.allocate(1024) + byteBuffer.position(3) // Cast necessary for Java 8. + byteBuffer.limit(23) // Cast necessary for Java 8. + val byteCount: Int = channel.read(byteBuffer) + assertEquals(20, byteCount) + assertEquals(23, byteBuffer.position()) + assertEquals(23, byteBuffer.limit()) + channel.close() + assertEquals(channel is Buffer, channel.isOpen()) // Buffer.close() does nothing. + byteBuffer.flip() // Cast necessary for Java 8. + byteBuffer.position(3) // Cast necessary for Java 8. + val data = ByteArray(byteBuffer.remaining()) + byteBuffer[data] + assertEquals("abcdefghijklmnopqrst", String(data, UTF_8)) + } +} diff --git a/core/jvm/test/ultil.kt b/core/jvm/test/utilJVM.kt similarity index 60% rename from core/jvm/test/ultil.kt rename to core/jvm/test/utilJVM.kt index 3e97a4ca7..2c14a3d48 100644 --- a/core/jvm/test/ultil.kt +++ b/core/jvm/test/utilJVM.kt @@ -5,6 +5,8 @@ package kotlinx.io import java.nio.file.* +import kotlin.test.assertEquals +import kotlin.test.assertTrue actual fun createTempFile(): String = Files.createTempFile(null, null).toString() @@ -13,4 +15,12 @@ actual fun deleteFile(path: String) { throw IllegalArgumentException("Path is not a file: $path.") } Files.delete(Paths.get(path)) +} + +fun assertNoEmptySegments(buffer: Buffer) { + assertTrue(segmentSizes(buffer).all { it != 0 }, "Expected all segments to be non-empty") +} + +fun assertByteArrayEquals(expectedUtf8: String, b: ByteArray) { + assertEquals(expectedUtf8, b.toString(Charsets.UTF_8)) } \ No newline at end of file diff --git a/core/native/src/Buffer.kt b/core/native/src/Buffer.kt index dd1528eda..3e36432e6 100644 --- a/core/native/src/Buffer.kt +++ b/core/native/src/Buffer.kt @@ -50,10 +50,10 @@ actual class Buffer : Source, Sink { byteCount: Long ): Buffer = commonCopyTo(out, offset, byteCount) - actual fun copyTo( - out: Buffer, - offset: Long - ): Buffer = copyTo(out, offset, size - offset) + //actual fun copyTo( + // out: Buffer, + // offset: Long + //): Buffer = copyTo(out, offset, size - offset) actual operator fun get(pos: Long): Byte = commonGet(pos) From 372c3c922cb8087787ecc16ae638fff06d482155 Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Thu, 1 Jun 2023 16:03:09 +0200 Subject: [PATCH 10/83] Remove some unused and commented out code --- core/common/src/internal/-Buffer.kt | 397 ------------------ core/common/src/internal/-RealBufferedSink.kt | 58 --- .../src/internal/-RealBufferedSource.kt | 139 ------ 3 files changed, 594 deletions(-) diff --git a/core/common/src/internal/-Buffer.kt b/core/common/src/internal/-Buffer.kt index 63518fbc6..ac65e4ad3 100644 --- a/core/common/src/internal/-Buffer.kt +++ b/core/common/src/internal/-Buffer.kt @@ -28,48 +28,6 @@ package kotlinx.io.internal import kotlinx.io.* import kotlin.native.concurrent.SharedImmutable -@SharedImmutable -internal val HEX_DIGIT_BYTES = "0123456789abcdef".asUtf8ToByteArray() - -/** - * Returns true if the range within this buffer starting at `segmentPos` in `segment` is equal to - * `bytes[bytesOffset..bytesLimit)`. - */ -// TODO: remove? -/* -internal fun rangeEquals( - segment: Segment, - segmentPos: Int, - bytes: ByteArray, - bytesOffset: Int, - bytesLimit: Int -): Boolean { - var segment = segment - var segmentPos = segmentPos - var segmentLimit = segment.limit - var data = segment.data - - var i = bytesOffset - while (i < bytesLimit) { - if (segmentPos == segmentLimit) { - segment = segment.next!! - data = segment.data - segmentPos = segment.pos - segmentLimit = segment.limit - } - - if (data[segmentPos] != bytes[i]) { - return false - } - - segmentPos++ - i++ - } - - return true -} -*/ - internal fun Buffer.readUtf8Line(newline: Long): String { return when { newline > 0 && this[newline - 1] == '\r'.code.toByte() -> { @@ -321,126 +279,6 @@ internal inline fun Buffer.commonSkip(byteCount: Long) { } } -//internal inline fun Buffer.commonWrite( -// byteString: ByteString, -// offset: Int = 0, -// byteCount: Int = byteString.size -//): Buffer { -// byteString.write(this, offset, byteCount) -// return this -//} - -/* -internal inline fun Buffer.commonWriteDecimalLong(v: Long): Buffer { - var v = v - if (v == 0L) { - // Both a shortcut and required since the following code can't handle zero. - return writeByte('0'.code) - } - - var negative = false - if (v < 0L) { - v = -v - if (v < 0L) { // Only true for Long.MIN_VALUE. - return writeUtf8("-9223372036854775808") - } - negative = true - } - - // Binary search for character width which favors matching lower numbers. - var width = - if (v < 100000000L) - if (v < 10000L) - if (v < 100L) - if (v < 10L) 1 - else 2 - else if (v < 1000L) 3 - else 4 - else if (v < 1000000L) - if (v < 100000L) 5 - else 6 - else if (v < 10000000L) 7 - else 8 - else if (v < 1000000000000L) - if (v < 10000000000L) - if (v < 1000000000L) 9 - else 10 - else if (v < 100000000000L) 11 - else 12 - else if (v < 1000000000000000L) - if (v < 10000000000000L) 13 - else if (v < 100000000000000L) 14 - else 15 - else if (v < 100000000000000000L) - if (v < 10000000000000000L) 16 - else 17 - else if (v < 1000000000000000000L) 18 - else 19 - if (negative) { - ++width - } - - val tail = writableSegment(width) - val data = tail.data - var pos = tail.limit + width // We write backwards from right to left. - while (v != 0L) { - val digit = (v % 10).toInt() - data[--pos] = HEX_DIGIT_BYTES[digit] - v /= 10 - } - if (negative) { - data[--pos] = '-'.code.toByte() - } - - tail.limit += width - this.size += width.toLong() - return this -} - -internal inline fun Buffer.commonWriteHexadecimalUnsignedLong(v: Long): Buffer { - var v = v - if (v == 0L) { - // Both a shortcut and required since the following code can't handle zero. - return writeByte('0'.code) - } - - // Mask every bit below the most significant bit to a 1 - // http://aggregate.org/MAGIC/#Most%20Significant%201%20Bit - var x = v - x = x or (x ushr 1) - x = x or (x ushr 2) - x = x or (x ushr 4) - x = x or (x ushr 8) - x = x or (x ushr 16) - x = x or (x ushr 32) - - // Count the number of 1s - // http://aggregate.org/MAGIC/#Population%20Count%20(Ones%20Count) - x -= x ushr 1 and 0x5555555555555555 - x = (x ushr 2 and 0x3333333333333333) + (x and 0x3333333333333333) - x = (x ushr 4) + x and 0x0f0f0f0f0f0f0f0f - x += x ushr 8 - x += x ushr 16 - x = (x and 0x3f) + ((x ushr 32) and 0x3f) - - // Round up to the nearest full byte - val width = ((x + 3) / 4).toInt() - - val tail = writableSegment(width) - val data = tail.data - var pos = tail.limit + width - 1 - val start = tail.limit - while (pos >= start) { - data[pos] = HEX_DIGIT_BYTES[(v and 0xF).toInt()] - v = v ushr 4 - pos-- - } - tail.limit += width - size += width.toLong() - return this -} - */ - internal inline fun Buffer.commonWritableSegment(minimumCapacity: Int): Segment { require(minimumCapacity >= 1 && minimumCapacity <= Segment.SIZE) { "unexpected capacity" } @@ -459,8 +297,6 @@ internal inline fun Buffer.commonWritableSegment(minimumCapacity: Int): Segment return tail } -internal inline fun Buffer.commonWrite(source: ByteArray) = write(source, 0, source.size) - internal inline fun Buffer.commonWrite( source: ByteArray, offset: Int, @@ -500,8 +336,6 @@ internal inline fun Buffer.commonReadByteArray(byteCount: Long): ByteArray { return result } -internal inline fun Buffer.commonRead(sink: ByteArray) = read(sink, 0, sink.size) - internal inline fun Buffer.commonReadFully(sink: ByteArray) { var offset = 0 while (offset < sink.size) { @@ -534,151 +368,6 @@ internal inline fun Buffer.commonRead(sink: ByteArray, offset: Int, byteCount: I internal const val OVERFLOW_ZONE = Long.MIN_VALUE / 10L internal const val OVERFLOW_DIGIT_START = Long.MIN_VALUE % 10L + 1 -internal inline fun Buffer.commonReadDecimalLong(): Long { - if (size == 0L) throw EOFException() - - // This value is always built negatively in order to accommodate Long.MIN_VALUE. - var value = 0L - var seen = 0 - var negative = false - var done = false - - var overflowDigit = OVERFLOW_DIGIT_START - - do { - val segment = head!! - - val data = segment.data - var pos = segment.pos - val limit = segment.limit - - while (pos < limit) { - val b = data[pos] - if (b >= '0'.code.toByte() && b <= '9'.code.toByte()) { - val digit = '0'.code.toByte() - b - - // Detect when the digit would cause an overflow. - if (value < OVERFLOW_ZONE || value == OVERFLOW_ZONE && digit < overflowDigit) { - val buffer = Buffer().writeDecimalLong(value).writeByte(b.toInt()) - if (!negative) buffer.readByte() // Skip negative sign. - throw NumberFormatException("Number too large: ${buffer.readUtf8()}") - } - value *= 10L - value += digit.toLong() - } else if (b == '-'.code.toByte() && seen == 0) { - negative = true - overflowDigit -= 1 - } else { - // Set a flag to stop iteration. We still need to run through segment updating below. - done = true - break - } - pos++ - seen++ - } - - if (pos == limit) { - head = segment.pop() - SegmentPool.recycle(segment) - } else { - segment.pos = pos - } - } while (!done && head != null) - - size -= seen.toLong() - - val minimumSeen = if (negative) 2 else 1 - if (seen < minimumSeen) { - if (size == 0L) throw EOFException() - val expected = if (negative) "Expected a digit" else "Expected a digit or '-'" - throw NumberFormatException("$expected but was 0x${get(0).toHexString()}") - } - - return if (negative) value else -value -} - -internal inline fun Buffer.commonReadHexadecimalUnsignedLong(): Long { - if (size == 0L) throw EOFException() - - var value = 0L - var seen = 0 - var done = false - - do { - val segment = head!! - - val data = segment.data - var pos = segment.pos - val limit = segment.limit - - while (pos < limit) { - val digit: Int - - val b = data[pos] - if (b >= '0'.code.toByte() && b <= '9'.code.toByte()) { - digit = b - '0'.code.toByte() - } else if (b >= 'a'.code.toByte() && b <= 'f'.code.toByte()) { - digit = b - 'a'.code.toByte() + 10 - } else if (b >= 'A'.code.toByte() && b <= 'F'.code.toByte()) { - digit = b - 'A'.code.toByte() + 10 // We never write uppercase, but we support reading it. - } else { - if (seen == 0) { - throw NumberFormatException( - "Expected leading [0-9a-fA-F] character but was 0x${b.toHexString()}" - ) - } - // Set a flag to stop iteration. We still need to run through segment updating below. - done = true - break - } - - // Detect when the shift will overflow. - if (value and -0x1000000000000000L != 0L) { - val buffer = Buffer().writeHexadecimalUnsignedLong(value).writeByte(b.toInt()) - throw NumberFormatException("Number too large: " + buffer.readUtf8()) - } - - value = value shl 4 - value = value or digit.toLong() - pos++ - seen++ - } - - if (pos == limit) { - head = segment.pop() - SegmentPool.recycle(segment) - } else { - segment.pos = pos - } - } while (!done && head != null) - - size -= seen.toLong() - return value -} - -//internal inline fun Buffer.commonReadByteString(): ByteString = readByteString(size) -// -//internal inline fun Buffer.commonReadByteString(byteCount: Long): ByteString { -// require(byteCount >= 0 && byteCount <= Int.MAX_VALUE) { "byteCount: $byteCount" } -// if (size < byteCount) throw EOFException() -// -// if (byteCount >= SEGMENTING_THRESHOLD) { -// return snapshot(byteCount.toInt()).also { skip(byteCount) } -// } else { -// return ByteString(readByteArray(byteCount)) -// } -//} - -//internal inline fun Buffer.commonSelect(options: Options): Int { -// val index = selectPrefix(options) -// if (index == -1) return -1 -// -// // If the prefix match actually matched a full byte string, consume it and return it. -// val selectedSize = options.byteStrings[index].size -// skip(selectedSize.toLong()) -// return index -//} - internal inline fun Buffer.commonReadFully(sink: Buffer, byteCount: Long) { if (size < byteCount) { sink.write(this, size) // Exhaust ourselves. @@ -719,38 +408,6 @@ internal inline fun Buffer.commonReadUtf8(byteCount: Long): String { return result } -internal inline fun Buffer.commonReadUtf8Line(): String? { - val newline = indexOf('\n'.code.toByte()) - - return when { - newline != -1L -> readUtf8Line(newline) - size != 0L -> readUtf8(size) - else -> null - } -} - -internal inline fun Buffer.commonReadUtf8LineStrict(limit: Long): String { - require(limit >= 0L) { "limit < 0: $limit" } - val scanLength = if (limit == Long.MAX_VALUE) Long.MAX_VALUE else limit + 1L - val newline = indexOf('\n'.code.toByte(), 0L, scanLength) - if (newline != -1L) return readUtf8Line(newline) - if (scanLength < size && - this[scanLength - 1] == '\r'.code.toByte() && - this[scanLength] == '\n'.code.toByte() - ) { - return readUtf8Line(scanLength) // The line was 'limit' UTF-8 bytes followed by \r\n. - } - val data = Buffer() - copyTo(data, 0, minOf(32, size)) - // TODO fzhinkin - throw EOFException( - "\\n not found: limit=${minOf( - size, - limit - )} content=#{NOT IMPLEMENTED}${'…'}" - ) -} - internal inline fun Buffer.commonReadUtf8CodePoint(): Int { if (size == 0L) throw EOFException() @@ -1140,42 +797,6 @@ internal inline fun Buffer.commonRead(sink: Buffer, byteCount: Long): Long { return bytesWritten } -/* -internal inline fun Buffer.commonIndexOf(b: Byte, fromIndex: Long, toIndex: Long): Long { - var fromIndex = fromIndex - var toIndex = toIndex - require(fromIndex in 0..toIndex) { "size=$size fromIndex=$fromIndex toIndex=$toIndex" } - - if (toIndex > size) toIndex = size - if (fromIndex == toIndex) return -1L - - seek(fromIndex) { s, offset -> - var s = s ?: return -1L - var offset = offset - - // Scan through the segments, searching for b. - while (offset < toIndex) { - val data = s.data - val limit = minOf(s.limit.toLong(), s.pos + toIndex - offset).toInt() - var pos = (s.pos + fromIndex - offset).toInt() - while (pos < limit) { - if (data[pos] == b) { - return pos - s.pos + offset - } - pos++ - } - - // Not in this segment. Try the next one. - offset += (s.limit - s.pos).toLong() - fromIndex = offset - s = s.next!! - } - - return -1L - } -} - */ - internal inline fun Buffer.commonEquals(other: Any?): Boolean { if (this === other) return true if (other !is Buffer) return false @@ -1305,22 +926,4 @@ private fun codePointIndexToCharIndex(s: ByteArray, codePointCount: Int): Int { charCount += if (c < 0x10000) 1 else 2 } return charCount -} - -internal inline fun forEachSegment( - segments: Array, - directory: IntArray, - action: (data: ByteArray, offset: Int, byteCount: Int) -> Unit -) { - val segmentCount = segments.size - var s = 0 - var pos = 0 - while (s < segmentCount) { - val segmentPos = directory[segmentCount + s] - val nextSegmentOffset = directory[s] - - action(segments[s], segmentPos, nextSegmentOffset - pos) - pos = nextSegmentOffset - s++ - } } \ No newline at end of file diff --git a/core/common/src/internal/-RealBufferedSink.kt b/core/common/src/internal/-RealBufferedSink.kt index 06ac09c8b..fa1f14c9b 100644 --- a/core/common/src/internal/-RealBufferedSink.kt +++ b/core/common/src/internal/-RealBufferedSink.kt @@ -33,34 +33,6 @@ internal inline fun RealSink.commonWrite(source: Buffer, byteCount: Long) { emitCompleteSegments() } -internal inline fun RealSink.commonWriteUtf8(string: String): Sink { - check(!closed) { "closed" } - buffer.writeUtf8(string) - return emitCompleteSegments() -} - -internal inline fun RealSink.commonWriteUtf8( - string: String, - beginIndex: Int, - endIndex: Int -): Sink { - check(!closed) { "closed" } - buffer.writeUtf8(string, beginIndex, endIndex) - return emitCompleteSegments() -} - -internal inline fun RealSink.commonWriteUtf8CodePoint(codePoint: Int): Sink { - check(!closed) { "closed" } - buffer.writeUtf8CodePoint(codePoint) - return emitCompleteSegments() -} - -internal inline fun RealSink.commonWrite(source: ByteArray): Sink { - check(!closed) { "closed" } - buffer.write(source) - return emitCompleteSegments() -} - internal inline fun RealSink.commonWrite( source: ByteArray, offset: Int, @@ -107,48 +79,18 @@ internal inline fun RealSink.commonWriteShort(s: Int): Sink { return emitCompleteSegments() } -internal inline fun RealSink.commonWriteShortLe(s: Int): Sink { - check(!closed) { "closed" } - buffer.writeShortLe(s) - return emitCompleteSegments() -} - internal inline fun RealSink.commonWriteInt(i: Int): Sink { check(!closed) { "closed" } buffer.writeInt(i) return emitCompleteSegments() } -internal inline fun RealSink.commonWriteIntLe(i: Int): Sink { - check(!closed) { "closed" } - buffer.writeIntLe(i) - return emitCompleteSegments() -} - internal inline fun RealSink.commonWriteLong(v: Long): Sink { check(!closed) { "closed" } buffer.writeLong(v) return emitCompleteSegments() } -internal inline fun RealSink.commonWriteLongLe(v: Long): Sink { - check(!closed) { "closed" } - buffer.writeLongLe(v) - return emitCompleteSegments() -} - -internal inline fun RealSink.commonWriteDecimalLong(v: Long): Sink { - check(!closed) { "closed" } - buffer.writeDecimalLong(v) - return emitCompleteSegments() -} - -internal inline fun RealSink.commonWriteHexadecimalUnsignedLong(v: Long): Sink { - check(!closed) { "closed" } - buffer.writeHexadecimalUnsignedLong(v) - return emitCompleteSegments() -} - internal inline fun RealSink.commonEmitCompleteSegments(): Sink { check(!closed) { "closed" } val byteCount = buffer.completeSegmentByteCount() diff --git a/core/common/src/internal/-RealBufferedSource.kt b/core/common/src/internal/-RealBufferedSource.kt index 89de6ae5c..f08fe91de 100644 --- a/core/common/src/internal/-RealBufferedSource.kt +++ b/core/common/src/internal/-RealBufferedSource.kt @@ -130,138 +130,21 @@ internal inline fun RealSource.commonReadAll(sink: RawSink): Long { return totalBytesWritten } -internal inline fun RealSource.commonReadUtf8(): String { - buffer.writeAll(source) - return buffer.readUtf8() -} - -internal inline fun RealSource.commonReadUtf8(byteCount: Long): String { - require(byteCount) - return buffer.readUtf8(byteCount) -} - -internal inline fun RealSource.commonReadUtf8Line(): String? { - val newline = indexOf('\n'.code.toByte()) - - return if (newline == -1L) { - if (buffer.size != 0L) { - readUtf8(buffer.size) - } else { - null - } - } else { - buffer.readUtf8Line(newline) - } -} - -internal inline fun RealSource.commonReadUtf8LineStrict(limit: Long): String { - require(limit >= 0) { "limit < 0: $limit" } - val scanLength = if (limit == Long.MAX_VALUE) Long.MAX_VALUE else limit + 1 - val newline = indexOf('\n'.code.toByte(), 0, scanLength) - if (newline != -1L) return buffer.readUtf8Line(newline) - if (scanLength < Long.MAX_VALUE && - request(scanLength) && buffer[scanLength - 1] == '\r'.code.toByte() && - request(scanLength + 1) && buffer[scanLength] == '\n'.code.toByte() - ) { - return buffer.readUtf8Line(scanLength) // The line was 'limit' UTF-8 bytes followed by \r\n. - } - val data = Buffer() - buffer.copyTo(data, 0, minOf(32, buffer.size)) - throw EOFException( - "\\n not found: limit=" + minOf(buffer.size, limit) + - " content=" + data + '…'.toString() - ) - -// throw EOFException( -// "\\n not found: limit=" + minOf(buffer.size, limit) + -// " content=" + data.readByteString().hex() + '…'.toString() -// ) -} - -internal inline fun RealSource.commonReadUtf8CodePoint(): Int { - require(1) - - val b0 = buffer[0].toInt() - when { - b0 and 0xe0 == 0xc0 -> require(2) - b0 and 0xf0 == 0xe0 -> require(3) - b0 and 0xf8 == 0xf0 -> require(4) - } - - return buffer.readUtf8CodePoint() -} - internal inline fun RealSource.commonReadShort(): Short { require(2) return buffer.readShort() } -internal inline fun RealSource.commonReadShortLe(): Short { - require(2) - return buffer.readShortLe() -} - internal inline fun RealSource.commonReadInt(): Int { require(4) return buffer.readInt() } -internal inline fun RealSource.commonReadIntLe(): Int { - require(4) - return buffer.readIntLe() -} - internal inline fun RealSource.commonReadLong(): Long { require(8) return buffer.readLong() } -internal inline fun RealSource.commonReadLongLe(): Long { - require(8) - return buffer.readLongLe() -} - -internal inline fun RealSource.commonReadDecimalLong(): Long { - require(1) - - var pos = 0L - while (request(pos + 1)) { - val b = buffer[pos] - if ((b < '0'.code.toByte() || b > '9'.code.toByte()) && (pos != 0L || b != '-'.code.toByte())) { - // Non-digit, or non-leading negative sign. - if (pos == 0L) { - throw NumberFormatException("Expected a digit or '-' but was 0x${b.toString(16)}") - } - break - } - pos++ - } - - return buffer.readDecimalLong() -} - -internal inline fun RealSource.commonReadHexadecimalUnsignedLong(): Long { - require(1) - - var pos = 0 - while (request((pos + 1).toLong())) { - val b = buffer[pos.toLong()] - if ((b < '0'.code.toByte() || b > '9'.code.toByte()) && - (b < 'a'.code.toByte() || b > 'f'.code.toByte()) && - (b < 'A'.code.toByte() || b > 'F'.code.toByte()) - ) { - // Non-digit, or non-leading negative sign. - if (pos == 0) { - throw NumberFormatException("Expected leading [0-9a-fA-F] character but was 0x${b.toString(16)}") - } - break - } - pos++ - } - - return buffer.readHexadecimalUnsignedLong() -} - internal inline fun RealSource.commonSkip(byteCount: Long) { var remainingByteCount = byteCount check(!closed) { "closed" } @@ -275,28 +158,6 @@ internal inline fun RealSource.commonSkip(byteCount: Long) { } } -/* -internal inline fun RealSource.commonIndexOf(b: Byte, fromIndex: Long, toIndex: Long): Long { - var fromIndex = fromIndex - check(!closed) { "closed" } - require(fromIndex in 0L..toIndex) { "fromIndex=$fromIndex toIndex=$toIndex" } - - while (fromIndex < toIndex) { - val result = buffer.indexOf(b, fromIndex, toIndex) - if (result != -1L) return result - - // The byte wasn't in the buffer. Give up if we've already reached our target size or if the - // underlying stream is exhausted. - val lastBufferSize = buffer.size - if (lastBufferSize >= toIndex || source.read(buffer, Segment.SIZE.toLong()) == -1L) return -1L - - // Continue the search from where we left off. - fromIndex = maxOf(fromIndex, lastBufferSize) - } - return -1L -} - */ - internal inline fun RealSource.commonPeek(): Source { return PeekSource(this).buffer() } From 1cc5e141198a0037a773c88ecfe60f4a62ca2cc4 Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Fri, 2 Jun 2023 12:39:47 +0200 Subject: [PATCH 11/83] Update documentation --- core/common/src/Buffer.kt | 38 ++++++++++++++++++++++++++++++++---- core/common/src/RawSink.kt | 12 ++++++------ core/common/src/RawSource.kt | 16 ++++++++------- core/common/src/Segment.kt | 3 +-- core/common/src/Sink.kt | 34 ++++++++++++++++---------------- core/common/src/Source.kt | 32 +++++++++++++++++++----------- core/common/src/Utf8.kt | 2 +- core/jvm/src/Buffer.kt | 20 ++++++++----------- core/native/src/Buffer.kt | 8 +++----- 9 files changed, 100 insertions(+), 65 deletions(-) diff --git a/core/common/src/Buffer.kt b/core/common/src/Buffer.kt index 065001b43..3d2cae48a 100644 --- a/core/common/src/Buffer.kt +++ b/core/common/src/Buffer.kt @@ -31,28 +31,46 @@ package kotlinx.io * from one place in memory to another, this class just changes ownership of the underlying data segments. * * To reduce allocations and speed up buffer's extension, it may use data segments pooling. + * + * [Buffer] implements both [Source] and [Sink] and could be used as a source or a sink, + * but unlike regular sinks and sources its [close], [cancel], [flush], [emit], [emitCompleteSegments] + * does not affect buffer's state and [exhausted] only indicates that a buffer is empty. */ expect class Buffer() : Source, Sink { internal var head: Segment? /** - * Amount of bytes accessible for read from this buffer. + * The number of bytes accessible for read from this buffer. */ var size: Long internal set + /** + * Returns the buffer itself. + */ override val buffer: Buffer + /** + * This method does not affect the buffer's content as there is no upstream to write data to. + */ override fun emitCompleteSegments(): Buffer + /** + * This method does not affect the buffer's content as there is no upstream to write data to. + */ override fun emit(): Buffer + /** + * This method does not affect the buffer's content as there is no upstream to write data to. + */ + override fun flush() + /** * Copy [byteCount] bytes from this buffer, starting at [offset], to [out] buffer. * * @param out the destination buffer to copy data into. * @param offset the offset to the first byte of data in this buffer to start copying from. - * @param byteCount amount of bytes to copy. + * @param byteCount the number of bytes to copy. * * @throws IndexOutOfBoundsException when [offset] and [byteCount] correspond to a range out of this buffer bounds. */ @@ -63,8 +81,10 @@ expect class Buffer() : Source, Sink { ): Buffer /** - * Returns the number of bytes in segments that are not writable. This is the number of bytes that - * can be flushed immediately to an underlying sink without harming throughput. + * Returns the number of bytes in segments that are fully filled and are no longer writable. + * + * TODO: is it? + * This is the number of bytes that can be flushed immediately to an underlying sink without harming throughput. */ fun completeSegmentByteCount(): Long @@ -116,4 +136,14 @@ expect class Buffer() : Source, Sink { * Returns a deep copy of this buffer. */ fun copy(): Buffer + + /** + * This method does not affect the buffer. + */ + override fun close() + + /** + * This method does not affect the buffer. + */ + override fun cancel() } diff --git a/core/common/src/RawSink.kt b/core/common/src/RawSink.kt index 34ae768e8..d113d8848 100644 --- a/core/common/src/RawSink.kt +++ b/core/common/src/RawSink.kt @@ -21,15 +21,15 @@ package kotlinx.io /** - * Receives a stream of bytes. Use this interface to write data wherever it's needed: to the - * network, storage, or a buffer in memory. Sinks may be layered to transform received data, such as - * to compress, encrypt, throttle, or add protocol framing. + * Receives a stream of bytes. RawSink is a base interface all other `kotlinx-io` data receivers are built upon it. + * + * This interface should be implemented to write data wherever it's needed: to the network, storage, + * or a buffer in memory. Sinks may be layered to transform received data, such as to compress, encrypt, throttle, + * or add protocol framing. * * Most application code shouldn't operate on a raw sink directly, but rather on a [Sink] which * is both more efficient and more convenient. Use [buffer] to wrap any raw sink with a buffer. * - * Sinks are easy to test: just use a [Buffer] in your tests, and read from it to confirm it - * received the data that was expected. */ expect interface RawSink : Closeable { // TODO: should it throw EOFException instead? @@ -37,7 +37,7 @@ expect interface RawSink : Closeable { * Removes [byteCount] bytes from [source] and appends them to this. * * @param source the source to read data from. - * @param byteCount amount of bytes to write. + * @param byteCount the number of bytes to write. * * @throws IndexOutOfBoundsException when the [source]'s size is below [byteCount]. */ diff --git a/core/common/src/RawSource.kt b/core/common/src/RawSource.kt index b49d6dfad..b61cab17a 100644 --- a/core/common/src/RawSource.kt +++ b/core/common/src/RawSource.kt @@ -21,20 +21,22 @@ package kotlinx.io /** - * Supplies a stream of bytes. Use this interface to read data from wherever it's located: from the - * network, storage, or a buffer in memory. Sources may be layered to transform supplied data, such - * as to decompress, decrypt, or remove protocol framing. + * Supplies a stream of bytes. RawSource is a base interface all other `kotlinx-io` data suppliers are built upon it. + * + * The interface should be implemented to read data from wherever it's located: from the network, storage, + * or a buffer in memory. Sources may be layered to transform supplied data, such as to decompress, decrypt, + * or remove protocol framing. * * Most applications shouldn't operate on a raw source directly, but rather on a [Source] which * is both more efficient and more convenient. Use [buffer] to wrap any raw source with a buffer. - * - * Sources are easy to test: just use a [Buffer] in your tests, and fill it with the data your - * application is to read. */ interface RawSource : Closeable { + // TODO: should be throw something if byteCount = 0? /** - * Removes at least 1, and up to `byteCount` bytes from this and appends them to `sink`. Returns + * Removes at least 1, and up to [byteCount] bytes from this and appends them to [sink]. Returns * the number of bytes read, or -1 if this source is exhausted. + * + * @throws IllegalArgumentException when [byteCount] is negative. */ @Throws(IOException::class) fun read(sink: Buffer, byteCount: Long): Long diff --git a/core/common/src/Segment.kt b/core/common/src/Segment.kt index a10a16b43..bbd41c9d4 100644 --- a/core/common/src/Segment.kt +++ b/core/common/src/Segment.kt @@ -28,8 +28,7 @@ import kotlin.jvm.JvmField * Each segment in a buffer is a circularly-linked list node referencing the following and * preceding segments in the buffer. * - * Each segment in the pool is a singly-linked list node referencing the rest of segments in the - * pool. + * Each segment in the pool is a singly-linked list node referencing the rest of segments in the pool. * * The underlying byte arrays of segments may be shared between buffers and byte strings. When a * segment's byte array is shared the segment may not be recycled, nor may its byte data be changed. diff --git a/core/common/src/Sink.kt b/core/common/src/Sink.kt index e4546c682..f86289068 100644 --- a/core/common/src/Sink.kt +++ b/core/common/src/Sink.kt @@ -21,8 +21,21 @@ package kotlinx.io /** - * A sink that keeps a buffer internally so that callers can do small writes without a performance - * penalty. + * A sink that facilitates typed data writes and keeps a buffer internally so that caller can write some data without + * sending it directly to an upstream. + * + * [Sink] is the main `kotlinx-io` interface to write data in client's code, + * any [RawSink] could be turned into [Sink] using [RawSink.buffer]. + * + * Depending on a kind of upstream and the number of bytes written, buffering may improve the performance + * by hiding the latency of small writes. + * + * Data stored inside the internal buffer could be sent to an upstream using [flush], [emit], or [emitCompleteSegments]: + * - [flush] writes the whole buffer to an upstream and then flushes the upstream. + * - [emit] writes all data from the buffer into the upstream without flushing it. + * - [emitCompleteSegments] writes only a part of data from the buffer. + * The latter is aimed to reduce memory footprint by keeping the buffer as small as possible without excessive writes + * to the upstream. */ expect sealed interface Sink : RawSink { /** @@ -35,7 +48,7 @@ expect sealed interface Sink : RawSink { * * @param source the array from which bytes will be written into this sink. * @param offset the beginning of data within the [source], 0 by default. - * @param byteCount amount of bytes to write, size of the [source] subarray starting at [offset] by default. + * @param byteCount the number of bytes to write, size of the [source] subarray starting at [offset] by default. * * @throws IndexOutOfBoundsException when a range specified by [offset] and [byteCount] * is out of range of [source] array indices. @@ -49,9 +62,6 @@ expect sealed interface Sink : RawSink { * Returns the number of bytes read which will be 0 if [source] is exhausted. * * @param source the source to consume data from. - * - * @sample kotlinx.io.AbstractSinkTest.writeAll - * @sample kotlinx.io.AbstractSinkTest.writeAllExhausted */ fun writeAll(source: RawSource): Long @@ -62,11 +72,9 @@ expect sealed interface Sink : RawSink { * attempt to read remaining bytes will be propagated to a caller of this method. * * @param source the source to consume data from. - * @param byteCount amount of bytes to read from [source] and to write into this sink. + * @param byteCount the number of bytes to read from [source] and to write into this sink. * * @throws IllegalArgumentException when [byteCount] is negative. - * - * @sample kotlinx.io.AbstractSinkTest.writeSource */ fun write(source: RawSource, byteCount: Long): Sink @@ -74,8 +82,6 @@ expect sealed interface Sink : RawSink { * Writes a byte to this sink. * * @param byte the byte to be written. - * - * @sample kotlinx.io.AbstractSinkTest.writeByte */ fun writeByte(byte: Int): Sink @@ -83,8 +89,6 @@ expect sealed interface Sink : RawSink { * Writes two bytes containing [short], in the big-endian order, to this sink. * * @param short the short integer to be written. - * - * @sample kotlinx.io.AbstractSinkTest.writeShort */ fun writeShort(short: Int): Sink @@ -92,8 +96,6 @@ expect sealed interface Sink : RawSink { * Writes four bytes containing [int], in the big-endian order, to this sink. * * @param int the integer to be written. - * - * @sample kotlinx.io.AbstractSinkTest.writeInt */ fun writeInt(int: Int): Sink @@ -101,8 +103,6 @@ expect sealed interface Sink : RawSink { * Writes eight bytes containing [long], in the big-endian order, to this sink. * * @param long the long integer to be written. - * - * @sample kotlinx.io.AbstractSinkTest.writeLong */ fun writeLong(long: Long): Sink diff --git a/core/common/src/Source.kt b/core/common/src/Source.kt index e4f1fe14f..a9404767c 100644 --- a/core/common/src/Source.kt +++ b/core/common/src/Source.kt @@ -21,9 +21,19 @@ package kotlinx.io /** - * A source that keeps a buffer internally so that callers can do small reads without a performance - * penalty. It also allows clients to read ahead, buffering as much as necessary before consuming - * input. + * A source that facilitates typed data reads and keeps a buffer internally so that callers can read chunks of data + * without requesting it from a downstream on every call. + * + * [Source] is the main `kotlinx-io` interface to read data in client's code, + * any [RawSource] could be converted into [Source] using [RawSource.buffer]. + * + * Depending on a kind of downstream and the number of bytes read, buffering may improve the performance by hiding + * the latency of small reads. + * + * The buffer is refilled on reads as necessary, but it is also possible to ensure it contains enough data + * using [require] or [request]. + * [Sink] also allows to skip unneeded prefix of data using [skip] and + * provides look ahead into incoming data, buffering as much as necessary, using [peek]. */ expect sealed interface Source : RawSource { /** @@ -42,10 +52,10 @@ expect sealed interface Source : RawSource { * Attempts to fill the buffer with at least [byteCount] bytes of data from the underlying source * and throw [EOFException] when the source is exhausted before fulfilling the requirement. * - * If the buffer already contains required amount of bytes then there will be no requests to + * If the buffer already contains required number of bytes then there will be no requests to * the underlying source. * - * @param byteCount amount of bytes that the buffer should contain. + * @param byteCount the number of bytes that the buffer should contain. * * @throws EOFException when the source is exhausted before the required bytes count could be read. */ @@ -58,7 +68,7 @@ expect sealed interface Source : RawSource { * `false` value returned by this method indicates that the underlying source was exhausted before * filling the buffer with [byteCount] bytes of data. * - * @param byteCount amount of bytes that the buffer should contain. + * @param byteCount the number of bytes that the buffer should contain. */ fun request(byteCount: Long): Boolean @@ -93,9 +103,9 @@ expect sealed interface Source : RawSource { /** * Reads and discards [byteCount] bytes from this source. * - * @param byteCount amount of bytes to be skipped. + * @param byteCount the number of bytes to be skipped. * - * @throws EOFException when the source is exhausted before the requested amount of bytes can be skipped. + * @throws EOFException when the source is exhausted before the requested number of bytes can be skipped. */ fun skip(byteCount: Long) @@ -107,7 +117,7 @@ expect sealed interface Source : RawSource { /** * Removes [byteCount] bytes from this source and returns them as a byte array. * - * @param byteCount amount of bytes that should be read from the source. + * @param byteCount the number of bytes that should be read from the source. * * @throws IllegalArgumentException when byteCount is negative. * @throws EOFException when the underlying source is exhausted before [byteCount] bytes of data could be read. @@ -127,7 +137,7 @@ expect sealed interface Source : RawSource { * * @param sink the array to which data will be written from this source. * @param offset the offset to start writing data into [sink] at, 0 by default. - * @param byteCount amount of bytes that should be written into [sink], + * @param byteCount the number of bytes that should be written into [sink], * size of the [sink] subarray starting at [offset] by default. * * @throws IndexOutOfBoundsException when a range specified by [offset] and [byteCount] @@ -139,7 +149,7 @@ expect sealed interface Source : RawSource { * Removes exactly [byteCount] bytes from this source and writes them to [sink]. * * @param sink the sink to which data will be written from this source. - * @param byteCount amount of bytes that should be written into [sink] + * @param byteCount the number of bytes that should be written into [sink] * * @throws IllegalArgumentException when [byteCount] is negative. * @throws EOFException when the requested number of bytes cannot be read. diff --git a/core/common/src/Utf8.kt b/core/common/src/Utf8.kt index af0439942..60ce40639 100644 --- a/core/common/src/Utf8.kt +++ b/core/common/src/Utf8.kt @@ -20,7 +20,7 @@ */ /** - * kotlinx-io assumes most applications use UTF-8 exclusively, and offers optimized implementations of + * `kotlinx-io` assumes most applications use UTF-8 exclusively, and offers optimized implementations of * common operations on UTF-8 strings. * *
diff --git a/core/jvm/src/Buffer.kt b/core/jvm/src/Buffer.kt index dea417f3c..d886c290c 100644 --- a/core/jvm/src/Buffer.kt +++ b/core/jvm/src/Buffer.kt @@ -21,18 +21,12 @@ package kotlinx.io import kotlinx.io.internal.* -import java.io.Closeable import java.io.EOFException import java.io.IOException import java.io.InputStream import java.io.OutputStream import java.nio.ByteBuffer import java.nio.channels.ByteChannel -import java.nio.charset.Charset -import java.security.InvalidKeyException -import java.security.MessageDigest -import javax.crypto.Mac -import javax.crypto.spec.SecretKeySpec actual class Buffer : Source, Sink, Cloneable, ByteChannel { @JvmField internal actual var head: Segment? = null @@ -307,15 +301,13 @@ actual class Buffer : Source, Sink, Cloneable, ByteChannel { override fun read(sink: Buffer, byteCount: Long): Long = commonRead(sink, byteCount) - override fun flush() {} + actual override fun flush() = Unit override fun isOpen() = true - override fun close() {} + actual override fun close() = Unit - override fun cancel() { - // Not cancelable. - } + actual override fun cancel() = Unit override fun equals(other: Any?): Boolean = commonEquals(other) @@ -329,6 +321,10 @@ actual class Buffer : Source, Sink, Cloneable, ByteChannel { actual fun copy(): Buffer = commonCopy() - /** Returns a deep copy of this buffer. */ + /** + * Returns a deep copy of this buffer. + * + * This method is equivalent to [copy]. + */ public override fun clone(): Buffer = copy() } diff --git a/core/native/src/Buffer.kt b/core/native/src/Buffer.kt index 3e36432e6..dd84d7b23 100644 --- a/core/native/src/Buffer.kt +++ b/core/native/src/Buffer.kt @@ -107,13 +107,11 @@ actual class Buffer : Source, Sink { override fun read(sink: Buffer, byteCount: Long): Long = commonRead(sink, byteCount) - override fun flush() = Unit + actual override fun flush() = Unit - override fun close() = Unit + actual override fun close() = Unit - override fun cancel() { - // Not cancelable. - } + actual override fun cancel() = Unit override fun equals(other: Any?): Boolean = commonEquals(other) From 2f25f19bc35efc239f8e83b18864e8886486742c Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Fri, 2 Jun 2023 16:46:45 +0200 Subject: [PATCH 12/83] Added module description --- build.gradle.kts | 1 - core/Module.md | 33 +++++++++++++++++++++++++++++++++ core/build.gradle.kts | 10 ++++++++++ 3 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 core/Module.md diff --git a/build.gradle.kts b/build.gradle.kts index 89dcac85d..7d1bea817 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -44,7 +44,6 @@ subprojects { apply(plugin = "maven-publish") apply(plugin = "signing") - apply(plugin = "org.jetbrains.dokka") publishing { repositories { diff --git a/core/Module.md b/core/Module.md new file mode 100644 index 000000000..404fa4763 --- /dev/null +++ b/core/Module.md @@ -0,0 +1,33 @@ +# Module kotlinx-io-core + +The module provides core multiplatform IO primitives and integrates it with platform-specific APIs. +`kotlinx-io` aims to provide a concise but powerful API along with efficient implementation. + +The main interfaces for the IO interaction are [kotlinx.io.Source] and [kotlinx.io.Sink] providing buffered read and +write operations for integer types, byte arrays, and other sources and sinks. There are also extension functions +bringing support for strings and other types. +Implementations of these interfaces are built on top of [kotlinx.io.Buffer], [kotlinx.io.RawSource], +and [kotlinx.io.RawSink]. + +A central part of the library, [kotlinx.io.Buffer], is a container optimized to reduce memory allocations and to avoid +data copying when possible. + +[kotlinx.io.RawSource] and [kotlinx.io.RawSink] are interfaces aimed for integration with anything that can provide +or receive data: network interfaces, files, etc. The module provides integration with some platform-specific IO APIs, +but if something not yet supported by the library needs to be integrated, then these interfaces are exactly what should +be implemented for that. + +Here is an example showing how to manually create BSON `kotlinx.io` +```kotlin + +// TODO: can't find out concise and simple exa,ple that will use multiple features of the library. + +``` + +# Package kotlinx.io + +Core IO primitives. + +# Package kotlinx.io.files + +Basic API for working with files. \ No newline at end of file diff --git a/core/build.gradle.kts b/core/build.gradle.kts index f5e159bcf..356a64c2f 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -3,11 +3,15 @@ * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENCE file. */ +import org.jetbrains.dokka.gradle.DokkaMultiModuleTask +import org.jetbrains.dokka.gradle.DokkaTask +import org.jetbrains.dokka.gradle.DokkaTaskPartial import org.jetbrains.kotlin.gradle.plugin.* plugins { kotlin("multiplatform") id("org.jetbrains.kotlinx.kover") version "0.7.0" + id("org.jetbrains.dokka") version "1.8.10" } kotlin { @@ -53,3 +57,9 @@ fun KotlinSourceSet.configureSourceSet() { progressiveMode = true } } + +tasks.withType().configureEach { + dokkaSourceSets.configureEach { + includes.from("Module.md") + } +} \ No newline at end of file From 863b35961a3ab72d4dc36165bec90356076f4442 Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Mon, 5 Jun 2023 10:21:14 +0200 Subject: [PATCH 13/83] Move internal utf8-related code to -Utf8.kt --- core/api/kotlinx-io-core.api | 6 - core/common/src/Utf8.kt | 443 ----------------------------- core/common/src/internal/-Utf8.kt | 433 +++++++++++++++++++++++++++- core/common/test/Utf8KotlinTest.kt | 2 + 4 files changed, 428 insertions(+), 456 deletions(-) diff --git a/core/api/kotlinx-io-core.api b/core/api/kotlinx-io-core.api index a52b8bcfe..191904061 100644 --- a/core/api/kotlinx-io-core.api +++ b/core/api/kotlinx-io-core.api @@ -196,9 +196,3 @@ public final class kotlinx/io/files/PathsKt { public static final fun source (Lkotlinx/io/files/Path;)Lkotlinx/io/Source; } -public final class kotlinx/io/internal/_Utf8Kt { - public static final fun commonAsUtf8ToByteArray (Ljava/lang/String;)[B - public static final fun commonToUtf8String ([BII)Ljava/lang/String; - public static synthetic fun commonToUtf8String$default ([BIIILjava/lang/Object;)Ljava/lang/String; -} - diff --git a/core/common/src/Utf8.kt b/core/common/src/Utf8.kt index 60ce40639..48f59cc46 100644 --- a/core/common/src/Utf8.kt +++ b/core/common/src/Utf8.kt @@ -127,449 +127,6 @@ fun String.utf8Size(beginIndex: Int = 0, endIndex: Int = length): Long { return result } -internal const val REPLACEMENT_BYTE: Byte = '?'.code.toByte() -internal const val REPLACEMENT_CHARACTER: Char = '\ufffd' -internal const val REPLACEMENT_CODE_POINT: Int = REPLACEMENT_CHARACTER.code - -@Suppress("NOTHING_TO_INLINE") // Syntactic sugar. -internal inline fun isIsoControl(codePoint: Int): Boolean = - (codePoint in 0x00..0x1F) || (codePoint in 0x7F..0x9F) - -@Suppress("NOTHING_TO_INLINE") // Syntactic sugar. -internal inline fun isUtf8Continuation(byte: Byte): Boolean { - // 0b10xxxxxx - return byte and 0xc0 == 0x80 -} - -// TODO combine with Buffer.writeUtf8? -// TODO combine with Buffer.writeUtf8CodePoint? -internal inline fun String.processUtf8Bytes( - beginIndex: Int, - endIndex: Int, - yield: (Byte) -> Unit -) { - // Transcode a UTF-16 String to UTF-8 bytes. - var index = beginIndex - while (index < endIndex) { - val c = this[index] - - when { - c < '\u0080' -> { - // Emit a 7-bit character with 1 byte. - yield(c.code.toByte()) // 0xxxxxxx - index++ - - // Assume there is going to be more ASCII - while (index < endIndex && this[index] < '\u0080') { - yield(this[index++].code.toByte()) - } - } - - c < '\u0800' -> { - // Emit a 11-bit character with 2 bytes. - /* ktlint-disable no-multi-spaces */ - yield((c.code shr 6 or 0xc0).toByte()) // 110xxxxx - yield((c.code and 0x3f or 0x80).toByte()) // 10xxxxxx - /* ktlint-enable no-multi-spaces */ - index++ - } - - c !in '\ud800'..'\udfff' -> { - // Emit a 16-bit character with 3 bytes. - /* ktlint-disable no-multi-spaces */ - yield((c.code shr 12 or 0xe0).toByte()) // 1110xxxx - yield((c.code shr 6 and 0x3f or 0x80).toByte()) // 10xxxxxx - yield((c.code and 0x3f or 0x80).toByte()) // 10xxxxxx - /* ktlint-enable no-multi-spaces */ - index++ - } - - else -> { - // c is a surrogate. Make sure it is a high surrogate & that its successor is a low - // surrogate. If not, the UTF-16 is invalid, in which case we emit a replacement - // byte. - if (c > '\udbff' || - endIndex <= index + 1 || - this[index + 1] !in '\udc00'..'\udfff' - ) { - yield(REPLACEMENT_BYTE) - index++ - } else { - // UTF-16 high surrogate: 110110xxxxxxxxxx (10 bits) - // UTF-16 low surrogate: 110111yyyyyyyyyy (10 bits) - // Unicode code point: 00010000000000000000 + xxxxxxxxxxyyyyyyyyyy (21 bits) - val codePoint = ( - ((c.code shl 10) + this[index + 1].code) + - (0x010000 - (0xd800 shl 10) - 0xdc00) - ) - - // Emit a 21-bit character with 4 bytes. - /* ktlint-disable no-multi-spaces */ - yield((codePoint shr 18 or 0xf0).toByte()) // 11110xxx - yield((codePoint shr 12 and 0x3f or 0x80).toByte()) // 10xxxxxx - yield((codePoint shr 6 and 0x3f or 0x80).toByte()) // 10xxyyyy - yield((codePoint and 0x3f or 0x80).toByte()) // 10yyyyyy - /* ktlint-enable no-multi-spaces */ - index += 2 - } - } - } - } -} - -// TODO combine with Buffer.readUtf8CodePoint? -internal inline fun ByteArray.processUtf8CodePoints( - beginIndex: Int, - endIndex: Int, - yield: (Int) -> Unit -) { - var index = beginIndex - while (index < endIndex) { - val b0 = this[index] - when { - b0 >= 0 -> { - // 0b0xxxxxxx - yield(b0.toInt()) - index++ - - // Assume there is going to be more ASCII - while (index < endIndex && this[index] >= 0) { - yield(this[index++].toInt()) - } - } - b0 shr 5 == -2 -> { - // 0b110xxxxx - index += process2Utf8Bytes(index, endIndex) { yield(it) } - } - b0 shr 4 == -2 -> { - // 0b1110xxxx - index += process3Utf8Bytes(index, endIndex) { yield(it) } - } - b0 shr 3 == -2 -> { - // 0b11110xxx - index += process4Utf8Bytes(index, endIndex) { yield(it) } - } - else -> { - // 0b10xxxxxx - Unexpected continuation - // 0b111111xxx - Unknown encoding - yield(REPLACEMENT_CODE_POINT) - index++ - } - } - } -} - -// Value added to the high UTF-16 surrogate after shifting -internal const val HIGH_SURROGATE_HEADER = 0xd800 - (0x010000 ushr 10) -// Value added to the low UTF-16 surrogate after masking -internal const val LOG_SURROGATE_HEADER = 0xdc00 - -// TODO combine with Buffer.readUtf8? -internal inline fun ByteArray.processUtf16Chars( - beginIndex: Int, - endIndex: Int, - yield: (Char) -> Unit -) { - var index = beginIndex - while (index < endIndex) { - val b0 = this[index] - when { - b0 >= 0 -> { - // 0b0xxxxxxx - yield(b0.toInt().toChar()) - index++ - - // Assume there is going to be more ASCII - // This is almost double the performance of the outer loop - while (index < endIndex && this[index] >= 0) { - yield(this[index++].toInt().toChar()) - } - } - b0 shr 5 == -2 -> { - // 0b110xxxxx - index += process2Utf8Bytes(index, endIndex) { yield(it.toChar()) } - } - b0 shr 4 == -2 -> { - // 0b1110xxxx - index += process3Utf8Bytes(index, endIndex) { yield(it.toChar()) } - } - b0 shr 3 == -2 -> { - // 0b11110xxx - index += process4Utf8Bytes(index, endIndex) { codePoint -> - if (codePoint != REPLACEMENT_CODE_POINT) { - // Unicode code point: 00010000000000000000 + xxxxxxxxxxyyyyyyyyyy (21 bits) - // UTF-16 high surrogate: 110110xxxxxxxxxx (10 bits) - // UTF-16 low surrogate: 110111yyyyyyyyyy (10 bits) - /* ktlint-disable no-multi-spaces paren-spacing */ - yield(((codePoint ushr 10 ) + HIGH_SURROGATE_HEADER).toChar()) - /* ktlint-enable no-multi-spaces paren-spacing */ - yield(((codePoint and 0x03ff) + LOG_SURROGATE_HEADER).toChar()) - } else { - yield(REPLACEMENT_CHARACTER) - } - } - } - else -> { - // 0b10xxxxxx - Unexpected continuation - // 0b111111xxx - Unknown encoding - yield(REPLACEMENT_CHARACTER) - index++ - } - } - } -} - -// ===== UTF-8 Encoding and Decoding ===== // -/* -The following 3 methods take advantage of using XOR on 2's complement store -numbers to quickly and efficiently combine the important data of UTF-8 encoded -bytes. This will be best explained using an example, so lets take the following -encoded character '∇' = \u2207. - -Using the Unicode code point for this character, 0x2207, we will split the -binary representation into 3 sections as follows: - - 0x2207 = 0b0010 0010 0000 0111 - xxxx yyyy yyzz zzzz - -Now take each section of bits and add the appropriate header: - - utf8(0x2207) = 0b1110 xxxx 0b10yy yyyy 0b10zz zzzz - = 0b1110 0010 0b1000 1000 0b1000 0111 - = 0xe2 0x88 0x87 - -We have now just encoded this as a 3 byte UTF-8 character. More information -about different sizes of characters can be found here: - https://en.wikipedia.org/wiki/UTF-8 - -Encoding was pretty easy, but decoding is a bit more complicated. We need to -first determine the number of bytes used to represent the character, strip all -the headers, and then combine all the bits into a single integer. Let's use the -character we just encoded and work backwards, taking advantage of 2's complement -integer representation and the XOR function. - -Let's look at the decimal representation of these bytes: - - 0xe2, 0x88, 0x87 = -30, -120, -121 - -The first interesting thing to notice is that UTF-8 headers all start with 1 - -except for ASCII which is encoded as a single byte - which means all UTF-8 bytes -will be negative. So converting these to integers results in a lot of 1's added -because they are store as 2's complement: - - 0xe2 = -30 = 0xffff ffe2 - 0x88 = -120 = 0xffff ff88 - 0x87 = -121 = 0xffff ff87 - -Now let's XOR these with their corresponding UTF-8 byte headers to see what -happens: - - 0xffff ffe2 xor 0xffff ffe0 = 0x0000 0002 - 0xffff ff88 xor 0xffff ff80 = 0x0000 0008 - 0xffff ff87 xor 0xffff ff80 = 0x0000 0007 - -***This is why we must first convert the byte header mask to a byte and then -back to an integer, so it is properly converted to a 2's complement negative -number which can be applied to each byte.*** - -Now let's look at the binary representation to see how we can combine these to -create the Unicode code point: - - 0b0000 0010 0b0000 1000 0b0000 0111 - 0b1110 xxxx 0b10yy yyyy 0b10zz zzzz - -Combining each section will require some bit shifting, but then they can just -be OR'd together. They can also be XOR'd together which makes use of a single, -COMMUTATIVE, operator through the entire calculation. - - << 12 = 00000010 - << 6 = 00001000 - << 0 = 00000111 - XOR = 00000010001000000111 - - code point = 0b0010 0010 0000 0111 - = 0x2207 - -And there we have it! The decoded UTF-8 character '∇'! And because the XOR -operator is commutative, we can re-arrange all this XOR and shifting to create -a single mask that can be applied to 3-byte UTF-8 characters after their bytes -have been shifted and XOR'd together. - */ - -// Mask used to remove byte headers from a 2 byte encoded UTF-8 character -internal const val MASK_2BYTES = 0x0f80 -// MASK_2BYTES = -// (0xc0.toByte() shl 6) xor -// (0x80.toByte().toInt()) - -internal inline fun ByteArray.process2Utf8Bytes( - beginIndex: Int, - endIndex: Int, - yield: (Int) -> Unit -): Int { - if (endIndex <= beginIndex + 1) { - yield(REPLACEMENT_CODE_POINT) - // Only 1 byte remaining - underflow - return 1 - } - - val b0 = this[beginIndex] - val b1 = this[beginIndex + 1] - if (!isUtf8Continuation(b1)) { - yield(REPLACEMENT_CODE_POINT) - return 1 - } - - val codePoint = - ( - MASK_2BYTES - xor (b1.toInt()) - xor (b0.toInt() shl 6) - ) - - when { - codePoint < 0x80 -> { - yield(REPLACEMENT_CODE_POINT) // Reject overlong code points. - } - else -> { - yield(codePoint) - } - } - return 2 -} - -// Mask used to remove byte headers from a 3 byte encoded UTF-8 character -internal const val MASK_3BYTES = -0x01e080 -// MASK_3BYTES = -// (0xe0.toByte() shl 12) xor -// (0x80.toByte() shl 6) xor -// (0x80.toByte().toInt()) - -internal inline fun ByteArray.process3Utf8Bytes( - beginIndex: Int, - endIndex: Int, - yield: (Int) -> Unit -): Int { - if (endIndex <= beginIndex + 2) { - // At least 2 bytes remaining - yield(REPLACEMENT_CODE_POINT) - if (endIndex <= beginIndex + 1 || !isUtf8Continuation(this[beginIndex + 1])) { - // Only 1 byte remaining - underflow - // Or 2nd byte is not a continuation - malformed - return 1 - } else { - // Only 2 bytes remaining - underflow - return 2 - } - } - - val b0 = this[beginIndex] - val b1 = this[beginIndex + 1] - if (!isUtf8Continuation(b1)) { - yield(REPLACEMENT_CODE_POINT) - return 1 - } - val b2 = this[beginIndex + 2] - if (!isUtf8Continuation(b2)) { - yield(REPLACEMENT_CODE_POINT) - return 2 - } - - val codePoint = - ( - MASK_3BYTES - xor (b2.toInt()) - xor (b1.toInt() shl 6) - xor (b0.toInt() shl 12) - ) - - when { - codePoint < 0x800 -> { - yield(REPLACEMENT_CODE_POINT) // Reject overlong code points. - } - codePoint in 0xd800..0xdfff -> { - yield(REPLACEMENT_CODE_POINT) // Reject partial surrogates. - } - else -> { - yield(codePoint) - } - } - return 3 -} - -// Mask used to remove byte headers from a 4 byte encoded UTF-8 character -internal const val MASK_4BYTES = 0x381f80 -// MASK_4BYTES = -// (0xf0.toByte() shl 18) xor -// (0x80.toByte() shl 12) xor -// (0x80.toByte() shl 6) xor -// (0x80.toByte().toInt()) - -internal inline fun ByteArray.process4Utf8Bytes( - beginIndex: Int, - endIndex: Int, - yield: (Int) -> Unit -): Int { - if (endIndex <= beginIndex + 3) { - // At least 3 bytes remaining - yield(REPLACEMENT_CODE_POINT) - if (endIndex <= beginIndex + 1 || !isUtf8Continuation(this[beginIndex + 1])) { - // Only 1 byte remaining - underflow - // Or 2nd byte is not a continuation - malformed - return 1 - } else if (endIndex <= beginIndex + 2 || !isUtf8Continuation(this[beginIndex + 2])) { - // Only 2 bytes remaining - underflow - // Or 3rd byte is not a continuation - malformed - return 2 - } else { - // Only 3 bytes remaining - underflow - return 3 - } - } - - val b0 = this[beginIndex] - val b1 = this[beginIndex + 1] - if (!isUtf8Continuation(b1)) { - yield(REPLACEMENT_CODE_POINT) - return 1 - } - val b2 = this[beginIndex + 2] - if (!isUtf8Continuation(b2)) { - yield(REPLACEMENT_CODE_POINT) - return 2 - } - val b3 = this[beginIndex + 3] - if (!isUtf8Continuation(b3)) { - yield(REPLACEMENT_CODE_POINT) - return 3 - } - - val codePoint = - ( - MASK_4BYTES - xor (b3.toInt()) - xor (b2.toInt() shl 6) - xor (b1.toInt() shl 12) - xor (b0.toInt() shl 18) - ) - - when { - codePoint > 0x10ffff -> { - yield(REPLACEMENT_CODE_POINT) // Reject code points larger than the Unicode maximum. - } - codePoint in 0xd800..0xdfff -> { - yield(REPLACEMENT_CODE_POINT) // Reject partial surrogates. - } - codePoint < 0x10000 -> { - yield(REPLACEMENT_CODE_POINT) // Reject overlong code points. - } - else -> { - yield(codePoint) - } - } - return 4 -} - /** * Encodes [codePoint] in UTF-8 and writes it to this sink. * diff --git a/core/common/src/internal/-Utf8.kt b/core/common/src/internal/-Utf8.kt index 8fcf1f87d..ffcd3298a 100644 --- a/core/common/src/internal/-Utf8.kt +++ b/core/common/src/internal/-Utf8.kt @@ -23,11 +23,7 @@ package kotlinx.io.internal import kotlinx.io.* -// TODO For benchmarking, these methods need to be available but preferably invisible -// to everything else. Putting them in this file, `-Utf8.kt`, makes them invisible to -// Java but still visible to Kotlin. - -fun ByteArray.commonToUtf8String(beginIndex: Int = 0, endIndex: Int = size): String { +internal fun ByteArray.commonToUtf8String(beginIndex: Int = 0, endIndex: Int = size): String { if (beginIndex < 0 || endIndex > size || beginIndex > endIndex) { throw ArrayIndexOutOfBoundsException("size=$size beginIndex=$beginIndex endIndex=$endIndex") } @@ -41,12 +37,12 @@ fun ByteArray.commonToUtf8String(beginIndex: Int = 0, endIndex: Int = size): Str return chars.concatToString(0, length) } -fun String.commonAsUtf8ToByteArray(): ByteArray { +internal fun String.commonAsUtf8ToByteArray(): ByteArray { val bytes = ByteArray(4 * length) // Assume ASCII until a UTF-8 code point is observed. This is ugly but yields // about a 2x performance increase for pure ASCII. - for (index in 0 until length) { + for (index in indices) { val b0 = this[index] if (b0 >= '\u0080') { var size = index @@ -60,3 +56,426 @@ fun String.commonAsUtf8ToByteArray(): ByteArray { return bytes.copyOf(length) } + +internal const val REPLACEMENT_BYTE: Byte = '?'.code.toByte() +internal const val REPLACEMENT_CHARACTER: Char = '\ufffd' +internal const val REPLACEMENT_CODE_POINT: Int = REPLACEMENT_CHARACTER.code + +@Suppress("NOTHING_TO_INLINE") // Syntactic sugar. +internal inline fun isIsoControl(codePoint: Int): Boolean = + (codePoint in 0x00..0x1F) || (codePoint in 0x7F..0x9F) + +@Suppress("NOTHING_TO_INLINE") // Syntactic sugar. +internal inline fun isUtf8Continuation(byte: Byte): Boolean { + // 0b10xxxxxx + return byte and 0xc0 == 0x80 +} + +internal inline fun String.processUtf8Bytes( + beginIndex: Int, + endIndex: Int, + yield: (Byte) -> Unit +) { + // Transcode a UTF-16 String to UTF-8 bytes. + var index = beginIndex + while (index < endIndex) { + val c = this[index] + + when { + c < '\u0080' -> { + // Emit a 7-bit character with 1 byte. + yield(c.code.toByte()) // 0xxxxxxx + index++ + + // Assume there is going to be more ASCII + while (index < endIndex && this[index] < '\u0080') { + yield(this[index++].code.toByte()) + } + } + + c < '\u0800' -> { + // Emit a 11-bit character with 2 bytes. + /* ktlint-disable no-multi-spaces */ + yield((c.code shr 6 or 0xc0).toByte()) // 110xxxxx + yield((c.code and 0x3f or 0x80).toByte()) // 10xxxxxx + /* ktlint-enable no-multi-spaces */ + index++ + } + + c !in '\ud800'..'\udfff' -> { + // Emit a 16-bit character with 3 bytes. + /* ktlint-disable no-multi-spaces */ + yield((c.code shr 12 or 0xe0).toByte()) // 1110xxxx + yield((c.code shr 6 and 0x3f or 0x80).toByte()) // 10xxxxxx + yield((c.code and 0x3f or 0x80).toByte()) // 10xxxxxx + /* ktlint-enable no-multi-spaces */ + index++ + } + + else -> { + // c is a surrogate. Make sure it is a high surrogate & that its successor is a low + // surrogate. If not, the UTF-16 is invalid, in which case we emit a replacement + // byte. + if (c > '\udbff' || + endIndex <= index + 1 || + this[index + 1] !in '\udc00'..'\udfff' + ) { + yield(REPLACEMENT_BYTE) + index++ + } else { + // UTF-16 high surrogate: 110110xxxxxxxxxx (10 bits) + // UTF-16 low surrogate: 110111yyyyyyyyyy (10 bits) + // Unicode code point: 00010000000000000000 + xxxxxxxxxxyyyyyyyyyy (21 bits) + val codePoint = (((c.code shl 10) + this[index + 1].code) + (0x010000 - (0xd800 shl 10) - 0xdc00)) + + // Emit a 21-bit character with 4 bytes. + /* ktlint-disable no-multi-spaces */ + yield((codePoint shr 18 or 0xf0).toByte()) // 11110xxx + yield((codePoint shr 12 and 0x3f or 0x80).toByte()) // 10xxxxxx + yield((codePoint shr 6 and 0x3f or 0x80).toByte()) // 10xxyyyy + yield((codePoint and 0x3f or 0x80).toByte()) // 10yyyyyy + /* ktlint-enable no-multi-spaces */ + index += 2 + } + } + } + } +} + +internal inline fun ByteArray.processUtf8CodePoints( + beginIndex: Int, + endIndex: Int, + yield: (Int) -> Unit +) { + var index = beginIndex + while (index < endIndex) { + val b0 = this[index] + when { + b0 >= 0 -> { + // 0b0xxxxxxx + yield(b0.toInt()) + index++ + + // Assume there is going to be more ASCII + while (index < endIndex && this[index] >= 0) { + yield(this[index++].toInt()) + } + } + b0 shr 5 == -2 -> { + // 0b110xxxxx + index += process2Utf8Bytes(index, endIndex) { yield(it) } + } + b0 shr 4 == -2 -> { + // 0b1110xxxx + index += process3Utf8Bytes(index, endIndex) { yield(it) } + } + b0 shr 3 == -2 -> { + // 0b11110xxx + index += process4Utf8Bytes(index, endIndex) { yield(it) } + } + else -> { + // 0b10xxxxxx - Unexpected continuation + // 0b111111xxx - Unknown encoding + yield(REPLACEMENT_CODE_POINT) + index++ + } + } + } +} + +// Value added to the high UTF-16 surrogate after shifting +internal const val HIGH_SURROGATE_HEADER = 0xd800 - (0x010000 ushr 10) +// Value added to the low UTF-16 surrogate after masking +internal const val LOG_SURROGATE_HEADER = 0xdc00 + +internal inline fun ByteArray.processUtf16Chars( + beginIndex: Int, + endIndex: Int, + yield: (Char) -> Unit +) { + var index = beginIndex + while (index < endIndex) { + val b0 = this[index] + when { + b0 >= 0 -> { + // 0b0xxxxxxx + yield(b0.toInt().toChar()) + index++ + + // Assume there is going to be more ASCII + // This is almost double the performance of the outer loop + while (index < endIndex && this[index] >= 0) { + yield(this[index++].toInt().toChar()) + } + } + b0 shr 5 == -2 -> { + // 0b110xxxxx + index += process2Utf8Bytes(index, endIndex) { yield(it.toChar()) } + } + b0 shr 4 == -2 -> { + // 0b1110xxxx + index += process3Utf8Bytes(index, endIndex) { yield(it.toChar()) } + } + b0 shr 3 == -2 -> { + // 0b11110xxx + index += process4Utf8Bytes(index, endIndex) { codePoint -> + if (codePoint != REPLACEMENT_CODE_POINT) { + // Unicode code point: 00010000000000000000 + xxxxxxxxxxyyyyyyyyyy (21 bits) + // UTF-16 high surrogate: 110110xxxxxxxxxx (10 bits) + // UTF-16 low surrogate: 110111yyyyyyyyyy (10 bits) + /* ktlint-disable no-multi-spaces paren-spacing */ + yield(((codePoint ushr 10 ) + HIGH_SURROGATE_HEADER).toChar()) + /* ktlint-enable no-multi-spaces paren-spacing */ + yield(((codePoint and 0x03ff) + LOG_SURROGATE_HEADER).toChar()) + } else { + yield(REPLACEMENT_CHARACTER) + } + } + } + else -> { + // 0b10xxxxxx - Unexpected continuation + // 0b111111xxx - Unknown encoding + yield(REPLACEMENT_CHARACTER) + index++ + } + } + } +} + +// ===== UTF-8 Encoding and Decoding ===== // +/* +The following 3 methods take advantage of using XOR on 2's complement store +numbers to quickly and efficiently combine the important data of UTF-8 encoded +bytes. This will be best explained using an example, so lets take the following +encoded character '∇' = \u2207. + +Using the Unicode code point for this character, 0x2207, we will split the +binary representation into 3 sections as follows: + + 0x2207 = 0b0010 0010 0000 0111 + xxxx yyyy yyzz zzzz + +Now take each section of bits and add the appropriate header: + + utf8(0x2207) = 0b1110 xxxx 0b10yy yyyy 0b10zz zzzz + = 0b1110 0010 0b1000 1000 0b1000 0111 + = 0xe2 0x88 0x87 + +We have now just encoded this as a 3 byte UTF-8 character. More information +about different sizes of characters can be found here: + https://en.wikipedia.org/wiki/UTF-8 + +Encoding was pretty easy, but decoding is a bit more complicated. We need to +first determine the number of bytes used to represent the character, strip all +the headers, and then combine all the bits into a single integer. Let's use the +character we just encoded and work backwards, taking advantage of 2's complement +integer representation and the XOR function. + +Let's look at the decimal representation of these bytes: + + 0xe2, 0x88, 0x87 = -30, -120, -121 + +The first interesting thing to notice is that UTF-8 headers all start with 1 - +except for ASCII which is encoded as a single byte - which means all UTF-8 bytes +will be negative. So converting these to integers results in a lot of 1's added +because they are store as 2's complement: + + 0xe2 = -30 = 0xffff ffe2 + 0x88 = -120 = 0xffff ff88 + 0x87 = -121 = 0xffff ff87 + +Now let's XOR these with their corresponding UTF-8 byte headers to see what +happens: + + 0xffff ffe2 xor 0xffff ffe0 = 0x0000 0002 + 0xffff ff88 xor 0xffff ff80 = 0x0000 0008 + 0xffff ff87 xor 0xffff ff80 = 0x0000 0007 + +***This is why we must first convert the byte header mask to a byte and then +back to an integer, so it is properly converted to a 2's complement negative +number which can be applied to each byte.*** + +Now let's look at the binary representation to see how we can combine these to +create the Unicode code point: + + 0b0000 0010 0b0000 1000 0b0000 0111 + 0b1110 xxxx 0b10yy yyyy 0b10zz zzzz + +Combining each section will require some bit shifting, but then they can just +be OR'd together. They can also be XOR'd together which makes use of a single, +COMMUTATIVE, operator through the entire calculation. + + << 12 = 00000010 + << 6 = 00001000 + << 0 = 00000111 + XOR = 00000010001000000111 + + code point = 0b0010 0010 0000 0111 + = 0x2207 + +And there we have it! The decoded UTF-8 character '∇'! And because the XOR +operator is commutative, we can re-arrange all this XOR and shifting to create +a single mask that can be applied to 3-byte UTF-8 characters after their bytes +have been shifted and XOR'd together. + */ + +// Mask used to remove byte headers from a 2 byte encoded UTF-8 character +internal const val MASK_2BYTES = 0x0f80 +// MASK_2BYTES = +// (0xc0.toByte() shl 6) xor +// (0x80.toByte().toInt()) + +internal inline fun ByteArray.process2Utf8Bytes( + beginIndex: Int, + endIndex: Int, + yield: (Int) -> Unit +): Int { + if (endIndex <= beginIndex + 1) { + yield(REPLACEMENT_CODE_POINT) + // Only 1 byte remaining - underflow + return 1 + } + + val b0 = this[beginIndex] + val b1 = this[beginIndex + 1] + if (!isUtf8Continuation(b1)) { + yield(REPLACEMENT_CODE_POINT) + return 1 + } + + val codePoint = (MASK_2BYTES + xor (b1.toInt()) + xor (b0.toInt() shl 6)) + + when { + codePoint < 0x80 -> yield(REPLACEMENT_CODE_POINT) // Reject overlong code points. + else -> yield(codePoint) + } + return 2 +} + +// Mask used to remove byte headers from a 3 byte encoded UTF-8 character +internal const val MASK_3BYTES = -0x01e080 +// MASK_3BYTES = +// (0xe0.toByte() shl 12) xor +// (0x80.toByte() shl 6) xor +// (0x80.toByte().toInt()) + +internal inline fun ByteArray.process3Utf8Bytes( + beginIndex: Int, + endIndex: Int, + yield: (Int) -> Unit +): Int { + if (endIndex <= beginIndex + 2) { + // At least 2 bytes remaining + yield(REPLACEMENT_CODE_POINT) + if (endIndex <= beginIndex + 1 || !isUtf8Continuation(this[beginIndex + 1])) { + // Only 1 byte remaining - underflow + // Or 2nd byte is not a continuation - malformed + return 1 + } else { + // Only 2 bytes remaining - underflow + return 2 + } + } + + val b0 = this[beginIndex] + val b1 = this[beginIndex + 1] + if (!isUtf8Continuation(b1)) { + yield(REPLACEMENT_CODE_POINT) + return 1 + } + val b2 = this[beginIndex + 2] + if (!isUtf8Continuation(b2)) { + yield(REPLACEMENT_CODE_POINT) + return 2 + } + + val codePoint = (MASK_3BYTES + xor (b2.toInt()) + xor (b1.toInt() shl 6) + xor (b0.toInt() shl 12)) + + when { + codePoint < 0x800 -> { + yield(REPLACEMENT_CODE_POINT) // Reject overlong code points. + } + codePoint in 0xd800..0xdfff -> { + yield(REPLACEMENT_CODE_POINT) // Reject partial surrogates. + } + else -> { + yield(codePoint) + } + } + return 3 +} + +// Mask used to remove byte headers from a 4 byte encoded UTF-8 character +internal const val MASK_4BYTES = 0x381f80 +// MASK_4BYTES = +// (0xf0.toByte() shl 18) xor +// (0x80.toByte() shl 12) xor +// (0x80.toByte() shl 6) xor +// (0x80.toByte().toInt()) + +internal inline fun ByteArray.process4Utf8Bytes( + beginIndex: Int, + endIndex: Int, + yield: (Int) -> Unit +): Int { + if (endIndex <= beginIndex + 3) { + // At least 3 bytes remaining + yield(REPLACEMENT_CODE_POINT) + if (endIndex <= beginIndex + 1 || !isUtf8Continuation(this[beginIndex + 1])) { + // Only 1 byte remaining - underflow + // Or 2nd byte is not a continuation - malformed + return 1 + } else if (endIndex <= beginIndex + 2 || !isUtf8Continuation(this[beginIndex + 2])) { + // Only 2 bytes remaining - underflow + // Or 3rd byte is not a continuation - malformed + return 2 + } else { + // Only 3 bytes remaining - underflow + return 3 + } + } + + val b0 = this[beginIndex] + val b1 = this[beginIndex + 1] + if (!isUtf8Continuation(b1)) { + yield(REPLACEMENT_CODE_POINT) + return 1 + } + val b2 = this[beginIndex + 2] + if (!isUtf8Continuation(b2)) { + yield(REPLACEMENT_CODE_POINT) + return 2 + } + val b3 = this[beginIndex + 3] + if (!isUtf8Continuation(b3)) { + yield(REPLACEMENT_CODE_POINT) + return 3 + } + + val codePoint = (MASK_4BYTES + xor (b3.toInt()) + xor (b2.toInt() shl 6) + xor (b1.toInt() shl 12) + xor (b0.toInt() shl 18)) + + when { + codePoint > 0x10ffff -> { + yield(REPLACEMENT_CODE_POINT) // Reject code points larger than the Unicode maximum. + } + codePoint in 0xd800..0xdfff -> { + yield(REPLACEMENT_CODE_POINT) // Reject partial surrogates. + } + codePoint < 0x10000 -> { + yield(REPLACEMENT_CODE_POINT) // Reject overlong code points. + } + else -> { + yield(codePoint) + } + } + return 4 +} \ No newline at end of file diff --git a/core/common/test/Utf8KotlinTest.kt b/core/common/test/Utf8KotlinTest.kt index 0d8e4bd7e..bdde7180f 100644 --- a/core/common/test/Utf8KotlinTest.kt +++ b/core/common/test/Utf8KotlinTest.kt @@ -21,7 +21,9 @@ package kotlinx.io +import kotlinx.io.internal.REPLACEMENT_CODE_POINT import kotlinx.io.internal.commonAsUtf8ToByteArray +import kotlinx.io.internal.processUtf8CodePoints import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFailsWith From 8b0028987a9fb3795b0ad36122c10c8d3b7fb984 Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Mon, 5 Jun 2023 11:02:17 +0200 Subject: [PATCH 14/83] Enabled toString test, fixed implementation. --- core/common/src/internal/-Buffer.kt | 7 +++-- core/common/test/CommonBufferTest.kt | 46 ++++++++++------------------ 2 files changed, 21 insertions(+), 32 deletions(-) diff --git a/core/common/src/internal/-Buffer.kt b/core/common/src/internal/-Buffer.kt index ac65e4ad3..fc9844a6a 100644 --- a/core/common/src/internal/-Buffer.kt +++ b/core/common/src/internal/-Buffer.kt @@ -887,16 +887,17 @@ internal inline fun Buffer.commonString(): String { } } - val text = data.decodeToString() + val text = peek().readUtf8() + val escapedText = data.decodeToString() .substring(0, i) .replace("\\", "\\\\") .replace("\n", "\\n") .replace("\r", "\\r") return if (i < text.length) { - "[size=${data.size} text=$text…]" + "[size=${data.size} text=$escapedText…]" } else { - "[text=$text]" + "[text=$escapedText]" } } diff --git a/core/common/test/CommonBufferTest.kt b/core/common/test/CommonBufferTest.kt index 352144e47..bc40d5d9a 100644 --- a/core/common/test/CommonBufferTest.kt +++ b/core/common/test/CommonBufferTest.kt @@ -29,7 +29,7 @@ import kotlin.test.assertTrue /** * Tests solely for the behavior of Buffer's implementation. For generic BufferedSink or - * BufferedSource behavior use BufferedSinkTest or BufferedSourceTest, respectively. + * BufferedSource behavior, use BufferedSinkTest or BufferedSourceTest, respectively. */ class CommonBufferTest { @Test fun readAndWriteUtf8() { @@ -47,29 +47,22 @@ class CommonBufferTest { } } - /** Buffer's toString is the same as ByteString's. */ -// @Test fun bufferToString() { -// assertEquals("[size=0]", Buffer().toString()) -// assertEquals( -// "[text=a\\r\\nb\\nc\\rd\\\\e]", -// Buffer().writeUtf8("a\r\nb\nc\rd\\e").toString() -// ) -// assertEquals( -// "[text=Tyrannosaur]", -// Buffer().writeUtf8("Tyrannosaur").toString() -// ) -// assertEquals( -// "[text=təˈranəˌsôr]", -// Buffer() -// .write("74c999cb8872616ec999cb8c73c3b472".decodeHex()) -// .toString() -// ) -// assertEquals( -// "[hex=0000000000000000000000000000000000000000000000000000000000000000000000000000" + -// "0000000000000000000000000000000000000000000000000000]", -// Buffer().write(ByteArray(64)).toString() -// ) -// } + @Test fun bufferToString() { + assertEquals("[size=0]", Buffer().toString()) + + assertEquals("[text=a\\r\\nb\\nc\\rd\\\\e]", + Buffer().writeUtf8("a\r\nb\nc\rd\\e").toString()) + + assertEquals("[text=Tyrannosaur]", + Buffer().writeUtf8("Tyrannosaur").toString()) + + assertEquals("[text=təˈranəˌsôr]", + Buffer().write("74c999cb8872616ec999cb8c73c3b472".decodeHex()).toString()) + + assertEquals("[hex=0000000000000000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000]", + Buffer().write(ByteArray(64)).toString()) + } @Test fun multipleSegmentBuffers() { val buffer = Buffer() @@ -426,9 +419,4 @@ class CommonBufferTest { assertEquals("aaa", source.readUtf8()) assertEquals("aaa", target.readUtf8()) } - -// @Test fun snapshotReportsAccurateSize() { -// val buf = Buffer().write(byteArrayOf(0, 1, 2, 3)) -// assertEquals(1, buf.snapshot(1).size) -// } } From 28cda06a0b78ed5bd6e81d55ccd8d1e651dfb234 Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Mon, 5 Jun 2023 11:02:36 +0200 Subject: [PATCH 15/83] Cleaned up the code. --- core/common/test/CommonOptionsTest.kt | 444 -------------- core/common/test/CommonRealSinkTest.kt | 5 +- core/common/test/PathTest.kt | 780 ------------------------- core/common/test/TestingSupport.kt | 31 - core/common/test/util.kt | 41 +- core/jvm/src/Buffer.kt | 2 +- 6 files changed, 11 insertions(+), 1292 deletions(-) delete mode 100644 core/common/test/CommonOptionsTest.kt delete mode 100644 core/common/test/PathTest.kt delete mode 100644 core/common/test/TestingSupport.kt diff --git a/core/common/test/CommonOptionsTest.kt b/core/common/test/CommonOptionsTest.kt deleted file mode 100644 index 1a8289e90..000000000 --- a/core/common/test/CommonOptionsTest.kt +++ /dev/null @@ -1,444 +0,0 @@ -/* - * Copyright 2017-2023 JetBrains s.r.o. and respective authors and developers. - * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENCE file. - */ - -/* - * 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 kotlinx.io - -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertFailsWith -import kotlin.test.fail - -//class CommonOptionsTest { -// /** Confirm that options prefers the first-listed option, not the longest or shortest one. */ -// @Test fun optionOrderTakesPrecedence() { -// assertSelect("abcdefg", 0, "abc", "abcdef") -// assertSelect("abcdefg", 0, "abcdef", "abc") -// } -// -// @Test fun simpleOptionsTrie() { -// assertEquals( -// utf8Options("hotdog", "hoth", "hot").trieString(), -// """ -// |hot -// | -> 2 -// | d -// | og -> 0 -// | h -> 1 -// |""".trimMargin() -// ) -// } -// -// @Test fun realisticOptionsTrie() { -// // These are the fields of OkHttpClient in 3.10. -// val options = utf8Options( -// "dispatcher", -// "proxy", -// "protocols", -// "connectionSpecs", -// "interceptors", -// "networkInterceptors", -// "eventListenerFactory", -// "proxySelector", // No index 7 in the trie because 'proxy' is a prefix! -// "cookieJar", -// "cache", -// "internalCache", -// "socketFactory", -// "sslSocketFactory", -// "certificateChainCleaner", -// "hostnameVerifier", -// "certificatePinner", -// "proxyAuthenticator", // No index 16 in the trie because 'proxy' is a prefix! -// "authenticator", -// "connectionPool", -// "dns", -// "followSslRedirects", -// "followRedirects", -// "retryOnConnectionFailure", -// "connectTimeout", -// "readTimeout", -// "writeTimeout", -// "pingInterval" -// ) -// assertEquals( -// options.trieString(), -// """ -// |a -// | uthenticator -> 17 -// |c -// | a -// | che -> 9 -// | e -// | rtificate -// | C -// | hainCleaner -> 13 -// | P -// | inner -> 15 -// | o -// | n -// | nect -// | T -// | imeout -> 23 -// | i -// | on -// | P -// | ool -> 18 -// | S -// | pecs -> 3 -// | o -// | kieJar -> 8 -// |d -// | i -// | spatcher -> 0 -// | n -// | s -> 19 -// |e -// | ventListenerFactory -> 6 -// |f -// | ollow -// | R -// | edirects -> 21 -// | S -// | slRedirects -> 20 -// |h -// | ostnameVerifier -> 14 -// |i -// | nter -// | c -// | eptors -> 4 -// | n -// | alCache -> 10 -// |n -// | etworkInterceptors -> 5 -// |p -// | i -// | ngInterval -> 26 -// | r -// | o -// | t -// | ocols -> 2 -// | x -// | y -> 1 -// |r -// | e -// | a -// | dTimeout -> 24 -// | t -// | ryOnConnectionFailure -> 22 -// |s -// | o -// | cketFactory -> 11 -// | s -// | lSocketFactory -> 12 -// |w -// | riteTimeout -> 25 -// |""".trimMargin() -// ) -// assertSelect("", -1, options) -// assertSelect("a", -1, options) -// assertSelect("eventListenerFactor", -1, options) -// assertSelect("dnst", 19, options) -// assertSelect("proxyproxy", 1, options) -// assertSelect("prox", -1, options) -// -// assertSelect("dispatcher", 0, options) -// assertSelect("proxy", 1, options) -// assertSelect("protocols", 2, options) -// assertSelect("connectionSpecs", 3, options) -// assertSelect("interceptors", 4, options) -// assertSelect("networkInterceptors", 5, options) -// assertSelect("eventListenerFactory", 6, options) -// assertSelect("proxySelector", 1, options) // 'proxy' is a prefix. -// assertSelect("cookieJar", 8, options) -// assertSelect("cache", 9, options) -// assertSelect("internalCache", 10, options) -// assertSelect("socketFactory", 11, options) -// assertSelect("sslSocketFactory", 12, options) -// assertSelect("certificateChainCleaner", 13, options) -// assertSelect("hostnameVerifier", 14, options) -// assertSelect("certificatePinner", 15, options) -// assertSelect("proxyAuthenticator", 1, options) // 'proxy' is a prefix. -// assertSelect("authenticator", 17, options) -// assertSelect("connectionPool", 18, options) -// assertSelect("dns", 19, options) -// assertSelect("followSslRedirects", 20, options) -// assertSelect("followRedirects", 21, options) -// assertSelect("retryOnConnectionFailure", 22, options) -// assertSelect("connectTimeout", 23, options) -// assertSelect("readTimeout", 24, options) -// assertSelect("writeTimeout", 25, options) -// assertSelect("pingInterval", 26, options) -// } -// -// @Test fun emptyOptions() { -// val options = utf8Options() -// assertSelect("", -1, options) -// assertSelect("a", -1, options) -// assertSelect("abc", -1, options) -// } -// -// @Test fun emptyStringInOptionsTrie() { -// assertFailsWith { -// utf8Options("") -// } -// assertFailsWith { -// utf8Options("abc", "") -// } -// } -// -// @Test fun multipleIdenticalValues() { -// try { -// utf8Options("abc", "abc") -// fail() -// } catch (expected: IllegalArgumentException) { -// assertEquals(expected.message, "duplicate option: [text=abc]") -// } -// } -// -// @Test fun prefixesAreStripped() { -// val options = utf8Options("abcA", "abc", "abcB") -// assertEquals( -// options.trieString(), -// """ -// |abc -// | -> 1 -// | A -> 0 -// |""".trimMargin() -// ) -// assertSelect("abc", 1, options) -// assertSelect("abcA", 0, options) -// assertSelect("abcB", 1, options) -// assertSelect("abcC", 1, options) -// assertSelect("ab", -1, options) -// } -// -// @Test fun multiplePrefixesAreStripped() { -// assertEquals( -// utf8Options("a", "ab", "abc", "abcd", "abcde").trieString(), -// """ -// |a -> 0 -// |""".trimMargin() -// ) -// assertEquals( -// utf8Options("abc", "a", "ab", "abe", "abcd", "abcf").trieString(), -// """ -// |a -// | -> 1 -// | bc -> 0 -// |""".trimMargin() -// ) -// assertEquals( -// utf8Options("abc", "ab", "a").trieString(), -// """ -// |a -// | -> 2 -// | b -// | -> 1 -// | c -> 0 -// |""".trimMargin() -// ) -// assertEquals( -// utf8Options("abcd", "abce", "abc", "abcf", "abcg").trieString(), -// """ -// |abc -// | -> 2 -// | d -> 0 -// | e -> 1 -// |""".trimMargin() -// ) -// } -// -// @Test fun scan() { -// val options = utf8Options("abc") -// assertSelect("abcde", 0, options) -// } -// -// @Test fun scanReturnsPrefix() { -// val options = utf8Options("abcdefg", "ab") -// assertSelect("ab", 1, options) -// assertSelect("abcd", 1, options) -// assertSelect("abcdefg", 0, options) -// assertSelect("abcdefghi", 0, options) -// assertSelect("abcdhi", 1, options) -// } -// -// @Test fun select() { -// val options = utf8Options("a", "b", "c") -// assertSelect("a", 0, options) -// assertSelect("b", 1, options) -// assertSelect("c", 2, options) -// assertSelect("d", -1, options) -// assertSelect("aa", 0, options) -// assertSelect("bb", 1, options) -// assertSelect("cc", 2, options) -// assertSelect("dd", -1, options) -// } -// -// @Test fun selectSelect() { -// val options = utf8Options("aa", "ab", "ba", "bb") -// assertSelect("a", -1, options) -// assertSelect("b", -1, options) -// assertSelect("c", -1, options) -// assertSelect("aa", 0, options) -// assertSelect("ab", 1, options) -// assertSelect("ac", -1, options) -// assertSelect("ba", 2, options) -// assertSelect("bb", 3, options) -// assertSelect("bc", -1, options) -// assertSelect("ca", -1, options) -// assertSelect("cb", -1, options) -// assertSelect("cc", -1, options) -// } -// -// @Test fun selectScan() { -// val options = utf8Options("abcd", "defg") -// assertSelect("a", -1, options) -// assertSelect("d", -1, options) -// assertSelect("h", -1, options) -// assertSelect("ab", -1, options) -// assertSelect("ae", -1, options) -// assertSelect("de", -1, options) -// assertSelect("db", -1, options) -// assertSelect("hi", -1, options) -// assertSelect("abcd", 0, options) -// assertSelect("aefg", -1, options) -// assertSelect("defg", 1, options) -// assertSelect("dbcd", -1, options) -// assertSelect("hijk", -1, options) -// assertSelect("abcdh", 0, options) -// assertSelect("defgh", 1, options) -// assertSelect("hijkl", -1, options) -// } -// -// @Test fun scanSelect() { -// val options = utf8Options("abcd", "abce") -// assertSelect("a", -1, options) -// assertSelect("f", -1, options) -// assertSelect("abc", -1, options) -// assertSelect("abf", -1, options) -// assertSelect("abcd", 0, options) -// assertSelect("abce", 1, options) -// assertSelect("abcf", -1, options) -// assertSelect("abcdf", 0, options) -// assertSelect("abcef", 1, options) -// } -// -// @Test fun scanSpansSegments() { -// val options = utf8Options("abcd") -// assertSelect(bufferWithSegments("a", "bcd"), 0, options) -// assertSelect(bufferWithSegments("a", "bcde"), 0, options) -// assertSelect(bufferWithSegments("ab", "cd"), 0, options) -// assertSelect(bufferWithSegments("ab", "cde"), 0, options) -// assertSelect(bufferWithSegments("abc", "d"), 0, options) -// assertSelect(bufferWithSegments("abc", "de"), 0, options) -// assertSelect(bufferWithSegments("abcd", "e"), 0, options) -// assertSelect(bufferWithSegments("a", "bce"), -1, options) -// assertSelect(bufferWithSegments("a", "bce"), -1, options) -// assertSelect(bufferWithSegments("ab", "ce"), -1, options) -// assertSelect(bufferWithSegments("ab", "ce"), -1, options) -// assertSelect(bufferWithSegments("abc", "e"), -1, options) -// assertSelect(bufferWithSegments("abc", "ef"), -1, options) -// assertSelect(bufferWithSegments("abce", "f"), -1, options) -// } -// -// @Test fun selectSpansSegments() { -// val options = utf8Options("aa", "ab", "ba", "bb") -// assertSelect(bufferWithSegments("a", "a"), 0, options) -// assertSelect(bufferWithSegments("a", "b"), 1, options) -// assertSelect(bufferWithSegments("a", "c"), -1, options) -// assertSelect(bufferWithSegments("b", "a"), 2, options) -// assertSelect(bufferWithSegments("b", "b"), 3, options) -// assertSelect(bufferWithSegments("b", "c"), -1, options) -// assertSelect(bufferWithSegments("c", "a"), -1, options) -// assertSelect(bufferWithSegments("c", "b"), -1, options) -// assertSelect(bufferWithSegments("c", "c"), -1, options) -// assertSelect(bufferWithSegments("a", "ad"), 0, options) -// assertSelect(bufferWithSegments("a", "bd"), 1, options) -// assertSelect(bufferWithSegments("a", "cd"), -1, options) -// assertSelect(bufferWithSegments("b", "ad"), 2, options) -// assertSelect(bufferWithSegments("b", "bd"), 3, options) -// assertSelect(bufferWithSegments("b", "cd"), -1, options) -// assertSelect(bufferWithSegments("c", "ad"), -1, options) -// assertSelect(bufferWithSegments("c", "bd"), -1, options) -// assertSelect(bufferWithSegments("c", "cd"), -1, options) -// } -// -// private fun utf8Options(vararg options: String): Options { -// return Options.of(*options.map { it.encodeUtf8() }.toTypedArray()) -// } -// -// private fun assertSelect(data: String, expected: Int, options: Options) { -// assertSelect(Buffer().writeUtf8(data), expected, options) -// } -// -// private fun assertSelect(data: String, expected: Int, vararg options: String) { -// assertSelect(data, expected, utf8Options(*options)) -// } -// -// private fun assertSelect(data: Buffer, expected: Int, options: Options) { -// val initialSize = data.size -// val actual = data.select(options) -// -// assertEquals(actual, expected) -// if (expected == -1) { -// assertEquals(data.size, initialSize) -// } else { -// assertEquals(data.size + options[expected].size, initialSize) -// } -// } -// -// private fun Options.trieString(): String { -// val result = StringBuilder() -// printTrieNode(result, 0) -// return result.toString() -// } -// -// private fun Options.printTrieNode(out: StringBuilder, offset: Int = 0, indent: String = "") { -// if (trie[offset + 1] != -1) { -// // Print the prefix. -// out.append("$indent-> ${trie[offset + 1]}\n") -// } -// -// if (trie[offset] > 0) { -// // Print the select. -// val selectChoiceCount = trie[offset] -// for (i in 0 until selectChoiceCount) { -// out.append("$indent${trie[offset + 2 + i].toChar()}") -// printTrieResult(out, trie[offset + 2 + selectChoiceCount + i], "$indent ") -// } -// } else { -// // Print the scan. -// val scanByteCount = -1 * trie[offset] -// out.append(indent) -// for (i in 0 until scanByteCount) { -// out.append(trie[offset + 2 + i].toChar()) -// } -// printTrieResult(out, trie[offset + 2 + scanByteCount], "$indent${" ".repeat(scanByteCount)}") -// } -// } -// -// private fun Options.printTrieResult(out: StringBuilder, result: Int, indent: String) { -// if (result >= 0) { -// out.append(" -> $result\n") -// } else { -// out.append("\n") -// printTrieNode(out, -1 * result, indent) -// } -// } -//} diff --git a/core/common/test/CommonRealSinkTest.kt b/core/common/test/CommonRealSinkTest.kt index 5fafc685c..75b736d4c 100644 --- a/core/common/test/CommonRealSinkTest.kt +++ b/core/common/test/CommonRealSinkTest.kt @@ -126,11 +126,8 @@ class CommonRealSinkTest { mockSink.scheduleThrow(1, IOException("second")) val bufferedSink = mockSink.buffer() bufferedSink.writeByte('a'.code) - try { + assertFailsWith("first.*") { bufferedSink.close() - fail() - } catch (expected: IOException) { - assertEquals("first", expected.message) } mockSink.assertLog("write([text=a], 1)", "close()") diff --git a/core/common/test/PathTest.kt b/core/common/test/PathTest.kt deleted file mode 100644 index 87eb91e98..000000000 --- a/core/common/test/PathTest.kt +++ /dev/null @@ -1,780 +0,0 @@ -/* - * Copyright 2017-2023 JetBrains s.r.o. and respective authors and developers. - * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENCE file. - */ - -/* - * Copyright (C) 2020 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 kotlinx.io - -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertFalse -import kotlin.test.assertNotEquals -import kotlin.test.assertNull -import kotlin.test.assertTrue - -//class PathTest { -// @Test -// fun unixRoot() { -// val path = "/".toPath() -// assertEquals(path, path.normalized()) -// assertEquals(path, path.root) -// assertEquals(listOf(), path.segments) -// assertEquals("/", path.toString()) -// assertNull(path.parent) -// assertNull(path.volumeLetter) -// assertEquals("", path.name) -// assertTrue(path.isAbsolute) -// assertTrue(path.isRoot) -// } -// -// @Test -// fun unixAbsolutePath() { -// val path = "/home/jesse/todo.txt".toPath() -// assertEquals(path, path.normalized()) -// assertEquals("/".toPath(), path.root) -// assertEquals(listOf("home", "jesse", "todo.txt"), path.segments) -// assertEquals("/home/jesse/todo.txt", path.toString()) -// assertEquals("/home/jesse".toPath(), path.parent) -// assertNull(path.volumeLetter) -// assertEquals("todo.txt", path.name) -// assertTrue(path.isAbsolute) -// assertFalse(path.isRoot) -// } -// -// @Test -// fun unixRelativePath() { -// val path = "project/todo.txt".toPath() -// assertEquals(path, path.normalized()) -// assertNull(path.root) -// assertEquals(listOf("project", "todo.txt"), path.segments) -// assertEquals("project/todo.txt", path.toString()) -// assertEquals("project".toPath(), path.parent) -// assertNull(path.volumeLetter) -// assertEquals("todo.txt", path.name) -// assertFalse(path.isAbsolute) -// assertFalse(path.isRoot) -// } -// -// @Test -// fun unixRelativePathWithDots() { -// val path = "../../project/todo.txt".toPath() -// assertEquals(path, path.normalized()) -// assertNull(path.root) -// assertEquals(listOf("..", "..", "project", "todo.txt"), path.segments) -// assertEquals("../../project/todo.txt", path.toString()) -// assertEquals("../../project".toPath(), path.parent) -// assertNull(path.volumeLetter) -// assertEquals("todo.txt", path.name) -// assertFalse(path.isAbsolute) -// assertFalse(path.isRoot) -// } -// -// @Test -// fun unixRelativeSeriesOfDotDots() { -// val path = "../../..".toPath() -// assertEquals(path, path.normalized()) -// assertNull(path.root) -// assertEquals(listOf("..", "..", ".."), path.segments) -// assertEquals("../../..", path.toString()) -// assertNull(path.parent) -// assertNull(path.volumeLetter) -// assertEquals("..", path.name) -// assertFalse(path.isAbsolute) -// assertFalse(path.isRoot) -// } -// -// @Test -// fun unixAbsoluteSeriesOfDotDots() { -// val path = "/../../..".toPath() -// assertEquals(path, path.normalized()) -// assertEquals("/".toPath(), path.root) -// assertEquals(listOf(), path.segments) -// assertEquals("/", path.toString()) -// assertNull(path.parent) -// assertNull(path.volumeLetter) -// assertEquals("", path.name) -// assertTrue(path.isAbsolute) -// assertTrue(path.isRoot) -// } -// -// @Test -// fun unixAbsoluteSingleDot() { -// val path = "/.".toPath() -// assertEquals(path, path.normalized()) -// assertEquals("/".toPath(), path.root) -// assertEquals(listOf(), path.segments) -// assertEquals("/", path.toString()) -// assertNull(path.parent) -// assertNull(path.volumeLetter) -// assertEquals("", path.name) -// assertTrue(path.isAbsolute) -// assertTrue(path.isRoot) -// } -// -// @Test -// fun unixRelativeDoubleDots() { -// val path = "..".toPath() -// assertEquals(path, path.normalized()) -// assertNull(path.root) -// assertEquals(listOf(".."), path.segments) -// assertEquals("..", path.toString()) -// assertNull(path.parent) -// assertNull(path.volumeLetter) -// assertEquals("..", path.name) -// assertFalse(path.isAbsolute) -// assertFalse(path.isRoot) -// } -// -// @Test -// fun unixRelativeSingleDot() { -// val path = ".".toPath() -// assertEquals(path, path.normalized()) -// assertNull(path.root) -// assertEquals(listOf("."), path.segments) -// assertEquals(".", path.toString()) -// assertNull(path.parent) -// assertNull(path.volumeLetter) -// assertEquals(".", path.name) -// assertFalse(path.isAbsolute) -// assertFalse(path.isRoot) -// } -// -// @Test -// fun windowsVolumeLetter() { -// val path = "C:\\".toPath() -// assertEquals(path, path.normalized()) -// assertEquals("C:\\".toPath(), path.root) -// assertEquals(listOf(), path.segments) -// assertEquals("C:\\", path.toString()) -// assertNull(path.parent) -// assertEquals('C', path.volumeLetter) -// assertEquals("", path.name) -// assertTrue(path.isAbsolute) -// assertTrue(path.isRoot) -// } -// -// @Test -// fun windowsAbsolutePathWithVolumeLetter() { -// val path = "C:\\Windows\\notepad.exe".toPath() -// assertEquals(path, path.normalized()) -// assertEquals("C:\\".toPath(), path.root) -// assertEquals(listOf("Windows", "notepad.exe"), path.segments) -// assertEquals("C:\\Windows\\notepad.exe", path.toString()) -// assertEquals("C:\\Windows".toPath(), path.parent) -// assertEquals('C', path.volumeLetter) -// assertEquals("notepad.exe", path.name) -// assertTrue(path.isAbsolute) -// assertFalse(path.isRoot) -// } -// -// @Test -// fun windowsAbsolutePath() { -// val path = "\\".toPath() -// assertEquals(path, path.normalized()) -// assertEquals("\\".toPath(), path.root) -// assertEquals(listOf(), path.segments) -// assertEquals("\\", path.toString()) -// assertEquals(null, path.parent) -// assertNull(path.volumeLetter) -// assertEquals("", path.name) -// assertTrue(path.isAbsolute) -// assertTrue(path.isRoot) -// } -// -// @Test -// fun windowsAbsolutePathWithoutVolumeLetter() { -// val path = "\\Windows\\notepad.exe".toPath() -// assertEquals(path, path.normalized()) -// assertEquals("\\".toPath(), path.root) -// assertEquals(listOf("Windows", "notepad.exe"), path.segments) -// assertEquals("\\Windows\\notepad.exe", path.toString()) -// assertEquals("\\Windows".toPath(), path.parent) -// assertNull(path.volumeLetter) -// assertEquals("notepad.exe", path.name) -// assertTrue(path.isAbsolute) -// assertFalse(path.isRoot) -// } -// -// @Test -// fun windowsRelativePathWithVolumeLetter() { -// val path = "C:Windows\\notepad.exe".toPath() -// assertEquals(path, path.normalized()) -// assertNull(path.root) -// assertEquals(listOf("C:Windows", "notepad.exe"), path.segments) -// assertEquals("C:Windows\\notepad.exe", path.toString()) -// assertEquals("C:Windows".toPath(), path.parent) -// assertEquals('C', path.volumeLetter) -// assertEquals("notepad.exe", path.name) -// assertFalse(path.isAbsolute) -// assertFalse(path.isRoot) -// } -// -// @Test -// fun windowsVolumeLetterRelative() { -// val path = "C:".toPath() -// assertEquals(path, path.normalized()) -// assertNull(path.root) -// assertEquals(listOf("C:"), path.segments) -// assertEquals("C:", path.toString()) -// assertNull(path.parent) -// assertEquals('C', path.volumeLetter) -// assertEquals("", path.name) -// assertFalse(path.isAbsolute) -// assertFalse(path.isRoot) -// } -// -// @Test -// fun windowsRelativePath() { -// val path = "Windows\\notepad.exe".toPath() -// assertEquals(path, path.normalized()) -// assertNull(path.root) -// assertEquals(listOf("Windows", "notepad.exe"), path.segments) -// assertEquals("Windows\\notepad.exe", path.toString()) -// assertEquals("Windows".toPath(), path.parent) -// assertNull(path.volumeLetter) -// assertEquals("notepad.exe", path.name) -// assertFalse(path.isAbsolute) -// assertFalse(path.isRoot) -// } -// -// @Test -// fun windowsUncServer() { -// val path = "\\\\server".toPath() -// assertEquals(path, path.normalized()) -// assertEquals("\\\\server".toPath(), path.root) -// assertEquals(listOf(), path.segments) -// assertEquals("\\\\server", path.toString()) -// assertNull(path.parent) -// assertNull(path.volumeLetter) -// assertEquals("server", path.name) -// assertTrue(path.isAbsolute) -// assertTrue(path.isRoot) -// } -// -// @Test -// fun windowsUncAbsolutePath() { -// val path = "\\\\server\\project\\notes.txt".toPath() -// assertEquals(path, path.normalized()) -// assertEquals("\\\\server".toPath(), path.root) -// assertEquals(listOf("project", "notes.txt"), path.segments) -// assertEquals("\\\\server\\project\\notes.txt", path.toString()) -// assertEquals("\\\\server\\project".toPath(), path.parent) -// assertNull(path.volumeLetter) -// assertEquals("notes.txt", path.name) -// assertTrue(path.isAbsolute) -// assertFalse(path.isRoot) -// } -// -// @Test -// fun absolutePathTraversalWithDivOperator() { -// val root = "/".toPath() -// assertEquals("/home".toPath(), root / "home") -// assertEquals("/home/jesse".toPath(), root / "home" / "jesse") -// assertEquals("/home/jesse/..".toPath(), root / "home" / "jesse" / "..") -// assertEquals("/home/jesse/../jake".toPath(), root / "home" / "jesse" / ".." / "jake") -// } -// -// @Test -// fun relativePathTraversalWithDivOperator() { -// val slash = Path.DIRECTORY_SEPARATOR -// val cwd = ".".toPath() -// assertEquals("home".toPath(), cwd / "home") -// assertEquals("home${slash}jesse".toPath(), cwd / "home" / "jesse") -// assertEquals("home${slash}jesse$slash..".toPath(), cwd / "home" / "jesse" / "..") -// assertEquals( -// "home${slash}jesse$slash..${slash}jake".toPath(), -// cwd / "home" / "jesse" / ".." / "jake" -// ) -// } -// -// @Test -// fun relativePathTraversalWithDots() { -// val slash = Path.DIRECTORY_SEPARATOR -// val cwd = ".".toPath() -// assertEquals("..".toPath(), cwd / "..") -// assertEquals("..$slash..".toPath(), cwd / ".." / "..") -// assertEquals("..$slash..${slash}etc".toPath(), cwd / ".." / ".." / "etc") -// assertEquals( -// "..$slash..${slash}etc${slash}passwd".toPath(), -// cwd / ".." / ".." / "etc" / "passwd" -// ) -// } -// -// @Test -// fun pathTraversalBaseIgnoredIfChildIsAnAbsolutePath() { -// assertEquals("/home".toPath(), "".toPath() / "/home") -// assertEquals("/home".toPath(), "relative".toPath() / "/home") -// assertEquals("/home".toPath(), "/base".toPath() / "/home") -// assertEquals("/home".toPath(), "/".toPath() / "/home") -// } -// -// @Test -// fun stringToAbsolutePath() { -// assertEquals("/", "/".toPath().toString()) -// assertEquals("/a", "/a".toPath().toString()) -// assertEquals("/a", "/a/".toPath().toString()) -// assertEquals("/a/b/c", "/a/b/c".toPath().toString()) -// assertEquals("/a/b/c", "/a/b/c/".toPath().toString()) -// assertEquals("/", "/".toPath(normalize = true).toString()) -// assertEquals("/a", "/a".toPath(normalize = true).toString()) -// assertEquals("/a", "/a/".toPath(normalize = true).toString()) -// assertEquals("/a/b/c", "/a/b/c".toPath(normalize = true).toString()) -// assertEquals("/a/b/c", "/a/b/c/".toPath(normalize = true).toString()) -// } -// -// @Test -// fun stringToAbsolutePathWithTraversal() { -// assertEquals("/", "/..".toPath().toString()) -// assertEquals("/", "/../".toPath().toString()) -// assertEquals("/", "/../..".toPath().toString()) -// assertEquals("/", "/../../".toPath().toString()) -// assertEquals("/", "/..".toPath(normalize = true).toString()) -// assertEquals("/", "/../".toPath(normalize = true).toString()) -// assertEquals("/", "/../..".toPath(normalize = true).toString()) -// assertEquals("/", "/../../".toPath(normalize = true).toString()) -// } -// -// @Test -// fun stringToAbsolutePathWithEmptySegments() { -// assertEquals("/", "//".toPath().toString()) -// assertEquals("/a", "//a".toPath().toString()) -// assertEquals("/a", "/a//".toPath().toString()) -// assertEquals("/a", "//a//".toPath().toString()) -// assertEquals("/a/b", "/a/b//".toPath().toString()) -// assertEquals("/", "//".toPath(normalize = true).toString()) -// assertEquals("/a", "//a".toPath(normalize = true).toString()) -// assertEquals("/a", "/a//".toPath(normalize = true).toString()) -// assertEquals("/a", "//a//".toPath(normalize = true).toString()) -// assertEquals("/a/b", "/a/b//".toPath(normalize = true).toString()) -// } -// -// @Test -// fun stringToAbsolutePathWithDots() { -// assertEquals("/", "/./".toPath().toString()) -// assertEquals("/a", "/./a".toPath().toString()) -// assertEquals("/a", "/a/./".toPath().toString()) -// assertEquals("/a", "/a//.".toPath().toString()) -// assertEquals("/a", "/./a//".toPath().toString()) -// assertEquals("/a", "/a/.".toPath().toString()) -// assertEquals("/a", "//a/./".toPath().toString()) -// assertEquals("/a", "//a/./.".toPath().toString()) -// assertEquals("/a/b", "/a/./b/".toPath().toString()) -// assertEquals("/", "/./".toPath(normalize = true).toString()) -// assertEquals("/a", "/./a".toPath(normalize = true).toString()) -// assertEquals("/a", "/a/./".toPath(normalize = true).toString()) -// assertEquals("/a", "/a//.".toPath(normalize = true).toString()) -// assertEquals("/a", "/./a//".toPath(normalize = true).toString()) -// assertEquals("/a", "/a/.".toPath(normalize = true).toString()) -// assertEquals("/a", "//a/./".toPath(normalize = true).toString()) -// assertEquals("/a", "//a/./.".toPath(normalize = true).toString()) -// assertEquals("/a/b", "/a/./b/".toPath(normalize = true).toString()) -// } -// -// @Test -// fun stringToRelativePath() { -// assertEquals(".", "".toPath().toString()) -// assertEquals(".", ".".toPath().toString()) -// assertEquals("a", "a/".toPath().toString()) -// assertEquals("a/b", "a/b".toPath().toString()) -// assertEquals("a/b", "a/b/".toPath().toString()) -// assertEquals("a/b/c/d", "a/b/c/d".toPath().toString()) -// assertEquals("a/b/c/d", "a/b/c/d/".toPath().toString()) -// assertEquals(".", "".toPath(normalize = true).toString()) -// assertEquals(".", ".".toPath(normalize = true).toString()) -// assertEquals("a", "a/".toPath(normalize = true).toString()) -// assertEquals("a/b", "a/b".toPath(normalize = true).toString()) -// assertEquals("a/b", "a/b/".toPath(normalize = true).toString()) -// assertEquals("a/b/c/d", "a/b/c/d".toPath(normalize = true).toString()) -// assertEquals("a/b/c/d", "a/b/c/d/".toPath(normalize = true).toString()) -// } -// -// @Test -// fun stringToRelativePathWithTraversal() { -// assertEquals("..", "..".toPath().toString()) -// assertEquals("..", "../".toPath().toString()) -// assertEquals("a/..", "a/..".toPath().toString()) -// assertEquals("a/..", "a/../".toPath().toString()) -// assertEquals("a/../..", "a/../..".toPath().toString()) -// assertEquals("a/../..", "a/../../".toPath().toString()) -// assertEquals("a/../../..", "a/../../..".toPath().toString()) -// assertEquals("../../b", "../../b".toPath().toString()) -// assertEquals("a/../../../b", "a/../../../b".toPath().toString()) -// assertEquals("a/../../../b/../c", "a/../../../b/../c".toPath().toString()) -// assertEquals("..", "..".toPath(normalize = true).toString()) -// assertEquals("..", "../".toPath(normalize = true).toString()) -// assertEquals(".", "a/..".toPath(normalize = true).toString()) -// assertEquals(".", "a/../".toPath(normalize = true).toString()) -// assertEquals("..", "a/../..".toPath(normalize = true).toString()) -// assertEquals("..", "a/../../".toPath(normalize = true).toString()) -// assertEquals("../..", "a/../../..".toPath(normalize = true).toString()) -// assertEquals("../../b", "../../b".toPath(normalize = true).toString()) -// assertEquals("../../b", "a/../../../b".toPath(normalize = true).toString()) -// assertEquals("../../c", "a/../../../b/../c".toPath(normalize = true).toString()) -// } -// -// @Test -// fun stringToRelativePathWithEmptySegments() { -// assertEquals("a", "a//".toPath().toString()) -// assertEquals("a/b", "a//b".toPath().toString()) -// assertEquals("a/b", "a/b//".toPath().toString()) -// assertEquals("a/b", "a//b//".toPath().toString()) -// assertEquals("a/b/c", "a/b/c//".toPath().toString()) -// assertEquals("a", "a//".toPath(normalize = true).toString()) -// assertEquals("a/b", "a//b".toPath(normalize = true).toString()) -// assertEquals("a/b", "a/b//".toPath(normalize = true).toString()) -// assertEquals("a/b", "a//b//".toPath(normalize = true).toString()) -// assertEquals("a/b/c", "a/b/c//".toPath(normalize = true).toString()) -// } -// -// @Test -// fun stringToRelativePathWithDots() { -// assertEquals(".", ".".toPath().toString()) -// assertEquals(".", "./".toPath().toString()) -// assertEquals(".", "././".toPath().toString()) -// assertEquals("a/..", "././a/..".toPath().toString()) -// assertEquals("a", "a/./".toPath().toString()) -// assertEquals("a/b", "a/./b".toPath().toString()) -// assertEquals("a/b", "a/b/./".toPath().toString()) -// assertEquals("a/b", "a/b//.".toPath().toString()) -// assertEquals("a/b", "a/./b//".toPath().toString()) -// assertEquals("a/b", "a/b/.".toPath().toString()) -// assertEquals("a/b", "a//b/./".toPath().toString()) -// assertEquals("a/b", "a//b/./.".toPath().toString()) -// assertEquals("a/b/c", "a/b/./c/".toPath().toString()) -// assertEquals(".", ".".toPath(normalize = true).toString()) -// assertEquals(".", "./".toPath(normalize = true).toString()) -// assertEquals(".", "././".toPath(normalize = true).toString()) -// assertEquals(".", "././a/..".toPath(normalize = true).toString()) -// assertEquals("a", "a/./".toPath(normalize = true).toString()) -// assertEquals("a/b", "a/./b".toPath(normalize = true).toString()) -// assertEquals("a/b", "a/b/./".toPath(normalize = true).toString()) -// assertEquals("a/b", "a/b//.".toPath(normalize = true).toString()) -// assertEquals("a/b", "a/./b//".toPath(normalize = true).toString()) -// assertEquals("a/b", "a/b/.".toPath(normalize = true).toString()) -// assertEquals("a/b", "a//b/./".toPath(normalize = true).toString()) -// assertEquals("a/b", "a//b/./.".toPath(normalize = true).toString()) -// assertEquals("a/b/c", "a/b/./c/".toPath(normalize = true).toString()) -// } -// -// @Test -// fun composingWindowsPath() { -// assertEquals("C:\\Windows\\notepad.exe".toPath(), "C:\\".toPath() / "Windows" / "notepad.exe") -// } -// -// @Test -// fun windowsResolveAbsolutePath() { -// assertEquals("\\Users".toPath(), "C:\\Windows".toPath() / "\\Users") -// } -// -// @Test -// fun windowsPathTraversalUp() { -// assertEquals("C:\\x\\y\\..\\..\\..\\z".toPath(), "C:\\x\\y\\..\\..\\..\\z".toPath()) -// assertEquals("C:x\\y\\..\\..\\..\\z".toPath(), "C:x\\y\\..\\..\\..\\z".toPath()) -// assertEquals("C:\\z".toPath(), "C:\\x\\y\\..\\..\\..\\z".toPath(normalize = true)) -// assertEquals("C:..\\z".toPath(), "C:x\\y\\..\\..\\..\\z".toPath(normalize = true)) -// } -// -// @Test -// fun samePathDifferentSlashesAreNotEqual() { -// assertNotEquals("/a".toPath(), "\\b".toPath()) -// assertNotEquals("a/b".toPath(), "a\\b".toPath()) -// } -// -// @Test -// fun samePathNoSlashesAreEqual() { -// assertEquals("a".toPath().parent!!, "a".toPath().parent!!) -// assertEquals("a/b".toPath().parent!!, "a\\b".toPath().parent!!) -// } -// -// @Test -// fun relativeToWindowsPaths() { -// val a = "C:\\Windows\\notepad.exe".toPath() -// val b = "C:\\".toPath() -// assertRelativeTo(a, b, "..\\..".toPath(), sameAsNio = false) -// assertRelativeTo(b, a, "Windows\\notepad.exe".toPath(), sameAsNio = false) -// -// val c = "C:\\Windows\\".toPath() -// val d = "C:\\Windows".toPath() -// assertRelativeTo(c, d, ".".toPath()) -// assertRelativeTo(d, c, ".".toPath()) -// -// val e = "C:\\Windows\\Downloads\\".toPath() -// val f = "C:\\Windows\\Documents\\Hello.txt".toPath() -// assertRelativeTo(e, f, "..\\Documents\\Hello.txt".toPath(), sameAsNio = false) -// assertRelativeTo(f, e, "..\\..\\Downloads".toPath(), sameAsNio = false) -// -// val g = "C:\\Windows\\".toPath() -// val h = "D:\\Windows\\".toPath() -// assertRelativeToFails(g, h, sameAsNio = false) -// assertRelativeToFails(h, g, sameAsNio = false) -// } -// -// @Test -// fun relativeToWindowsUncPaths() { -// val a = "\\\\localhost\\c$\\development\\schema.proto".toPath() -// val b = "\\\\localhost\\c$\\project\\notes.txt".toPath() -// assertRelativeTo(a, b, "..\\..\\project\\notes.txt".toPath(), sameAsNio = false) -// assertRelativeTo(b, a, "..\\..\\development\\schema.proto".toPath(), sameAsNio = false) -// -// val c = "C:\\Windows\\".toPath() -// val d = "\\\\localhost\\c$\\project\\notes.txt".toPath() -// assertRelativeToFails(c, d, sameAsNio = false) -// assertRelativeToFails(d, c, sameAsNio = false) -// } -// -// @Test -// fun absoluteUnixRoot() { -// val a = "/Users/jesse/hello.txt".toPath() -// val b = "/".toPath() -// assertRelativeTo(a, b, "../../..".toPath()) -// assertRelativeTo(b, a, "Users/jesse/hello.txt".toPath()) -// -// val c = "/Users/jesse/hello.txt".toPath() -// val d = "/Admin/Secret".toPath() -// assertRelativeTo(c, d, "../../../Admin/Secret".toPath()) -// assertRelativeTo(d, c, "../../Users/jesse/hello.txt".toPath()) -// -// val e = "/Users/".toPath() -// val f = "/Users".toPath() -// assertRelativeTo(e, f, ".".toPath()) -// assertRelativeTo(f, e, ".".toPath()) -// } -// -// // Note that we handle the normalized version of the paths when computing relative paths. -// @Test -// fun relativeToUnnormalizedPath() { -// val a = "Users/../a".toPath() // `a` if normalized. -// val b = "Users/b/../c".toPath() // `Users/c` if normalized. -// assertRelativeToFails(a, b, sameAsNio = false) -// assertRelativeToFails(b, a, sameAsNio = false) -// assertRelativeTo(a.normalized(), b.normalized(), "../Users/c".toPath()) -// assertRelativeTo(b.normalized(), a.normalized(), "../../a".toPath()) -// } -// -// @Test -// fun relativeToNormalizedPath() { -// val a = "Users/../a".toPath(normalize = true) // results to `a`. -// val b = "Users/b/../c".toPath(normalize = true) // results to `Users/c`. -// assertRelativeTo(a, b, "../Users/c".toPath()) -// assertRelativeTo(b, a, "../../a".toPath()) -// } -// -// @Test -// fun absoluteToRelative() { -// val a = "/Users/jesse/hello.txt".toPath() -// val b = "Desktop/goodbye.txt".toPath() -// -// var exception = assertRelativeToFails(a, b) -// assertEquals( -// "Paths of different roots cannot be relative to each other: " + -// "Desktop/goodbye.txt and /Users/jesse/hello.txt", -// exception.message -// ) -// -// exception = assertRelativeToFails(b, a) -// assertEquals( -// "Paths of different roots cannot be relative to each other: " + -// "/Users/jesse/hello.txt and Desktop/goodbye.txt", -// exception.message -// ) -// } -// -// @Test -// fun absoluteToAbsolute() { -// val a = "/Users/jesse/hello.txt".toPath() -// val b = "/Users/benoit/Desktop/goodbye.txt".toPath() -// assertRelativeTo(a, b, "../../benoit/Desktop/goodbye.txt".toPath()) -// assertRelativeTo(b, a, "../../../jesse/hello.txt".toPath()) -// } -// -// @Test -// fun absoluteToSelf() { -// val a = "/Users/jesse/hello.txt".toPath() -// assertRelativeTo(a, a, ".".toPath()) -// -// val b = "/Users/benoit/../jesse/hello.txt".toPath() -// // NIO normalizes. -// assertRelativeTo(a, b, "../../benoit/../jesse/hello.txt".toPath(), sameAsNio = false) -// assertRelativeToFails(b, a, sameAsNio = false) -// assertRelativeTo(b.normalized(), a, ".".toPath()) -// assertRelativeTo(a, b.normalized(), ".".toPath()) -// } -// -// @Test -// fun relativeToSelf() { -// val a = "Desktop/hello.txt".toPath() -// assertRelativeTo(a, a, ".".toPath()) -// -// val b = "Documents/../Desktop/hello.txt".toPath() -// // NIO normalizes. -// assertRelativeTo(a, b, "../../Documents/../Desktop/hello.txt".toPath(), sameAsNio = false) -// assertRelativeToFails(b, a, sameAsNio = false) -// assertRelativeTo(a, b.normalized(), ".".toPath()) -// assertRelativeTo(b.normalized(), a, ".".toPath()) -// } -// -// @Test -// fun relativeToRelative() { -// val a = "Desktop/documents/resume.txt".toPath() -// val b = "Desktop/documents/2021/taxes.txt".toPath() -// assertRelativeTo(a, b, "../2021/taxes.txt".toPath(), sameAsNio = false) -// assertRelativeTo(b, a, "../../resume.txt".toPath(), sameAsNio = false) -// -// val c = "documents/resume.txt".toPath() -// val d = "downloads/2021/taxes.txt".toPath() -// assertRelativeTo(c, d, "../../downloads/2021/taxes.txt".toPath(), sameAsNio = false) -// assertRelativeTo(d, c, "../../../documents/resume.txt".toPath(), sameAsNio = false) -// } -// -// @Test -// fun relativeToRelativeWithMiddleDots() { -// val a = "Desktop/documents/a...n".toPath() -// val b = "Desktop/documents/m...z".toPath() -// assertRelativeTo(a, b, "../m...z".toPath()) -// assertRelativeTo(b, a, "../a...n".toPath()) -// } -// -// @Test -// fun relativeToRelativeWithMiddleDotsInCommonPrefix() { -// val a = "Desktop/documents/a...n/red".toPath() -// val b = "Desktop/documents/a...m/blue".toPath() -// assertRelativeTo(a, b, "../../a...m/blue".toPath()) -// assertRelativeTo(b, a, "../../a...n/red".toPath()) -// } -// -// @Test -// fun relativeToRelativeWithUpNavigationPrefix() { -// // We can't navigate from 'taxes' to 'resumes' because we don't know the name of 'Documents'. -// // /Users/jwilson/Documents/2021/Current -// // /Users/jwilson/Documents/resumes -// // /Users/jwilson/taxes -// val a = "../../resumes".toPath() -// val b = "../../../taxes".toPath() -// assertRelativeTo(a, b, "../../taxes".toPath()) -// assertRelativeToFails(b, a, sameAsNio = false) -// } -// -// @Test -// fun relativeToRelativeDifferentSlashes() { -// val a = "Desktop/documents/resume.txt".toPath() -// val b = "Desktop\\documents\\2021\\taxes.txt".toPath() -// assertRelativeTo(a, b, "../2021/taxes.txt".toPath(), sameAsNio = false) -// assertRelativeTo(b, a, "..\\..\\resume.txt".toPath(), sameAsNio = false) -// -// val c = "documents/resume.txt".toPath() -// val d = "downloads\\2021\\taxes.txt".toPath() -// assertRelativeTo(c, d, "../../downloads/2021/taxes.txt".toPath(), sameAsNio = false) -// assertRelativeTo(d, c, "..\\..\\..\\documents\\resume.txt".toPath(), sameAsNio = false) -// } -// -// @Test -// fun windowsUncPathsDoNotDotDot() { -// assertEquals( -// """\\localhost\c$\Windows""", -// """\\localhost\c$\Windows""".toPath().toString() -// ) -// assertEquals( -// """\\127.0.0.1\c$\Windows""", -// """\\127.0.0.1\c$\Windows""".toPath().toString() -// ) -// assertEquals( -// """\\127.0.0.1\c$\Windows\..\Windows""", -// """\\127.0.0.1\c$\Windows\..\Windows""".toPath().toString() -// ) -// assertEquals( -// """\\127.0.0.1\..\localhost\c$\Windows""", -// """\\127.0.0.1\..\localhost\c$\Windows""".toPath().toString() -// ) -// assertEquals( -// """\\127.0.0.1\c$\..\d$""", -// """\\127.0.0.1\c$\..\d$""".toPath().toString() -// ) -// -// assertEquals( -// """\\localhost\c$\Windows""", -// """\\localhost\c$\Windows""".toPath(normalize = true).toString() -// ) -// assertEquals( -// """\\127.0.0.1\c$\Windows""", -// """\\127.0.0.1\c$\Windows""".toPath(normalize = true).toString() -// ) -// assertEquals( -// """\\127.0.0.1\c$\Windows""", -// """\\127.0.0.1\c$\Windows\..\Windows""".toPath(normalize = true).toString() -// ) -// assertEquals( -// """\\127.0.0.1\localhost\c$\Windows""", -// """\\127.0.0.1\..\localhost\c$\Windows""".toPath(normalize = true).toString() -// ) -// assertEquals( -// """\\127.0.0.1\d$""", -// """\\127.0.0.1\c$\..\d$""".toPath(normalize = true).toString() -// ) -// assertEquals( -// """\\127.0.0.1\c$""", -// """\\..\127.0.0.1\..\c$""".toPath(normalize = true).toString() -// ) -// } -// @Test fun normalizeAbsolute() { -// assertEquals("/", "/.".toPath(normalize = true).toString()) -// assertEquals("/", "/.".toPath(normalize = false).toString()) -// assertEquals("/", "/..".toPath(normalize = true).toString()) -// assertEquals("/", "/..".toPath(normalize = false).toString()) -// assertEquals("/", "/../..".toPath(normalize = true).toString()) -// assertEquals("/", "/../..".toPath(normalize = false).toString()) -// -// assertEquals("/a/b", "/a/./b".toPath(normalize = true).toString()) -// assertEquals("/a/b", "/a/./b".toPath(normalize = false).toString()) -// assertEquals("/a/.../b", "/a/..././b".toPath(normalize = true).toString()) -// assertEquals("/a/.../b", "/a/..././b".toPath(normalize = false).toString()) -// assertEquals("/", "/a/..".toPath(normalize = true).toString()) -// assertEquals("/a/..", "/a/..".toPath(normalize = false).toString()) -// assertEquals("/b", "/../a/../b".toPath(normalize = true).toString()) -// assertEquals("/a/../b", "/../a/../b".toPath(normalize = false).toString()) -// } -// -// @Test fun normalizeRelative() { -// assertEquals(".", ".".toPath(normalize = true).toString()) -// assertEquals(".", ".".toPath(normalize = false).toString()) -// assertEquals("..", "..".toPath(normalize = true).toString()) -// assertEquals("..", "..".toPath(normalize = false).toString()) -// assertEquals("../..", "../..".toPath(normalize = true).toString()) -// assertEquals("../..", "../..".toPath(normalize = false).toString()) -// -// assertEquals("a/b", "a/./b".toPath(normalize = true).toString()) -// assertEquals("a/b", "a/./b".toPath(normalize = false).toString()) -// assertEquals("a/.../b", "a/..././b".toPath(normalize = true).toString()) -// assertEquals("a/.../b", "a/..././b".toPath(normalize = false).toString()) -// assertEquals(".", "a/..".toPath(normalize = true).toString()) -// assertEquals("a/..", "a/..".toPath(normalize = false).toString()) -// assertEquals("../b", "../a/../b".toPath(normalize = true).toString()) -// assertEquals("../a/../b", "../a/../b".toPath(normalize = false).toString()) -// } -// -// @Test fun normalized() { -// val normalizedRoot = "/".toPath() -// assertEquals(normalizedRoot, "/a/..".toPath(normalize = true)) -// assertEquals(normalizedRoot, "/a/..".toPath(normalize = false).normalized()) -// assertEquals(normalizedRoot, "/a/..".toPath(normalize = true).normalized()) -// -// val normalizedRelative = "../b".toPath() -// assertEquals(normalizedRelative, "../a/../b".toPath(normalize = true)) -// assertEquals(normalizedRelative, "../a/../b".toPath(normalize = false).normalized()) -// assertEquals(normalizedRelative, "../a/../b".toPath(normalize = true).normalized()) -// } -//} diff --git a/core/common/test/TestingSupport.kt b/core/common/test/TestingSupport.kt deleted file mode 100644 index 416998113..000000000 --- a/core/common/test/TestingSupport.kt +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2017-2023 JetBrains s.r.o. and respective authors and developers. - * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENCE file. - */ - -/* - * Copyright (C) 2019 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 kotlinx.io - -import kotlin.test.assertEquals - -fun Char.repeat(count: Int): String { - return toString().repeat(count) -} - -fun assertArrayEquals(a: ByteArray, b: ByteArray) { - assertEquals(a.contentToString(), b.contentToString()) -} \ No newline at end of file diff --git a/core/common/test/util.kt b/core/common/test/util.kt index 1ed7ab5d2..11d956eca 100644 --- a/core/common/test/util.kt +++ b/core/common/test/util.kt @@ -21,6 +21,7 @@ package kotlinx.io import kotlin.random.Random +import kotlin.test.assertEquals fun segmentSizes(buffer: Buffer): List { var segment = buffer.head ?: return emptyList() @@ -71,38 +72,6 @@ fun bufferWithSegments(vararg segments: String): Buffer { return result } -//fun makeSegments(source: ByteString): ByteString { -// val buffer = Buffer() -// for (i in 0 until source.size) { -// val segment = buffer.writableSegment(Segment.SIZE) -// segment.data[segment.pos] = source[i] -// segment.limit++ -// buffer.size++ -// } -// return buffer.snapshot() -//} -// -///** -// * Returns a string with all '\' slashes replaced with '/' slashes. This is useful for test -// * assertions that intend to ignore slashes. -// */ -//fun Path.withUnixSlashes(): String { -// return toString().replace('\\', '/') -//} -// -//expect fun assertRelativeTo( -// a: Path, -// b: Path, -// bRelativeToA: Path, -// sameAsNio: Boolean = true, -//) -// -//expect fun assertRelativeToFails( -// a: Path, -// b: Path, -// sameAsNio: Boolean = true, -//): IllegalArgumentException - expect fun createTempFile(): String expect fun deleteFile(path: String) @@ -127,4 +96,12 @@ fun String.decodeHex(): ByteArray { } return result +} + +fun Char.repeat(count: Int): String { + return toString().repeat(count) +} + +fun assertArrayEquals(a: ByteArray, b: ByteArray) { + assertEquals(a.contentToString(), b.contentToString()) } \ No newline at end of file diff --git a/core/jvm/src/Buffer.kt b/core/jvm/src/Buffer.kt index d886c290c..c0d6f8ab4 100644 --- a/core/jvm/src/Buffer.kt +++ b/core/jvm/src/Buffer.kt @@ -314,7 +314,7 @@ actual class Buffer : Source, Sink, Cloneable, ByteChannel { override fun hashCode(): Int = commonHashCode() /** - * Returns a human-readable string that describes the contents of this buffer. Typically this + * Returns a human-readable string that describes the contents of this buffer. Typically, this * is a string like `[text=Hello]` or `[hex=0000ffff]`. */ override fun toString() = commonString() From 198b1408e42216adbdb249c31cd2e5b175ba0066 Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Mon, 5 Jun 2023 12:09:49 +0200 Subject: [PATCH 16/83] Improve test coverage --- core/common/test/AbstractSourceTest.kt | 35 ++++++++++-- core/jvm/src/JvmCore.kt | 2 + core/jvm/test/AbstractSinkTestJVM.kt | 35 +++++++++++- core/jvm/test/AbstractSourceTestJVM.kt | 73 +++++++++++++++++++++++++- core/jvm/test/BufferTest.kt | 60 +++++++++++++++------ 5 files changed, 182 insertions(+), 23 deletions(-) diff --git a/core/common/test/AbstractSourceTest.kt b/core/common/test/AbstractSourceTest.kt index 60065bd53..c42bc74c2 100644 --- a/core/common/test/AbstractSourceTest.kt +++ b/core/common/test/AbstractSourceTest.kt @@ -21,11 +21,7 @@ package kotlinx.io -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertFailsWith -import kotlin.test.assertFalse -import kotlin.test.assertTrue +import kotlin.test.* class BufferSourceTest : AbstractBufferedSourceTest(SourceFactory.BUFFER) class RealBufferedSourceTest : AbstractBufferedSourceTest(SourceFactory.REAL_BUFFERED_SOURCE) @@ -301,6 +297,23 @@ abstract class AbstractBufferedSourceTest internal constructor( assertTrue(source.exhausted()) } + @Test fun readNegativeBytesFromSource() { + assertFailsWith { + source.read(Buffer().writeByte(0), -1L) + } + } + + @Test fun readFromClosedSource() { + if (source is Buffer) { + return + } + + source.close() + assertFailsWith { + source.read(Buffer().writeByte(0), 1L) + } + } + @Test fun readFully() { sink.writeUtf8("a".repeat(10000)) sink.emit() @@ -717,6 +730,18 @@ abstract class AbstractBufferedSourceTest internal constructor( assertEquals(-1, source.readHexadecimalUnsignedLong()) } + @Test fun longHexTerminatedByNonDigit() { + sink.writeUtf8("abcd,").emit() + assertEquals(0xabcdL, source.readHexadecimalUnsignedLong()) + } + + @Test fun longHexAlphabet() { + sink.writeUtf8("7896543210abcdef").emit() + assertEquals(0x7896543210abcdefL, source.readHexadecimalUnsignedLong()) + sink.writeUtf8("ABCDEF").emit() + assertEquals(0xabcdefL, source.readHexadecimalUnsignedLong()) + } + @Test fun longHexStringTooLongThrows() { sink.writeUtf8("fffffffffffffffff") sink.emit() diff --git a/core/jvm/src/JvmCore.kt b/core/jvm/src/JvmCore.kt index bf2a80939..7192d6db5 100644 --- a/core/jvm/src/JvmCore.kt +++ b/core/jvm/src/JvmCore.kt @@ -36,6 +36,7 @@ import java.util.logging.Logger import javax.crypto.Cipher import javax.crypto.Mac +// TODO: improve test coverage /** Returns a sink that writes to `out`. */ fun OutputStream.sink(): RawSink = OutputStreamSink(this) @@ -74,6 +75,7 @@ private open class OutputStreamSink( override fun toString() = "sink($out)" } +// TODO: improve test coverage /** Returns a source that reads from `in`. */ fun InputStream.source(): RawSource = InputStreamSource(this) diff --git a/core/jvm/test/AbstractSinkTestJVM.kt b/core/jvm/test/AbstractSinkTestJVM.kt index 0d97a28d8..6ca1ae760 100644 --- a/core/jvm/test/AbstractSinkTestJVM.kt +++ b/core/jvm/test/AbstractSinkTestJVM.kt @@ -21,7 +21,6 @@ package kotlinx.io -import org.junit.jupiter.api.Test import java.io.OutputStream import java.nio.ByteBuffer import java.nio.charset.Charset @@ -57,6 +56,29 @@ abstract class AbstractSinkTestJVM internal constructor(factory: SinkFactory) { } } + @Test + fun writeToClosedOutputStream() { + if (sink is Buffer) { + return + } + val out = sink.outputStream() + sink.close() + assertFailsWith { out.write(0) } + assertFailsWith { out.write(ByteArray(1)) } + assertFailsWith { out.write(ByteArray(42), 0, 1) } + } + + @Test + fun outputStreamClosesSink() { + if (sink is Buffer) { + return + } + + val out = sink.outputStream() + out.close() + assertFailsWith { sink.writeByte(0) } + } + @Test @Throws(java.lang.Exception::class) fun writeNioBuffer() { @@ -87,6 +109,17 @@ abstract class AbstractSinkTestJVM internal constructor(factory: SinkFactory) { assertEquals(expected, data.readUtf8()) } + @Test + fun writeNioBufferToClosedSink() { + if (sink is Buffer) { + return + } + sink.close() + assertFailsWith { + sink.write(ByteBuffer.allocate(10)) + } + } + @Test @Throws(IOException::class) fun writeStringWithCharset() { diff --git a/core/jvm/test/AbstractSourceTestJVM.kt b/core/jvm/test/AbstractSourceTestJVM.kt index e43d16c3f..ad64bf4a6 100644 --- a/core/jvm/test/AbstractSourceTestJVM.kt +++ b/core/jvm/test/AbstractSourceTestJVM.kt @@ -21,7 +21,6 @@ package kotlinx.io -import org.junit.jupiter.api.Test import java.io.InputStream import java.nio.Buffer import java.nio.ByteBuffer @@ -124,6 +123,73 @@ abstract class AbstractSourceTestJVM(private val factory: SourceFactory) { } } + @Test + fun inputStreamForClosedSource() { + if (source is kotlinx.io.Buffer) { + return + } + + sink.writeByte(0) + sink.emit() + + val input = source.inputStream() + source.close() + assertFailsWith { input.read() } + assertFailsWith { input.read(ByteArray(1)) } + assertFailsWith { input.read(ByteArray(10), 0, 1) } + } + + @Test + fun inputStreamClosesSource() { + if (source is kotlinx.io.Buffer) { + return + } + + sink.writeByte(0) + sink.emit() + + val input = source.inputStream() + input.close() + + assertFailsWith { source.readByte() } + } + + @Test + fun inputStreamAvailable() { + val input = source.inputStream() + assertEquals(0, input.available()) + + sink.writeInt(42) + sink.emit() + assertTrue(source.request(4)) // fill the buffer + + assertEquals(4, input.available()) + + input.read() + assertEquals(3, input.available()) + + source.readByte() + assertEquals(2, input.available()) + + sink.writeByte(0) + sink.emit() + + val expectedBytes = if (source is kotlinx.io.Buffer) { 3 } else { 2 } + assertEquals(expectedBytes, input.available()) + } + + @Test + fun inputStreamAvailableForClosedSource() { + if (source is kotlinx.io.Buffer) { + return + } + + val input = source.inputStream() + source.close() + + assertFailsWith { input.available() } + } + @Test @Throws(java.lang.Exception::class) fun readNioBuffer() { @@ -159,6 +225,11 @@ abstract class AbstractSourceTestJVM(private val factory: SourceFactory) { assertEquals(expected, String(data)) } + @Test + fun readNioBufferFromEmptySource() { + assertEquals(-1, source.read(ByteBuffer.allocate(10))) + } + @Test @Throws(java.lang.Exception::class) fun readSpecificCharsetPartial() { diff --git a/core/jvm/test/BufferTest.kt b/core/jvm/test/BufferTest.kt index b8aee0122..ce0b41a69 100644 --- a/core/jvm/test/BufferTest.kt +++ b/core/jvm/test/BufferTest.kt @@ -44,6 +44,18 @@ class BufferTest { source.readUtf8(SEGMENT_SIZE * 4L)) } + @Test + fun copyToSkippingSegments() { + val source = Buffer() + source.writeUtf8("a".repeat(SEGMENT_SIZE * 2)) + source.writeUtf8("b".repeat( SEGMENT_SIZE * 2)) + val out = ByteArrayOutputStream() + source.copyTo(out, SEGMENT_SIZE * 2 + 1L, 3L) + assertEquals("bbb", out.toString()) + assertEquals("a".repeat(SEGMENT_SIZE * 2) + "b".repeat( SEGMENT_SIZE * 2), + source.readUtf8(SEGMENT_SIZE * 4L)) + } + @Test @Throws(Exception::class) fun copyToStream() { @@ -82,9 +94,9 @@ class BufferTest { @Test @Throws(java.lang.Exception::class) fun readFromStream() { - val `in`: InputStream = ByteArrayInputStream("hello, world!".toByteArray(UTF_8)) + val input: InputStream = ByteArrayInputStream("hello, world!".toByteArray(UTF_8)) val buffer = Buffer() - buffer.readFrom(`in`) + buffer.readFrom(input) val out = buffer.readUtf8() assertEquals("hello, world!", out) } @@ -92,9 +104,9 @@ class BufferTest { @Test @Throws(java.lang.Exception::class) fun readFromSpanningSegments() { - val `in`: InputStream = ByteArrayInputStream("hello, world!".toByteArray(UTF_8)) + val input: InputStream = ByteArrayInputStream("hello, world!".toByteArray(UTF_8)) val buffer = Buffer().writeUtf8("a".repeat(SEGMENT_SIZE - 10)) - buffer.readFrom(`in`) + buffer.readFrom(input) val out = buffer.readUtf8() assertEquals("a".repeat(SEGMENT_SIZE - 10) + "hello, world!", out) } @@ -102,13 +114,29 @@ class BufferTest { @Test @Throws(java.lang.Exception::class) fun readFromStreamWithCount() { - val `in`: InputStream = ByteArrayInputStream("hello, world!".toByteArray(UTF_8)) + val input: InputStream = ByteArrayInputStream("hello, world!".toByteArray(UTF_8)) val buffer = Buffer() - buffer.readFrom(`in`, 10) + buffer.readFrom(input, 10) val out = buffer.readUtf8() assertEquals("hello, wor", out) } + @Test + fun readFromStreamThrowsEOFOnExhaustion() { + val input = ByteArrayInputStream("hello, world!".toByteArray(UTF_8)) + val buffer = Buffer() + assertFailsWith { + buffer.readFrom(input, input.available() + 1L) + } + } + + @Test + fun readFromStreamWithNegativeBytesCount() { + assertFailsWith { + Buffer().readFrom(ByteArrayInputStream(ByteArray(1)), -1) + } + } + @Test @Throws(IOException::class) fun readFromDoesNotLeaveEmptyTailSegment() { @@ -122,13 +150,13 @@ class BufferTest { fun bufferInputStreamByteByByte() { val source = Buffer() source.writeUtf8("abc") - val `in`: InputStream = source.inputStream() - assertEquals(3, `in`.available()) - assertEquals('a'.code, `in`.read()) - assertEquals('b'.code, `in`.read()) - assertEquals('c'.code, `in`.read()) - assertEquals(-1, `in`.read()) - assertEquals(0, `in`.available()) + val input: InputStream = source.inputStream() + assertEquals(3, input.available()) + assertEquals('a'.code, input.read()) + assertEquals('b'.code, input.read()) + assertEquals('c'.code, input.read()) + assertEquals(-1, input.read()) + assertEquals(0, input.available()) } @Test @@ -138,11 +166,11 @@ class BufferTest { source.writeUtf8("abc") val byteArray = ByteArray(4) Arrays.fill(byteArray, (-5).toByte()) - val `in`: InputStream = source.inputStream() - assertEquals(3, `in`.read(byteArray)) + val input: InputStream = source.inputStream() + assertEquals(3, input.read(byteArray)) assertEquals("[97, 98, 99, -5]", byteArray.contentToString()) Arrays.fill(byteArray, (-7).toByte()) - assertEquals(-1, `in`.read(byteArray)) + assertEquals(-1, input.read(byteArray)) assertEquals("[-7, -7, -7, -7]", byteArray.contentToString()) } From 34510ff05b09fb5eb21cc6c3c803cf64e1834d87 Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Mon, 5 Jun 2023 14:44:22 +0200 Subject: [PATCH 17/83] Add more tests on UTF-8 --- core/common/src/internal/-Buffer.kt | 18 -- ...nOkioKotlinTest.kt => CommonKotlinTest.kt} | 2 +- core/common/test/Utf8KotlinTest.kt | 154 +++++++++++++++++- 3 files changed, 150 insertions(+), 24 deletions(-) rename core/common/test/{CommonOkioKotlinTest.kt => CommonKotlinTest.kt} (97%) diff --git a/core/common/src/internal/-Buffer.kt b/core/common/src/internal/-Buffer.kt index fc9844a6a..9550d40f7 100644 --- a/core/common/src/internal/-Buffer.kt +++ b/core/common/src/internal/-Buffer.kt @@ -26,24 +26,6 @@ package kotlinx.io.internal import kotlinx.io.* -import kotlin.native.concurrent.SharedImmutable - -internal fun Buffer.readUtf8Line(newline: Long): String { - return when { - newline > 0 && this[newline - 1] == '\r'.code.toByte() -> { - // Read everything until '\r\n', then skip the '\r\n'. - val result = readUtf8(newline - 1L) - skip(2L) - result - } - else -> { - // Read everything until '\n', then skip the '\n'. - val result = readUtf8(newline) - skip(1L) - result - } - } -} /** * Invoke `lambda` with the segment and offset at `fromIndex`. Searches from the front or the back diff --git a/core/common/test/CommonOkioKotlinTest.kt b/core/common/test/CommonKotlinTest.kt similarity index 97% rename from core/common/test/CommonOkioKotlinTest.kt rename to core/common/test/CommonKotlinTest.kt index 77310875c..db05e7f44 100644 --- a/core/common/test/CommonOkioKotlinTest.kt +++ b/core/common/test/CommonKotlinTest.kt @@ -24,7 +24,7 @@ package kotlinx.io import kotlin.test.Test import kotlin.test.assertEquals -class CommonOkioKotlinTest { +class CommonKotlinTest { @Test fun sourceBuffer() { val source = Buffer().writeUtf8("a") val buffered = (source as RawSource).buffer() diff --git a/core/common/test/Utf8KotlinTest.kt b/core/common/test/Utf8KotlinTest.kt index bdde7180f..089d48fb4 100644 --- a/core/common/test/Utf8KotlinTest.kt +++ b/core/common/test/Utf8KotlinTest.kt @@ -24,9 +24,7 @@ package kotlinx.io import kotlinx.io.internal.REPLACEMENT_CODE_POINT import kotlinx.io.internal.commonAsUtf8ToByteArray import kotlinx.io.internal.processUtf8CodePoints -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertFailsWith +import kotlin.test.* class Utf8KotlinTest { @Test fun oneByteCharacters() { @@ -131,6 +129,49 @@ class Utf8KotlinTest { assertCodePointDecoded("ee8080", '\ue000'.code) } + @Test fun bufferWriteCodePoints() { + val buffer = Buffer() + buffer.assertCodePointEncoded("40", '@'.code) + buffer.assertCodePointEncoded("7f", '\u007f'.code) + buffer.assertCodePointEncoded("c280", '\u0080'.code) + buffer.assertCodePointEncoded("c2a9", '\u00a9'.code) + buffer.assertCodePointEncoded("c3bf", '\u00ff'.code) + buffer.assertCodePointEncoded("dfbf", '\u07ff'.code) + buffer.assertCodePointEncoded("e0a080", '\u0800'.code) + buffer.assertCodePointEncoded("e1839a", '\u10da'.code) + buffer.assertCodePointEncoded("efbfbf", '\uffff'.code) + buffer.assertCodePointEncoded("f0908080", 0x10000) + buffer.assertCodePointEncoded("f48087bf", 0x1001FF) + } + + @Test fun bufferReadCodePoints() { + val buffer = Buffer() + buffer.assertCodePointDecoded('@'.code, "40") + buffer.assertCodePointDecoded('\u007f'.code, "7f", ) + buffer.assertCodePointDecoded('\u0080'.code, "c280") + buffer.assertCodePointDecoded('\u00a9'.code, "c2a9") + buffer.assertCodePointDecoded('\u00ff'.code, "c3bf") + buffer.assertCodePointDecoded('\u07ff'.code, "dfbf") + buffer.assertCodePointDecoded('\u0800'.code, "e0a080") + buffer.assertCodePointDecoded('\u10da'.code, "e1839a") + buffer.assertCodePointDecoded('\uffff'.code, "efbfbf", ) + buffer.assertCodePointDecoded(0x10000, "f0908080") + buffer.assertCodePointDecoded(0x1001FF, "f48087bf") + } + + @Test fun bufferWriteUtf8String() { + val buffer = Buffer() + buffer.assertUtf8StringEncoded("68656c6c6f", "hello") + buffer.assertUtf8StringEncoded("cf87ceb5cf81ceb5cf84ceb9cf83cebccf8ccf82", "χερετισμός") + buffer.assertUtf8StringEncoded("e18392e18390e1839be18390e183a0e183afe1839de18391e18390", + "გამარჯობა") + buffer.assertUtf8StringEncoded("f093878bf0938bb4f09380a5", + "\uD80C\uDDCB\uD80C\uDEF4\uD80C\uDC25" /* 𓇋𓋴𓀥, to hail, AN EGYPTIAN HIEROGLYPHIC DICTIONARY, p. 79b */) + + // two consecutive high surrogates, replace with '?' + buffer.assertUtf8StringEncoded("3f3f", "\ud801\uD801") + } + @Test fun size() { assertEquals(0, "".utf8Size()) assertEquals(3, "abc".utf8Size()) @@ -161,6 +202,88 @@ class Utf8KotlinTest { } } + @Test + fun readCodePointFromEmptyBufferThrowsEofException() { + val buffer = Buffer() + assertFailsWith { buffer.readUtf8CodePoint() } + } + + @Test + fun readLeadingContinuationByteReturnsReplacementCharacter() { + val buffer = Buffer() + buffer.writeByte(0xbf) + assertEquals(REPLACEMENT_CODE_POINT, buffer.readUtf8CodePoint()) + assertTrue(buffer.exhausted()) + } + + @Test + fun readMissingContinuationBytesThrowsEofException() { + val buffer = Buffer() + buffer.writeByte(0xdf) + assertFailsWith { buffer.readUtf8CodePoint() } + assertFalse(buffer.exhausted()) // Prefix byte wasn't consumed. + } + + @Test + fun readTooLargeCodepointReturnsReplacementCharacter() { + // 5-byte and 6-byte code points are not supported. + val buffer = Buffer() + buffer.write("f888808080".decodeHex()) + assertEquals(REPLACEMENT_CODE_POINT, buffer.readUtf8CodePoint()) + assertEquals(REPLACEMENT_CODE_POINT, buffer.readUtf8CodePoint()) + assertEquals(REPLACEMENT_CODE_POINT, buffer.readUtf8CodePoint()) + assertEquals(REPLACEMENT_CODE_POINT, buffer.readUtf8CodePoint()) + assertEquals(REPLACEMENT_CODE_POINT, buffer.readUtf8CodePoint()) + assertTrue(buffer.exhausted()) + } + + @Test + fun readNonContinuationBytesReturnsReplacementCharacter() { + // Use a non-continuation byte where a continuation byte is expected. + val buffer = Buffer() + buffer.write("df20".decodeHex()) + assertEquals(REPLACEMENT_CODE_POINT, buffer.readUtf8CodePoint()) + assertEquals(0x20, buffer.readUtf8CodePoint()) // Non-continuation character not consumed. + assertTrue(buffer.exhausted()) + } + + @Test + fun readCodePointBeyondUnicodeMaximum() { + // A 4-byte encoding with data above the U+10ffff Unicode maximum. + val buffer = Buffer() + buffer.write("f4908080".decodeHex()) + assertEquals(REPLACEMENT_CODE_POINT, buffer.readUtf8CodePoint()) + assertTrue(buffer.exhausted()) + } + + @Test + fun readSurrogateCodePoint() { + val buffer = Buffer() + buffer.write("eda080".decodeHex()) + assertEquals(REPLACEMENT_CODE_POINT, buffer.readUtf8CodePoint()) + assertTrue(buffer.exhausted()) + buffer.write("edbfbf".decodeHex()) + assertEquals(REPLACEMENT_CODE_POINT, buffer.readUtf8CodePoint()) + assertTrue(buffer.exhausted()) + } + + @Test + fun readOverlongCodePoint() { + // Use 2 bytes to encode data that only needs 1 byte. + val buffer = Buffer() + buffer.write("c080".decodeHex()) + assertEquals(REPLACEMENT_CODE_POINT, buffer.readUtf8CodePoint()) + assertTrue(buffer.exhausted()) + } + + @Test + fun writeCodePointBeyondUnicodeMaximum() { + val buffer = Buffer() + assertFailsWith("Unexpected code point: 0x110000") { + buffer.writeUtf8CodePoint(0x110000) + } + } + private fun assertEncoded(hex: String, vararg codePoints: Int) { assertCodePointDecoded(hex, *codePoints) } @@ -175,8 +298,23 @@ class Utf8KotlinTest { assertEquals(i, codePoints.size) // Checked them all } + private fun Buffer.assertCodePointEncoded(expectedHex: String, codePoint: Int) { + buffer.writeUtf8CodePoint(codePoint) + assertArrayEquals(expectedHex.decodeHex(), buffer.readByteArray()) + } + + private fun Buffer.assertCodePointDecoded(expectedCodePoint: Int, hex: String) { + buffer.write(hex.decodeHex()) + assertEquals(expectedCodePoint, buffer.readUtf8CodePoint()) + } + + private fun Buffer.assertUtf8StringEncoded(expectedHex: String, string: String) { + buffer.writeUtf8(string) + assertArrayEquals(expectedHex.decodeHex(), buffer.readByteArray()) + } + private fun assertStringEncoded(hex: String, string: String) { - val expectedUtf8 = hex.decodeHex() + val expectedUtf8 = hex.decodeHex() // Confirm our expectations are consistent with the platform. val platformUtf8 = string.asUtf8ToByteArray() @@ -186,7 +324,13 @@ class Utf8KotlinTest { val actualUtf8 = string.commonAsUtf8ToByteArray() assertArrayEquals(expectedUtf8, actualUtf8) - // TODO Confirm we are consistent when writing one code point at a time. + // Confirm we are consistent when writing one code point at a time. + val bufferUtf8 = Buffer() + for (charIdx in string.indices) { + val c = string[charIdx] + bufferUtf8.writeUtf8CodePoint(c.code) + } + assertArrayEquals(expectedUtf8, bufferUtf8.readByteArray()) // Confirm we are consistent when measuring lengths. assertEquals(expectedUtf8.size.toLong(), string.utf8Size()) From efc3a27c2d19f227809c3add0757a30237000382 Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Mon, 5 Jun 2023 16:01:12 +0200 Subject: [PATCH 18/83] Fix FS-related tests on Windows --- core/jvm/test/JvmPlatformTest.kt | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/core/jvm/test/JvmPlatformTest.kt b/core/jvm/test/JvmPlatformTest.kt index 03e7e3289..37063e055 100644 --- a/core/jvm/test/JvmPlatformTest.kt +++ b/core/jvm/test/JvmPlatformTest.kt @@ -50,50 +50,55 @@ class JvmPlatformTest { @Test fun fileSink() { val file = File(tempDir, "test") - val sink = file.sink() - sink.write(Buffer().writeUtf8("a"), 1L) + file.sink().use { sink -> + sink.write(Buffer().writeUtf8("a"), 1L) + } assertEquals(file.readText(), "a") } @Test fun fileAppendingSink() { val file = File(tempDir, "test") file.writeText("a") - val sink = file.sink(append = true) - sink.write(Buffer().writeUtf8("b"), 1L) - sink.close() + file.sink(append = true).use { sink -> + sink.write(Buffer().writeUtf8("b"), 1L) + } assertEquals(file.readText(), "ab") } @Test fun fileSource() { val file = File(tempDir, "test") file.writeText("a") - val source = file.source() val buffer = Buffer() - source.read(buffer, 1L) + file.source().use { source -> + source.read(buffer, 1L) + } assertEquals(buffer.readUtf8(), "a") } @Test fun pathSink() { val file = File(tempDir, "test") - val sink = file.toPath().sink() - sink.write(Buffer().writeUtf8("a"), 1L) + file.toPath().sink().use { sink -> + sink.write(Buffer().writeUtf8("a"), 1L) + } assertEquals(file.readText(), "a") } @Test fun pathSinkWithOptions() { val file = File(tempDir, "test") file.writeText("a") - val sink = file.toPath().sink(StandardOpenOption.APPEND) - sink.write(Buffer().writeUtf8("b"), 1L) + file.toPath().sink(StandardOpenOption.APPEND).use { sink -> + sink.write(Buffer().writeUtf8("b"), 1L) + } assertEquals(file.readText(), "ab") } @Test fun pathSource() { val file = File(tempDir, "test") file.writeText("a") - val source = file.toPath().source() val buffer = Buffer() - source.read(buffer, 1L) + file.toPath().source().use { source -> + source.read(buffer, 1L) + } assertEquals(buffer.readUtf8(), "a") } From 45da73f25f8a19c2589bcc6b0c435321f9d49035 Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Mon, 5 Jun 2023 17:44:05 +0200 Subject: [PATCH 19/83] Updated documentation --- core/Module.md | 1 + core/common/src/-CommonPlatform.kt | 4 +-- core/common/src/-Util.kt | 2 +- core/common/src/Buffer.kt | 7 ++-- core/common/src/Core.kt | 4 +-- core/common/src/RawSink.kt | 6 ++-- core/common/src/RawSource.kt | 11 ++++--- core/common/src/SourceExt.kt | 6 +++- core/common/src/Utf8.kt | 13 +++++--- core/common/src/internal/-Utf8.kt | 2 +- core/jvm/src/-JvmPlatform.kt | 4 +-- core/jvm/src/Buffer.kt | 37 ++++++++++++++++++++-- core/jvm/src/JvmCore.kt | 51 +++++++++++++++++++++++++----- core/jvm/src/Sink.kt | 16 ++++++++-- core/jvm/src/Source.kt | 17 +++++++++- core/native/src/-NonJvmPlatform.kt | 8 ++--- core/native/src/Buffer.kt | 9 ------ 17 files changed, 143 insertions(+), 55 deletions(-) diff --git a/core/Module.md b/core/Module.md index 404fa4763..b29adb774 100644 --- a/core/Module.md +++ b/core/Module.md @@ -1,6 +1,7 @@ # Module kotlinx-io-core The module provides core multiplatform IO primitives and integrates it with platform-specific APIs. + `kotlinx-io` aims to provide a concise but powerful API along with efficient implementation. The main interfaces for the IO interaction are [kotlinx.io.Source] and [kotlinx.io.Sink] providing buffered read and diff --git a/core/common/src/-CommonPlatform.kt b/core/common/src/-CommonPlatform.kt index b5b73c8a4..79a5cd073 100644 --- a/core/common/src/-CommonPlatform.kt +++ b/core/common/src/-CommonPlatform.kt @@ -21,12 +21,10 @@ package kotlinx.io -internal expect fun ByteArray.toUtf8String(): String - internal expect fun String.asUtf8ToByteArray(): ByteArray // TODO make internal https://youtrack.jetbrains.com/issue/KT-37316 -expect class ArrayIndexOutOfBoundsException(message: String?) : IndexOutOfBoundsException +// expect class ArrayIndexOutOfBoundsException(message: String?) : IndexOutOfBoundsException expect open class IOException(message: String?, cause: Throwable?) : Exception { constructor(message: String? = null) diff --git a/core/common/src/-Util.kt b/core/common/src/-Util.kt index 3eaf209aa..c4b814e5b 100644 --- a/core/common/src/-Util.kt +++ b/core/common/src/-Util.kt @@ -31,7 +31,7 @@ internal val HEX_DIGIT_CHARS = internal fun checkOffsetAndCount(size: Long, offset: Long, byteCount: Long) { if (offset or byteCount < 0 || offset > size || size - offset < byteCount) { - throw ArrayIndexOutOfBoundsException("size=$size offset=$offset byteCount=$byteCount") + throw IndexOutOfBoundsException("size=$size offset=$offset byteCount=$byteCount") } } diff --git a/core/common/src/Buffer.kt b/core/common/src/Buffer.kt index 3d2cae48a..3aefc8052 100644 --- a/core/common/src/Buffer.kt +++ b/core/common/src/Buffer.kt @@ -24,13 +24,13 @@ package kotlinx.io * A collection of bytes in memory. * * The buffer can be viewed as an unbound queue whose size grows with the data being written - * and shrinks with data being consumed. Internally, the buffer consists of data segments and buffer's capacity - * grows and shrinks in units of data segments instead of individual bytes. + * and shrinks with data being consumed. Internally, the buffer consists of data segments, + * and the buffer's capacity grows and shrinks in units of data segments instead of individual bytes. * * The buffer was designed to reduce memory allocations when possible. Instead of copying bytes * from one place in memory to another, this class just changes ownership of the underlying data segments. * - * To reduce allocations and speed up buffer's extension, it may use data segments pooling. + * To reduce allocations and speed up the buffer's extension, it may use data segments pooling. * * [Buffer] implements both [Source] and [Sink] and could be used as a source or a sink, * but unlike regular sinks and sources its [close], [cancel], [flush], [emit], [emitCompleteSegments] @@ -83,7 +83,6 @@ expect class Buffer() : Source, Sink { /** * Returns the number of bytes in segments that are fully filled and are no longer writable. * - * TODO: is it? * This is the number of bytes that can be flushed immediately to an underlying sink without harming throughput. */ fun completeSegmentByteCount(): Long diff --git a/core/common/src/Core.kt b/core/common/src/Core.kt index 59e0f5d0a..f19a1f9e0 100644 --- a/core/common/src/Core.kt +++ b/core/common/src/Core.kt @@ -22,14 +22,14 @@ package kotlinx.io /** * Returns a new source that buffers reads from the source. The returned source will perform bulk - * reads into its in-memory buffer. Use this wherever you read a source to get an ergonomic and + * reads into its in-memory buffer. Use this wherever you read a source to get ergonomic and * efficient access to data. */ fun RawSource.buffer(): Source = RealSource(this) /** * Returns a new sink that buffers writes to the sink. The returned sink will batch writes to the sink. - * Use this wherever you write to a sink to get an ergonomic and efficient access to data. + * Use this wherever you write to a sink to get ergonomic and efficient access to data. */ fun RawSink.buffer(): Sink = RealSink(this) diff --git a/core/common/src/RawSink.kt b/core/common/src/RawSink.kt index d113d8848..668c8833f 100644 --- a/core/common/src/RawSink.kt +++ b/core/common/src/RawSink.kt @@ -21,20 +21,20 @@ package kotlinx.io /** - * Receives a stream of bytes. RawSink is a base interface all other `kotlinx-io` data receivers are built upon it. + * Receives a stream of bytes. RawSink is a base interface for `kotlinx-io` data receivers. * * This interface should be implemented to write data wherever it's needed: to the network, storage, * or a buffer in memory. Sinks may be layered to transform received data, such as to compress, encrypt, throttle, * or add protocol framing. * - * Most application code shouldn't operate on a raw sink directly, but rather on a [Sink] which + * Most application code shouldn't operate on a raw sink directly, but rather on a buffered [Sink] which * is both more efficient and more convenient. Use [buffer] to wrap any raw sink with a buffer. * */ expect interface RawSink : Closeable { // TODO: should it throw EOFException instead? /** - * Removes [byteCount] bytes from [source] and appends them to this. + * Removes [byteCount] bytes from [source] and appends them to this sink. * * @param source the source to read data from. * @param byteCount the number of bytes to write. diff --git a/core/common/src/RawSource.kt b/core/common/src/RawSource.kt index b61cab17a..55f9efa18 100644 --- a/core/common/src/RawSource.kt +++ b/core/common/src/RawSource.kt @@ -21,20 +21,23 @@ package kotlinx.io /** - * Supplies a stream of bytes. RawSource is a base interface all other `kotlinx-io` data suppliers are built upon it. + * Supplies a stream of bytes. RawSource is a base interface for `kotlinx-io` data suppliers. * * The interface should be implemented to read data from wherever it's located: from the network, storage, * or a buffer in memory. Sources may be layered to transform supplied data, such as to decompress, decrypt, * or remove protocol framing. * - * Most applications shouldn't operate on a raw source directly, but rather on a [Source] which + * Most applications shouldn't operate on a raw source directly, but rather on a buffered [Source] which * is both more efficient and more convenient. Use [buffer] to wrap any raw source with a buffer. */ interface RawSource : Closeable { // TODO: should be throw something if byteCount = 0? /** - * Removes at least 1, and up to [byteCount] bytes from this and appends them to [sink]. Returns - * the number of bytes read, or -1 if this source is exhausted. + * Removes at least 1, and up to [byteCount] bytes from this source and appends them to [sink]. + * Returns the number of bytes read, or -1 if this source is exhausted. + * + * @param sink the destination to write the data from this source. + * @param byteCount the number of bytes to read. * * @throws IllegalArgumentException when [byteCount] is negative. */ diff --git a/core/common/src/SourceExt.kt b/core/common/src/SourceExt.kt index 288a6647c..b5eb57c63 100644 --- a/core/common/src/SourceExt.kt +++ b/core/common/src/SourceExt.kt @@ -135,12 +135,16 @@ fun Source.readHexadecimalUnsignedLong(): Long { /** * Returns an index of [b] first occurrence in the range of [fromIndex] inclusive to [toIndex] - * exclusive, or `-1` if [b]. + * exclusive, or `-1` when the range doesn't contain [b]. * * The scan terminates at either [toIndex] or source's exhaustion, whichever comes first. The * maximum number of bytes scanned is `toIndex-fromIndex`. * If [b] not found in buffered data, [toIndex] is yet to be reached and the underlying source is not yet exhausted * then new data will be read from the underlying source into the buffer. + * + * @param b the value to find. + * @param fromIndex the start of the range to find [b], inclusive. + * @param toIndex the end of the range to find [b], exclusive. */ fun Source.indexOf(b: Byte, fromIndex: Long = 0L, toIndex: Long = Long.MAX_VALUE): Long { require(fromIndex in 0..toIndex) diff --git a/core/common/src/Utf8.kt b/core/common/src/Utf8.kt index 48f59cc46..dbdf0027f 100644 --- a/core/common/src/Utf8.kt +++ b/core/common/src/Utf8.kt @@ -80,8 +80,8 @@ import kotlin.jvm.JvmOverloads /** * Returns the number of bytes used to encode the slice of `string` as UTF-8 when using [Sink.writeUtf8]. * - * @param beginIndex the index of the first character to encode. - * @param endIndex the index of the character past the last character to encode. + * @param beginIndex the index of the first character to encode, inclusive. + * @param endIndex the index of the character past the last character to encode, exclusive. * * @throws IndexOutOfBoundsException when [beginIndex] or [endIndex] correspond to a range * out of the current string bounds. @@ -179,6 +179,9 @@ fun Buffer.readUtf8(): String { /** * Removes [byteCount] bytes from this source, decodes them as UTF-8, and returns the string. * + * @param byteCount the number of bytes to read from the source for string decoding. + * + * @throws IllegalArgumentException when [byteCount] is negative. * @throws EOFException when the source is exhausted before reading [byteCount] bytes from it. */ fun Source.readUtf8(byteCount: Long): String { @@ -193,9 +196,9 @@ fun Source.readUtf8(byteCount: Long): String { * [EOFException] and consumes no input. * * If this source doesn't start with a properly-encoded UTF-8 code point, this method will remove - * 1 or more non-UTF-8 bytes and return the replacement character (`U+FFFD`). This covers encoding + * 1 or more non-UTF-8 bytes and return the replacement character (`U+fffd`). This covers encoding * problems (the input is not properly-encoded UTF-8), characters out of range (beyond the - * 0x10ffff limit of Unicode), code points for UTF-16 surrogates (U+d800..U+dfff) and overlong + * `0x10ffff` limit of Unicode), code points for UTF-16 surrogates (`U+d800`..`U+dfff`) and overlong * encodings (such as `0xc080` for the NUL character in modified UTF-8). * * @throws EOFException when the source is exhausted before a complete code point can be read. @@ -224,7 +227,7 @@ fun Buffer.readUtf8CodePoint(): Int { * Removes and returns characters up to but not including the next line break. A line break is * either `"\n"` or `"\r\n"`; these characters are not included in the result. * - * On the end of the stream this method returns null. If the source doesn't end with a line break then + * On the end of the stream this method returns null. If the source doesn't end with a line break, then * an implicit line break is assumed. Null is returned once the source is exhausted. */ fun Source.readUtf8Line(): String? { diff --git a/core/common/src/internal/-Utf8.kt b/core/common/src/internal/-Utf8.kt index ffcd3298a..6cf4bc058 100644 --- a/core/common/src/internal/-Utf8.kt +++ b/core/common/src/internal/-Utf8.kt @@ -25,7 +25,7 @@ import kotlinx.io.* internal fun ByteArray.commonToUtf8String(beginIndex: Int = 0, endIndex: Int = size): String { if (beginIndex < 0 || endIndex > size || beginIndex > endIndex) { - throw ArrayIndexOutOfBoundsException("size=$size beginIndex=$beginIndex endIndex=$endIndex") + throw IndexOutOfBoundsException("size=$size beginIndex=$beginIndex endIndex=$endIndex") } val chars = CharArray(endIndex - beginIndex) diff --git a/core/jvm/src/-JvmPlatform.kt b/core/jvm/src/-JvmPlatform.kt index 94fb82260..a3ebaefb1 100644 --- a/core/jvm/src/-JvmPlatform.kt +++ b/core/jvm/src/-JvmPlatform.kt @@ -21,12 +21,10 @@ package kotlinx.io -internal actual fun ByteArray.toUtf8String(): String = String(this, Charsets.UTF_8) - internal actual fun String.asUtf8ToByteArray(): ByteArray = toByteArray(Charsets.UTF_8) // TODO remove if https://youtrack.jetbrains.com/issue/KT-20641 provides a better solution -actual typealias ArrayIndexOutOfBoundsException = java.lang.ArrayIndexOutOfBoundsException +// actual typealias ArrayIndexOutOfBoundsException = java.lang.ArrayIndexOutOfBoundsException actual typealias IOException = java.io.IOException diff --git a/core/jvm/src/Buffer.kt b/core/jvm/src/Buffer.kt index c0d6f8ab4..1cf7d03ce 100644 --- a/core/jvm/src/Buffer.kt +++ b/core/jvm/src/Buffer.kt @@ -37,6 +37,11 @@ actual class Buffer : Source, Sink, Cloneable, ByteChannel { actual override val buffer get() = this + /** + * Returns a new [OutputStream] to write data into this buffer. + * + * Closing the stream won't have any effect as buffers don't support closing. + */ override fun outputStream(): OutputStream { return object : OutputStream() { override fun write(b: Int) { @@ -72,6 +77,11 @@ actual class Buffer : Source, Sink, Cloneable, ByteChannel { return PeekSource(this).buffer() } + /** + * Returns a new [InputStream] to read data from this buffer. + * + * Closing the stream won't have any effect as buffers don't support closing. + */ override fun inputStream(): InputStream { return object : InputStream() { override fun read(): Int { @@ -94,7 +104,15 @@ actual class Buffer : Source, Sink, Cloneable, ByteChannel { } } - /** Copy `byteCount` bytes from this, starting at `offset`, to `out`. */ + /** + * Copy [byteCount] bytes from this buffer, starting at [offset], to [out]. + * + * @param out the destination to copy data into. + * @param offset the offset to start copying data from, `0` by default. + * @param byteCount the number of bytes to copy, all data starting from the [offset] by default. + * + * @throws IndexOutOfBoundsException when [byteCount] and [offset] represents a range out of the buffer bounds. + */ @Throws(IOException::class) @JvmOverloads fun copyTo( @@ -161,14 +179,27 @@ actual class Buffer : Source, Sink, Cloneable, ByteChannel { return this } - /** Read and exhaust bytes from `input` into this. */ + /** + * Read and exhaust bytes from [input] into this buffer. Stops reading data on [input] exhaustion. + * + * @param input the stream to read data from. + */ @Throws(IOException::class) fun readFrom(input: InputStream): Buffer { readFrom(input, Long.MAX_VALUE, true) return this } - /** Read `byteCount` bytes from `input` into this. */ + /** + * Read [byteCount] bytes from [input] into this buffer. Throws an exception when [input] is + * exhausted before reading [byteCount] bytes. + * + * @param input the stream to read data from. + * @param byteCount the number of bytes read from [input]. + * + * @throws IOException when [input] exhausted before reading [byteCount] bytes from it. + * + */ @Throws(IOException::class) fun readFrom(input: InputStream, byteCount: Long): Buffer { require(byteCount >= 0L) { "byteCount < 0: $byteCount" } diff --git a/core/jvm/src/JvmCore.kt b/core/jvm/src/JvmCore.kt index 7192d6db5..2ba063f74 100644 --- a/core/jvm/src/JvmCore.kt +++ b/core/jvm/src/JvmCore.kt @@ -37,7 +37,11 @@ import javax.crypto.Cipher import javax.crypto.Mac // TODO: improve test coverage -/** Returns a sink that writes to `out`. */ +/** + * Returns [RawSink] that writes to an output stream. + * + * Use [RawSink.buffer] to create a buffered sink from it. + */ fun OutputStream.sink(): RawSink = OutputStreamSink(this) private open class OutputStreamSink( @@ -76,7 +80,11 @@ private open class OutputStreamSink( } // TODO: improve test coverage -/** Returns a source that reads from `in`. */ +/** + * Returns [RawSource] that reads from an input stream. + * + * Use [RawSource.buffer] to create a buffered source from it. + */ fun InputStream.source(): RawSource = InputStreamSource(this) private open class InputStreamSource( @@ -117,9 +125,11 @@ private open class InputStreamSource( } /** - * Returns a sink that writes to `socket`. Prefer this over [sink] + * Returns [RawSink] that writes to a socket. Prefer this over [sink] * because this method honors timeouts. When the socket * write times out, the socket is asynchronously closed by a watchdog thread. + * + * Use [RawSink.buffer] to create a buffered sink from it. */ @Throws(IOException::class) fun Socket.sink(): RawSink { @@ -131,9 +141,11 @@ fun Socket.sink(): RawSink { } /** - * Returns a source that reads from `socket`. Prefer this over [source] + * Returns [RawSource] that reads from a socket. Prefer this over [source] * because this method honors timeouts. When the socket * read times out, the socket is asynchronously closed by a watchdog thread. + * + * Use [RawSource.buffer] to create a buffered source from it. */ @Throws(IOException::class) fun Socket.source(): RawSource { @@ -144,17 +156,40 @@ fun Socket.source(): RawSource { } } -/** Returns a sink that writes to `file`. */ +/** + * Returns [RawSink] that writes to a file. + * + * Use [RawSink.buffer] to create a buffered sink from it. + * + * @param append the flag indicating whether the file should be overwritten or appended, `false` by default, + * meaning the file will be overwritten. + */ fun File.sink(append: Boolean = false): RawSink = FileOutputStream(this, append).sink() -/** Returns a source that reads from `file`. */ +/** + * Returns [RawSource] that reads from a file. + * + * Use [RawSource.buffer] to create a buffered source from it. + */ fun File.source(): RawSource = InputStreamSource(inputStream()) -/** Returns a source that reads from `path`. */ +/** + * Returns [RawSink] that reads from a path. + * + * Use [RawSink.buffer] to create a buffered sink from it. + * + * @param options set of [OpenOption] for opening a file. + */ fun NioPath.sink(vararg options: OpenOption): RawSink = Files.newOutputStream(this, *options).sink() -/** Returns a sink that writes to `path`. */ +/** + * Returns [RawSource] that writes to a path. + * + * Use [RawSource.buffer] to create a buffered source from it. + * + * @param options set of [OpenOption] for opening a file. + */ fun NioPath.source(vararg options: OpenOption): RawSource = Files.newInputStream(this, *options).source() diff --git a/core/jvm/src/Sink.kt b/core/jvm/src/Sink.kt index bedf9edcb..a1dde85ec 100644 --- a/core/jvm/src/Sink.kt +++ b/core/jvm/src/Sink.kt @@ -58,11 +58,23 @@ actual sealed interface Sink : RawSink, WritableByteChannel { @Throws(IOException::class) actual fun emitCompleteSegments(): Sink - /** Returns an output stream that writes to this sink. */ + /** + * Returns an output stream that writes to this sink. Closing the stream will also close this sink. + */ fun outputStream(): OutputStream } - +/** + * Encodes substring of [string] starting at [beginIndex] and ending at [endIndex] using [charset] + * and writes into this sink. + * + * @param string the string to encode into this sink. + * @param charset the [Charset] to use for encoding. + * @param beginIndex the index of the first character to encode, inclusive, 0 by default. + * @param endIndex the index of the last character to encode, exclusive, `string.size` by default. + * + * @throws IndexOutOfBoundsException when [beginIndex] and [endIndex] correspond to a range out of [string] bounds. + */ fun T.writeString(string: String, charset: Charset, beginIndex: Int = 0, endIndex: Int = string.length): T { require(beginIndex >= 0) { "beginIndex < 0: $beginIndex" } require(endIndex >= beginIndex) { "endIndex < beginIndex: $endIndex < $beginIndex" } diff --git a/core/jvm/src/Source.kt b/core/jvm/src/Source.kt index d2ceb4c8d..29f157ad4 100644 --- a/core/jvm/src/Source.kt +++ b/core/jvm/src/Source.kt @@ -73,7 +73,9 @@ actual sealed interface Source : RawSource, ReadableByteChannel { actual fun peek(): Source - /** Returns an input stream that reads from this source. */ + /** + * Returns an input stream that reads from this source. Closing the stream will also close this source. + */ fun inputStream(): InputStream } @@ -100,6 +102,11 @@ private fun Buffer.readStringImpl(byteCount: Long, charset: Charset): String { return result } +/** + * Decodes whole content of this stream into a string using [charset]. Returns empty string if the source is exhausted. + * + * @param charset the [Charset] to use for string decoding. + */ @Throws(IOException::class) fun Source.readString(charset: Charset): String { var req = 1L @@ -109,6 +116,14 @@ fun Source.readString(charset: Charset): String { return buffer.readStringImpl(buffer.size, charset) } +/** + * Decodes [byteCount] bytes of this stream into a string using [charset]. + * + * @param byteCount the number of bytes to read from the source for decoding. + * @param charset the [Charset] to use for string decoding. + * + * @throws EOFException when the source exhausted before [byteCount] bytes could be read from it. + */ @Throws(IOException::class) fun Source.readString(byteCount: Long, charset: Charset): String { require(byteCount) diff --git a/core/native/src/-NonJvmPlatform.kt b/core/native/src/-NonJvmPlatform.kt index 4ee335ef7..a4b2e23a8 100644 --- a/core/native/src/-NonJvmPlatform.kt +++ b/core/native/src/-NonJvmPlatform.kt @@ -22,13 +22,11 @@ package kotlinx.io import kotlinx.io.internal.* -internal actual fun ByteArray.toUtf8String(): String = commonToUtf8String() - internal actual fun String.asUtf8ToByteArray(): ByteArray = commonAsUtf8ToByteArray() -actual open class ArrayIndexOutOfBoundsException actual constructor( - message: String? -) : IndexOutOfBoundsException(message) +//actual open class ArrayIndexOutOfBoundsException actual constructor( +// message: String? +//) : IndexOutOfBoundsException(message) actual open class IOException actual constructor( message: String?, diff --git a/core/native/src/Buffer.kt b/core/native/src/Buffer.kt index dd84d7b23..be7c44cfd 100644 --- a/core/native/src/Buffer.kt +++ b/core/native/src/Buffer.kt @@ -50,11 +50,6 @@ actual class Buffer : Source, Sink { byteCount: Long ): Buffer = commonCopyTo(out, offset, byteCount) - //actual fun copyTo( - // out: Buffer, - // offset: Long - //): Buffer = copyTo(out, offset, size - offset) - actual operator fun get(pos: Long): Byte = commonGet(pos) actual fun completeSegmentByteCount(): Long = commonCompleteSegmentByteCount() @@ -117,10 +112,6 @@ actual class Buffer : Source, Sink { override fun hashCode(): Int = commonHashCode() - /** - * Returns a human-readable string that describes the contents of this buffer. Typically this - * is a string like `[text=Hello]` or `[hex=0000ffff]`. - */ override fun toString() = commonString() actual fun copy(): Buffer = commonCopy() From cd368b419e97bd4933145f91716034d3c5f6ac8a Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Tue, 6 Jun 2023 11:05:29 +0200 Subject: [PATCH 20/83] Updated usage example --- core/Module.md | 49 +++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/core/Module.md b/core/Module.md index b29adb774..d53b315e4 100644 --- a/core/Module.md +++ b/core/Module.md @@ -18,11 +18,56 @@ or receive data: network interfaces, files, etc. The module provides integration but if something not yet supported by the library needs to be integrated, then these interfaces are exactly what should be implemented for that. -Here is an example showing how to manually create BSON `kotlinx.io` +Example below shows how to manually serialize an object to [BSON](https://bsonspec.org/spec.html) +and then back to an object using `kotlinx.io`. Please note that the example aimed to show `kotlinx-io` API in action, +rather than to provide a robust BSON-serialization. ```kotlin +data class Message(val timestamp: Long, val text: String) { + companion object +} -// TODO: can't find out concise and simple exa,ple that will use multiple features of the library. +fun Message.toBson(sink: Sink) { + val buffer = Buffer() + buffer.writeByte(0x9) // UTC-timestamp field + .writeUtf8("timestamp").writeByte(0) // field name + .writeLongLe(timestamp) // field value + .writeByte(0x2) // string field + .writeUtf8("text").writeByte(0) // field name + .writeIntLe(text.utf8Size().toInt() + 1) // field value: length followed by the string + .writeUtf8(text).writeByte(0) + .writeByte(0) // end of BSON document + // Write document length and then its body + sink.writeIntLe(buffer.size.toInt() + 4) + .writeAll(buffer) + sink.flush() +} + +fun Message.Companion.fromBson(source: Source): Message { + source.require(4) // check if the source contains length + val length = source.readIntLe() - 4L + source.require(length) // check if the source contains the whole message + + fun readFieldName(source: Source): String { + val delimiterOffset = source.indexOf(0) // find offset of the 0-byte terminating the name + check(delimiterOffset >= 0) // indexOf return -1 if value not found + val fieldName = source.readUtf8(delimiterOffset) // read the string until terminator + source.skip(1) // skip the terminator + return fieldName + } + + // for simplicity, let's assume that the order of fields matches serialization order + var tag = source.readByte().toInt() // read the field type + check(tag == 0x9 && readFieldName(source) == "timestamp") + val timestamp = source.readLongLe() // read long value + tag = source.readByte().toInt() + check(tag == 0x2 && readFieldName(source) == "text") + val textLen = source.readIntLe() - 1L // read string length (it includes the terminator) + val text = source.readUtf8(textLen) // read value + source.skip(1) // skip terminator + source.skip(1) // skip end of the document + return Message(timestamp, text) +} ``` # Package kotlinx.io From 90c9ba39ace2d6089d2940e15e4ce210d65a5ca2 Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Tue, 6 Jun 2023 13:29:09 +0200 Subject: [PATCH 21/83] Enable explicit API mode --- core/build.gradle.kts | 5 +--- core/common/src/-CommonPlatform.kt | 10 +++---- core/common/src/Buffer.kt | 14 ++++----- core/common/src/Core.kt | 8 ++--- core/common/src/RawSink.kt | 8 ++--- core/common/src/RawSource.kt | 6 ++-- core/common/src/Sink.kt | 22 +++++++------- core/common/src/SinkExt.kt | 10 +++---- core/common/src/Source.kt | 34 ++++++++++----------- core/common/src/SourceExt.kt | 12 ++++---- core/common/src/Utf8.kt | 20 ++++++------- core/jvm/src/-JvmPlatform.kt | 6 ++-- core/jvm/src/Buffer.kt | 48 +++++++++++++++--------------- core/jvm/src/JvmCore.kt | 16 +++++----- core/jvm/src/RawSink.kt | 6 ++-- core/jvm/src/Sink.kt | 26 ++++++++-------- core/jvm/src/Source.kt | 40 ++++++++++++------------- core/native/src/-NonJvmPlatform.kt | 10 +++---- core/native/src/Buffer.kt | 22 +++++++------- core/native/src/RawSink.kt | 8 ++--- core/native/src/Sink.kt | 22 +++++++------- core/native/src/Source.kt | 34 ++++++++++----------- 22 files changed, 192 insertions(+), 195 deletions(-) diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 356a64c2f..620feeb55 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -3,8 +3,6 @@ * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENCE file. */ -import org.jetbrains.dokka.gradle.DokkaMultiModuleTask -import org.jetbrains.dokka.gradle.DokkaTask import org.jetbrains.dokka.gradle.DokkaTaskPartial import org.jetbrains.kotlin.gradle.plugin.* @@ -37,8 +35,7 @@ kotlin { createSourceSet("nativeTest", parent = commonTest, children = nativeTargets) } - // TODO quite a lot of effort, should be done after the initial set of API is migrated -// explicitApi() + explicitApi() sourceSets.configureEach { configureSourceSet() } diff --git a/core/common/src/-CommonPlatform.kt b/core/common/src/-CommonPlatform.kt index 79a5cd073..ded3f97a5 100644 --- a/core/common/src/-CommonPlatform.kt +++ b/core/common/src/-CommonPlatform.kt @@ -26,17 +26,17 @@ internal expect fun String.asUtf8ToByteArray(): ByteArray // TODO make internal https://youtrack.jetbrains.com/issue/KT-37316 // expect class ArrayIndexOutOfBoundsException(message: String?) : IndexOutOfBoundsException -expect open class IOException(message: String?, cause: Throwable?) : Exception { - constructor(message: String? = null) +public expect open class IOException(message: String?, cause: Throwable?) : Exception { + public constructor(message: String? = null) } -expect open class EOFException(message: String? = null) : IOException +public expect open class EOFException(message: String? = null) : IOException -expect interface Closeable { +public expect interface Closeable { /** * Closes this object and releases the resources it holds. It is an error to use an object after * it has been closed. It is safe to close an object more than once. */ @Throws(IOException::class) - fun close() + public fun close() } diff --git a/core/common/src/Buffer.kt b/core/common/src/Buffer.kt index 3aefc8052..fe9eb5ecb 100644 --- a/core/common/src/Buffer.kt +++ b/core/common/src/Buffer.kt @@ -36,13 +36,13 @@ package kotlinx.io * but unlike regular sinks and sources its [close], [cancel], [flush], [emit], [emitCompleteSegments] * does not affect buffer's state and [exhausted] only indicates that a buffer is empty. */ -expect class Buffer() : Source, Sink { +public expect class Buffer() : Source, Sink { internal var head: Segment? /** * The number of bytes accessible for read from this buffer. */ - var size: Long + public var size: Long internal set /** @@ -74,7 +74,7 @@ expect class Buffer() : Source, Sink { * * @throws IndexOutOfBoundsException when [offset] and [byteCount] correspond to a range out of this buffer bounds. */ - fun copyTo( + public fun copyTo( out: Buffer, offset: Long = 0L, byteCount: Long = size - offset @@ -85,7 +85,7 @@ expect class Buffer() : Source, Sink { * * This is the number of bytes that can be flushed immediately to an underlying sink without harming throughput. */ - fun completeSegmentByteCount(): Long + public fun completeSegmentByteCount(): Long /** * Returns the byte at [pos]. @@ -95,14 +95,14 @@ expect class Buffer() : Source, Sink { * * @throws IndexOutOfBoundsException when [pos] is out of this buffer's bounds. */ - operator fun get(pos: Long): Byte + public operator fun get(pos: Long): Byte /** * Discards all bytes in this buffer. * * Call to this method is equivalent to [skip] with `byteCount = size`. */ - fun clear() + public fun clear() // TODO: figure out what this method may actually throw /** @@ -134,7 +134,7 @@ expect class Buffer() : Source, Sink { /** * Returns a deep copy of this buffer. */ - fun copy(): Buffer + public fun copy(): Buffer /** * This method does not affect the buffer. diff --git a/core/common/src/Core.kt b/core/common/src/Core.kt index f19a1f9e0..610a3cad8 100644 --- a/core/common/src/Core.kt +++ b/core/common/src/Core.kt @@ -25,18 +25,18 @@ package kotlinx.io * reads into its in-memory buffer. Use this wherever you read a source to get ergonomic and * efficient access to data. */ -fun RawSource.buffer(): Source = RealSource(this) +public fun RawSource.buffer(): Source = RealSource(this) /** * Returns a new sink that buffers writes to the sink. The returned sink will batch writes to the sink. * Use this wherever you write to a sink to get ergonomic and efficient access to data. */ -fun RawSink.buffer(): Sink = RealSink(this) +public fun RawSink.buffer(): Sink = RealSink(this) /** * Returns a sink that writes nowhere. */ -fun blackholeSink(): RawSink = BlackholeSink() +public fun blackholeSink(): RawSink = BlackholeSink() private class BlackholeSink : RawSink { override fun write(source: Buffer, byteCount: Long) = source.skip(byteCount) @@ -48,7 +48,7 @@ private class BlackholeSink : RawSink { /** * Execute [block] then close this. This will be closed even if [block] throws. */ -inline fun T.use(block: (T) -> R): R { +public inline fun T.use(block: (T) -> R): R { var result: R? = null var thrown: Throwable? = null diff --git a/core/common/src/RawSink.kt b/core/common/src/RawSink.kt index 668c8833f..63d268f75 100644 --- a/core/common/src/RawSink.kt +++ b/core/common/src/RawSink.kt @@ -31,7 +31,7 @@ package kotlinx.io * is both more efficient and more convenient. Use [buffer] to wrap any raw sink with a buffer. * */ -expect interface RawSink : Closeable { +public expect interface RawSink : Closeable { // TODO: should it throw EOFException instead? /** * Removes [byteCount] bytes from [source] and appends them to this sink. @@ -42,13 +42,13 @@ expect interface RawSink : Closeable { * @throws IndexOutOfBoundsException when the [source]'s size is below [byteCount]. */ @Throws(IOException::class) - fun write(source: Buffer, byteCount: Long) + public fun write(source: Buffer, byteCount: Long) /** * Pushes all buffered bytes to their final destination. */ @Throws(IOException::class) - fun flush() + public fun flush() /** * Asynchronously cancel this sink. Any [write] or [flush] in flight should immediately fail @@ -57,7 +57,7 @@ expect interface RawSink : Closeable { * * Note that it is always necessary to call [close] on a sink, even if it has been canceled. */ - fun cancel() + public fun cancel() /** * Pushes all buffered bytes to their final destination and releases the resources held by this diff --git a/core/common/src/RawSource.kt b/core/common/src/RawSource.kt index 55f9efa18..c14194fc6 100644 --- a/core/common/src/RawSource.kt +++ b/core/common/src/RawSource.kt @@ -30,7 +30,7 @@ package kotlinx.io * Most applications shouldn't operate on a raw source directly, but rather on a buffered [Source] which * is both more efficient and more convenient. Use [buffer] to wrap any raw source with a buffer. */ -interface RawSource : Closeable { +public interface RawSource : Closeable { // TODO: should be throw something if byteCount = 0? /** * Removes at least 1, and up to [byteCount] bytes from this source and appends them to [sink]. @@ -42,7 +42,7 @@ interface RawSource : Closeable { * @throws IllegalArgumentException when [byteCount] is negative. */ @Throws(IOException::class) - fun read(sink: Buffer, byteCount: Long): Long + public fun read(sink: Buffer, byteCount: Long): Long /** * Asynchronously cancel this source. Any [read] in flight should immediately fail with an @@ -50,7 +50,7 @@ interface RawSource : Closeable { * * Note that it is always necessary to call [close] on a source, even if it has been canceled. */ - fun cancel() + public fun cancel() /** * Closes this source and releases the resources held by this source. It is an error to read a diff --git a/core/common/src/Sink.kt b/core/common/src/Sink.kt index f86289068..75ca4ed03 100644 --- a/core/common/src/Sink.kt +++ b/core/common/src/Sink.kt @@ -37,11 +37,11 @@ package kotlinx.io * The latter is aimed to reduce memory footprint by keeping the buffer as small as possible without excessive writes * to the upstream. */ -expect sealed interface Sink : RawSink { +public expect sealed interface Sink : RawSink { /** * This sink's internal buffer. */ - val buffer: Buffer + public val buffer: Buffer /** * Writes bytes from [source] array or its subrange to this sink. @@ -55,7 +55,7 @@ expect sealed interface Sink : RawSink { * * @sample kotlinx.io.AbstractSinkTest.writeByteArray */ - fun write(source: ByteArray, offset: Int = 0, byteCount: Int = source.size - offset): Sink + public fun write(source: ByteArray, offset: Int = 0, byteCount: Int = source.size - offset): Sink /** * Removes all bytes from [source] and write them to this sink. @@ -63,7 +63,7 @@ expect sealed interface Sink : RawSink { * * @param source the source to consume data from. */ - fun writeAll(source: RawSource): Long + public fun writeAll(source: RawSource): Long /** * Removes [byteCount] bytes from [source] and write them to this sink. @@ -76,35 +76,35 @@ expect sealed interface Sink : RawSink { * * @throws IllegalArgumentException when [byteCount] is negative. */ - fun write(source: RawSource, byteCount: Long): Sink + public fun write(source: RawSource, byteCount: Long): Sink /** * Writes a byte to this sink. * * @param byte the byte to be written. */ - fun writeByte(byte: Int): Sink + public fun writeByte(byte: Int): Sink /** * Writes two bytes containing [short], in the big-endian order, to this sink. * * @param short the short integer to be written. */ - fun writeShort(short: Int): Sink + public fun writeShort(short: Int): Sink /** * Writes four bytes containing [int], in the big-endian order, to this sink. * * @param int the integer to be written. */ - fun writeInt(int: Int): Sink + public fun writeInt(int: Int): Sink /** * Writes eight bytes containing [long], in the big-endian order, to this sink. * * @param long the long integer to be written. */ - fun writeLong(long: Long): Sink + public fun writeLong(long: Long): Sink /** * Writes all buffered data to the underlying sink, if one exists. @@ -119,7 +119,7 @@ expect sealed interface Sink : RawSink { * This method behaves like [flush], but has weaker guarantees. * Call this method before a buffered sink goes out of scope so that its data can reach its destination. */ - fun emit(): Sink + public fun emit(): Sink /** * Writes complete segments to the underlying sink, if one exists. @@ -129,5 +129,5 @@ expect sealed interface Sink : RawSink { * Typically, application code will not need to call this: it is only necessary when * application code writes directly to this [buffer]. */ - fun emitCompleteSegments(): Sink + public fun emitCompleteSegments(): Sink } diff --git a/core/common/src/SinkExt.kt b/core/common/src/SinkExt.kt index c52470923..c76251ad1 100644 --- a/core/common/src/SinkExt.kt +++ b/core/common/src/SinkExt.kt @@ -10,7 +10,7 @@ package kotlinx.io * * @param short the short integer to be written. */ -fun T.writeShortLe(short: Int): T { +public fun T.writeShortLe(short: Int): T { this.writeShort(short.toShort().reverseBytes().toInt()) return this } @@ -21,7 +21,7 @@ fun T.writeShortLe(short: Int): T { * @param int the integer to be written. * */ -fun T.writeIntLe(int: Int): T { +public fun T.writeIntLe(int: Int): T { this.writeInt(int.reverseBytes()) return this } @@ -31,7 +31,7 @@ fun T.writeIntLe(int: Int): T { * * @param long the long integer to be written. */ -fun T.writeLongLe(long: Long): T { +public fun T.writeLongLe(long: Long): T { this.writeLong(long.reverseBytes()) return this } @@ -43,7 +43,7 @@ fun T.writeLongLe(long: Long): T { * * @param long the long to be written. */ -fun T.writeDecimalLong(long: Long): T { +public fun T.writeDecimalLong(long: Long): T { // TODO: optimize writeUtf8(long.toString()) return this @@ -56,7 +56,7 @@ fun T.writeDecimalLong(long: Long): T { * * @param long the long to be written. */ -fun T.writeHexadecimalUnsignedLong(long: Long): T { +public fun T.writeHexadecimalUnsignedLong(long: Long): T { if (long == 0L) { writeByte('0'.code) } else { diff --git a/core/common/src/Source.kt b/core/common/src/Source.kt index a9404767c..3f4159c61 100644 --- a/core/common/src/Source.kt +++ b/core/common/src/Source.kt @@ -35,18 +35,18 @@ package kotlinx.io * [Sink] also allows to skip unneeded prefix of data using [skip] and * provides look ahead into incoming data, buffering as much as necessary, using [peek]. */ -expect sealed interface Source : RawSource { +public expect sealed interface Source : RawSource { /** * This source's internal buffer. */ - val buffer: Buffer + public val buffer: Buffer /** * Returns true if there are no more bytes in this source. * * The call of this method will block until there are bytes to read or the source is definitely exhausted. */ - fun exhausted(): Boolean + public fun exhausted(): Boolean /** * Attempts to fill the buffer with at least [byteCount] bytes of data from the underlying source @@ -59,7 +59,7 @@ expect sealed interface Source : RawSource { * * @throws EOFException when the source is exhausted before the required bytes count could be read. */ - fun require(byteCount: Long) + public fun require(byteCount: Long) /** * Attempts to fill the buffer with at least [byteCount] bytes of data from the underlying source @@ -70,35 +70,35 @@ expect sealed interface Source : RawSource { * * @param byteCount the number of bytes that the buffer should contain. */ - fun request(byteCount: Long): Boolean + public fun request(byteCount: Long): Boolean /** * Removes a byte from this source and returns it. * * @throws EOFException when there are no more bytes to read. */ - fun readByte(): Byte + public fun readByte(): Byte /** * Removes two bytes from this source and returns a short integer composed of it according to the big-endian order. * * @throws EOFException when there are not enough data to read a short value. */ - fun readShort(): Short + public fun readShort(): Short /** * Removes four bytes from this source and returns an integer composed of it according to the big-endian order. * * @throws EOFException when there are not enough data to read an int value. */ - fun readInt(): Int + public fun readInt(): Int /** * Removes eight bytes from this source and returns a long integer composed of it according to the big-endian order. * * @throws EOFException when there are not enough data to read a long value. */ - fun readLong(): Long + public fun readLong(): Long /** * Reads and discards [byteCount] bytes from this source. @@ -107,12 +107,12 @@ expect sealed interface Source : RawSource { * * @throws EOFException when the source is exhausted before the requested number of bytes can be skipped. */ - fun skip(byteCount: Long) + public fun skip(byteCount: Long) /** * Removes all bytes from this source and returns them as a byte array. */ - fun readByteArray(): ByteArray + public fun readByteArray(): ByteArray /** * Removes [byteCount] bytes from this source and returns them as a byte array. @@ -122,14 +122,14 @@ expect sealed interface Source : RawSource { * @throws IllegalArgumentException when byteCount is negative. * @throws EOFException when the underlying source is exhausted before [byteCount] bytes of data could be read. */ - fun readByteArray(byteCount: Long): ByteArray + public fun readByteArray(byteCount: Long): ByteArray /** * Removes exactly `sink.length` bytes from this source and copies them into [sink]. * * @throws EOFException when the requested number of bytes cannot be read. */ - fun readFully(sink: ByteArray) + public fun readFully(sink: ByteArray) /** * Removes up to [byteCount] bytes from this source, copies them into [sink] starting at [offset] and returns the @@ -143,7 +143,7 @@ expect sealed interface Source : RawSource { * @throws IndexOutOfBoundsException when a range specified by [offset] and [byteCount] * is out of range of [sink] array indices. */ - fun read(sink: ByteArray, offset: Int = 0, byteCount: Int = sink.size - offset): Int + public fun read(sink: ByteArray, offset: Int = 0, byteCount: Int = sink.size - offset): Int /** * Removes exactly [byteCount] bytes from this source and writes them to [sink]. @@ -154,7 +154,7 @@ expect sealed interface Source : RawSource { * @throws IllegalArgumentException when [byteCount] is negative. * @throws EOFException when the requested number of bytes cannot be read. */ - fun readFully(sink: Buffer, byteCount: Long) + public fun readFully(sink: Buffer, byteCount: Long) /** * Removes all bytes from this source, writes them to [sink], and returns the total number of bytes @@ -164,7 +164,7 @@ expect sealed interface Source : RawSource { * * @param sink the sink to which data will be written from this source. */ - fun readAll(sink: RawSink): Long + public fun readAll(sink: RawSink): Long /** * Returns a new [Source] that can read data from this source without consuming it. @@ -172,5 +172,5 @@ expect sealed interface Source : RawSource { * * Peek could be used to lookahead and read the same data multiple times. */ - fun peek(): Source + public fun peek(): Source } diff --git a/core/common/src/SourceExt.kt b/core/common/src/SourceExt.kt index b5eb57c63..9235a04f3 100644 --- a/core/common/src/SourceExt.kt +++ b/core/common/src/SourceExt.kt @@ -10,7 +10,7 @@ package kotlinx.io * * @throws EOFException when there are not enough data to read a short value. */ -fun Source.readShortLe(): Short { +public fun Source.readShortLe(): Short { return readShort().reverseBytes() } @@ -19,7 +19,7 @@ fun Source.readShortLe(): Short { * * @throws EOFException when there are not enough data to read an int value. */ -fun Source.readIntLe(): Int { +public fun Source.readIntLe(): Int { return readInt().reverseBytes() } @@ -28,7 +28,7 @@ fun Source.readIntLe(): Int { * * @throws EOFException when there are not enough data to read a long value. */ -fun Source.readLongLe(): Long { +public fun Source.readLongLe(): Long { return readLong().reverseBytes() } @@ -46,7 +46,7 @@ internal const val OVERFLOW_DIGIT_START = Long.MIN_VALUE % 10L + 1 * number was not present. * @throws EOFException if the source is exhausted before a call of this method. */ -fun Source.readDecimalLong(): Long { +public fun Source.readDecimalLong(): Long { require(1) var b = readByte() var negative = false @@ -105,7 +105,7 @@ fun Source.readDecimalLong(): Long { * hexadecimal was not found. * @throws EOFException if the source is exhausted before a call of this method. */ -fun Source.readHexadecimalUnsignedLong(): Long { +public fun Source.readHexadecimalUnsignedLong(): Long { require(1) var b = readByte() var result = when (b) { @@ -146,7 +146,7 @@ fun Source.readHexadecimalUnsignedLong(): Long { * @param fromIndex the start of the range to find [b], inclusive. * @param toIndex the end of the range to find [b], exclusive. */ -fun Source.indexOf(b: Byte, fromIndex: Long = 0L, toIndex: Long = Long.MAX_VALUE): Long { +public fun Source.indexOf(b: Byte, fromIndex: Long = 0L, toIndex: Long = Long.MAX_VALUE): Long { require(fromIndex in 0..toIndex) if (fromIndex == toIndex) return -1L diff --git a/core/common/src/Utf8.kt b/core/common/src/Utf8.kt index dbdf0027f..f7fcac268 100644 --- a/core/common/src/Utf8.kt +++ b/core/common/src/Utf8.kt @@ -88,7 +88,7 @@ import kotlin.jvm.JvmOverloads */ @JvmOverloads @JvmName("size") -fun String.utf8Size(beginIndex: Int = 0, endIndex: Int = length): Long { +public fun String.utf8Size(beginIndex: Int = 0, endIndex: Int = length): Long { require(beginIndex >= 0) { "beginIndex < 0: $beginIndex" } require(endIndex >= beginIndex) { "endIndex < beginIndex: $endIndex < $beginIndex" } require(endIndex <= length) { "endIndex > string.length: $endIndex > $length" } @@ -132,7 +132,7 @@ fun String.utf8Size(beginIndex: Int = 0, endIndex: Int = length): Long { * * @param codePoint the codePoint to be written. */ -fun T.writeUtf8CodePoint(codePoint: Int): T { +public fun T.writeUtf8CodePoint(codePoint: Int): T { buffer.commonWriteUtf8CodePoint(codePoint) emitCompleteSegments() return this @@ -148,7 +148,7 @@ fun T.writeUtf8CodePoint(codePoint: Int): T { * @throws IndexOutOfBoundsException when [beginIndex] or [endIndex] correspond to a range * out of the current string bounds. */ -fun T.writeUtf8(string: String, beginIndex: Int = 0, endIndex: Int = string.length): T { +public fun T.writeUtf8(string: String, beginIndex: Int = 0, endIndex: Int = string.length): T { buffer.commonWriteUtf8(string, beginIndex, endIndex) emitCompleteSegments() return this @@ -159,7 +159,7 @@ fun T.writeUtf8(string: String, beginIndex: Int = 0, endIndex: Int = s * * Returns the empty string if this source is empty. */ -fun Source.readUtf8(): String { +public fun Source.readUtf8(): String { var req: Long = 1 while (request(req)) { req *= 2 @@ -172,7 +172,7 @@ fun Source.readUtf8(): String { * * Returns the empty string if this buffer is empty. */ -fun Buffer.readUtf8(): String { +public fun Buffer.readUtf8(): String { return commonReadUtf8(buffer.size) } @@ -184,7 +184,7 @@ fun Buffer.readUtf8(): String { * @throws IllegalArgumentException when [byteCount] is negative. * @throws EOFException when the source is exhausted before reading [byteCount] bytes from it. */ -fun Source.readUtf8(byteCount: Long): String { +public fun Source.readUtf8(byteCount: Long): String { require(byteCount) return buffer.commonReadUtf8(byteCount) } @@ -203,7 +203,7 @@ fun Source.readUtf8(byteCount: Long): String { * * @throws EOFException when the source is exhausted before a complete code point can be read. */ -fun Source.readUtf8CodePoint(): Int { +public fun Source.readUtf8CodePoint(): Int { require(1) val b0 = buffer[0].toInt() @@ -219,7 +219,7 @@ fun Source.readUtf8CodePoint(): Int { /** * @see Source.readUtf8CodePoint */ -fun Buffer.readUtf8CodePoint(): Int { +public fun Buffer.readUtf8CodePoint(): Int { return buffer.commonReadUtf8CodePoint() } @@ -230,7 +230,7 @@ fun Buffer.readUtf8CodePoint(): Int { * On the end of the stream this method returns null. If the source doesn't end with a line break, then * an implicit line break is assumed. Null is returned once the source is exhausted. */ -fun Source.readUtf8Line(): String? { +public fun Source.readUtf8Line(): String? { if (!request(1)) return null val peekSource = peek() @@ -270,7 +270,7 @@ fun Source.readUtf8Line(): String? { * @throws EOFException when the source does not contain a string consisting with at most [limit] bytes followed by * line break characters. */ -fun Source.readUtf8LineStrict(limit: Long = Long.MAX_VALUE): String { +public fun Source.readUtf8LineStrict(limit: Long = Long.MAX_VALUE): String { if (!request(1)) throw EOFException() val peekSource = peek() diff --git a/core/jvm/src/-JvmPlatform.kt b/core/jvm/src/-JvmPlatform.kt index a3ebaefb1..73c458591 100644 --- a/core/jvm/src/-JvmPlatform.kt +++ b/core/jvm/src/-JvmPlatform.kt @@ -26,8 +26,8 @@ internal actual fun String.asUtf8ToByteArray(): ByteArray = toByteArray(Charsets // TODO remove if https://youtrack.jetbrains.com/issue/KT-20641 provides a better solution // actual typealias ArrayIndexOutOfBoundsException = java.lang.ArrayIndexOutOfBoundsException -actual typealias IOException = java.io.IOException +public actual typealias IOException = java.io.IOException -actual typealias EOFException = java.io.EOFException +public actual typealias EOFException = java.io.EOFException -actual typealias Closeable = java.io.Closeable +public actual typealias Closeable = java.io.Closeable diff --git a/core/jvm/src/Buffer.kt b/core/jvm/src/Buffer.kt index 1cf7d03ce..b5afc3a5a 100644 --- a/core/jvm/src/Buffer.kt +++ b/core/jvm/src/Buffer.kt @@ -28,14 +28,14 @@ import java.io.OutputStream import java.nio.ByteBuffer import java.nio.channels.ByteChannel -actual class Buffer : Source, Sink, Cloneable, ByteChannel { +public actual class Buffer : Source, Sink, Cloneable, ByteChannel { @JvmField internal actual var head: Segment? = null @get:JvmName("size") - actual var size: Long = 0L + public actual var size: Long = 0L internal set - actual override val buffer get() = this + actual override val buffer: Buffer get() = this /** * Returns a new [OutputStream] to write data into this buffer. @@ -60,18 +60,18 @@ actual class Buffer : Source, Sink, Cloneable, ByteChannel { } } - actual override fun emitCompleteSegments() = this // Nowhere to emit to! + actual override fun emitCompleteSegments(): Buffer = this // Nowhere to emit to! - actual override fun emit() = this // Nowhere to emit to! + actual override fun emit(): Buffer = this // Nowhere to emit to! - override fun exhausted() = size == 0L + override fun exhausted(): Boolean = size == 0L @Throws(EOFException::class) override fun require(byteCount: Long) { if (size < byteCount) throw EOFException() } - override fun request(byteCount: Long) = size >= byteCount + override fun request(byteCount: Long): Boolean = size >= byteCount override fun peek(): Source { return PeekSource(this).buffer() @@ -115,7 +115,7 @@ actual class Buffer : Source, Sink, Cloneable, ByteChannel { */ @Throws(IOException::class) @JvmOverloads - fun copyTo( + public fun copyTo( out: OutputStream, offset: Long = 0L, byteCount: Long = size - offset @@ -146,7 +146,7 @@ actual class Buffer : Source, Sink, Cloneable, ByteChannel { return this } - actual fun copyTo( + public actual fun copyTo( out: Buffer, offset: Long, byteCount: Long @@ -155,7 +155,7 @@ actual class Buffer : Source, Sink, Cloneable, ByteChannel { /** Write `byteCount` bytes from this to `out`. */ @Throws(IOException::class) @JvmOverloads - fun writeTo(out: OutputStream, byteCount: Long = size): Buffer { + public fun writeTo(out: OutputStream, byteCount: Long = size): Buffer { checkOffsetAndCount(size, 0, byteCount) var remainingByteCount = byteCount @@ -185,7 +185,7 @@ actual class Buffer : Source, Sink, Cloneable, ByteChannel { * @param input the stream to read data from. */ @Throws(IOException::class) - fun readFrom(input: InputStream): Buffer { + public fun readFrom(input: InputStream): Buffer { readFrom(input, Long.MAX_VALUE, true) return this } @@ -201,7 +201,7 @@ actual class Buffer : Source, Sink, Cloneable, ByteChannel { * */ @Throws(IOException::class) - fun readFrom(input: InputStream, byteCount: Long): Buffer { + public fun readFrom(input: InputStream, byteCount: Long): Buffer { require(byteCount >= 0L) { "byteCount < 0: $byteCount" } readFrom(input, byteCount, false) return this @@ -229,13 +229,13 @@ actual class Buffer : Source, Sink, Cloneable, ByteChannel { } } - actual fun completeSegmentByteCount(): Long = commonCompleteSegmentByteCount() + public actual fun completeSegmentByteCount(): Long = commonCompleteSegmentByteCount() @Throws(EOFException::class) override fun readByte(): Byte = commonReadByte() @JvmName("getByte") - actual operator fun get(pos: Long): Byte = commonGet(pos) + public actual operator fun get(pos: Long): Byte = commonGet(pos) @Throws(EOFException::class) override fun readShort(): Short = commonReadShort() @@ -252,13 +252,13 @@ actual class Buffer : Source, Sink, Cloneable, ByteChannel { @Throws(IOException::class) override fun readAll(sink: RawSink): Long = commonReadAll(sink) - override fun readByteArray() = commonReadByteArray() + override fun readByteArray(): ByteArray = commonReadByteArray() @Throws(EOFException::class) override fun readByteArray(byteCount: Long): ByteArray = commonReadByteArray(byteCount) @Throws(EOFException::class) - override fun readFully(sink: ByteArray) = commonReadFully(sink) + override fun readFully(sink: ByteArray): Unit = commonReadFully(sink) override fun read(sink: ByteArray, offset: Int, byteCount: Int): Int = commonRead(sink, offset, byteCount) @@ -281,10 +281,10 @@ actual class Buffer : Source, Sink, Cloneable, ByteChannel { return toCopy } - actual fun clear() = commonClear() + public actual fun clear(): Unit = commonClear() @Throws(EOFException::class) - actual override fun skip(byteCount: Long) = commonSkip(byteCount) + public actual override fun skip(byteCount: Long): Unit = commonSkip(byteCount) actual override fun write( source: ByteArray, @@ -332,13 +332,13 @@ actual class Buffer : Source, Sink, Cloneable, ByteChannel { override fun read(sink: Buffer, byteCount: Long): Long = commonRead(sink, byteCount) - actual override fun flush() = Unit + public actual override fun flush(): Unit = Unit - override fun isOpen() = true + override fun isOpen(): Boolean = true - actual override fun close() = Unit + actual override fun close(): Unit = Unit - actual override fun cancel() = Unit + actual override fun cancel(): Unit = Unit override fun equals(other: Any?): Boolean = commonEquals(other) @@ -348,9 +348,9 @@ actual class Buffer : Source, Sink, Cloneable, ByteChannel { * Returns a human-readable string that describes the contents of this buffer. Typically, this * is a string like `[text=Hello]` or `[hex=0000ffff]`. */ - override fun toString() = commonString() + override fun toString(): String = commonString() - actual fun copy(): Buffer = commonCopy() + public actual fun copy(): Buffer = commonCopy() /** * Returns a deep copy of this buffer. diff --git a/core/jvm/src/JvmCore.kt b/core/jvm/src/JvmCore.kt index 2ba063f74..e44995c45 100644 --- a/core/jvm/src/JvmCore.kt +++ b/core/jvm/src/JvmCore.kt @@ -42,7 +42,7 @@ import javax.crypto.Mac * * Use [RawSink.buffer] to create a buffered sink from it. */ -fun OutputStream.sink(): RawSink = OutputStreamSink(this) +public fun OutputStream.sink(): RawSink = OutputStreamSink(this) private open class OutputStreamSink( private val out: OutputStream, @@ -85,7 +85,7 @@ private open class OutputStreamSink( * * Use [RawSource.buffer] to create a buffered source from it. */ -fun InputStream.source(): RawSource = InputStreamSource(this) +public fun InputStream.source(): RawSource = InputStreamSource(this) private open class InputStreamSource( private val input: InputStream, @@ -132,7 +132,7 @@ private open class InputStreamSource( * Use [RawSink.buffer] to create a buffered sink from it. */ @Throws(IOException::class) -fun Socket.sink(): RawSink { +public fun Socket.sink(): RawSink { return object : OutputStreamSink(getOutputStream()) { override fun cancel() { this@sink.close() @@ -148,7 +148,7 @@ fun Socket.sink(): RawSink { * Use [RawSource.buffer] to create a buffered source from it. */ @Throws(IOException::class) -fun Socket.source(): RawSource { +public fun Socket.source(): RawSource { return object : InputStreamSource(getInputStream()) { override fun cancel() { this@source.close() @@ -164,14 +164,14 @@ fun Socket.source(): RawSource { * @param append the flag indicating whether the file should be overwritten or appended, `false` by default, * meaning the file will be overwritten. */ -fun File.sink(append: Boolean = false): RawSink = FileOutputStream(this, append).sink() +public fun File.sink(append: Boolean = false): RawSink = FileOutputStream(this, append).sink() /** * Returns [RawSource] that reads from a file. * * Use [RawSource.buffer] to create a buffered source from it. */ -fun File.source(): RawSource = InputStreamSource(inputStream()) +public fun File.source(): RawSource = InputStreamSource(inputStream()) /** * Returns [RawSink] that reads from a path. @@ -180,7 +180,7 @@ fun File.source(): RawSource = InputStreamSource(inputStream()) * * @param options set of [OpenOption] for opening a file. */ -fun NioPath.sink(vararg options: OpenOption): RawSink = +public fun NioPath.sink(vararg options: OpenOption): RawSink = Files.newOutputStream(this, *options).sink() /** @@ -190,7 +190,7 @@ fun NioPath.sink(vararg options: OpenOption): RawSink = * * @param options set of [OpenOption] for opening a file. */ -fun NioPath.source(vararg options: OpenOption): RawSource = +public fun NioPath.source(vararg options: OpenOption): RawSource = Files.newInputStream(this, *options).source() /** diff --git a/core/jvm/src/RawSink.kt b/core/jvm/src/RawSink.kt index 93f51ed3b..eee82b04c 100644 --- a/core/jvm/src/RawSink.kt +++ b/core/jvm/src/RawSink.kt @@ -24,14 +24,14 @@ import java.io.Closeable import java.io.Flushable import java.io.IOException -actual interface RawSink : Closeable, Flushable { +public actual interface RawSink : Closeable, Flushable { @Throws(IOException::class) - actual fun write(source: Buffer, byteCount: Long) + public actual fun write(source: Buffer, byteCount: Long) @Throws(IOException::class) actual override fun flush() - actual fun cancel() + public actual fun cancel() @Throws(IOException::class) actual override fun close() diff --git a/core/jvm/src/Sink.kt b/core/jvm/src/Sink.kt index a1dde85ec..5933eeb15 100644 --- a/core/jvm/src/Sink.kt +++ b/core/jvm/src/Sink.kt @@ -25,43 +25,43 @@ import java.io.OutputStream import java.nio.channels.WritableByteChannel import java.nio.charset.Charset -actual sealed interface Sink : RawSink, WritableByteChannel { - actual val buffer: Buffer +public actual sealed interface Sink : RawSink, WritableByteChannel { + public actual val buffer: Buffer @Throws(IOException::class) - actual fun write(source: ByteArray, offset: Int, byteCount: Int): Sink + public actual fun write(source: ByteArray, offset: Int, byteCount: Int): Sink @Throws(IOException::class) - actual fun writeAll(source: RawSource): Long + public actual fun writeAll(source: RawSource): Long @Throws(IOException::class) - actual fun write(source: RawSource, byteCount: Long): Sink + public actual fun write(source: RawSource, byteCount: Long): Sink @Throws(IOException::class) - actual fun writeByte(byte: Int): Sink + public actual fun writeByte(byte: Int): Sink @Throws(IOException::class) - actual fun writeShort(short: Int): Sink + public actual fun writeShort(short: Int): Sink @Throws(IOException::class) - actual fun writeInt(int: Int): Sink + public actual fun writeInt(int: Int): Sink @Throws(IOException::class) - actual fun writeLong(long: Long): Sink + public actual fun writeLong(long: Long): Sink @Throws(IOException::class) actual override fun flush() @Throws(IOException::class) - actual fun emit(): Sink + public actual fun emit(): Sink @Throws(IOException::class) - actual fun emitCompleteSegments(): Sink + public actual fun emitCompleteSegments(): Sink /** * Returns an output stream that writes to this sink. Closing the stream will also close this sink. */ - fun outputStream(): OutputStream + public fun outputStream(): OutputStream } /** @@ -75,7 +75,7 @@ actual sealed interface Sink : RawSink, WritableByteChannel { * * @throws IndexOutOfBoundsException when [beginIndex] and [endIndex] correspond to a range out of [string] bounds. */ -fun T.writeString(string: String, charset: Charset, beginIndex: Int = 0, endIndex: Int = string.length): T { +public fun T.writeString(string: String, charset: Charset, beginIndex: Int = 0, endIndex: Int = string.length): T { require(beginIndex >= 0) { "beginIndex < 0: $beginIndex" } require(endIndex >= beginIndex) { "endIndex < beginIndex: $endIndex < $beginIndex" } require(endIndex <= string.length) { "endIndex > string.length: $endIndex > ${string.length}" } diff --git a/core/jvm/src/Source.kt b/core/jvm/src/Source.kt index 29f157ad4..0a4955543 100644 --- a/core/jvm/src/Source.kt +++ b/core/jvm/src/Source.kt @@ -26,57 +26,57 @@ import java.io.InputStream import java.nio.channels.ReadableByteChannel import java.nio.charset.Charset -actual sealed interface Source : RawSource, ReadableByteChannel { - actual val buffer: Buffer +public actual sealed interface Source : RawSource, ReadableByteChannel { + public actual val buffer: Buffer @Throws(IOException::class) - actual fun exhausted(): Boolean + public actual fun exhausted(): Boolean @Throws(IOException::class) - actual fun require(byteCount: Long) + public actual fun require(byteCount: Long) @Throws(IOException::class) - actual fun request(byteCount: Long): Boolean + public actual fun request(byteCount: Long): Boolean @Throws(IOException::class) - actual fun readByte(): Byte + public actual fun readByte(): Byte @Throws(IOException::class) - actual fun readShort(): Short + public actual fun readShort(): Short @Throws(IOException::class) - actual fun readInt(): Int + public actual fun readInt(): Int @Throws(IOException::class) - actual fun readLong(): Long + public actual fun readLong(): Long @Throws(IOException::class) - actual fun skip(byteCount: Long) + public actual fun skip(byteCount: Long) @Throws(IOException::class) - actual fun readByteArray(): ByteArray + public actual fun readByteArray(): ByteArray @Throws(IOException::class) - actual fun readByteArray(byteCount: Long): ByteArray + public actual fun readByteArray(byteCount: Long): ByteArray @Throws(IOException::class) - actual fun readFully(sink: ByteArray) + public actual fun readFully(sink: ByteArray) @Throws(IOException::class) - actual fun read(sink: ByteArray, offset: Int, byteCount: Int): Int + public actual fun read(sink: ByteArray, offset: Int, byteCount: Int): Int @Throws(IOException::class) - actual fun readFully(sink: Buffer, byteCount: Long) + public actual fun readFully(sink: Buffer, byteCount: Long) @Throws(IOException::class) - actual fun readAll(sink: RawSink): Long + public actual fun readAll(sink: RawSink): Long - actual fun peek(): Source + public actual fun peek(): Source /** * Returns an input stream that reads from this source. Closing the stream will also close this source. */ - fun inputStream(): InputStream + public fun inputStream(): InputStream } private fun Buffer.readStringImpl(byteCount: Long, charset: Charset): String { @@ -108,7 +108,7 @@ private fun Buffer.readStringImpl(byteCount: Long, charset: Charset): String { * @param charset the [Charset] to use for string decoding. */ @Throws(IOException::class) -fun Source.readString(charset: Charset): String { +public fun Source.readString(charset: Charset): String { var req = 1L while (request(req)) { req *= 2 @@ -125,7 +125,7 @@ fun Source.readString(charset: Charset): String { * @throws EOFException when the source exhausted before [byteCount] bytes could be read from it. */ @Throws(IOException::class) -fun Source.readString(byteCount: Long, charset: Charset): String { +public fun Source.readString(byteCount: Long, charset: Charset): String { require(byteCount) return buffer.readStringImpl(byteCount, charset) } \ No newline at end of file diff --git a/core/native/src/-NonJvmPlatform.kt b/core/native/src/-NonJvmPlatform.kt index a4b2e23a8..f7a0212c3 100644 --- a/core/native/src/-NonJvmPlatform.kt +++ b/core/native/src/-NonJvmPlatform.kt @@ -28,16 +28,16 @@ internal actual fun String.asUtf8ToByteArray(): ByteArray = commonAsUtf8ToByteAr // message: String? //) : IndexOutOfBoundsException(message) -actual open class IOException actual constructor( +public actual open class IOException actual constructor( message: String?, cause: Throwable? ) : Exception(message, cause) { - actual constructor(message: String?) : this(message, null) + public actual constructor(message: String?) : this(message, null) } -actual open class EOFException actual constructor(message: String?) : IOException(message) +public actual open class EOFException actual constructor(message: String?) : IOException(message) -actual interface Closeable { +public actual interface Closeable { @Throws(IOException::class) - actual fun close() + public actual fun close() } diff --git a/core/native/src/Buffer.kt b/core/native/src/Buffer.kt index be7c44cfd..9d27dc8e1 100644 --- a/core/native/src/Buffer.kt +++ b/core/native/src/Buffer.kt @@ -22,10 +22,10 @@ package kotlinx.io import kotlinx.io.internal.* -actual class Buffer : Source, Sink { +public actual class Buffer : Source, Sink { internal actual var head: Segment? = null - actual var size: Long = 0L + public actual var size: Long = 0L internal set actual override val buffer: Buffer get() = this @@ -44,15 +44,15 @@ actual class Buffer : Source, Sink { override fun peek(): Source = PeekSource(this).buffer() - actual fun copyTo( + public actual fun copyTo( out: Buffer, offset: Long, byteCount: Long ): Buffer = commonCopyTo(out, offset, byteCount) - actual operator fun get(pos: Long): Byte = commonGet(pos) + public actual operator fun get(pos: Long): Byte = commonGet(pos) - actual fun completeSegmentByteCount(): Long = commonCompleteSegmentByteCount() + public actual fun completeSegmentByteCount(): Long = commonCompleteSegmentByteCount() override fun readByte(): Byte = commonReadByte() @@ -75,7 +75,7 @@ actual class Buffer : Source, Sink { override fun read(sink: ByteArray, offset: Int, byteCount: Int): Int = commonRead(sink, offset, byteCount) - actual fun clear(): Unit = commonClear() + public actual fun clear(): Unit = commonClear() actual override fun skip(byteCount: Long): Unit = commonSkip(byteCount) @@ -102,17 +102,17 @@ actual class Buffer : Source, Sink { override fun read(sink: Buffer, byteCount: Long): Long = commonRead(sink, byteCount) - actual override fun flush() = Unit + actual override fun flush(): Unit = Unit - actual override fun close() = Unit + actual override fun close(): Unit = Unit - actual override fun cancel() = Unit + actual override fun cancel(): Unit = Unit override fun equals(other: Any?): Boolean = commonEquals(other) override fun hashCode(): Int = commonHashCode() - override fun toString() = commonString() + override fun toString(): String = commonString() - actual fun copy(): Buffer = commonCopy() + public actual fun copy(): Buffer = commonCopy() } diff --git a/core/native/src/RawSink.kt b/core/native/src/RawSink.kt index a943039a0..bb007f7f3 100644 --- a/core/native/src/RawSink.kt +++ b/core/native/src/RawSink.kt @@ -20,14 +20,14 @@ */ package kotlinx.io -actual interface RawSink : Closeable { +public actual interface RawSink : Closeable { @Throws(IOException::class) - actual fun write(source: Buffer, byteCount: Long) + public actual fun write(source: Buffer, byteCount: Long) @Throws(IOException::class) - actual fun flush() + public actual fun flush() - actual fun cancel() + public actual fun cancel() @Throws(IOException::class) actual override fun close() diff --git a/core/native/src/Sink.kt b/core/native/src/Sink.kt index 6bf593a53..b6c334c99 100644 --- a/core/native/src/Sink.kt +++ b/core/native/src/Sink.kt @@ -20,24 +20,24 @@ */ package kotlinx.io -actual sealed interface Sink : RawSink { - actual val buffer: Buffer +public actual sealed interface Sink : RawSink { + public actual val buffer: Buffer - actual fun write(source: ByteArray, offset: Int, byteCount: Int): Sink + public actual fun write(source: ByteArray, offset: Int, byteCount: Int): Sink - actual fun writeAll(source: RawSource): Long + public actual fun writeAll(source: RawSource): Long - actual fun write(source: RawSource, byteCount: Long): Sink + public actual fun write(source: RawSource, byteCount: Long): Sink - actual fun writeByte(byte: Int): Sink + public actual fun writeByte(byte: Int): Sink - actual fun writeShort(short: Int): Sink + public actual fun writeShort(short: Int): Sink - actual fun writeInt(int: Int): Sink + public actual fun writeInt(int: Int): Sink - actual fun writeLong(long: Long): Sink + public actual fun writeLong(long: Long): Sink - actual fun emit(): Sink + public actual fun emit(): Sink - actual fun emitCompleteSegments(): Sink + public actual fun emitCompleteSegments(): Sink } diff --git a/core/native/src/Source.kt b/core/native/src/Source.kt index af543ea2d..1317cbe18 100644 --- a/core/native/src/Source.kt +++ b/core/native/src/Source.kt @@ -20,36 +20,36 @@ */ package kotlinx.io -actual sealed interface Source : RawSource { - actual val buffer: Buffer +public actual sealed interface Source : RawSource { + public actual val buffer: Buffer - actual fun exhausted(): Boolean + public actual fun exhausted(): Boolean - actual fun require(byteCount: Long) + public actual fun require(byteCount: Long) - actual fun request(byteCount: Long): Boolean + public actual fun request(byteCount: Long): Boolean - actual fun readByte(): Byte + public actual fun readByte(): Byte - actual fun readShort(): Short + public actual fun readShort(): Short - actual fun readInt(): Int + public actual fun readInt(): Int - actual fun readLong(): Long + public actual fun readLong(): Long - actual fun skip(byteCount: Long) + public actual fun skip(byteCount: Long) - actual fun readByteArray(): ByteArray + public actual fun readByteArray(): ByteArray - actual fun readByteArray(byteCount: Long): ByteArray + public actual fun readByteArray(byteCount: Long): ByteArray - actual fun readFully(sink: ByteArray) + public actual fun readFully(sink: ByteArray) - actual fun read(sink: ByteArray, offset: Int, byteCount: Int): Int + public actual fun read(sink: ByteArray, offset: Int, byteCount: Int): Int - actual fun readFully(sink: Buffer, byteCount: Long) + public actual fun readFully(sink: Buffer, byteCount: Long) - actual fun readAll(sink: RawSink): Long + public actual fun readAll(sink: RawSink): Long - actual fun peek(): Source + public actual fun peek(): Source } From 1568f29458d574cdfe58b322933483cdf9f04a62 Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Tue, 6 Jun 2023 13:47:09 +0200 Subject: [PATCH 22/83] Treat all warnings as errors for native compilation tasks --- build.gradle.kts | 5 +++++ core/native/test/util.kt | 8 ++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 7d1bea817..e58aff659 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -66,6 +66,11 @@ subprojects { allWarningsAsErrors = true } } + tasks.withType().configureEach { + kotlinOptions { + allWarningsAsErrors = true + } + } } apiValidation { diff --git a/core/native/test/util.kt b/core/native/test/util.kt index 3f54cbdcf..79f09c738 100644 --- a/core/native/test/util.kt +++ b/core/native/test/util.kt @@ -16,11 +16,11 @@ actual fun createTempFile(): String { if (path.toKString() == "") { val tmpDir = getenv("TMPDIR")?.toKString() ?: getenv("TMP")?.toKString() ?: "" val rnd = Random(getTimeMillis()) - var path: String + var manuallyConstructedPath: String do { - path = "$tmpDir/tmp-${rnd.nextInt()}" - } while (access(path, F_OK) == 0) - return path + manuallyConstructedPath = "$tmpDir/tmp-${rnd.nextInt()}" + } while (access(manuallyConstructedPath, F_OK) == 0) + return manuallyConstructedPath } return path.toKString() } From 9b94d6b237a413992a493ebb6026f99262653a54 Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Tue, 6 Jun 2023 13:47:42 +0200 Subject: [PATCH 23/83] Get rid of unneeded annotations --- core/api/kotlinx-io-core.api | 12 +++++------- core/common/src/-CommonPlatform.kt | 1 - core/common/src/-Util.kt | 12 ------------ core/common/src/RawSink.kt | 3 --- core/common/src/RawSource.kt | 2 -- core/common/src/Utf8.kt | 5 ----- core/jvm/src/Buffer.kt | 18 ------------------ core/jvm/src/JvmCore.kt | 2 -- core/jvm/src/Sink.kt | 10 ---------- core/jvm/src/Source.kt | 16 ---------------- core/native/src/-NonJvmPlatform.kt | 1 - core/native/src/RawSink.kt | 3 --- 12 files changed, 5 insertions(+), 80 deletions(-) diff --git a/core/api/kotlinx-io-core.api b/core/api/kotlinx-io-core.api index 191904061..99e2e5258 100644 --- a/core/api/kotlinx-io-core.api +++ b/core/api/kotlinx-io-core.api @@ -20,8 +20,9 @@ public final class kotlinx/io/Buffer : java/lang/Cloneable, java/nio/channels/By public fun equals (Ljava/lang/Object;)Z public fun exhausted ()Z public fun flush ()V + public final fun get (J)B public fun getBuffer ()Lkotlinx/io/Buffer; - public final fun getByte (J)B + public final fun getSize ()J public fun hashCode ()I public fun inputStream ()Ljava/io/InputStream; public fun isOpen ()Z @@ -43,7 +44,6 @@ public final class kotlinx/io/Buffer : java/lang/Cloneable, java/nio/channels/By public fun readShort ()S public fun request (J)Z public fun require (J)V - public final fun size ()J public fun skip (J)V public fun toString ()Ljava/lang/String; public fun write (Ljava/nio/ByteBuffer;)I @@ -169,7 +169,7 @@ public final class kotlinx/io/SourceKt { public static final fun readString (Lkotlinx/io/Source;Ljava/nio/charset/Charset;)Ljava/lang/String; } -public final class kotlinx/io/Utf8 { +public final class kotlinx/io/Utf8Kt { public static final fun readUtf8 (Lkotlinx/io/Buffer;)Ljava/lang/String; public static final fun readUtf8 (Lkotlinx/io/Source;)Ljava/lang/String; public static final fun readUtf8 (Lkotlinx/io/Source;J)Ljava/lang/String; @@ -178,10 +178,8 @@ public final class kotlinx/io/Utf8 { public static final fun readUtf8Line (Lkotlinx/io/Source;)Ljava/lang/String; public static final fun readUtf8LineStrict (Lkotlinx/io/Source;J)Ljava/lang/String; public static synthetic fun readUtf8LineStrict$default (Lkotlinx/io/Source;JILjava/lang/Object;)Ljava/lang/String; - public static final fun size (Ljava/lang/String;)J - public static final fun size (Ljava/lang/String;I)J - public static final fun size (Ljava/lang/String;II)J - public static synthetic fun size$default (Ljava/lang/String;IIILjava/lang/Object;)J + public static final fun utf8Size (Ljava/lang/String;II)J + public static synthetic fun utf8Size$default (Ljava/lang/String;IIILjava/lang/Object;)J public static final fun writeUtf8 (Lkotlinx/io/Sink;Ljava/lang/String;II)Lkotlinx/io/Sink; public static synthetic fun writeUtf8$default (Lkotlinx/io/Sink;Ljava/lang/String;IIILjava/lang/Object;)Lkotlinx/io/Sink; public static final fun writeUtf8CodePoint (Lkotlinx/io/Sink;I)Lkotlinx/io/Sink; diff --git a/core/common/src/-CommonPlatform.kt b/core/common/src/-CommonPlatform.kt index ded3f97a5..2612531aa 100644 --- a/core/common/src/-CommonPlatform.kt +++ b/core/common/src/-CommonPlatform.kt @@ -37,6 +37,5 @@ public expect interface Closeable { * Closes this object and releases the resources it holds. It is an error to use an object after * it has been closed. It is safe to close an object more than once. */ - @Throws(IOException::class) public fun close() } diff --git a/core/common/src/-Util.kt b/core/common/src/-Util.kt index c4b814e5b..3c14fa723 100644 --- a/core/common/src/-Util.kt +++ b/core/common/src/-Util.kt @@ -169,15 +169,3 @@ internal fun Long.toHexString(): String { return result.concatToString(i, result.size) } - -// Work around a problem where Kotlin/JS IR can't handle default parameters on expect functions -// that depend on the receiver. We use well-known, otherwise-impossible values here and must check -// for them in the receiving function, then swap in the true default value. -// https://youtrack.jetbrains.com/issue/KT-45542 - -internal val DEFAULT__ByteString_size = -1234567890 - -internal fun ByteArray.resolveDefaultParameter(sizeParam: Int): Int { - if (sizeParam == DEFAULT__ByteString_size) return size - return sizeParam -} diff --git a/core/common/src/RawSink.kt b/core/common/src/RawSink.kt index 63d268f75..ecc6fa8b0 100644 --- a/core/common/src/RawSink.kt +++ b/core/common/src/RawSink.kt @@ -41,13 +41,11 @@ public expect interface RawSink : Closeable { * * @throws IndexOutOfBoundsException when the [source]'s size is below [byteCount]. */ - @Throws(IOException::class) public fun write(source: Buffer, byteCount: Long) /** * Pushes all buffered bytes to their final destination. */ - @Throws(IOException::class) public fun flush() /** @@ -63,6 +61,5 @@ public expect interface RawSink : Closeable { * Pushes all buffered bytes to their final destination and releases the resources held by this * sink. It is an error to write a closed sink. It is safe to close a sink more than once. */ - @Throws(IOException::class) override fun close() } diff --git a/core/common/src/RawSource.kt b/core/common/src/RawSource.kt index c14194fc6..8179fae74 100644 --- a/core/common/src/RawSource.kt +++ b/core/common/src/RawSource.kt @@ -41,7 +41,6 @@ public interface RawSource : Closeable { * * @throws IllegalArgumentException when [byteCount] is negative. */ - @Throws(IOException::class) public fun read(sink: Buffer, byteCount: Long): Long /** @@ -56,6 +55,5 @@ public interface RawSource : Closeable { * Closes this source and releases the resources held by this source. It is an error to read a * closed source. It is safe to close a source more than once. */ - @Throws(IOException::class) override fun close() } diff --git a/core/common/src/Utf8.kt b/core/common/src/Utf8.kt index f7fcac268..d7849c6d9 100644 --- a/core/common/src/Utf8.kt +++ b/core/common/src/Utf8.kt @@ -66,7 +66,6 @@ * *
*/ -@file:JvmName("Utf8") package kotlinx.io @@ -74,8 +73,6 @@ import kotlinx.io.internal.commonReadUtf8 import kotlinx.io.internal.commonReadUtf8CodePoint import kotlinx.io.internal.commonWriteUtf8 import kotlinx.io.internal.commonWriteUtf8CodePoint -import kotlin.jvm.JvmName -import kotlin.jvm.JvmOverloads /** * Returns the number of bytes used to encode the slice of `string` as UTF-8 when using [Sink.writeUtf8]. @@ -86,8 +83,6 @@ import kotlin.jvm.JvmOverloads * @throws IndexOutOfBoundsException when [beginIndex] or [endIndex] correspond to a range * out of the current string bounds. */ -@JvmOverloads -@JvmName("size") public fun String.utf8Size(beginIndex: Int = 0, endIndex: Int = length): Long { require(beginIndex >= 0) { "beginIndex < 0: $beginIndex" } require(endIndex >= beginIndex) { "endIndex < beginIndex: $endIndex < $beginIndex" } diff --git a/core/jvm/src/Buffer.kt b/core/jvm/src/Buffer.kt index b5afc3a5a..8127f836f 100644 --- a/core/jvm/src/Buffer.kt +++ b/core/jvm/src/Buffer.kt @@ -31,7 +31,6 @@ import java.nio.channels.ByteChannel public actual class Buffer : Source, Sink, Cloneable, ByteChannel { @JvmField internal actual var head: Segment? = null - @get:JvmName("size") public actual var size: Long = 0L internal set @@ -184,7 +183,6 @@ public actual class Buffer : Source, Sink, Cloneable, ByteChannel { * * @param input the stream to read data from. */ - @Throws(IOException::class) public fun readFrom(input: InputStream): Buffer { readFrom(input, Long.MAX_VALUE, true) return this @@ -200,14 +198,12 @@ public actual class Buffer : Source, Sink, Cloneable, ByteChannel { * @throws IOException when [input] exhausted before reading [byteCount] bytes from it. * */ - @Throws(IOException::class) public fun readFrom(input: InputStream, byteCount: Long): Buffer { require(byteCount >= 0L) { "byteCount < 0: $byteCount" } readFrom(input, byteCount, false) return this } - @Throws(IOException::class) private fun readFrom(input: InputStream, byteCount: Long, forever: Boolean) { var remainingByteCount = byteCount while (remainingByteCount > 0L || forever) { @@ -231,39 +227,29 @@ public actual class Buffer : Source, Sink, Cloneable, ByteChannel { public actual fun completeSegmentByteCount(): Long = commonCompleteSegmentByteCount() - @Throws(EOFException::class) override fun readByte(): Byte = commonReadByte() - @JvmName("getByte") public actual operator fun get(pos: Long): Byte = commonGet(pos) - @Throws(EOFException::class) override fun readShort(): Short = commonReadShort() - @Throws(EOFException::class) override fun readInt(): Int = commonReadInt() - @Throws(EOFException::class) override fun readLong(): Long = commonReadLong() - @Throws(EOFException::class) override fun readFully(sink: Buffer, byteCount: Long): Unit = commonReadFully(sink, byteCount) - @Throws(IOException::class) override fun readAll(sink: RawSink): Long = commonReadAll(sink) override fun readByteArray(): ByteArray = commonReadByteArray() - @Throws(EOFException::class) override fun readByteArray(byteCount: Long): ByteArray = commonReadByteArray(byteCount) - @Throws(EOFException::class) override fun readFully(sink: ByteArray): Unit = commonReadFully(sink) override fun read(sink: ByteArray, offset: Int, byteCount: Int): Int = commonRead(sink, offset, byteCount) - @Throws(IOException::class) override fun read(sink: ByteBuffer): Int { val s = head ?: return -1 @@ -283,7 +269,6 @@ public actual class Buffer : Source, Sink, Cloneable, ByteChannel { public actual fun clear(): Unit = commonClear() - @Throws(EOFException::class) public actual override fun skip(byteCount: Long): Unit = commonSkip(byteCount) actual override fun write( @@ -292,7 +277,6 @@ public actual class Buffer : Source, Sink, Cloneable, ByteChannel { byteCount: Int ): Buffer = commonWrite(source, offset, byteCount) - @Throws(IOException::class) override fun write(source: ByteBuffer): Int { val byteCount = source.remaining() var remaining = byteCount @@ -310,10 +294,8 @@ public actual class Buffer : Source, Sink, Cloneable, ByteChannel { return byteCount } - @Throws(IOException::class) override fun writeAll(source: RawSource): Long = commonWriteAll(source) - @Throws(IOException::class) actual override fun write(source: RawSource, byteCount: Long): Buffer = commonWrite(source, byteCount) diff --git a/core/jvm/src/JvmCore.kt b/core/jvm/src/JvmCore.kt index e44995c45..235c84a67 100644 --- a/core/jvm/src/JvmCore.kt +++ b/core/jvm/src/JvmCore.kt @@ -131,7 +131,6 @@ private open class InputStreamSource( * * Use [RawSink.buffer] to create a buffered sink from it. */ -@Throws(IOException::class) public fun Socket.sink(): RawSink { return object : OutputStreamSink(getOutputStream()) { override fun cancel() { @@ -147,7 +146,6 @@ public fun Socket.sink(): RawSink { * * Use [RawSource.buffer] to create a buffered source from it. */ -@Throws(IOException::class) public fun Socket.source(): RawSource { return object : InputStreamSource(getInputStream()) { override fun cancel() { diff --git a/core/jvm/src/Sink.kt b/core/jvm/src/Sink.kt index 5933eeb15..7c1e23bd2 100644 --- a/core/jvm/src/Sink.kt +++ b/core/jvm/src/Sink.kt @@ -28,34 +28,24 @@ import java.nio.charset.Charset public actual sealed interface Sink : RawSink, WritableByteChannel { public actual val buffer: Buffer - @Throws(IOException::class) public actual fun write(source: ByteArray, offset: Int, byteCount: Int): Sink - @Throws(IOException::class) public actual fun writeAll(source: RawSource): Long - @Throws(IOException::class) public actual fun write(source: RawSource, byteCount: Long): Sink - @Throws(IOException::class) public actual fun writeByte(byte: Int): Sink - @Throws(IOException::class) public actual fun writeShort(short: Int): Sink - @Throws(IOException::class) public actual fun writeInt(int: Int): Sink - @Throws(IOException::class) public actual fun writeLong(long: Long): Sink - @Throws(IOException::class) actual override fun flush() - @Throws(IOException::class) public actual fun emit(): Sink - @Throws(IOException::class) public actual fun emitCompleteSegments(): Sink /** diff --git a/core/jvm/src/Source.kt b/core/jvm/src/Source.kt index 0a4955543..2ce2a01c1 100644 --- a/core/jvm/src/Source.kt +++ b/core/jvm/src/Source.kt @@ -29,46 +29,32 @@ import java.nio.charset.Charset public actual sealed interface Source : RawSource, ReadableByteChannel { public actual val buffer: Buffer - @Throws(IOException::class) public actual fun exhausted(): Boolean - @Throws(IOException::class) public actual fun require(byteCount: Long) - @Throws(IOException::class) public actual fun request(byteCount: Long): Boolean - @Throws(IOException::class) public actual fun readByte(): Byte - @Throws(IOException::class) public actual fun readShort(): Short - @Throws(IOException::class) public actual fun readInt(): Int - @Throws(IOException::class) public actual fun readLong(): Long - @Throws(IOException::class) public actual fun skip(byteCount: Long) - @Throws(IOException::class) public actual fun readByteArray(): ByteArray - @Throws(IOException::class) public actual fun readByteArray(byteCount: Long): ByteArray - @Throws(IOException::class) public actual fun readFully(sink: ByteArray) - @Throws(IOException::class) public actual fun read(sink: ByteArray, offset: Int, byteCount: Int): Int - @Throws(IOException::class) public actual fun readFully(sink: Buffer, byteCount: Long) - @Throws(IOException::class) public actual fun readAll(sink: RawSink): Long public actual fun peek(): Source @@ -107,7 +93,6 @@ private fun Buffer.readStringImpl(byteCount: Long, charset: Charset): String { * * @param charset the [Charset] to use for string decoding. */ -@Throws(IOException::class) public fun Source.readString(charset: Charset): String { var req = 1L while (request(req)) { @@ -124,7 +109,6 @@ public fun Source.readString(charset: Charset): String { * * @throws EOFException when the source exhausted before [byteCount] bytes could be read from it. */ -@Throws(IOException::class) public fun Source.readString(byteCount: Long, charset: Charset): String { require(byteCount) return buffer.readStringImpl(byteCount, charset) diff --git a/core/native/src/-NonJvmPlatform.kt b/core/native/src/-NonJvmPlatform.kt index f7a0212c3..5a9860468 100644 --- a/core/native/src/-NonJvmPlatform.kt +++ b/core/native/src/-NonJvmPlatform.kt @@ -38,6 +38,5 @@ public actual open class IOException actual constructor( public actual open class EOFException actual constructor(message: String?) : IOException(message) public actual interface Closeable { - @Throws(IOException::class) public actual fun close() } diff --git a/core/native/src/RawSink.kt b/core/native/src/RawSink.kt index bb007f7f3..337b9aef9 100644 --- a/core/native/src/RawSink.kt +++ b/core/native/src/RawSink.kt @@ -21,14 +21,11 @@ package kotlinx.io public actual interface RawSink : Closeable { - @Throws(IOException::class) public actual fun write(source: Buffer, byteCount: Long) - @Throws(IOException::class) public actual fun flush() public actual fun cancel() - @Throws(IOException::class) actual override fun close() } From e6cf5c97ec3194ea9d0482ada778dcc32871eb70 Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Tue, 6 Jun 2023 13:53:46 +0200 Subject: [PATCH 24/83] Cleanup imports --- core/jvm/src/JvmCore.kt | 5 ----- core/jvm/src/RealSink.kt | 1 - core/jvm/src/RealSource.kt | 1 - core/jvm/src/Sink.kt | 1 - core/jvm/src/Source.kt | 1 - 5 files changed, 9 deletions(-) diff --git a/core/jvm/src/JvmCore.kt b/core/jvm/src/JvmCore.kt index 235c84a67..0ab31cb79 100644 --- a/core/jvm/src/JvmCore.kt +++ b/core/jvm/src/JvmCore.kt @@ -22,7 +22,6 @@ package kotlinx.io import java.io.File -import java.io.FileNotFoundException import java.io.FileOutputStream import java.io.IOException import java.io.InputStream @@ -31,10 +30,6 @@ import java.net.Socket import java.nio.file.Files import java.nio.file.OpenOption import java.nio.file.Path as NioPath -import java.security.MessageDigest -import java.util.logging.Logger -import javax.crypto.Cipher -import javax.crypto.Mac // TODO: improve test coverage /** diff --git a/core/jvm/src/RealSink.kt b/core/jvm/src/RealSink.kt index a4e34e8d2..8a314de83 100644 --- a/core/jvm/src/RealSink.kt +++ b/core/jvm/src/RealSink.kt @@ -23,7 +23,6 @@ package kotlinx.io import java.io.IOException import java.io.OutputStream import java.nio.ByteBuffer -import java.nio.charset.Charset import kotlinx.io.internal.* internal actual class RealSink actual constructor( diff --git a/core/jvm/src/RealSource.kt b/core/jvm/src/RealSource.kt index 3ce3a97e0..0281052f9 100644 --- a/core/jvm/src/RealSource.kt +++ b/core/jvm/src/RealSource.kt @@ -23,7 +23,6 @@ package kotlinx.io import java.io.IOException import java.io.InputStream import java.nio.ByteBuffer -import java.nio.charset.Charset import kotlinx.io.internal.* internal actual class RealSource actual constructor( diff --git a/core/jvm/src/Sink.kt b/core/jvm/src/Sink.kt index 7c1e23bd2..ec432eca2 100644 --- a/core/jvm/src/Sink.kt +++ b/core/jvm/src/Sink.kt @@ -20,7 +20,6 @@ */ package kotlinx.io -import java.io.IOException import java.io.OutputStream import java.nio.channels.WritableByteChannel import java.nio.charset.Charset diff --git a/core/jvm/src/Source.kt b/core/jvm/src/Source.kt index 2ce2a01c1..45c48df4b 100644 --- a/core/jvm/src/Source.kt +++ b/core/jvm/src/Source.kt @@ -21,7 +21,6 @@ package kotlinx.io import java.io.EOFException -import java.io.IOException import java.io.InputStream import java.nio.channels.ReadableByteChannel import java.nio.charset.Charset From 36e779a664811f3395700d039b490a30e2161012 Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Tue, 6 Jun 2023 14:41:33 +0200 Subject: [PATCH 25/83] Remove more Throws annotations --- core/common/src/RawSink.kt | 2 ++ core/common/src/RawSource.kt | 1 + core/jvm/src/Buffer.kt | 3 --- core/jvm/src/RawSink.kt | 3 --- core/jvm/test/AbstractSinkTestJVM.kt | 7 ------- core/jvm/test/AbstractSourceTestJVM.kt | 10 ---------- core/jvm/test/BufferTest.kt | 10 ---------- core/jvm/test/NioTest.kt | 10 ---------- 8 files changed, 3 insertions(+), 43 deletions(-) diff --git a/core/common/src/RawSink.kt b/core/common/src/RawSink.kt index ecc6fa8b0..d0a8fb18e 100644 --- a/core/common/src/RawSink.kt +++ b/core/common/src/RawSink.kt @@ -40,6 +40,7 @@ public expect interface RawSink : Closeable { * @param byteCount the number of bytes to write. * * @throws IndexOutOfBoundsException when the [source]'s size is below [byteCount]. + * @throws IOException if the sink was canceled. */ public fun write(source: Buffer, byteCount: Long) @@ -57,6 +58,7 @@ public expect interface RawSink : Closeable { */ public fun cancel() + // TODO: what kind of error it is to write into a closed sink? /** * Pushes all buffered bytes to their final destination and releases the resources held by this * sink. It is an error to write a closed sink. It is safe to close a sink more than once. diff --git a/core/common/src/RawSource.kt b/core/common/src/RawSource.kt index 8179fae74..85764617d 100644 --- a/core/common/src/RawSource.kt +++ b/core/common/src/RawSource.kt @@ -40,6 +40,7 @@ public interface RawSource : Closeable { * @param byteCount the number of bytes to read. * * @throws IllegalArgumentException when [byteCount] is negative. + * @throws IOException if the source was canceled. */ public fun read(sink: Buffer, byteCount: Long): Long diff --git a/core/jvm/src/Buffer.kt b/core/jvm/src/Buffer.kt index 8127f836f..188be4cef 100644 --- a/core/jvm/src/Buffer.kt +++ b/core/jvm/src/Buffer.kt @@ -65,7 +65,6 @@ public actual class Buffer : Source, Sink, Cloneable, ByteChannel { override fun exhausted(): Boolean = size == 0L - @Throws(EOFException::class) override fun require(byteCount: Long) { if (size < byteCount) throw EOFException() } @@ -112,7 +111,6 @@ public actual class Buffer : Source, Sink, Cloneable, ByteChannel { * * @throws IndexOutOfBoundsException when [byteCount] and [offset] represents a range out of the buffer bounds. */ - @Throws(IOException::class) @JvmOverloads public fun copyTo( out: OutputStream, @@ -152,7 +150,6 @@ public actual class Buffer : Source, Sink, Cloneable, ByteChannel { ): Buffer = commonCopyTo(out, offset, byteCount) /** Write `byteCount` bytes from this to `out`. */ - @Throws(IOException::class) @JvmOverloads public fun writeTo(out: OutputStream, byteCount: Long = size): Buffer { checkOffsetAndCount(size, 0, byteCount) diff --git a/core/jvm/src/RawSink.kt b/core/jvm/src/RawSink.kt index eee82b04c..b73746b88 100644 --- a/core/jvm/src/RawSink.kt +++ b/core/jvm/src/RawSink.kt @@ -25,14 +25,11 @@ import java.io.Flushable import java.io.IOException public actual interface RawSink : Closeable, Flushable { - @Throws(IOException::class) public actual fun write(source: Buffer, byteCount: Long) - @Throws(IOException::class) actual override fun flush() public actual fun cancel() - @Throws(IOException::class) actual override fun close() } diff --git a/core/jvm/test/AbstractSinkTestJVM.kt b/core/jvm/test/AbstractSinkTestJVM.kt index 6ca1ae760..db2d4d9bd 100644 --- a/core/jvm/test/AbstractSinkTestJVM.kt +++ b/core/jvm/test/AbstractSinkTestJVM.kt @@ -37,7 +37,6 @@ abstract class AbstractSinkTestJVM internal constructor(factory: SinkFactory) { private val sink: Sink = factory.create(data) @Test - @Throws(Exception::class) fun outputStream() { val out: OutputStream = sink.outputStream() out.write('a'.code) @@ -48,7 +47,6 @@ abstract class AbstractSinkTestJVM internal constructor(factory: SinkFactory) { } @Test - @Throws(Exception::class) fun outputStreamBounds() { val out: OutputStream = sink.outputStream() assertFailsWith { @@ -80,7 +78,6 @@ abstract class AbstractSinkTestJVM internal constructor(factory: SinkFactory) { } @Test - @Throws(java.lang.Exception::class) fun writeNioBuffer() { val expected = "abcdefg" val nioByteBuffer: ByteBuffer = ByteBuffer.allocate(1024) @@ -95,7 +92,6 @@ abstract class AbstractSinkTestJVM internal constructor(factory: SinkFactory) { } @Test - @Throws(java.lang.Exception::class) fun writeLargeNioBufferWritesAllData() { val expected: String = "a".repeat(SEGMENT_SIZE * 3) val nioByteBuffer: ByteBuffer = ByteBuffer.allocate(SEGMENT_SIZE * 4) @@ -121,7 +117,6 @@ abstract class AbstractSinkTestJVM internal constructor(factory: SinkFactory) { } @Test - @Throws(IOException::class) fun writeStringWithCharset() { sink.writeString("təˈranəˌsôr", Charset.forName("utf-32be")) sink.flush() @@ -130,7 +125,6 @@ abstract class AbstractSinkTestJVM internal constructor(factory: SinkFactory) { } @Test - @Throws(IOException::class) fun writeSubstringWithCharset() { sink.writeString("təˈranəˌsôr", Charset.forName("utf-32be"), 3, 7) sink.flush() @@ -138,7 +132,6 @@ abstract class AbstractSinkTestJVM internal constructor(factory: SinkFactory) { } @Test - @Throws(IOException::class) fun writeUtf8SubstringWithCharset() { sink.writeString("təˈranəˌsôr", Charset.forName("utf-8"), 3, 7) sink.flush() diff --git a/core/jvm/test/AbstractSourceTestJVM.kt b/core/jvm/test/AbstractSourceTestJVM.kt index ad64bf4a6..74890261c 100644 --- a/core/jvm/test/AbstractSourceTestJVM.kt +++ b/core/jvm/test/AbstractSourceTestJVM.kt @@ -45,7 +45,6 @@ abstract class AbstractSourceTestJVM(private val factory: SourceFactory) { source = pipe.source } @Test - @Throws(Exception::class) fun inputStream() { sink.writeUtf8("abc") sink.emit() @@ -69,7 +68,6 @@ abstract class AbstractSourceTestJVM(private val factory: SourceFactory) { } @Test - @Throws(Exception::class) fun inputStreamOffsetCount() { sink.writeUtf8("abcde") sink.emit() @@ -87,7 +85,6 @@ abstract class AbstractSourceTestJVM(private val factory: SourceFactory) { } @Test - @Throws(Exception::class) fun inputStreamSkip() { sink.writeUtf8("abcde") sink.emit() @@ -101,7 +98,6 @@ abstract class AbstractSourceTestJVM(private val factory: SourceFactory) { } @Test - @Throws(Exception::class) fun inputStreamCharByChar() { sink.writeUtf8("abc") sink.emit() @@ -113,7 +109,6 @@ abstract class AbstractSourceTestJVM(private val factory: SourceFactory) { } @Test - @Throws(IOException::class) fun inputStreamBounds() { sink.writeUtf8("a".repeat(100)) sink.emit() @@ -191,7 +186,6 @@ abstract class AbstractSourceTestJVM(private val factory: SourceFactory) { } @Test - @Throws(java.lang.Exception::class) fun readNioBuffer() { val expected = if (factory.isOneByteAtATime) "a" else "abcdefg" sink.writeUtf8("abcdefg") @@ -209,7 +203,6 @@ abstract class AbstractSourceTestJVM(private val factory: SourceFactory) { /** Note that this test crashes the VM on Android. */ @Test - @Throws(java.lang.Exception::class) fun readLargeNioBufferOnlyReadsOneSegment() { val expected: String = if (factory.isOneByteAtATime) "a" else "a".repeat(SEGMENT_SIZE) sink.writeUtf8("a".repeat(SEGMENT_SIZE * 4)) @@ -231,7 +224,6 @@ abstract class AbstractSourceTestJVM(private val factory: SourceFactory) { } @Test - @Throws(java.lang.Exception::class) fun readSpecificCharsetPartial() { sink.write(("0000007600000259000002c80000006c000000e40000007300000259000002" + "cc000000720000006100000070000000740000025900000072").decodeHex()) @@ -240,7 +232,6 @@ abstract class AbstractSourceTestJVM(private val factory: SourceFactory) { } @Test - @Throws(java.lang.Exception::class) fun readSpecificCharset() { sink.write(("0000007600000259000002c80000006c000000e40000007300000259000002" + "cc000000720000006100000070000000740000025900000072").decodeHex()) @@ -250,7 +241,6 @@ abstract class AbstractSourceTestJVM(private val factory: SourceFactory) { } @Test - @Throws(IOException::class) fun readStringTooShortThrows() { sink.writeString("abc", Charsets.US_ASCII) sink.emit() diff --git a/core/jvm/test/BufferTest.kt b/core/jvm/test/BufferTest.kt index ce0b41a69..12dcab2bb 100644 --- a/core/jvm/test/BufferTest.kt +++ b/core/jvm/test/BufferTest.kt @@ -31,7 +31,6 @@ private const val SEGMENT_SIZE = Segment.SIZE class BufferTest { @Test - @Throws(Exception::class) fun copyToSpanningSegments() { val source = Buffer() source.writeUtf8("a".repeat(SEGMENT_SIZE * 2)) @@ -57,7 +56,6 @@ class BufferTest { } @Test - @Throws(Exception::class) fun copyToStream() { val buffer = Buffer().writeUtf8("hello, world!") val out = ByteArrayOutputStream() @@ -68,7 +66,6 @@ class BufferTest { } @Test - @Throws(java.lang.Exception::class) fun writeToSpanningSegments() { val buffer = Buffer() buffer.writeUtf8("a".repeat(SEGMENT_SIZE * 2)) @@ -81,7 +78,6 @@ class BufferTest { } @Test - @Throws(java.lang.Exception::class) fun writeToStream() { val buffer = Buffer().writeUtf8("hello, world!") val out = ByteArrayOutputStream() @@ -92,7 +88,6 @@ class BufferTest { } @Test - @Throws(java.lang.Exception::class) fun readFromStream() { val input: InputStream = ByteArrayInputStream("hello, world!".toByteArray(UTF_8)) val buffer = Buffer() @@ -102,7 +97,6 @@ class BufferTest { } @Test - @Throws(java.lang.Exception::class) fun readFromSpanningSegments() { val input: InputStream = ByteArrayInputStream("hello, world!".toByteArray(UTF_8)) val buffer = Buffer().writeUtf8("a".repeat(SEGMENT_SIZE - 10)) @@ -112,7 +106,6 @@ class BufferTest { } @Test - @Throws(java.lang.Exception::class) fun readFromStreamWithCount() { val input: InputStream = ByteArrayInputStream("hello, world!".toByteArray(UTF_8)) val buffer = Buffer() @@ -138,7 +131,6 @@ class BufferTest { } @Test - @Throws(IOException::class) fun readFromDoesNotLeaveEmptyTailSegment() { val buffer = Buffer() buffer.readFrom(ByteArrayInputStream(ByteArray(SEGMENT_SIZE))) @@ -146,7 +138,6 @@ class BufferTest { } @Test - @Throws(java.lang.Exception::class) fun bufferInputStreamByteByByte() { val source = Buffer() source.writeUtf8("abc") @@ -160,7 +151,6 @@ class BufferTest { } @Test - @Throws(java.lang.Exception::class) fun bufferInputStreamBulkReads() { val source = Buffer() source.writeUtf8("abc") diff --git a/core/jvm/test/NioTest.kt b/core/jvm/test/NioTest.kt index 331a264c2..cebde4c92 100644 --- a/core/jvm/test/NioTest.kt +++ b/core/jvm/test/NioTest.kt @@ -39,7 +39,6 @@ class NioTest { @TempDir lateinit var temporaryFolder: Path @Test - @Throws(Exception::class) fun sourceIsOpen() { val source: Source = RealSource(Buffer()) assertTrue(source.isOpen()) @@ -48,7 +47,6 @@ class NioTest { } @Test - @Throws(Exception::class) fun sinkIsOpen() { val sink: Sink = RealSink(Buffer()) assertTrue(sink.isOpen()) @@ -57,7 +55,6 @@ class NioTest { } @Test - @Throws(Exception::class) fun writableChannelNioFile() { val file = Paths.get(temporaryFolder.toString(), "test").createFile() val fileChannel: FileChannel = FileChannel.open(file, StandardOpenOption.WRITE) @@ -68,7 +65,6 @@ class NioTest { } @Test - @Throws(Exception::class) fun writableChannelBuffer() { val buffer = Buffer() testWritableByteChannel(buffer) @@ -76,7 +72,6 @@ class NioTest { } @Test - @Throws(Exception::class) fun writableChannelBufferedSink() { val buffer = Buffer() val bufferedSink: Sink = buffer @@ -85,7 +80,6 @@ class NioTest { } @Test - @Throws(Exception::class) fun readableChannelNioFile() { val file: File = Paths.get(temporaryFolder.toString(), "test").toFile() val initialData: Sink = file.sink().buffer() @@ -96,7 +90,6 @@ class NioTest { } @Test - @Throws(Exception::class) fun readableChannelBuffer() { val buffer = Buffer() buffer.writeUtf8("abcdefghijklmnopqrstuvwxyz") @@ -104,7 +97,6 @@ class NioTest { } @Test - @Throws(Exception::class) fun readableChannelBufferedSource() { val buffer = Buffer() val bufferedSource: Source = buffer @@ -116,7 +108,6 @@ class NioTest { * Does some basic writes to `channel`. We execute this against both Okio's channels and * also a standard implementation from the JDK to confirm that their behavior is consistent. */ - @Throws(Exception::class) private fun testWritableByteChannel(channel: WritableByteChannel) { assertTrue(channel.isOpen()) val byteBuffer = ByteBuffer.allocate(1024) @@ -136,7 +127,6 @@ class NioTest { * Does some basic reads from `channel`. We execute this against both Okio's channels and * also a standard implementation from the JDK to confirm that their behavior is consistent. */ - @Throws(Exception::class) private fun testReadableByteChannel(channel: ReadableByteChannel) { assertTrue(channel.isOpen) val byteBuffer = ByteBuffer.allocate(1024) From 93c37af497b4518bb47095ba05b9b8b2e62735c7 Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Tue, 6 Jun 2023 15:52:19 +0200 Subject: [PATCH 26/83] Extract some JVM-specific member functions to extension functions --- core/api/kotlinx-io-core.api | 22 ++- core/jvm/src/Buffer.kt | 284 +++++++++++++++-------------------- core/jvm/src/RealSink.kt | 27 ---- core/jvm/src/RealSource.kt | 34 ----- core/jvm/src/Sink.kt | 36 ++++- core/jvm/src/Source.kt | 37 ++++- 6 files changed, 193 insertions(+), 247 deletions(-) diff --git a/core/api/kotlinx-io-core.api b/core/api/kotlinx-io-core.api index 99e2e5258..dc7ffe3d1 100644 --- a/core/api/kotlinx-io-core.api +++ b/core/api/kotlinx-io-core.api @@ -7,11 +7,7 @@ public final class kotlinx/io/Buffer : java/lang/Cloneable, java/nio/channels/By public fun close ()V public final fun completeSegmentByteCount ()J public final fun copy ()Lkotlinx/io/Buffer; - public final fun copyTo (Ljava/io/OutputStream;)Lkotlinx/io/Buffer; - public final fun copyTo (Ljava/io/OutputStream;J)Lkotlinx/io/Buffer; - public final fun copyTo (Ljava/io/OutputStream;JJ)Lkotlinx/io/Buffer; public final fun copyTo (Lkotlinx/io/Buffer;JJ)Lkotlinx/io/Buffer; - public static synthetic fun copyTo$default (Lkotlinx/io/Buffer;Ljava/io/OutputStream;JJILjava/lang/Object;)Lkotlinx/io/Buffer; public static synthetic fun copyTo$default (Lkotlinx/io/Buffer;Lkotlinx/io/Buffer;JJILjava/lang/Object;)Lkotlinx/io/Buffer; public fun emit ()Lkotlinx/io/Buffer; public synthetic fun emit ()Lkotlinx/io/Sink; @@ -24,9 +20,7 @@ public final class kotlinx/io/Buffer : java/lang/Cloneable, java/nio/channels/By public fun getBuffer ()Lkotlinx/io/Buffer; public final fun getSize ()J public fun hashCode ()I - public fun inputStream ()Ljava/io/InputStream; public fun isOpen ()Z - public fun outputStream ()Ljava/io/OutputStream; public fun peek ()Lkotlinx/io/Source; public fun read (Ljava/nio/ByteBuffer;)I public fun read (Lkotlinx/io/Buffer;J)J @@ -35,8 +29,6 @@ public final class kotlinx/io/Buffer : java/lang/Cloneable, java/nio/channels/By public fun readByte ()B public fun readByteArray ()[B public fun readByteArray (J)[B - public final fun readFrom (Ljava/io/InputStream;)Lkotlinx/io/Buffer; - public final fun readFrom (Ljava/io/InputStream;J)Lkotlinx/io/Buffer; public fun readFully (Lkotlinx/io/Buffer;J)V public fun readFully ([B)V public fun readInt ()I @@ -61,8 +53,14 @@ public final class kotlinx/io/Buffer : java/lang/Cloneable, java/nio/channels/By public synthetic fun writeLong (J)Lkotlinx/io/Sink; public fun writeShort (I)Lkotlinx/io/Buffer; public synthetic fun writeShort (I)Lkotlinx/io/Sink; - public final fun writeTo (Ljava/io/OutputStream;)Lkotlinx/io/Buffer; - public final fun writeTo (Ljava/io/OutputStream;J)Lkotlinx/io/Buffer; +} + +public final class kotlinx/io/BufferKt { + public static final fun copyTo (Lkotlinx/io/Buffer;Ljava/io/OutputStream;JJ)Lkotlinx/io/Buffer; + public static synthetic fun copyTo$default (Lkotlinx/io/Buffer;Ljava/io/OutputStream;JJILjava/lang/Object;)Lkotlinx/io/Buffer; + public static final fun readFrom (Lkotlinx/io/Buffer;Ljava/io/InputStream;)Lkotlinx/io/Buffer; + public static final fun readFrom (Lkotlinx/io/Buffer;Ljava/io/InputStream;J)Lkotlinx/io/Buffer; + public static final fun writeTo (Lkotlinx/io/Buffer;Ljava/io/OutputStream;J)Lkotlinx/io/Buffer; public static synthetic fun writeTo$default (Lkotlinx/io/Buffer;Ljava/io/OutputStream;JILjava/lang/Object;)Lkotlinx/io/Buffer; } @@ -103,7 +101,6 @@ public abstract interface class kotlinx/io/Sink : java/nio/channels/WritableByte public abstract fun emitCompleteSegments ()Lkotlinx/io/Sink; public abstract fun flush ()V public abstract fun getBuffer ()Lkotlinx/io/Buffer; - public abstract fun outputStream ()Ljava/io/OutputStream; public abstract fun write (Lkotlinx/io/RawSource;J)Lkotlinx/io/Sink; public abstract fun write ([BII)Lkotlinx/io/Sink; public abstract fun writeAll (Lkotlinx/io/RawSource;)J @@ -126,6 +123,7 @@ public final class kotlinx/io/SinkExtKt { } public final class kotlinx/io/SinkKt { + public static final fun outputStream (Lkotlinx/io/Sink;)Ljava/io/OutputStream; public static final fun writeString (Lkotlinx/io/Sink;Ljava/lang/String;Ljava/nio/charset/Charset;II)Lkotlinx/io/Sink; public static synthetic fun writeString$default (Lkotlinx/io/Sink;Ljava/lang/String;Ljava/nio/charset/Charset;IIILjava/lang/Object;)Lkotlinx/io/Sink; } @@ -133,7 +131,6 @@ public final class kotlinx/io/SinkKt { public abstract interface class kotlinx/io/Source : java/nio/channels/ReadableByteChannel, kotlinx/io/RawSource { public abstract fun exhausted ()Z public abstract fun getBuffer ()Lkotlinx/io/Buffer; - public abstract fun inputStream ()Ljava/io/InputStream; public abstract fun peek ()Lkotlinx/io/Source; public abstract fun read ([BII)I public abstract fun readAll (Lkotlinx/io/RawSink;)J @@ -165,6 +162,7 @@ public final class kotlinx/io/SourceExtKt { } public final class kotlinx/io/SourceKt { + public static final fun inputStream (Lkotlinx/io/Source;)Ljava/io/InputStream; public static final fun readString (Lkotlinx/io/Source;JLjava/nio/charset/Charset;)Ljava/lang/String; public static final fun readString (Lkotlinx/io/Source;Ljava/nio/charset/Charset;)Ljava/lang/String; } diff --git a/core/jvm/src/Buffer.kt b/core/jvm/src/Buffer.kt index 188be4cef..d80fd24ff 100644 --- a/core/jvm/src/Buffer.kt +++ b/core/jvm/src/Buffer.kt @@ -36,29 +36,6 @@ public actual class Buffer : Source, Sink, Cloneable, ByteChannel { actual override val buffer: Buffer get() = this - /** - * Returns a new [OutputStream] to write data into this buffer. - * - * Closing the stream won't have any effect as buffers don't support closing. - */ - override fun outputStream(): OutputStream { - return object : OutputStream() { - override fun write(b: Int) { - writeByte(b) - } - - override fun write(data: ByteArray, offset: Int, byteCount: Int) { - this@Buffer.write(data, offset, byteCount) - } - - override fun flush() {} - - override fun close() {} - - override fun toString(): String = "${this@Buffer}.outputStream()" - } - } - actual override fun emitCompleteSegments(): Buffer = this // Nowhere to emit to! actual override fun emit(): Buffer = this // Nowhere to emit to! @@ -75,153 +52,12 @@ public actual class Buffer : Source, Sink, Cloneable, ByteChannel { return PeekSource(this).buffer() } - /** - * Returns a new [InputStream] to read data from this buffer. - * - * Closing the stream won't have any effect as buffers don't support closing. - */ - override fun inputStream(): InputStream { - return object : InputStream() { - override fun read(): Int { - return if (size > 0L) { - readByte() and 0xff - } else { - -1 - } - } - - override fun read(sink: ByteArray, offset: Int, byteCount: Int): Int { - return this@Buffer.read(sink, offset, byteCount) - } - - override fun available() = minOf(size, Integer.MAX_VALUE).toInt() - - override fun close() {} - - override fun toString() = "${this@Buffer}.inputStream()" - } - } - - /** - * Copy [byteCount] bytes from this buffer, starting at [offset], to [out]. - * - * @param out the destination to copy data into. - * @param offset the offset to start copying data from, `0` by default. - * @param byteCount the number of bytes to copy, all data starting from the [offset] by default. - * - * @throws IndexOutOfBoundsException when [byteCount] and [offset] represents a range out of the buffer bounds. - */ - @JvmOverloads - public fun copyTo( - out: OutputStream, - offset: Long = 0L, - byteCount: Long = size - offset - ): Buffer { - checkOffsetAndCount(size, offset, byteCount) - if (byteCount == 0L) return this - - var currentOffset = offset - var remainingByteCount = byteCount - - // Skip segments that we aren't copying from. - var s = head - while (currentOffset >= s!!.limit - s.pos) { - currentOffset -= (s.limit - s.pos).toLong() - s = s.next - } - - // Copy from one segment at a time. - while (remainingByteCount > 0L) { - val pos = (s!!.pos + currentOffset).toInt() - val toCopy = minOf(s.limit - pos, remainingByteCount).toInt() - out.write(s.data, pos, toCopy) - remainingByteCount -= toCopy.toLong() - currentOffset = 0L - s = s.next - } - - return this - } - public actual fun copyTo( out: Buffer, offset: Long, byteCount: Long ): Buffer = commonCopyTo(out, offset, byteCount) - /** Write `byteCount` bytes from this to `out`. */ - @JvmOverloads - public fun writeTo(out: OutputStream, byteCount: Long = size): Buffer { - checkOffsetAndCount(size, 0, byteCount) - var remainingByteCount = byteCount - - var s = head - while (remainingByteCount > 0L) { - val toCopy = minOf(remainingByteCount, s!!.limit - s.pos).toInt() - out.write(s.data, s.pos, toCopy) - - s.pos += toCopy - size -= toCopy.toLong() - remainingByteCount -= toCopy.toLong() - - if (s.pos == s.limit) { - val toRecycle = s - s = toRecycle.pop() - head = s - SegmentPool.recycle(toRecycle) - } - } - - return this - } - - /** - * Read and exhaust bytes from [input] into this buffer. Stops reading data on [input] exhaustion. - * - * @param input the stream to read data from. - */ - public fun readFrom(input: InputStream): Buffer { - readFrom(input, Long.MAX_VALUE, true) - return this - } - - /** - * Read [byteCount] bytes from [input] into this buffer. Throws an exception when [input] is - * exhausted before reading [byteCount] bytes. - * - * @param input the stream to read data from. - * @param byteCount the number of bytes read from [input]. - * - * @throws IOException when [input] exhausted before reading [byteCount] bytes from it. - * - */ - public fun readFrom(input: InputStream, byteCount: Long): Buffer { - require(byteCount >= 0L) { "byteCount < 0: $byteCount" } - readFrom(input, byteCount, false) - return this - } - - private fun readFrom(input: InputStream, byteCount: Long, forever: Boolean) { - var remainingByteCount = byteCount - while (remainingByteCount > 0L || forever) { - val tail = writableSegment(1) - val maxToCopy = minOf(remainingByteCount, Segment.SIZE - tail.limit).toInt() - val bytesRead = input.read(tail.data, tail.limit, maxToCopy) - if (bytesRead == -1) { - if (tail.pos == tail.limit) { - // We allocated a tail segment, but didn't end up needing it. Recycle! - head = tail.pop() - SegmentPool.recycle(tail) - } - if (forever) return - throw EOFException() - } - tail.limit += bytesRead - size += bytesRead.toLong() - remainingByteCount -= bytesRead.toLong() - } - } - public actual fun completeSegmentByteCount(): Long = commonCompleteSegmentByteCount() override fun readByte(): Byte = commonReadByte() @@ -338,3 +174,123 @@ public actual class Buffer : Source, Sink, Cloneable, ByteChannel { */ public override fun clone(): Buffer = copy() } + +/** + * Read and exhaust bytes from [input] into this buffer. Stops reading data on [input] exhaustion. + * + * @param input the stream to read data from. + * + * @throws ??? + */ +public fun Buffer.readFrom(input: InputStream): Buffer { + readFrom(input, Long.MAX_VALUE, true) + return this +} + +/** + * Read [byteCount] bytes from [input] into this buffer. Throws an exception when [input] is + * exhausted before reading [byteCount] bytes. + * + * @param input the stream to read data from. + * @param byteCount the number of bytes read from [input]. + * + * @throws IOException when [input] exhausted before reading [byteCount] bytes from it. + */ +public fun Buffer.readFrom(input: InputStream, byteCount: Long): Buffer { + require(byteCount >= 0L) { "byteCount < 0: $byteCount" } + readFrom(input, byteCount, false) + return this +} + +private fun Buffer.readFrom(input: InputStream, byteCount: Long, forever: Boolean) { + var remainingByteCount = byteCount + while (remainingByteCount > 0L || forever) { + val tail = writableSegment(1) + val maxToCopy = minOf(remainingByteCount, Segment.SIZE - tail.limit).toInt() + val bytesRead = input.read(tail.data, tail.limit, maxToCopy) + if (bytesRead == -1) { + if (tail.pos == tail.limit) { + // We allocated a tail segment, but didn't end up needing it. Recycle! + head = tail.pop() + SegmentPool.recycle(tail) + } + if (forever) return + throw EOFException() + } + tail.limit += bytesRead + size += bytesRead.toLong() + remainingByteCount -= bytesRead.toLong() + } +} + +/** + * Writes [byteCount] bytes from this buffer to [out]. + * + * @param out the [OutputStream] to write to. + * @param byteCount the number of bytes to be written, [Buffer.size] by default. + * + * @throws ??? + */ +public fun Buffer.writeTo(out: OutputStream, byteCount: Long = size): Buffer { + checkOffsetAndCount(size, 0, byteCount) + var remainingByteCount = byteCount + + var s = head + while (remainingByteCount > 0L) { + val toCopy = minOf(remainingByteCount, s!!.limit - s.pos).toInt() + out.write(s.data, s.pos, toCopy) + + s.pos += toCopy + size -= toCopy.toLong() + remainingByteCount -= toCopy.toLong() + + if (s.pos == s.limit) { + val toRecycle = s + s = toRecycle.pop() + head = s + SegmentPool.recycle(toRecycle) + } + } + + return this +} + +/** + * Copy [byteCount] bytes from this buffer, starting at [offset], to [out]. + * + * @param out the destination to copy data into. + * @param offset the offset to start copying data from, `0` by default. + * @param byteCount the number of bytes to copy, all data starting from the [offset] by default. + * + * @throws IndexOutOfBoundsException when [byteCount] and [offset] represents a range out of the buffer bounds. + */ +public fun Buffer.copyTo( + out: OutputStream, + offset: Long = 0L, + byteCount: Long = size - offset +): Buffer { + checkOffsetAndCount(size, offset, byteCount) + if (byteCount == 0L) return this + + var currentOffset = offset + var remainingByteCount = byteCount + + // Skip segments that we aren't copying from. + var s = head + while (currentOffset >= s!!.limit - s.pos) { + currentOffset -= (s.limit - s.pos).toLong() + s = s.next + } + + // Copy from one segment at a time. + while (remainingByteCount > 0L) { + val pos = (s!!.pos + currentOffset).toInt() + val toCopy = minOf(s.limit - pos, remainingByteCount).toInt() + out.write(s.data, pos, toCopy) + remainingByteCount -= toCopy.toLong() + currentOffset = 0L + s = s.next + } + + return this +} diff --git a/core/jvm/src/RealSink.kt b/core/jvm/src/RealSink.kt index 8a314de83..be1dd5160 100644 --- a/core/jvm/src/RealSink.kt +++ b/core/jvm/src/RealSink.kt @@ -56,33 +56,6 @@ internal actual class RealSink actual constructor( override fun emitCompleteSegments() = commonEmitCompleteSegments() override fun emit() = commonEmit() - override fun outputStream(): OutputStream { - return object : OutputStream() { - override fun write(b: Int) { - if (closed) throw IOException("closed") - buffer.writeByte(b.toByte().toInt()) - emitCompleteSegments() - } - - override fun write(data: ByteArray, offset: Int, byteCount: Int) { - if (closed) throw IOException("closed") - buffer.write(data, offset, byteCount) - emitCompleteSegments() - } - - override fun flush() { - // For backwards compatibility, a flush() on a closed stream is a no-op. - if (!closed) { - this@RealSink.flush() - } - } - - override fun close() = this@RealSink.close() - - override fun toString() = "${this@RealSink}.outputStream()" - } - } - override fun flush() = commonFlush() override fun isOpen() = !closed diff --git a/core/jvm/src/RealSource.kt b/core/jvm/src/RealSource.kt index 0281052f9..b49d4ceba 100644 --- a/core/jvm/src/RealSource.kt +++ b/core/jvm/src/RealSource.kt @@ -65,40 +65,6 @@ internal actual class RealSource actual constructor( override fun peek(): Source = commonPeek() - override fun inputStream(): InputStream { - return object : InputStream() { - override fun read(): Int { - if (closed) throw IOException("closed") - if (buffer.size == 0L) { - val count = source.read(buffer, Segment.SIZE.toLong()) - if (count == -1L) return -1 - } - return buffer.readByte() and 0xff - } - - override fun read(data: ByteArray, offset: Int, byteCount: Int): Int { - if (closed) throw IOException("closed") - checkOffsetAndCount(data.size.toLong(), offset.toLong(), byteCount.toLong()) - - if (buffer.size == 0L) { - val count = source.read(buffer, Segment.SIZE.toLong()) - if (count == -1L) return -1 - } - - return buffer.read(data, offset, byteCount) - } - - override fun available(): Int { - if (closed) throw IOException("closed") - return minOf(buffer.size, Integer.MAX_VALUE).toInt() - } - - override fun close() = this@RealSource.close() - - override fun toString() = "${this@RealSource}.inputStream()" - } - } - override fun isOpen() = !closed override fun close(): Unit = commonClose() diff --git a/core/jvm/src/Sink.kt b/core/jvm/src/Sink.kt index ec432eca2..580ce261b 100644 --- a/core/jvm/src/Sink.kt +++ b/core/jvm/src/Sink.kt @@ -20,6 +20,7 @@ */ package kotlinx.io +import java.io.IOException import java.io.OutputStream import java.nio.channels.WritableByteChannel import java.nio.charset.Charset @@ -46,11 +47,6 @@ public actual sealed interface Sink : RawSink, WritableByteChannel { public actual fun emit(): Sink public actual fun emitCompleteSegments(): Sink - - /** - * Returns an output stream that writes to this sink. Closing the stream will also close this sink. - */ - public fun outputStream(): OutputStream } /** @@ -72,4 +68,34 @@ public fun T.writeString(string: String, charset: Charset, beginIndex: val data = string.substring(beginIndex, endIndex).toByteArray(charset) write(data, 0, data.size) return this +} + +/** + * Returns an output stream that writes to this sink. Closing the stream will also close this sink. + */ +public fun Sink.outputStream(): OutputStream { + return object : OutputStream() { + override fun write(b: Int) { + if (!isOpen) throw IOException("closed") + buffer.writeByte(b.toByte().toInt()) + emitCompleteSegments() + } + + override fun write(data: ByteArray, offset: Int, byteCount: Int) { + if (!isOpen) throw IOException("closed") + buffer.write(data, offset, byteCount) + emitCompleteSegments() + } + + override fun flush() { + // For backwards compatibility, a flush() on a closed stream is a no-op. + if (isOpen) { + this@outputStream.flush() + } + } + + override fun close() = this@outputStream.close() + + override fun toString() = "${this@outputStream}.outputStream()" + } } \ No newline at end of file diff --git a/core/jvm/src/Source.kt b/core/jvm/src/Source.kt index 45c48df4b..faf846b7c 100644 --- a/core/jvm/src/Source.kt +++ b/core/jvm/src/Source.kt @@ -21,6 +21,7 @@ package kotlinx.io import java.io.EOFException +import java.io.IOException import java.io.InputStream import java.nio.channels.ReadableByteChannel import java.nio.charset.Charset @@ -57,11 +58,6 @@ public actual sealed interface Source : RawSource, ReadableByteChannel { public actual fun readAll(sink: RawSink): Long public actual fun peek(): Source - - /** - * Returns an input stream that reads from this source. Closing the stream will also close this source. - */ - public fun inputStream(): InputStream } private fun Buffer.readStringImpl(byteCount: Long, charset: Charset): String { @@ -111,4 +107,35 @@ public fun Source.readString(charset: Charset): String { public fun Source.readString(byteCount: Long, charset: Charset): String { require(byteCount) return buffer.readStringImpl(byteCount, charset) +} + +/** + * Returns an input stream that reads from this source. Closing the stream will also close this source. + */ +public fun Source.inputStream(): InputStream { + return object : InputStream() { + override fun read(): Int { + if (!isOpen) throw IOException("closed") + if (exhausted()) { + return -1 + } + return readByte() and 0xff + } + + override fun read(data: ByteArray, offset: Int, byteCount: Int): Int { + if (!isOpen) throw IOException("closed") + checkOffsetAndCount(data.size.toLong(), offset.toLong(), byteCount.toLong()) + + return this@inputStream.read(data, offset, byteCount) + } + + override fun available(): Int { + if (!isOpen) throw IOException("closed") + return minOf(buffer.size, Integer.MAX_VALUE).toInt() + } + + override fun close() = this@inputStream.close() + + override fun toString() = "${this@inputStream}.inputStream()" + } } \ No newline at end of file From 04ab7f79f73d8f32c26e90d30088d76cbd1961e7 Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Wed, 7 Jun 2023 12:55:39 +0200 Subject: [PATCH 27/83] Move Source.readByteArray to extensions --- core/api/kotlinx-io-core.api | 6 +-- core/common/src/Source.kt | 15 ------- core/common/src/SourceExt.kt | 40 +++++++++++++++++++ core/common/src/internal/-Buffer.kt | 11 ----- .../src/internal/-RealBufferedSource.kt | 10 ----- core/jvm/src/Buffer.kt | 4 -- core/jvm/src/RealSource.kt | 2 - core/jvm/src/Source.kt | 4 -- core/native/src/Buffer.kt | 4 -- core/native/src/RealSource.kt | 2 - core/native/src/Source.kt | 4 -- 11 files changed, 42 insertions(+), 60 deletions(-) diff --git a/core/api/kotlinx-io-core.api b/core/api/kotlinx-io-core.api index dc7ffe3d1..961f03800 100644 --- a/core/api/kotlinx-io-core.api +++ b/core/api/kotlinx-io-core.api @@ -27,8 +27,6 @@ public final class kotlinx/io/Buffer : java/lang/Cloneable, java/nio/channels/By public fun read ([BII)I public fun readAll (Lkotlinx/io/RawSink;)J public fun readByte ()B - public fun readByteArray ()[B - public fun readByteArray (J)[B public fun readFully (Lkotlinx/io/Buffer;J)V public fun readFully ([B)V public fun readInt ()I @@ -135,8 +133,6 @@ public abstract interface class kotlinx/io/Source : java/nio/channels/ReadableBy public abstract fun read ([BII)I public abstract fun readAll (Lkotlinx/io/RawSink;)J public abstract fun readByte ()B - public abstract fun readByteArray ()[B - public abstract fun readByteArray (J)[B public abstract fun readFully (Lkotlinx/io/Buffer;J)V public abstract fun readFully ([B)V public abstract fun readInt ()I @@ -154,6 +150,8 @@ public final class kotlinx/io/Source$DefaultImpls { public final class kotlinx/io/SourceExtKt { public static final fun indexOf (Lkotlinx/io/Source;BJJ)J public static synthetic fun indexOf$default (Lkotlinx/io/Source;BJJILjava/lang/Object;)J + public static final fun readByteArray (Lkotlinx/io/Source;)[B + public static final fun readByteArray (Lkotlinx/io/Source;J)[B public static final fun readDecimalLong (Lkotlinx/io/Source;)J public static final fun readHexadecimalUnsignedLong (Lkotlinx/io/Source;)J public static final fun readIntLe (Lkotlinx/io/Source;)I diff --git a/core/common/src/Source.kt b/core/common/src/Source.kt index 3f4159c61..68c7295ae 100644 --- a/core/common/src/Source.kt +++ b/core/common/src/Source.kt @@ -109,21 +109,6 @@ public expect sealed interface Source : RawSource { */ public fun skip(byteCount: Long) - /** - * Removes all bytes from this source and returns them as a byte array. - */ - public fun readByteArray(): ByteArray - - /** - * Removes [byteCount] bytes from this source and returns them as a byte array. - * - * @param byteCount the number of bytes that should be read from the source. - * - * @throws IllegalArgumentException when byteCount is negative. - * @throws EOFException when the underlying source is exhausted before [byteCount] bytes of data could be read. - */ - public fun readByteArray(byteCount: Long): ByteArray - /** * Removes exactly `sink.length` bytes from this source and copies them into [sink]. * diff --git a/core/common/src/SourceExt.kt b/core/common/src/SourceExt.kt index 9235a04f3..b91b433e1 100644 --- a/core/common/src/SourceExt.kt +++ b/core/common/src/SourceExt.kt @@ -162,4 +162,44 @@ public fun Source.indexOf(b: Byte, fromIndex: Long = 0L, toIndex: Long = Long.MA offset++ } return -1L +} + +/** + * Removes all bytes from this source and returns them as a byte array. + */ +public fun Source.readByteArray(): ByteArray { + return readByteArrayImpl( -1L) +} + +/** + * Removes [byteCount] bytes from this source and returns them as a byte array. + * + * @param byteCount the number of bytes that should be read from the source. + * + * @throws IllegalArgumentException when byteCount is negative. + * @throws EOFException when the underlying source is exhausted before [byteCount] bytes of data could be read. + */ +public fun Source.readByteArray(byteCount: Long): ByteArray { + check(byteCount >= 0) + return readByteArrayImpl(byteCount) +} + +@Suppress("NOTHING_TO_INLINE") +private inline fun Source.readByteArrayImpl(size: Long): ByteArray { + var arraySize = size + if (size == -1L) { + var fetchSize = Int.MAX_VALUE.toLong() + while (buffer.size < Int.MAX_VALUE && request(fetchSize)) { + fetchSize *= 2 + } + if (buffer.size >= Int.MAX_VALUE) { + throw IllegalStateException() + } + arraySize = buffer.size + } else { + require(size) + } + val array = ByteArray(arraySize.toInt()) + buffer.readFully(array) + return array } \ No newline at end of file diff --git a/core/common/src/internal/-Buffer.kt b/core/common/src/internal/-Buffer.kt index 9550d40f7..626544d1e 100644 --- a/core/common/src/internal/-Buffer.kt +++ b/core/common/src/internal/-Buffer.kt @@ -307,17 +307,6 @@ internal inline fun Buffer.commonWrite( return this } -internal inline fun Buffer.commonReadByteArray() = readByteArray(size) - -internal inline fun Buffer.commonReadByteArray(byteCount: Long): ByteArray { - require(byteCount >= 0 && byteCount <= Int.MAX_VALUE) { "byteCount: $byteCount" } - if (size < byteCount) throw EOFException() - - val result = ByteArray(byteCount.toInt()) - readFully(result) - return result -} - internal inline fun Buffer.commonReadFully(sink: ByteArray) { var offset = 0 while (offset < sink.size) { diff --git a/core/common/src/internal/-RealBufferedSource.kt b/core/common/src/internal/-RealBufferedSource.kt index f08fe91de..676e75ccf 100644 --- a/core/common/src/internal/-RealBufferedSource.kt +++ b/core/common/src/internal/-RealBufferedSource.kt @@ -63,16 +63,6 @@ internal inline fun RealSource.commonReadByte(): Byte { return buffer.readByte() } -internal inline fun RealSource.commonReadByteArray(): ByteArray { - buffer.writeAll(source) - return buffer.readByteArray() -} - -internal inline fun RealSource.commonReadByteArray(byteCount: Long): ByteArray { - require(byteCount) - return buffer.readByteArray(byteCount) -} - internal inline fun RealSource.commonReadFully(sink: ByteArray) { try { require(sink.size.toLong()) diff --git a/core/jvm/src/Buffer.kt b/core/jvm/src/Buffer.kt index d80fd24ff..69a128c61 100644 --- a/core/jvm/src/Buffer.kt +++ b/core/jvm/src/Buffer.kt @@ -74,10 +74,6 @@ public actual class Buffer : Source, Sink, Cloneable, ByteChannel { override fun readAll(sink: RawSink): Long = commonReadAll(sink) - override fun readByteArray(): ByteArray = commonReadByteArray() - - override fun readByteArray(byteCount: Long): ByteArray = commonReadByteArray(byteCount) - override fun readFully(sink: ByteArray): Unit = commonReadFully(sink) override fun read(sink: ByteArray, offset: Int, byteCount: Int): Int = diff --git a/core/jvm/src/RealSource.kt b/core/jvm/src/RealSource.kt index b49d4ceba..e14c60d2a 100644 --- a/core/jvm/src/RealSource.kt +++ b/core/jvm/src/RealSource.kt @@ -40,8 +40,6 @@ internal actual class RealSource actual constructor( override fun require(byteCount: Long): Unit = commonRequire(byteCount) override fun request(byteCount: Long): Boolean = commonRequest(byteCount) override fun readByte(): Byte = commonReadByte() - override fun readByteArray(): ByteArray = commonReadByteArray() - override fun readByteArray(byteCount: Long): ByteArray = commonReadByteArray(byteCount) override fun readFully(sink: ByteArray): Unit = commonReadFully(sink) override fun read(sink: ByteArray, offset: Int, byteCount: Int): Int = commonRead(sink, offset, byteCount) diff --git a/core/jvm/src/Source.kt b/core/jvm/src/Source.kt index faf846b7c..86dea1ec9 100644 --- a/core/jvm/src/Source.kt +++ b/core/jvm/src/Source.kt @@ -45,10 +45,6 @@ public actual sealed interface Source : RawSource, ReadableByteChannel { public actual fun skip(byteCount: Long) - public actual fun readByteArray(): ByteArray - - public actual fun readByteArray(byteCount: Long): ByteArray - public actual fun readFully(sink: ByteArray) public actual fun read(sink: ByteArray, offset: Int, byteCount: Int): Int diff --git a/core/native/src/Buffer.kt b/core/native/src/Buffer.kt index 9d27dc8e1..56cd8b16f 100644 --- a/core/native/src/Buffer.kt +++ b/core/native/src/Buffer.kt @@ -66,10 +66,6 @@ public actual class Buffer : Source, Sink { override fun readAll(sink: RawSink): Long = commonReadAll(sink) - override fun readByteArray(): ByteArray = commonReadByteArray() - - override fun readByteArray(byteCount: Long): ByteArray = commonReadByteArray(byteCount) - override fun readFully(sink: ByteArray): Unit = commonReadFully(sink) override fun read(sink: ByteArray, offset: Int, byteCount: Int): Int = diff --git a/core/native/src/RealSource.kt b/core/native/src/RealSource.kt index 29858e7de..9f8cc7b69 100644 --- a/core/native/src/RealSource.kt +++ b/core/native/src/RealSource.kt @@ -33,8 +33,6 @@ internal actual class RealSource actual constructor( override fun require(byteCount: Long): Unit = commonRequire(byteCount) override fun request(byteCount: Long): Boolean = commonRequest(byteCount) override fun readByte(): Byte = commonReadByte() - override fun readByteArray(): ByteArray = commonReadByteArray() - override fun readByteArray(byteCount: Long): ByteArray = commonReadByteArray(byteCount) override fun readFully(sink: ByteArray): Unit = commonReadFully(sink) override fun read(sink: ByteArray, offset: Int, byteCount: Int): Int = commonRead(sink, offset, byteCount) diff --git a/core/native/src/Source.kt b/core/native/src/Source.kt index 1317cbe18..e3d705cdf 100644 --- a/core/native/src/Source.kt +++ b/core/native/src/Source.kt @@ -39,10 +39,6 @@ public actual sealed interface Source : RawSource { public actual fun skip(byteCount: Long) - public actual fun readByteArray(): ByteArray - - public actual fun readByteArray(byteCount: Long): ByteArray - public actual fun readFully(sink: ByteArray) public actual fun read(sink: ByteArray, offset: Int, byteCount: Int): Int From 18c863716ff418c490679964e3801c2995b7b66e Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Wed, 7 Jun 2023 16:29:55 +0200 Subject: [PATCH 28/83] Move readFully([B) to extensions --- core/api/kotlinx-io-core.api | 3 +-- core/common/src/Source.kt | 7 ------- core/common/src/SourceExt.kt | 18 ++++++++++++++++++ core/common/src/internal/-Buffer.kt | 9 --------- core/jvm/src/Buffer.kt | 2 -- core/jvm/src/RealSource.kt | 1 - core/jvm/src/Source.kt | 2 -- core/native/src/Buffer.kt | 2 -- core/native/src/RealSource.kt | 1 - core/native/src/Source.kt | 2 -- 10 files changed, 19 insertions(+), 28 deletions(-) diff --git a/core/api/kotlinx-io-core.api b/core/api/kotlinx-io-core.api index 961f03800..20b27aa10 100644 --- a/core/api/kotlinx-io-core.api +++ b/core/api/kotlinx-io-core.api @@ -28,7 +28,6 @@ public final class kotlinx/io/Buffer : java/lang/Cloneable, java/nio/channels/By public fun readAll (Lkotlinx/io/RawSink;)J public fun readByte ()B public fun readFully (Lkotlinx/io/Buffer;J)V - public fun readFully ([B)V public fun readInt ()I public fun readLong ()J public fun readShort ()S @@ -134,7 +133,6 @@ public abstract interface class kotlinx/io/Source : java/nio/channels/ReadableBy public abstract fun readAll (Lkotlinx/io/RawSink;)J public abstract fun readByte ()B public abstract fun readFully (Lkotlinx/io/Buffer;J)V - public abstract fun readFully ([B)V public abstract fun readInt ()I public abstract fun readLong ()J public abstract fun readShort ()S @@ -153,6 +151,7 @@ public final class kotlinx/io/SourceExtKt { public static final fun readByteArray (Lkotlinx/io/Source;)[B public static final fun readByteArray (Lkotlinx/io/Source;J)[B public static final fun readDecimalLong (Lkotlinx/io/Source;)J + public static final fun readFully (Lkotlinx/io/Source;[B)V public static final fun readHexadecimalUnsignedLong (Lkotlinx/io/Source;)J public static final fun readIntLe (Lkotlinx/io/Source;)I public static final fun readLongLe (Lkotlinx/io/Source;)J diff --git a/core/common/src/Source.kt b/core/common/src/Source.kt index 68c7295ae..855517c7f 100644 --- a/core/common/src/Source.kt +++ b/core/common/src/Source.kt @@ -109,13 +109,6 @@ public expect sealed interface Source : RawSource { */ public fun skip(byteCount: Long) - /** - * Removes exactly `sink.length` bytes from this source and copies them into [sink]. - * - * @throws EOFException when the requested number of bytes cannot be read. - */ - public fun readFully(sink: ByteArray) - /** * Removes up to [byteCount] bytes from this source, copies them into [sink] starting at [offset] and returns the * number of bytes read, or -1 if this source is exhausted. diff --git a/core/common/src/SourceExt.kt b/core/common/src/SourceExt.kt index b91b433e1..8e6e5ce22 100644 --- a/core/common/src/SourceExt.kt +++ b/core/common/src/SourceExt.kt @@ -202,4 +202,22 @@ private inline fun Source.readByteArrayImpl(size: Long): ByteArray { val array = ByteArray(arraySize.toInt()) buffer.readFully(array) return array +} + + +/** + * Removes exactly `sink.length` bytes from this source and copies them into [sink]. + * + * @throws EOFException when the requested number of bytes cannot be read. + */ +public fun Source.readFully(sink: ByteArray) { + var offset = 0 + while (offset < sink.size) { + val bytesRead = read(sink, offset) + // TODO: can we read 0 bytes indefinitely? + if (bytesRead == -1) { + throw EOFException() + } + offset += bytesRead + } } \ No newline at end of file diff --git a/core/common/src/internal/-Buffer.kt b/core/common/src/internal/-Buffer.kt index 626544d1e..416a248f4 100644 --- a/core/common/src/internal/-Buffer.kt +++ b/core/common/src/internal/-Buffer.kt @@ -307,15 +307,6 @@ internal inline fun Buffer.commonWrite( return this } -internal inline fun Buffer.commonReadFully(sink: ByteArray) { - var offset = 0 - while (offset < sink.size) { - val read = read(sink, offset, sink.size - offset) - if (read == -1) throw EOFException() - offset += read - } -} - internal inline fun Buffer.commonRead(sink: ByteArray, offset: Int, byteCount: Int): Int { checkOffsetAndCount(sink.size.toLong(), offset.toLong(), byteCount.toLong()) diff --git a/core/jvm/src/Buffer.kt b/core/jvm/src/Buffer.kt index 69a128c61..df218b8f0 100644 --- a/core/jvm/src/Buffer.kt +++ b/core/jvm/src/Buffer.kt @@ -74,8 +74,6 @@ public actual class Buffer : Source, Sink, Cloneable, ByteChannel { override fun readAll(sink: RawSink): Long = commonReadAll(sink) - override fun readFully(sink: ByteArray): Unit = commonReadFully(sink) - override fun read(sink: ByteArray, offset: Int, byteCount: Int): Int = commonRead(sink, offset, byteCount) diff --git a/core/jvm/src/RealSource.kt b/core/jvm/src/RealSource.kt index e14c60d2a..0b9823cb3 100644 --- a/core/jvm/src/RealSource.kt +++ b/core/jvm/src/RealSource.kt @@ -40,7 +40,6 @@ internal actual class RealSource actual constructor( override fun require(byteCount: Long): Unit = commonRequire(byteCount) override fun request(byteCount: Long): Boolean = commonRequest(byteCount) override fun readByte(): Byte = commonReadByte() - override fun readFully(sink: ByteArray): Unit = commonReadFully(sink) override fun read(sink: ByteArray, offset: Int, byteCount: Int): Int = commonRead(sink, offset, byteCount) diff --git a/core/jvm/src/Source.kt b/core/jvm/src/Source.kt index 86dea1ec9..c1e6fa9dc 100644 --- a/core/jvm/src/Source.kt +++ b/core/jvm/src/Source.kt @@ -45,8 +45,6 @@ public actual sealed interface Source : RawSource, ReadableByteChannel { public actual fun skip(byteCount: Long) - public actual fun readFully(sink: ByteArray) - public actual fun read(sink: ByteArray, offset: Int, byteCount: Int): Int public actual fun readFully(sink: Buffer, byteCount: Long) diff --git a/core/native/src/Buffer.kt b/core/native/src/Buffer.kt index 56cd8b16f..fea8f9b76 100644 --- a/core/native/src/Buffer.kt +++ b/core/native/src/Buffer.kt @@ -66,8 +66,6 @@ public actual class Buffer : Source, Sink { override fun readAll(sink: RawSink): Long = commonReadAll(sink) - override fun readFully(sink: ByteArray): Unit = commonReadFully(sink) - override fun read(sink: ByteArray, offset: Int, byteCount: Int): Int = commonRead(sink, offset, byteCount) diff --git a/core/native/src/RealSource.kt b/core/native/src/RealSource.kt index 9f8cc7b69..2746b9e2f 100644 --- a/core/native/src/RealSource.kt +++ b/core/native/src/RealSource.kt @@ -33,7 +33,6 @@ internal actual class RealSource actual constructor( override fun require(byteCount: Long): Unit = commonRequire(byteCount) override fun request(byteCount: Long): Boolean = commonRequest(byteCount) override fun readByte(): Byte = commonReadByte() - override fun readFully(sink: ByteArray): Unit = commonReadFully(sink) override fun read(sink: ByteArray, offset: Int, byteCount: Int): Int = commonRead(sink, offset, byteCount) diff --git a/core/native/src/Source.kt b/core/native/src/Source.kt index e3d705cdf..2dbf1bb7d 100644 --- a/core/native/src/Source.kt +++ b/core/native/src/Source.kt @@ -39,8 +39,6 @@ public actual sealed interface Source : RawSource { public actual fun skip(byteCount: Long) - public actual fun readFully(sink: ByteArray) - public actual fun read(sink: ByteArray, offset: Int, byteCount: Int): Int public actual fun readFully(sink: Buffer, byteCount: Long) From ee0dd500fb83769fecebf19576ce871e64454562 Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Thu, 8 Jun 2023 15:01:31 +0200 Subject: [PATCH 29/83] Remove 'cancel' method --- core/api/kotlinx-io-core.api | 3 --- core/common/src/Buffer.kt | 5 ---- core/common/src/Core.kt | 1 - core/common/src/PeekSource.kt | 4 ---- core/common/src/RawSink.kt | 9 ------- core/common/src/RawSource.kt | 8 ------- core/common/src/internal/-RealBufferedSink.kt | 2 -- .../src/internal/-RealBufferedSource.kt | 2 -- core/common/test/MockSink.kt | 4 ---- core/jvm/src/Buffer.kt | 2 -- core/jvm/src/JvmCore.kt | 24 ++----------------- core/jvm/src/RawSink.kt | 2 -- core/jvm/src/RealSink.kt | 5 ---- core/jvm/src/RealSource.kt | 4 ---- core/native/src/Buffer.kt | 2 -- core/native/src/RawSink.kt | 2 -- core/native/src/RealSink.kt | 5 ---- core/native/src/RealSource.kt | 5 ---- core/native/src/files/PathsNative.kt | 8 ------- 19 files changed, 2 insertions(+), 95 deletions(-) diff --git a/core/api/kotlinx-io-core.api b/core/api/kotlinx-io-core.api index 20b27aa10..c3373b3f9 100644 --- a/core/api/kotlinx-io-core.api +++ b/core/api/kotlinx-io-core.api @@ -1,6 +1,5 @@ public final class kotlinx/io/Buffer : java/lang/Cloneable, java/nio/channels/ByteChannel, kotlinx/io/Sink, kotlinx/io/Source { public fun ()V - public fun cancel ()V public final fun clear ()V public synthetic fun clone ()Ljava/lang/Object; public fun clone ()Lkotlinx/io/Buffer; @@ -81,14 +80,12 @@ public final class kotlinx/io/JvmCoreKt { } public abstract interface class kotlinx/io/RawSink : java/io/Closeable, java/io/Flushable { - public abstract fun cancel ()V public abstract fun close ()V public abstract fun flush ()V public abstract fun write (Lkotlinx/io/Buffer;J)V } public abstract interface class kotlinx/io/RawSource : java/io/Closeable { - public abstract fun cancel ()V public abstract fun close ()V public abstract fun read (Lkotlinx/io/Buffer;J)J } diff --git a/core/common/src/Buffer.kt b/core/common/src/Buffer.kt index fe9eb5ecb..2569a9805 100644 --- a/core/common/src/Buffer.kt +++ b/core/common/src/Buffer.kt @@ -140,9 +140,4 @@ public expect class Buffer() : Source, Sink { * This method does not affect the buffer. */ override fun close() - - /** - * This method does not affect the buffer. - */ - override fun cancel() } diff --git a/core/common/src/Core.kt b/core/common/src/Core.kt index 610a3cad8..3a9bd190e 100644 --- a/core/common/src/Core.kt +++ b/core/common/src/Core.kt @@ -41,7 +41,6 @@ public fun blackholeSink(): RawSink = BlackholeSink() private class BlackholeSink : RawSink { override fun write(source: Buffer, byteCount: Long) = source.skip(byteCount) override fun flush() {} - override fun cancel() {} override fun close() {} } diff --git a/core/common/src/PeekSource.kt b/core/common/src/PeekSource.kt index 68a52ec41..ca4fa53a5 100644 --- a/core/common/src/PeekSource.kt +++ b/core/common/src/PeekSource.kt @@ -68,10 +68,6 @@ internal class PeekSource( return toCopy } - override fun cancel() { - return upstream.cancel() - } - override fun close() { closed = true } diff --git a/core/common/src/RawSink.kt b/core/common/src/RawSink.kt index d0a8fb18e..70cc7d379 100644 --- a/core/common/src/RawSink.kt +++ b/core/common/src/RawSink.kt @@ -49,15 +49,6 @@ public expect interface RawSink : Closeable { */ public fun flush() - /** - * Asynchronously cancel this sink. Any [write] or [flush] in flight should immediately fail - * with an [IOException], and any future writes and flushes should also immediately fail with an - * [IOException]. - * - * Note that it is always necessary to call [close] on a sink, even if it has been canceled. - */ - public fun cancel() - // TODO: what kind of error it is to write into a closed sink? /** * Pushes all buffered bytes to their final destination and releases the resources held by this diff --git a/core/common/src/RawSource.kt b/core/common/src/RawSource.kt index 85764617d..cebfb0574 100644 --- a/core/common/src/RawSource.kt +++ b/core/common/src/RawSource.kt @@ -44,14 +44,6 @@ public interface RawSource : Closeable { */ public fun read(sink: Buffer, byteCount: Long): Long - /** - * Asynchronously cancel this source. Any [read] in flight should immediately fail with an - * [IOException], and any future read should also immediately fail with an [IOException]. - * - * Note that it is always necessary to call [close] on a source, even if it has been canceled. - */ - public fun cancel() - /** * Closes this source and releases the resources held by this source. It is an error to read a * closed source. It is safe to close a source more than once. diff --git a/core/common/src/internal/-RealBufferedSink.kt b/core/common/src/internal/-RealBufferedSink.kt index fa1f14c9b..01b9636d3 100644 --- a/core/common/src/internal/-RealBufferedSink.kt +++ b/core/common/src/internal/-RealBufferedSink.kt @@ -138,6 +138,4 @@ internal inline fun RealSink.commonClose() { if (thrown != null) throw thrown } -internal inline fun RealSink.commonCancel() = sink.cancel() - internal inline fun RealSink.commonToString() = "buffer($sink)" diff --git a/core/common/src/internal/-RealBufferedSource.kt b/core/common/src/internal/-RealBufferedSource.kt index 676e75ccf..61c09391d 100644 --- a/core/common/src/internal/-RealBufferedSource.kt +++ b/core/common/src/internal/-RealBufferedSource.kt @@ -159,6 +159,4 @@ internal inline fun RealSource.commonClose() { buffer.clear() } -internal inline fun RealSource.commonCancel() = source.cancel() - internal inline fun RealSource.commonToString() = "buffer($source)" diff --git a/core/common/test/MockSink.kt b/core/common/test/MockSink.kt index b2bed268c..d6aa45323 100644 --- a/core/common/test/MockSink.kt +++ b/core/common/test/MockSink.kt @@ -56,10 +56,6 @@ class MockSink : RawSink { throwIfScheduled() } - override fun cancel() { - log.add("cancel()") - } - override fun close() { log.add("close()") throwIfScheduled() diff --git a/core/jvm/src/Buffer.kt b/core/jvm/src/Buffer.kt index df218b8f0..5b4c5e446 100644 --- a/core/jvm/src/Buffer.kt +++ b/core/jvm/src/Buffer.kt @@ -147,8 +147,6 @@ public actual class Buffer : Source, Sink, Cloneable, ByteChannel { actual override fun close(): Unit = Unit - actual override fun cancel(): Unit = Unit - override fun equals(other: Any?): Boolean = commonEquals(other) override fun hashCode(): Int = commonHashCode() diff --git a/core/jvm/src/JvmCore.kt b/core/jvm/src/JvmCore.kt index 0ab31cb79..f310c5204 100644 --- a/core/jvm/src/JvmCore.kt +++ b/core/jvm/src/JvmCore.kt @@ -67,10 +67,6 @@ private open class OutputStreamSink( override fun close() = out.close() - override fun cancel() { - // Not cancelable. - } - override fun toString() = "sink($out)" } @@ -112,10 +108,6 @@ private open class InputStreamSource( override fun close() = input.close() - override fun cancel() { - // Not cancelable. - } - override fun toString() = "source($input)" } @@ -126,13 +118,7 @@ private open class InputStreamSource( * * Use [RawSink.buffer] to create a buffered sink from it. */ -public fun Socket.sink(): RawSink { - return object : OutputStreamSink(getOutputStream()) { - override fun cancel() { - this@sink.close() - } - } -} +public fun Socket.sink(): RawSink = OutputStreamSink(getOutputStream()) /** * Returns [RawSource] that reads from a socket. Prefer this over [source] @@ -141,13 +127,7 @@ public fun Socket.sink(): RawSink { * * Use [RawSource.buffer] to create a buffered source from it. */ -public fun Socket.source(): RawSource { - return object : InputStreamSource(getInputStream()) { - override fun cancel() { - this@source.close() - } - } -} +public fun Socket.source(): RawSource = InputStreamSource(getInputStream()) /** * Returns [RawSink] that writes to a file. diff --git a/core/jvm/src/RawSink.kt b/core/jvm/src/RawSink.kt index b73746b88..8cc18a2b9 100644 --- a/core/jvm/src/RawSink.kt +++ b/core/jvm/src/RawSink.kt @@ -29,7 +29,5 @@ public actual interface RawSink : Closeable, Flushable { actual override fun flush() - public actual fun cancel() - actual override fun close() } diff --git a/core/jvm/src/RealSink.kt b/core/jvm/src/RealSink.kt index be1dd5160..ace7fbcd0 100644 --- a/core/jvm/src/RealSink.kt +++ b/core/jvm/src/RealSink.kt @@ -60,12 +60,7 @@ internal actual class RealSink actual constructor( override fun isOpen() = !closed - override fun close() = commonClose() - override fun cancel() { - commonCancel() - } - override fun toString() = commonToString() } diff --git a/core/jvm/src/RealSource.kt b/core/jvm/src/RealSource.kt index 0b9823cb3..f852acf89 100644 --- a/core/jvm/src/RealSource.kt +++ b/core/jvm/src/RealSource.kt @@ -66,9 +66,5 @@ internal actual class RealSource actual constructor( override fun close(): Unit = commonClose() - override fun cancel() { - commonCancel() - } - override fun toString(): String = commonToString() } diff --git a/core/native/src/Buffer.kt b/core/native/src/Buffer.kt index fea8f9b76..9b4baf4e7 100644 --- a/core/native/src/Buffer.kt +++ b/core/native/src/Buffer.kt @@ -100,8 +100,6 @@ public actual class Buffer : Source, Sink { actual override fun close(): Unit = Unit - actual override fun cancel(): Unit = Unit - override fun equals(other: Any?): Boolean = commonEquals(other) override fun hashCode(): Int = commonHashCode() diff --git a/core/native/src/RawSink.kt b/core/native/src/RawSink.kt index 337b9aef9..d4ed38c5c 100644 --- a/core/native/src/RawSink.kt +++ b/core/native/src/RawSink.kt @@ -25,7 +25,5 @@ public actual interface RawSink : Closeable { public actual fun flush() - public actual fun cancel() - actual override fun close() } diff --git a/core/native/src/RealSink.kt b/core/native/src/RealSink.kt index b90b2121b..da69f4182 100644 --- a/core/native/src/RealSink.kt +++ b/core/native/src/RealSink.kt @@ -44,10 +44,5 @@ internal actual class RealSink actual constructor( override fun emit() = commonEmit() override fun flush() = commonFlush() override fun close() = commonClose() - - override fun cancel() { - commonCancel() - } - override fun toString() = commonToString() } diff --git a/core/native/src/RealSource.kt b/core/native/src/RealSource.kt index 2746b9e2f..5544a6e71 100644 --- a/core/native/src/RealSource.kt +++ b/core/native/src/RealSource.kt @@ -45,10 +45,5 @@ internal actual class RealSource actual constructor( override fun peek(): Source = commonPeek() override fun close(): Unit = commonClose() - - override fun cancel() { - commonCancel() - } - override fun toString(): String = commonToString() } diff --git a/core/native/src/files/PathsNative.kt b/core/native/src/files/PathsNative.kt index 80b58c7ad..e2c93b8f2 100644 --- a/core/native/src/files/PathsNative.kt +++ b/core/native/src/files/PathsNative.kt @@ -56,10 +56,6 @@ internal class FileSource( } } - override fun cancel() { - // Not cancelable. - } - override fun close() { if (closed) return closed = true @@ -110,10 +106,6 @@ internal class FileSink( } } - override fun cancel() { - // Not cancelable. - } - override fun close() { if (closed) return closed = true From fadd590a4cac28f03017f6144bfb6cb20836da8d Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Fri, 9 Jun 2023 11:11:19 +0200 Subject: [PATCH 30/83] Accept Byte and Short in corresponding write methods, support unsigned types --- benchmarks/src/commonMain/kotlin/BufferOps.kt | 8 +- core/api/kotlinx-io-core.api | 28 +++- core/common/src/Buffer.kt | 4 +- core/common/src/Sink.kt | 4 +- core/common/src/SinkExt.kt | 79 +++++++++- core/common/src/SourceExt.kt | 61 +++++++- core/common/src/internal/-Buffer.kt | 16 +- core/common/src/internal/-RealBufferedSink.kt | 4 +- core/common/test/AbstractSinkTest.kt | 49 +++++- core/common/test/AbstractSourceTest.kt | 146 +++++++++++++++++- core/common/test/CommonRealSinkTest.kt | 12 +- core/common/test/Utf8KotlinTest.kt | 4 +- core/jvm/src/Buffer.kt | 4 +- core/jvm/src/RealSink.kt | 4 +- core/jvm/src/Sink.kt | 8 +- core/native/src/Buffer.kt | 4 +- core/native/src/RealSink.kt | 4 +- core/native/src/Sink.kt | 4 +- 18 files changed, 373 insertions(+), 70 deletions(-) diff --git a/benchmarks/src/commonMain/kotlin/BufferOps.kt b/benchmarks/src/commonMain/kotlin/BufferOps.kt index a08ae9186..eb0b1e9df 100644 --- a/benchmarks/src/commonMain/kotlin/BufferOps.kt +++ b/benchmarks/src/commonMain/kotlin/BufferOps.kt @@ -100,7 +100,7 @@ open class DecimalLongBenchmark: BufferRWBenchmarkBase() { val tmpBuffer = Buffer() while (tmpBuffer.size < minGap) { // use space as a delimiter between consecutive decimal values - tmpBuffer.writeDecimalLong(value).writeByte(' '.code) + tmpBuffer.writeDecimalLong(value).writeByte(' '.code.toByte()) } return tmpBuffer.readByteArray() } @@ -108,7 +108,7 @@ open class DecimalLongBenchmark: BufferRWBenchmarkBase() { @Benchmark fun benchmark(): Long { // use space as a delimiter between consecutive decimal values - buffer.writeDecimalLong(value).writeByte(' '.code) + buffer.writeDecimalLong(value).writeByte(' '.code.toByte()) val l = buffer.readDecimalLong() buffer.readByte() // consume the delimiter return l @@ -122,14 +122,14 @@ open class HexadecimalLongBenchmark: BufferRWBenchmarkBase() { override fun padding(): ByteArray { val tmpBuffer = Buffer() while (tmpBuffer.size < minGap) { - tmpBuffer.writeHexadecimalUnsignedLong(value).writeByte(' '.code) + tmpBuffer.writeHexadecimalUnsignedLong(value).writeByte(' '.code.toByte()) } return tmpBuffer.readByteArray() } @Benchmark fun benchmark(): Long { - buffer.writeHexadecimalUnsignedLong(value).writeByte(' '.code) + buffer.writeHexadecimalUnsignedLong(value).writeByte(' '.code.toByte()) val l = buffer.readHexadecimalUnsignedLong() buffer.readByte() return l diff --git a/core/api/kotlinx-io-core.api b/core/api/kotlinx-io-core.api index c3373b3f9..069af801d 100644 --- a/core/api/kotlinx-io-core.api +++ b/core/api/kotlinx-io-core.api @@ -41,14 +41,14 @@ public final class kotlinx/io/Buffer : java/lang/Cloneable, java/nio/channels/By public fun write ([BII)Lkotlinx/io/Buffer; public synthetic fun write ([BII)Lkotlinx/io/Sink; public fun writeAll (Lkotlinx/io/RawSource;)J - public fun writeByte (I)Lkotlinx/io/Buffer; - public synthetic fun writeByte (I)Lkotlinx/io/Sink; + public fun writeByte (B)Lkotlinx/io/Buffer; + public synthetic fun writeByte (B)Lkotlinx/io/Sink; public fun writeInt (I)Lkotlinx/io/Buffer; public synthetic fun writeInt (I)Lkotlinx/io/Sink; public fun writeLong (J)Lkotlinx/io/Buffer; public synthetic fun writeLong (J)Lkotlinx/io/Sink; - public fun writeShort (I)Lkotlinx/io/Buffer; - public synthetic fun writeShort (I)Lkotlinx/io/Sink; + public fun writeShort (S)Lkotlinx/io/Buffer; + public synthetic fun writeShort (S)Lkotlinx/io/Sink; } public final class kotlinx/io/BufferKt { @@ -98,10 +98,10 @@ public abstract interface class kotlinx/io/Sink : java/nio/channels/WritableByte public abstract fun write (Lkotlinx/io/RawSource;J)Lkotlinx/io/Sink; public abstract fun write ([BII)Lkotlinx/io/Sink; public abstract fun writeAll (Lkotlinx/io/RawSource;)J - public abstract fun writeByte (I)Lkotlinx/io/Sink; + public abstract fun writeByte (B)Lkotlinx/io/Sink; public abstract fun writeInt (I)Lkotlinx/io/Sink; public abstract fun writeLong (J)Lkotlinx/io/Sink; - public abstract fun writeShort (I)Lkotlinx/io/Sink; + public abstract fun writeShort (S)Lkotlinx/io/Sink; } public final class kotlinx/io/Sink$DefaultImpls { @@ -109,11 +109,18 @@ public final class kotlinx/io/Sink$DefaultImpls { } public final class kotlinx/io/SinkExtKt { + public static final fun writeByte-EK-6454 (Lkotlinx/io/Sink;B)Lkotlinx/io/Sink; public static final fun writeDecimalLong (Lkotlinx/io/Sink;J)Lkotlinx/io/Sink; public static final fun writeHexadecimalUnsignedLong (Lkotlinx/io/Sink;J)Lkotlinx/io/Sink; + public static final fun writeInt-Qn1smSk (Lkotlinx/io/Sink;I)Lkotlinx/io/Sink; public static final fun writeIntLe (Lkotlinx/io/Sink;I)Lkotlinx/io/Sink; + public static final fun writeIntLe-Qn1smSk (Lkotlinx/io/Sink;I)Lkotlinx/io/Sink; + public static final fun writeLong-2TYgG_w (Lkotlinx/io/Sink;J)Lkotlinx/io/Sink; public static final fun writeLongLe (Lkotlinx/io/Sink;J)Lkotlinx/io/Sink; - public static final fun writeShortLe (Lkotlinx/io/Sink;I)Lkotlinx/io/Sink; + public static final fun writeLongLe-2TYgG_w (Lkotlinx/io/Sink;J)Lkotlinx/io/Sink; + public static final fun writeShort-i8woANY (Lkotlinx/io/Sink;S)Lkotlinx/io/Sink; + public static final fun writeShortLe (Lkotlinx/io/Sink;S)Lkotlinx/io/Sink; + public static final fun writeShortLe-i8woANY (Lkotlinx/io/Sink;S)Lkotlinx/io/Sink; } public final class kotlinx/io/SinkKt { @@ -153,6 +160,13 @@ public final class kotlinx/io/SourceExtKt { public static final fun readIntLe (Lkotlinx/io/Source;)I public static final fun readLongLe (Lkotlinx/io/Source;)J public static final fun readShortLe (Lkotlinx/io/Source;)S + public static final fun readUByte (Lkotlinx/io/Source;)B + public static final fun readUInt (Lkotlinx/io/Source;)I + public static final fun readUIntLe (Lkotlinx/io/Source;)I + public static final fun readULong (Lkotlinx/io/Source;)J + public static final fun readULongLe (Lkotlinx/io/Source;)J + public static final fun readUShort (Lkotlinx/io/Source;)S + public static final fun readUShortLe (Lkotlinx/io/Source;)S } public final class kotlinx/io/SourceKt { diff --git a/core/common/src/Buffer.kt b/core/common/src/Buffer.kt index 2569a9805..81a8be746 100644 --- a/core/common/src/Buffer.kt +++ b/core/common/src/Buffer.kt @@ -123,9 +123,9 @@ public expect class Buffer() : Source, Sink { override fun write(source: RawSource, byteCount: Long): Buffer - override fun writeByte(byte: Int): Buffer + override fun writeByte(byte: Byte): Buffer - override fun writeShort(short: Int): Buffer + override fun writeShort(short: Short): Buffer override fun writeInt(int: Int): Buffer diff --git a/core/common/src/Sink.kt b/core/common/src/Sink.kt index 75ca4ed03..4d7ad479a 100644 --- a/core/common/src/Sink.kt +++ b/core/common/src/Sink.kt @@ -83,14 +83,14 @@ public expect sealed interface Sink : RawSink { * * @param byte the byte to be written. */ - public fun writeByte(byte: Int): Sink + public fun writeByte(byte: Byte): Sink /** * Writes two bytes containing [short], in the big-endian order, to this sink. * * @param short the short integer to be written. */ - public fun writeShort(short: Int): Sink + public fun writeShort(short: Short): Sink /** * Writes four bytes containing [int], in the big-endian order, to this sink. diff --git a/core/common/src/SinkExt.kt b/core/common/src/SinkExt.kt index c76251ad1..a08688c74 100644 --- a/core/common/src/SinkExt.kt +++ b/core/common/src/SinkExt.kt @@ -10,8 +10,8 @@ package kotlinx.io * * @param short the short integer to be written. */ -public fun T.writeShortLe(short: Int): T { - this.writeShort(short.toShort().reverseBytes().toInt()) +public fun T.writeShortLe(short: Short): T { + this.writeShort(short.reverseBytes()) return this } @@ -19,7 +19,6 @@ public fun T.writeShortLe(short: Int): T { * Writes four bytes containing [int], in the little-endian order, to this sink. * * @param int the integer to be written. - * */ public fun T.writeIntLe(int: Int): T { this.writeInt(int.reverseBytes()) @@ -58,10 +57,80 @@ public fun T.writeDecimalLong(long: Long): T { */ public fun T.writeHexadecimalUnsignedLong(long: Long): T { if (long == 0L) { - writeByte('0'.code) + writeByte('0'.code.toByte()) } else { // TODO: optimize writeUtf8(long.toHexString()) } return this -} \ No newline at end of file +} + +/** + * Writes am unsigned byte to this sink. + * + * @param byte the byte to be written. + */ +public fun T.writeByte(byte: UByte): T { + writeByte(byte.toByte()) + return this +} + +/** + * Writes two bytes containing [short], in the big-endian order, to this sink. + * + * @param short the unsigned short integer to be written. + */ +public fun T.writeShort(short: UShort): T { + writeShort(short.toShort()) + return this +} + +/** + * Writes four bytes containing [int], in the big-endian order, to this sink. + * + * @param int the unsigned integer to be written. + */ +public fun T.writeInt(int: UInt): T { + writeInt(int.toInt()) + return this +} + +/** + * Writes eight bytes containing [long], in the big-endian order, to this sink. + * + * @param long the unsigned long integer to be written. + */ +public fun T.writeLong(long: ULong): T { + writeLong(long.toLong()) + return this +} + +/** + * Writes two bytes containing [short], in the little-endian order, to this sink. + * + * @param short the unsigned short integer to be written. + */ +public fun T.writeShortLe(short: UShort): T { + writeShortLe(short.toShort()) + return this +} + +/** + * Writes four bytes containing [int], in the little-endian order, to this sink. + * + * @param int the unsugned integer to be written. + */ +public fun T.writeIntLe(int: UInt): T { + writeIntLe(int.toInt()) + return this +} + +/** + * Writes eight bytes containing [long], in the little-endian order, to this sink. + * + * @param long the unsigned long integer to be written. + */ +public fun T.writeLongLe(long: ULong): T { + writeLongLe(long.toLong()) + return this +} diff --git a/core/common/src/SourceExt.kt b/core/common/src/SourceExt.kt index 8e6e5ce22..16c2d9dd0 100644 --- a/core/common/src/SourceExt.kt +++ b/core/common/src/SourceExt.kt @@ -75,7 +75,7 @@ public fun Source.readDecimalLong(): Long { // Detect when the digit would cause an overflow. if (value < OVERFLOW_ZONE || value == OVERFLOW_ZONE && digit < overflowDigit) { - val buffer = Buffer().writeDecimalLong(value).writeByte(b.toInt()) + val buffer = Buffer().writeDecimalLong(value).writeByte(b) if (!negative) buffer.readByte() // Skip negative sign. throw NumberFormatException("Number too large: ${buffer.readUtf8()}") } @@ -124,7 +124,7 @@ public fun Source.readHexadecimalUnsignedLong(): Long { else -> break } if (result and -0x1000000000000000L != 0L) { - val buffer = Buffer().writeHexadecimalUnsignedLong(result).writeByte(b.toInt()) + val buffer = Buffer().writeHexadecimalUnsignedLong(result).writeByte(b) throw NumberFormatException("Number too large: " + buffer.readUtf8()) } readByte() // consume byte @@ -220,4 +220,59 @@ public fun Source.readFully(sink: ByteArray) { } offset += bytesRead } -} \ No newline at end of file +} + +/** + * Removes an unsigned byte from this source and returns it. + * + * @throws EOFException when there are no more bytes to read. + */ +public fun Source.readUByte(): UByte = readByte().toUByte() + +/** + * Removes two bytes from this source and returns an unsigned short integer composed of it + * according to the big-endian order. + * + * @throws EOFException when there are not enough data to read an unsigned short value. + */ +public fun Source.readUShort(): UShort = readShort().toUShort() + +/** + * Removes four bytes from this source and returns an unsigned integer composed of it + * according to the big-endian order. + * + * @throws EOFException when there are not enough data to read an unsigned int value. + */ +public fun Source.readUInt(): UInt = readInt().toUInt() + +/** + * Removes eight bytes from this source and returns an unsigned long integer composed of it + * according to the big-endian order. + * + * @throws EOFException when there are not enough data to read an unsigned long value. + */ +public fun Source.readULong(): ULong = readLong().toULong() + +/** + * Removes two bytes from this source and returns an unsigned short integer composed of it + * according to the little-endian order. + * + * @throws EOFException when there are not enough data to read an unsigned short value. + */ +public fun Source.readUShortLe(): UShort = readShortLe().toUShort() + +/** + * Removes four bytes from this source and returns an unsigned integer composed of it + * according to the little-endian order. + * + * @throws EOFException when there are not enough data to read an unsigned int value. + */ +public fun Source.readUIntLe(): UInt = readIntLe().toUInt() + +/** + * Removes eight bytes from this source and returns an unsigned long integer composed of it + * according to the little-endian order. + * + * @throws EOFException when there are not enough data to read an unsigned long value. + */ +public fun Source.readULongLe(): ULong = readLongLe().toULong() diff --git a/core/common/src/internal/-Buffer.kt b/core/common/src/internal/-Buffer.kt index 416a248f4..8ce3d22bc 100644 --- a/core/common/src/internal/-Buffer.kt +++ b/core/common/src/internal/-Buffer.kt @@ -510,7 +510,7 @@ internal inline fun Buffer.commonWriteUtf8(string: String, beginIndex: Int, endI // character. val low = (if (i + 1 < endIndex) string[i + 1].code else 0) if (c > 0xdbff || low !in 0xdc00..0xdfff) { - writeByte('?'.code) + writeByte('?'.code.toByte()) i++ } else { // UTF-16 high surrogate: 110110xxxxxxxxxx (10 bits) @@ -541,7 +541,7 @@ internal inline fun Buffer.commonWriteUtf8CodePoint(codePoint: Int): Buffer { when { codePoint < 0x80 -> { // Emit a 7-bit code point with 1 byte. - writeByte(codePoint) + writeByte(codePoint.toByte()) } codePoint < 0x800 -> { // Emit a 11-bit code point with 2 bytes. @@ -555,7 +555,7 @@ internal inline fun Buffer.commonWriteUtf8CodePoint(codePoint: Int): Buffer { } codePoint in 0xd800..0xdfff -> { // Emit a replacement character for a partial surrogate. - writeByte('?'.code) + writeByte('?'.code.toByte()) } codePoint < 0x10000 -> { // Emit a 16-bit code point with 3 bytes. @@ -608,19 +608,19 @@ internal inline fun Buffer.commonWrite(source: RawSource, byteCount: Long): Buff return this } -internal inline fun Buffer.commonWriteByte(b: Int): Buffer { +internal inline fun Buffer.commonWriteByte(b: Byte): Buffer { val tail = writableSegment(1) tail.data[tail.limit++] = b.toByte() size += 1L return this } -internal inline fun Buffer.commonWriteShort(s: Int): Buffer { +internal inline fun Buffer.commonWriteShort(s: Short): Buffer { val tail = writableSegment(2) val data = tail.data var limit = tail.limit - data[limit++] = (s ushr 8 and 0xff).toByte() - data[limit++] = (s and 0xff).toByte() // ktlint-disable no-multi-spaces + data[limit++] = (s.toInt() ushr 8 and 0xff).toByte() + data[limit++] = (s.toInt() and 0xff).toByte() // ktlint-disable no-multi-spaces tail.limit = limit size += 2L return this @@ -889,4 +889,4 @@ private fun codePointIndexToCharIndex(s: ByteArray, codePointCount: Int): Int { charCount += if (c < 0x10000) 1 else 2 } return charCount -} \ No newline at end of file +} diff --git a/core/common/src/internal/-RealBufferedSink.kt b/core/common/src/internal/-RealBufferedSink.kt index 01b9636d3..2cbdb7f13 100644 --- a/core/common/src/internal/-RealBufferedSink.kt +++ b/core/common/src/internal/-RealBufferedSink.kt @@ -67,13 +67,13 @@ internal inline fun RealSink.commonWrite(source: RawSource, byteCount: Long): Si return this } -internal inline fun RealSink.commonWriteByte(b: Int): Sink { +internal inline fun RealSink.commonWriteByte(b: Byte): Sink { check(!closed) { "closed" } buffer.writeByte(b) return emitCompleteSegments() } -internal inline fun RealSink.commonWriteShort(s: Int): Sink { +internal inline fun RealSink.commonWriteShort(s: Short): Sink { check(!closed) { "closed" } buffer.writeShort(s) return emitCompleteSegments() diff --git a/core/common/test/AbstractSinkTest.kt b/core/common/test/AbstractSinkTest.kt index 68e262af8..aef71b805 100644 --- a/core/common/test/AbstractSinkTest.kt +++ b/core/common/test/AbstractSinkTest.kt @@ -67,14 +67,14 @@ abstract class AbstractSinkTest internal constructor( } @Test fun writeByte() { - sink.writeByte(0xba) + sink.writeByte(0xba.toByte()) sink.flush() assertEquals("[hex=ba]", data.toString()) } @Test fun writeBytes() { - sink.writeByte(0xab) - sink.writeByte(0xcd) + sink.writeByte(0xab.toByte()) + sink.writeByte(0xcd.toByte()) sink.flush() assertEquals("[hex=abcd]", data.toString()) } @@ -90,20 +90,20 @@ abstract class AbstractSinkTest internal constructor( } @Test fun writeShort() { - sink.writeShort(0xab01) + sink.writeShort(0xab01.toShort()) sink.flush() assertEquals("[hex=ab01]", data.toString()) } @Test fun writeShorts() { - sink.writeShort(0xabcd) + sink.writeShort(0xabcd.toShort()) sink.writeShort(0x4321) sink.flush() assertEquals("[hex=abcd4321]", data.toString()) } @Test fun writeShortLe() { - sink.writeShortLe(0xcdab) + sink.writeShortLe(0xcdab.toShort()) sink.writeShortLe(0x2143) sink.flush() assertEquals("[hex=abcd4321]", data.toString()) @@ -254,7 +254,7 @@ abstract class AbstractSinkTest internal constructor( } @Test fun closeEmitsBufferedBytes() { - sink.writeByte('a'.code) + sink.writeByte('a'.code.toByte()) sink.close() assertEquals('a', data.readByte().toInt().toChar()) } @@ -347,4 +347,39 @@ abstract class AbstractSinkTest internal constructor( assertFailsWith { sink.writeUtf8("hello", 0, 6) } assertFailsWith { sink.writeUtf8("hello", 6) } } + + @Test fun writeUByte() { + sink.writeByte(0xffu).flush() + assertEquals(-1, data.readByte()) + } + + @Test fun writeUShort() { + sink.writeShort(0xffffu).flush() + assertEquals(-1, data.readShort()) + } + + @Test fun writeUShortLe() { + sink.writeShortLe(0x1234u).flush() + assertEquals("[hex=3412]", data.toString()) + } + + @Test fun writeUInt() { + sink.writeInt(0xffffffffu).flush() + assertEquals(-1, data.readInt()) + } + + @Test fun writeUIntLe() { + sink.writeIntLe(0x12345678u).flush() + assertEquals("[hex=78563412]", data.toString()) + } + + @Test fun writeULong() { + sink.writeLong(0xffffffffffffffffu).flush() + assertEquals(-1, data.readLong()) + } + + @Test fun writeULongLe() { + sink.writeLongLe(0x1234567890abcdefu).flush() + assertEquals("[hex=efcdab9078563412]", data.toString()) + } } diff --git a/core/common/test/AbstractSourceTest.kt b/core/common/test/AbstractSourceTest.kt index c42bc74c2..dcc2f745a 100644 --- a/core/common/test/AbstractSourceTest.kt +++ b/core/common/test/AbstractSourceTest.kt @@ -86,7 +86,7 @@ abstract class AbstractBufferedSourceTest internal constructor( } @Test fun readShortTooShortThrows() { - sink.writeShort(Short.MAX_VALUE.toInt()) + sink.writeShort(Short.MAX_VALUE) sink.emit() source.readByte() assertFailsWith { @@ -95,7 +95,7 @@ abstract class AbstractBufferedSourceTest internal constructor( } @Test fun readShortLeTooShortThrows() { - sink.writeShortLe(Short.MAX_VALUE.toInt()) + sink.writeShortLe(Short.MAX_VALUE) sink.emit() source.readByte() assertFailsWith { @@ -866,31 +866,32 @@ abstract class AbstractBufferedSourceTest internal constructor( sink.emit() assertEquals(0x7f, source.readUtf8CodePoint().toLong()) - sink.writeByte(0xdf).writeByte(0xbf) + sink.writeByte(0xdf.toByte()).writeByte(0xbf.toByte()) sink.emit() assertEquals(0x07ff, source.readUtf8CodePoint().toLong()) - sink.writeByte(0xef).writeByte(0xbf).writeByte(0xbf) + sink.writeByte(0xef.toByte()).writeByte(0xbf.toByte()).writeByte(0xbf.toByte()) sink.emit() assertEquals(0xffff, source.readUtf8CodePoint().toLong()) - sink.writeByte(0xf4).writeByte(0x8f).writeByte(0xbf).writeByte(0xbf) + sink.writeByte(0xf4.toByte()).writeByte(0x8f.toByte()) + .writeByte(0xbf.toByte()).writeByte(0xbf.toByte()) sink.emit() assertEquals(0x10ffff, source.readUtf8CodePoint().toLong()) } @Test fun codePointsFromExhaustedSource() { - sink.writeByte(0xdf) // a second byte is missing + sink.writeByte(0xdf.toByte()) // a second byte is missing sink.emit() assertFailsWith { source.readUtf8CodePoint() } assertEquals(1, source.readByteArray().size) - sink.writeByte(0xe2).writeByte(0x98) // a third byte is missing + sink.writeByte(0xe2.toByte()).writeByte(0x98.toByte()) // a third byte is missing sink.emit() assertFailsWith { source.readUtf8CodePoint() } assertEquals(2, source.readByteArray().size) - sink.writeByte(0xf0).writeByte(0x9f).writeByte(0x92) // a forth byte is missing + sink.writeByte(0xf0.toByte()).writeByte(0x9f.toByte()).writeByte(0x92.toByte()) // a forth byte is missing sink.emit() assertFailsWith { source.readUtf8CodePoint() } assertEquals(3, source.readByteArray().size) @@ -1101,4 +1102,133 @@ abstract class AbstractBufferedSourceTest internal constructor( assertEquals("line", buf.readUtf8LineStrict(5)) assertEquals(0, buf.size) } + + @Test fun readUnsignedByte() { + sink.writeByte(0) + .writeByte(-1) + .writeByte(-128) + .writeByte(127) + .flush() + + assertEquals(0u, source.readUByte()) + assertEquals(255u, source.readUByte()) + assertEquals(128u, source.readUByte()) + assertEquals(127u, source.readUByte()) + assertTrue(source.exhausted()) + } + + @Test fun readTooShortUnsignedByteThrows() { + assertFailsWith { source.readUByte() } + } + + @Test fun readUnsignedShort() { + sink.writeShort(0) + .writeShort(-1) + .writeShort(-32768) + .writeShort(32767) + .flush() + + assertEquals(0u, source.readUShort()) + assertEquals(65535u, source.readUShort()) + assertEquals(32768u, source.readUShort()) + assertEquals(32767u, source.readUShort()) + assertTrue(source.exhausted()) + } + + @Test fun readUnsignedShortLe() { + sink.write(byteArrayOf(0x12, 0x34)).flush() + assertEquals(0x3412u, source.readUShortLe()) + } + + @Test fun readTooShortUnsignedShortThrows() { + assertFailsWith { source.readUShort() } + sink.writeByte(0).flush() + assertFailsWith { source.readUShort() } + assertTrue(source.request(1)) + } + + @Test fun readTooShortUnsignedShortLeThrows() { + assertFailsWith { source.readUShortLe() } + sink.writeByte(0).flush() + assertFailsWith { source.readUShortLe() } + assertTrue(source.request(1)) + } + + @Test fun readUnsignedInt() { + sink.writeInt(0) + .writeInt(-1) + .writeInt(Int.MIN_VALUE) + .writeInt(Int.MAX_VALUE) + .flush() + + assertEquals(0u, source.readUInt()) + assertEquals(UInt.MAX_VALUE, source.readUInt()) + assertEquals(2147483648u, source.readUInt()) + assertEquals(Int.MAX_VALUE.toUInt(), source.readUInt()) + assertTrue(source.exhausted()) + } + + @Test fun readUnsignedIntLe() { + sink.write(byteArrayOf(0x12, 0x34, 0x56, 0x78)).flush() + assertEquals(0x78563412u, source.readUIntLe()) + } + + @Test fun readTooShortUnsignedIntThrows() { + assertFailsWith { source.readUInt() } + sink.writeByte(0).flush() + assertFailsWith { source.readUInt() } + sink.writeByte(0).flush() + assertFailsWith { source.readUInt() } + sink.writeByte(0).flush() + assertFailsWith { source.readUInt() } + assertTrue(source.request(3)) + } + + @Test fun readTooShortUnsignedIntLeThrows() { + assertFailsWith { source.readUIntLe() } + sink.writeByte(0).flush() + assertFailsWith { source.readUIntLe() } + sink.writeByte(0).flush() + assertFailsWith { source.readUIntLe() } + sink.writeByte(0).flush() + assertFailsWith { source.readUIntLe() } + assertTrue(source.request(3)) + } + + @Test fun readUnsignedLong() { + sink.writeLong(0) + .writeLong(-1) + .writeLong(Long.MIN_VALUE) + .writeLong(Long.MAX_VALUE) + .flush() + + assertEquals(0u, source.readULong()) + assertEquals(ULong.MAX_VALUE, source.readULong()) + assertEquals(9223372036854775808u, source.readULong()) + assertEquals(Long.MAX_VALUE.toULong(), source.readULong()) + assertTrue(source.exhausted()) + } + + @Test fun readUnsignedLongLe() { + sink.write(byteArrayOf(0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0xff.toByte())).flush() + assertEquals(0xff07060504030201u, source.readULongLe()) + } + + @Test fun readTooShortUnsignedLongThrows() { + assertFailsWith { source.readULong() } + for (i in 0 until 7) { + sink.writeByte(0).flush() + assertFailsWith { source.readULong() } + } + assertTrue(source.request(7)) + } + + @Test fun readTooShortUnsignedLongLeThrows() { + assertFailsWith { source.readULongLe() } + for (i in 0 until 7) { + sink.writeByte(0).flush() + assertFailsWith { source.readULongLe() } + } + assertTrue(source.request(7)) + } } diff --git a/core/common/test/CommonRealSinkTest.kt b/core/common/test/CommonRealSinkTest.kt index 75b736d4c..f0b1a6269 100644 --- a/core/common/test/CommonRealSinkTest.kt +++ b/core/common/test/CommonRealSinkTest.kt @@ -52,7 +52,7 @@ class CommonRealSinkTest { @Test fun bufferedSinkFlush() { val sink = Buffer() val bufferedSink = (sink as RawSink).buffer() - bufferedSink.writeByte('a'.code) + bufferedSink.writeByte('a'.code.toByte()) assertEquals(0, sink.size) bufferedSink.flush() assertEquals(0, bufferedSink.buffer.size) @@ -100,7 +100,7 @@ class CommonRealSinkTest { val mockSink = MockSink() mockSink.scheduleThrow(0, IOException()) val bufferedSink = mockSink.buffer() - bufferedSink.writeByte('a'.code) + bufferedSink.writeByte('a'.code.toByte()) assertFailsWith { bufferedSink.close() } @@ -112,7 +112,7 @@ class CommonRealSinkTest { val mockSink = MockSink() mockSink.scheduleThrow(1, IOException()) val bufferedSink = mockSink.buffer() - bufferedSink.writeByte('a'.code) + bufferedSink.writeByte('a'.code.toByte()) assertFailsWith { bufferedSink.close() } @@ -125,7 +125,7 @@ class CommonRealSinkTest { mockSink.scheduleThrow(0, IOException("first")) mockSink.scheduleThrow(1, IOException("second")) val bufferedSink = mockSink.buffer() - bufferedSink.writeByte('a'.code) + bufferedSink.writeByte('a'.code.toByte()) assertFailsWith("first.*") { bufferedSink.close() } @@ -136,12 +136,12 @@ class CommonRealSinkTest { @Test fun operationsAfterClose() { val mockSink = MockSink() val bufferedSink = mockSink.buffer() - bufferedSink.writeByte('a'.code) + bufferedSink.writeByte('a'.code.toByte()) bufferedSink.close() // Test a sample set of methods. assertFailsWith { - bufferedSink.writeByte('a'.code) + bufferedSink.writeByte('a'.code.toByte()) } assertFailsWith { diff --git a/core/common/test/Utf8KotlinTest.kt b/core/common/test/Utf8KotlinTest.kt index 089d48fb4..27a29a02a 100644 --- a/core/common/test/Utf8KotlinTest.kt +++ b/core/common/test/Utf8KotlinTest.kt @@ -211,7 +211,7 @@ class Utf8KotlinTest { @Test fun readLeadingContinuationByteReturnsReplacementCharacter() { val buffer = Buffer() - buffer.writeByte(0xbf) + buffer.writeByte(0xbf.toByte()) assertEquals(REPLACEMENT_CODE_POINT, buffer.readUtf8CodePoint()) assertTrue(buffer.exhausted()) } @@ -219,7 +219,7 @@ class Utf8KotlinTest { @Test fun readMissingContinuationBytesThrowsEofException() { val buffer = Buffer() - buffer.writeByte(0xdf) + buffer.writeByte(0xdf.toByte()) assertFailsWith { buffer.readUtf8CodePoint() } assertFalse(buffer.exhausted()) // Prefix byte wasn't consumed. } diff --git a/core/jvm/src/Buffer.kt b/core/jvm/src/Buffer.kt index 5b4c5e446..70bc67bd1 100644 --- a/core/jvm/src/Buffer.kt +++ b/core/jvm/src/Buffer.kt @@ -126,9 +126,9 @@ public actual class Buffer : Source, Sink, Cloneable, ByteChannel { actual override fun write(source: RawSource, byteCount: Long): Buffer = commonWrite(source, byteCount) - actual override fun writeByte(byte: Int): Buffer = commonWriteByte(byte) + actual override fun writeByte(byte: Byte): Buffer = commonWriteByte(byte) - actual override fun writeShort(short: Int): Buffer = commonWriteShort(short) + actual override fun writeShort(short: Short): Buffer = commonWriteShort(short) actual override fun writeInt(int: Int): Buffer = commonWriteInt(int) diff --git a/core/jvm/src/RealSink.kt b/core/jvm/src/RealSink.kt index ace7fbcd0..928e58520 100644 --- a/core/jvm/src/RealSink.kt +++ b/core/jvm/src/RealSink.kt @@ -49,8 +49,8 @@ internal actual class RealSink actual constructor( override fun writeAll(source: RawSource) = commonWriteAll(source) override fun write(source: RawSource, byteCount: Long): Sink = commonWrite(source, byteCount) - override fun writeByte(byte: Int) = commonWriteByte(byte) - override fun writeShort(short: Int) = commonWriteShort(short) + override fun writeByte(byte: Byte) = commonWriteByte(byte) + override fun writeShort(short: Short) = commonWriteShort(short) override fun writeInt(int: Int) = commonWriteInt(int) override fun writeLong(long: Long) = commonWriteLong(long) override fun emitCompleteSegments() = commonEmitCompleteSegments() diff --git a/core/jvm/src/Sink.kt b/core/jvm/src/Sink.kt index 580ce261b..958e598bd 100644 --- a/core/jvm/src/Sink.kt +++ b/core/jvm/src/Sink.kt @@ -34,9 +34,9 @@ public actual sealed interface Sink : RawSink, WritableByteChannel { public actual fun write(source: RawSource, byteCount: Long): Sink - public actual fun writeByte(byte: Int): Sink + public actual fun writeByte(byte: Byte): Sink - public actual fun writeShort(short: Int): Sink + public actual fun writeShort(short: Short): Sink public actual fun writeInt(int: Int): Sink @@ -77,7 +77,7 @@ public fun Sink.outputStream(): OutputStream { return object : OutputStream() { override fun write(b: Int) { if (!isOpen) throw IOException("closed") - buffer.writeByte(b.toByte().toInt()) + buffer.writeByte(b.toByte()) emitCompleteSegments() } @@ -98,4 +98,4 @@ public fun Sink.outputStream(): OutputStream { override fun toString() = "${this@outputStream}.outputStream()" } -} \ No newline at end of file +} diff --git a/core/native/src/Buffer.kt b/core/native/src/Buffer.kt index 9b4baf4e7..d24f5bee7 100644 --- a/core/native/src/Buffer.kt +++ b/core/native/src/Buffer.kt @@ -84,9 +84,9 @@ public actual class Buffer : Source, Sink { actual override fun write(source: RawSource, byteCount: Long): Buffer = commonWrite(source, byteCount) - actual override fun writeByte(byte: Int): Buffer = commonWriteByte(byte) + actual override fun writeByte(byte: Byte): Buffer = commonWriteByte(byte) - actual override fun writeShort(short: Int): Buffer = commonWriteShort(short) + actual override fun writeShort(short: Short): Buffer = commonWriteShort(short) actual override fun writeInt(int: Int): Buffer = commonWriteInt(int) diff --git a/core/native/src/RealSink.kt b/core/native/src/RealSink.kt index da69f4182..f0374894f 100644 --- a/core/native/src/RealSink.kt +++ b/core/native/src/RealSink.kt @@ -36,8 +36,8 @@ internal actual class RealSink actual constructor( override fun writeAll(source: RawSource) = commonWriteAll(source) override fun write(source: RawSource, byteCount: Long): Sink = commonWrite(source, byteCount) - override fun writeByte(byte: Int) = commonWriteByte(byte) - override fun writeShort(short: Int) = commonWriteShort(short) + override fun writeByte(byte: Byte) = commonWriteByte(byte) + override fun writeShort(short: Short) = commonWriteShort(short) override fun writeInt(int: Int) = commonWriteInt(int) override fun writeLong(long: Long) = commonWriteLong(long) override fun emitCompleteSegments() = commonEmitCompleteSegments() diff --git a/core/native/src/Sink.kt b/core/native/src/Sink.kt index b6c334c99..54dfa113f 100644 --- a/core/native/src/Sink.kt +++ b/core/native/src/Sink.kt @@ -29,9 +29,9 @@ public actual sealed interface Sink : RawSink { public actual fun write(source: RawSource, byteCount: Long): Sink - public actual fun writeByte(byte: Int): Sink + public actual fun writeByte(byte: Byte): Sink - public actual fun writeShort(short: Int): Sink + public actual fun writeShort(short: Short): Sink public actual fun writeInt(int: Int): Sink From 0fc1fadd11415fe0ef465aa09dedadcb34512f18 Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Fri, 9 Jun 2023 12:05:09 +0200 Subject: [PATCH 31/83] Don't implement channels on JVM, return proxy objects instead --- core/api/kotlinx-io-core.api | 16 +++-- core/common/test/AbstractSourceTest.kt | 1 + core/jvm/src/Buffer.kt | 95 ++++++++++++++++---------- core/jvm/src/RealSink.kt | 9 --- core/jvm/src/RealSource.kt | 11 --- core/jvm/src/Sink.kt | 50 ++++++++++++-- core/jvm/src/Source.kt | 49 +++++++++++-- core/jvm/test/NioTest.kt | 24 +++---- 8 files changed, 171 insertions(+), 84 deletions(-) diff --git a/core/api/kotlinx-io-core.api b/core/api/kotlinx-io-core.api index 069af801d..8bae9e659 100644 --- a/core/api/kotlinx-io-core.api +++ b/core/api/kotlinx-io-core.api @@ -1,4 +1,4 @@ -public final class kotlinx/io/Buffer : java/lang/Cloneable, java/nio/channels/ByteChannel, kotlinx/io/Sink, kotlinx/io/Source { +public final class kotlinx/io/Buffer : java/lang/Cloneable, kotlinx/io/Sink, kotlinx/io/Source { public fun ()V public final fun clear ()V public synthetic fun clone ()Ljava/lang/Object; @@ -19,9 +19,7 @@ public final class kotlinx/io/Buffer : java/lang/Cloneable, java/nio/channels/By public fun getBuffer ()Lkotlinx/io/Buffer; public final fun getSize ()J public fun hashCode ()I - public fun isOpen ()Z public fun peek ()Lkotlinx/io/Source; - public fun read (Ljava/nio/ByteBuffer;)I public fun read (Lkotlinx/io/Buffer;J)J public fun read ([BII)I public fun readAll (Lkotlinx/io/RawSink;)J @@ -34,7 +32,6 @@ public final class kotlinx/io/Buffer : java/lang/Cloneable, java/nio/channels/By public fun require (J)V public fun skip (J)V public fun toString ()Ljava/lang/String; - public fun write (Ljava/nio/ByteBuffer;)I public fun write (Lkotlinx/io/Buffer;J)V public fun write (Lkotlinx/io/RawSource;J)Lkotlinx/io/Buffer; public synthetic fun write (Lkotlinx/io/RawSource;J)Lkotlinx/io/Sink; @@ -52,11 +49,14 @@ public final class kotlinx/io/Buffer : java/lang/Cloneable, java/nio/channels/By } public final class kotlinx/io/BufferKt { + public static final fun channel (Lkotlinx/io/Buffer;)Ljava/nio/channels/ByteChannel; public static final fun copyTo (Lkotlinx/io/Buffer;Ljava/io/OutputStream;JJ)Lkotlinx/io/Buffer; public static synthetic fun copyTo$default (Lkotlinx/io/Buffer;Ljava/io/OutputStream;JJILjava/lang/Object;)Lkotlinx/io/Buffer; public static final fun readFrom (Lkotlinx/io/Buffer;Ljava/io/InputStream;)Lkotlinx/io/Buffer; public static final fun readFrom (Lkotlinx/io/Buffer;Ljava/io/InputStream;J)Lkotlinx/io/Buffer; + public static final fun readFrom (Lkotlinx/io/Buffer;Ljava/nio/ByteBuffer;)Lkotlinx/io/Buffer; public static final fun writeTo (Lkotlinx/io/Buffer;Ljava/io/OutputStream;J)Lkotlinx/io/Buffer; + public static final fun writeTo (Lkotlinx/io/Buffer;Ljava/nio/ByteBuffer;)I public static synthetic fun writeTo$default (Lkotlinx/io/Buffer;Ljava/io/OutputStream;JILjava/lang/Object;)Lkotlinx/io/Buffer; } @@ -90,7 +90,7 @@ public abstract interface class kotlinx/io/RawSource : java/io/Closeable { public abstract fun read (Lkotlinx/io/Buffer;J)J } -public abstract interface class kotlinx/io/Sink : java/nio/channels/WritableByteChannel, kotlinx/io/RawSink { +public abstract interface class kotlinx/io/Sink : kotlinx/io/RawSink { public abstract fun emit ()Lkotlinx/io/Sink; public abstract fun emitCompleteSegments ()Lkotlinx/io/Sink; public abstract fun flush ()V @@ -124,12 +124,14 @@ public final class kotlinx/io/SinkExtKt { } public final class kotlinx/io/SinkKt { + public static final fun channel (Lkotlinx/io/Sink;)Ljava/nio/channels/WritableByteChannel; public static final fun outputStream (Lkotlinx/io/Sink;)Ljava/io/OutputStream; + public static final fun write (Lkotlinx/io/Sink;Ljava/nio/ByteBuffer;)I public static final fun writeString (Lkotlinx/io/Sink;Ljava/lang/String;Ljava/nio/charset/Charset;II)Lkotlinx/io/Sink; public static synthetic fun writeString$default (Lkotlinx/io/Sink;Ljava/lang/String;Ljava/nio/charset/Charset;IIILjava/lang/Object;)Lkotlinx/io/Sink; } -public abstract interface class kotlinx/io/Source : java/nio/channels/ReadableByteChannel, kotlinx/io/RawSource { +public abstract interface class kotlinx/io/Source : kotlinx/io/RawSource { public abstract fun exhausted ()Z public abstract fun getBuffer ()Lkotlinx/io/Buffer; public abstract fun peek ()Lkotlinx/io/Source; @@ -170,7 +172,9 @@ public final class kotlinx/io/SourceExtKt { } public final class kotlinx/io/SourceKt { + public static final fun channel (Lkotlinx/io/Source;)Ljava/nio/channels/ReadableByteChannel; public static final fun inputStream (Lkotlinx/io/Source;)Ljava/io/InputStream; + public static final fun read (Lkotlinx/io/Source;Ljava/nio/ByteBuffer;)I public static final fun readString (Lkotlinx/io/Source;JLjava/nio/charset/Charset;)Ljava/lang/String; public static final fun readString (Lkotlinx/io/Source;Ljava/nio/charset/Charset;)Ljava/lang/String; } diff --git a/core/common/test/AbstractSourceTest.kt b/core/common/test/AbstractSourceTest.kt index dcc2f745a..2898cf25b 100644 --- a/core/common/test/AbstractSourceTest.kt +++ b/core/common/test/AbstractSourceTest.kt @@ -1046,6 +1046,7 @@ abstract class AbstractBufferedSourceTest internal constructor( } } + // TODO: fix tests @Test fun readUtf8Line() { val buf = Buffer().writeUtf8("first line\nsecond line\n") assertEquals("first line", buf.readUtf8Line()) diff --git a/core/jvm/src/Buffer.kt b/core/jvm/src/Buffer.kt index 70bc67bd1..3da4cec58 100644 --- a/core/jvm/src/Buffer.kt +++ b/core/jvm/src/Buffer.kt @@ -28,7 +28,7 @@ import java.io.OutputStream import java.nio.ByteBuffer import java.nio.channels.ByteChannel -public actual class Buffer : Source, Sink, Cloneable, ByteChannel { +public actual class Buffer : Source, Sink, Cloneable { @JvmField internal actual var head: Segment? = null public actual var size: Long = 0L @@ -77,23 +77,6 @@ public actual class Buffer : Source, Sink, Cloneable, ByteChannel { override fun read(sink: ByteArray, offset: Int, byteCount: Int): Int = commonRead(sink, offset, byteCount) - override fun read(sink: ByteBuffer): Int { - val s = head ?: return -1 - - val toCopy = minOf(sink.remaining(), s.limit - s.pos) - sink.put(s.data, s.pos, toCopy) - - s.pos += toCopy - size -= toCopy.toLong() - - if (s.pos == s.limit) { - head = s.pop() - SegmentPool.recycle(s) - } - - return toCopy - } - public actual fun clear(): Unit = commonClear() public actual override fun skip(byteCount: Long): Unit = commonSkip(byteCount) @@ -104,23 +87,6 @@ public actual class Buffer : Source, Sink, Cloneable, ByteChannel { byteCount: Int ): Buffer = commonWrite(source, offset, byteCount) - override fun write(source: ByteBuffer): Int { - val byteCount = source.remaining() - var remaining = byteCount - while (remaining > 0) { - val tail = writableSegment(1) - - val toCopy = minOf(remaining, Segment.SIZE - tail.limit) - source.get(tail.data, tail.limit, toCopy) - - remaining -= toCopy - tail.limit += toCopy - } - - size += byteCount.toLong() - return byteCount - } - override fun writeAll(source: RawSource): Long = commonWriteAll(source) actual override fun write(source: RawSource, byteCount: Long): Buffer = @@ -143,8 +109,6 @@ public actual class Buffer : Source, Sink, Cloneable, ByteChannel { public actual override fun flush(): Unit = Unit - override fun isOpen(): Boolean = true - actual override fun close(): Unit = Unit override fun equals(other: Any?): Boolean = commonEquals(other) @@ -286,3 +250,60 @@ public fun Buffer.copyTo( return this } + +/** + * Writes up to [ByteBuffer.remaining] bytes from this buffer to the sink. + * Return the number of bytes written. + * + * @param sink the sink to write data to. + */ +public fun Buffer.writeTo(sink: ByteBuffer): Int { + val s = head ?: return -1 + + val toCopy = minOf(sink.remaining(), s.limit - s.pos) + sink.put(s.data, s.pos, toCopy) + + s.pos += toCopy + size -= toCopy.toLong() + + if (s.pos == s.limit) { + head = s.pop() + SegmentPool.recycle(s) + } + + return toCopy +} + +/** + * Reads all data from [source] into this buffer. + */ +public fun Buffer.readFrom(source: ByteBuffer): Buffer { + val byteCount = source.remaining() + var remaining = byteCount + while (remaining > 0) { + val tail = writableSegment(1) + + val toCopy = minOf(remaining, Segment.SIZE - tail.limit) + source.get(tail.data, tail.limit, toCopy) + + remaining -= toCopy + tail.limit += toCopy + } + + size += byteCount.toLong() + return this +} + +public fun Buffer.channel(): ByteChannel = object : ByteChannel { + override fun read(sink: ByteBuffer): Int = writeTo(sink) + + override fun write(source: ByteBuffer): Int { + val sizeBefore = size + readFrom(source) + return (size - sizeBefore).toInt() + } + + override fun close() {} + + override fun isOpen(): Boolean = true +} \ No newline at end of file diff --git a/core/jvm/src/RealSink.kt b/core/jvm/src/RealSink.kt index 928e58520..679b8b837 100644 --- a/core/jvm/src/RealSink.kt +++ b/core/jvm/src/RealSink.kt @@ -40,13 +40,6 @@ internal actual class RealSink actual constructor( override fun write(source: ByteArray, offset: Int, byteCount: Int) = commonWrite(source, offset, byteCount) - override fun write(source: ByteBuffer): Int { - check(!closed) { "closed" } - val result = buffer.write(source) - emitCompleteSegments() - return result - } - override fun writeAll(source: RawSource) = commonWriteAll(source) override fun write(source: RawSource, byteCount: Long): Sink = commonWrite(source, byteCount) override fun writeByte(byte: Byte) = commonWriteByte(byte) @@ -58,8 +51,6 @@ internal actual class RealSink actual constructor( override fun flush() = commonFlush() - override fun isOpen() = !closed - override fun close() = commonClose() override fun toString() = commonToString() diff --git a/core/jvm/src/RealSource.kt b/core/jvm/src/RealSource.kt index f852acf89..43a2e1c5b 100644 --- a/core/jvm/src/RealSource.kt +++ b/core/jvm/src/RealSource.kt @@ -43,15 +43,6 @@ internal actual class RealSource actual constructor( override fun read(sink: ByteArray, offset: Int, byteCount: Int): Int = commonRead(sink, offset, byteCount) - override fun read(sink: ByteBuffer): Int { - if (buffer.size == 0L) { - val read = source.read(buffer, Segment.SIZE.toLong()) - if (read == -1L) return -1 - } - - return buffer.read(sink) - } - override fun readFully(sink: Buffer, byteCount: Long): Unit = commonReadFully(sink, byteCount) override fun readAll(sink: RawSink): Long = commonReadAll(sink) @@ -62,8 +53,6 @@ internal actual class RealSource actual constructor( override fun peek(): Source = commonPeek() - override fun isOpen() = !closed - override fun close(): Unit = commonClose() override fun toString(): String = commonToString() diff --git a/core/jvm/src/Sink.kt b/core/jvm/src/Sink.kt index 958e598bd..4e09173dc 100644 --- a/core/jvm/src/Sink.kt +++ b/core/jvm/src/Sink.kt @@ -22,10 +22,11 @@ package kotlinx.io import java.io.IOException import java.io.OutputStream +import java.nio.ByteBuffer import java.nio.channels.WritableByteChannel import java.nio.charset.Charset -public actual sealed interface Sink : RawSink, WritableByteChannel { +public actual sealed interface Sink : RawSink { public actual val buffer: Buffer public actual fun write(source: ByteArray, offset: Int, byteCount: Int): Sink @@ -74,22 +75,27 @@ public fun T.writeString(string: String, charset: Charset, beginIndex: * Returns an output stream that writes to this sink. Closing the stream will also close this sink. */ public fun Sink.outputStream(): OutputStream { + val isClosed: () -> Boolean = when (this) { + is RealSink -> this::closed + is Buffer -> { { false } } + } + return object : OutputStream() { override fun write(b: Int) { - if (!isOpen) throw IOException("closed") + if (isClosed()) throw IOException("closed") buffer.writeByte(b.toByte()) emitCompleteSegments() } override fun write(data: ByteArray, offset: Int, byteCount: Int) { - if (!isOpen) throw IOException("closed") + if (isClosed()) throw IOException("closed") buffer.write(data, offset, byteCount) emitCompleteSegments() } override fun flush() { // For backwards compatibility, a flush() on a closed stream is a no-op. - if (isOpen) { + if (!isClosed()) { this@outputStream.flush() } } @@ -99,3 +105,39 @@ public fun Sink.outputStream(): OutputStream { override fun toString() = "${this@outputStream}.outputStream()" } } + +/** + * Writes data from the [source] into this sink and returns the number of bytes written. + * + * @param source the source to read from. + */ +public fun Sink.write(source: ByteBuffer): Int { + val sizeBefore = buffer.size + buffer.readFrom(source) + val bytesRead = buffer.size - sizeBefore + emitCompleteSegments() + return bytesRead.toInt() +} + +/** + * Returns [WritableByteChannel] backed by this sink. Closing the channel will also close the sink. + */ +public fun Sink.channel(): WritableByteChannel { + val isClosed: () -> Boolean = when (this) { + is RealSink -> this::closed + is Buffer -> { { false } } + } + + return object : WritableByteChannel { + override fun close() { + this@channel.close() + } + + override fun isOpen(): Boolean = !isClosed() + + override fun write(source: ByteBuffer): Int { + check(!isClosed()) { "closed" } + return this@channel.write(source) + } + } +} \ No newline at end of file diff --git a/core/jvm/src/Source.kt b/core/jvm/src/Source.kt index c1e6fa9dc..7e15b57fd 100644 --- a/core/jvm/src/Source.kt +++ b/core/jvm/src/Source.kt @@ -21,12 +21,12 @@ package kotlinx.io import java.io.EOFException -import java.io.IOException import java.io.InputStream +import java.nio.ByteBuffer import java.nio.channels.ReadableByteChannel import java.nio.charset.Charset -public actual sealed interface Source : RawSource, ReadableByteChannel { +public actual sealed interface Source : RawSource { public actual val buffer: Buffer public actual fun exhausted(): Boolean @@ -107,9 +107,14 @@ public fun Source.readString(byteCount: Long, charset: Charset): String { * Returns an input stream that reads from this source. Closing the stream will also close this source. */ public fun Source.inputStream(): InputStream { + val isClosed: () -> Boolean = when (this) { + is RealSource -> this::closed + is Buffer -> { { false } } + } + return object : InputStream() { override fun read(): Int { - if (!isOpen) throw IOException("closed") + if (isClosed()) throw IOException("closed") if (exhausted()) { return -1 } @@ -117,14 +122,14 @@ public fun Source.inputStream(): InputStream { } override fun read(data: ByteArray, offset: Int, byteCount: Int): Int { - if (!isOpen) throw IOException("closed") + if (isClosed()) throw IOException("closed") checkOffsetAndCount(data.size.toLong(), offset.toLong(), byteCount.toLong()) return this@inputStream.read(data, offset, byteCount) } override fun available(): Int { - if (!isOpen) throw IOException("closed") + if (isClosed()) throw IOException("closed") return minOf(buffer.size, Integer.MAX_VALUE).toInt() } @@ -132,4 +137,38 @@ public fun Source.inputStream(): InputStream { override fun toString() = "${this@inputStream}.inputStream()" } +} + +/** + * Reads data from this source into [sink]. + * + * @param sink the sink to write the data to. + */ +public fun Source.read(sink: ByteBuffer): Int { + if (buffer.size == 0L) { + request(Segment.SIZE.toLong()) + if (buffer.size == 0L) return -1 + } + + return buffer.writeTo(sink) +} + +/** + * Returns [ReadableByteChannel] backed by this source. Closing the source will close the source. + */ +public fun Source.channel(): ReadableByteChannel { + val isClosed: () -> Boolean = when (this) { + is RealSource -> this::closed + is Buffer -> { { false } } + } + + return object : ReadableByteChannel { + override fun close() { + this@channel.close() + } + + override fun isOpen(): Boolean = !isClosed() + + override fun read(sink: ByteBuffer): Int = this@channel.read(sink) + } } \ No newline at end of file diff --git a/core/jvm/test/NioTest.kt b/core/jvm/test/NioTest.kt index cebde4c92..7b9225cbc 100644 --- a/core/jvm/test/NioTest.kt +++ b/core/jvm/test/NioTest.kt @@ -40,7 +40,7 @@ class NioTest { lateinit var temporaryFolder: Path @Test fun sourceIsOpen() { - val source: Source = RealSource(Buffer()) + val source = RealSource(Buffer()).channel() assertTrue(source.isOpen()) source.close() assertFalse(source.isOpen()) @@ -48,7 +48,7 @@ class NioTest { @Test fun sinkIsOpen() { - val sink: Sink = RealSink(Buffer()) + val sink = RealSink(Buffer()).channel() assertTrue(sink.isOpen()) sink.close() assertFalse(sink.isOpen()) @@ -58,7 +58,7 @@ class NioTest { fun writableChannelNioFile() { val file = Paths.get(temporaryFolder.toString(), "test").createFile() val fileChannel: FileChannel = FileChannel.open(file, StandardOpenOption.WRITE) - testWritableByteChannel(fileChannel) + testWritableByteChannel(false, fileChannel) val emitted: Source = file.source().buffer() assertEquals("defghijklmnopqrstuvw", emitted.readUtf8()) emitted.close() @@ -67,7 +67,7 @@ class NioTest { @Test fun writableChannelBuffer() { val buffer = Buffer() - testWritableByteChannel(buffer) + testWritableByteChannel(true, buffer.channel()) assertEquals("defghijklmnopqrstuvw", buffer.readUtf8()) } @@ -75,7 +75,7 @@ class NioTest { fun writableChannelBufferedSink() { val buffer = Buffer() val bufferedSink: Sink = buffer - testWritableByteChannel(bufferedSink) + testWritableByteChannel(true, bufferedSink.channel()) assertEquals("defghijklmnopqrstuvw", buffer.readUtf8()) } @@ -86,14 +86,14 @@ class NioTest { initialData.writeUtf8("abcdefghijklmnopqrstuvwxyz") initialData.close() val fileChannel: FileChannel = FileChannel.open(file.toPath(), StandardOpenOption.READ) - testReadableByteChannel(fileChannel) + testReadableByteChannel(false, fileChannel) } @Test fun readableChannelBuffer() { val buffer = Buffer() buffer.writeUtf8("abcdefghijklmnopqrstuvwxyz") - testReadableByteChannel(buffer) + testReadableByteChannel(true, buffer.channel()) } @Test @@ -101,14 +101,14 @@ class NioTest { val buffer = Buffer() val bufferedSource: Source = buffer buffer.writeUtf8("abcdefghijklmnopqrstuvwxyz") - testReadableByteChannel(bufferedSource) + testReadableByteChannel(true, bufferedSource.channel()) } /** * Does some basic writes to `channel`. We execute this against both Okio's channels and * also a standard implementation from the JDK to confirm that their behavior is consistent. */ - private fun testWritableByteChannel(channel: WritableByteChannel) { + private fun testWritableByteChannel(isBuffer: Boolean, channel: WritableByteChannel) { assertTrue(channel.isOpen()) val byteBuffer = ByteBuffer.allocate(1024) byteBuffer.put("abcdefghijklmnopqrstuvwxyz".toByteArray(UTF_8)) @@ -120,14 +120,14 @@ class NioTest { assertEquals(23, byteBuffer.position()) assertEquals(23, byteBuffer.limit()) channel.close() - assertEquals(channel is Buffer, channel.isOpen) // Buffer.close() does nothing. + assertEquals(isBuffer, channel.isOpen) // Buffer.close() does nothing. } /** * Does some basic reads from `channel`. We execute this against both Okio's channels and * also a standard implementation from the JDK to confirm that their behavior is consistent. */ - private fun testReadableByteChannel(channel: ReadableByteChannel) { + private fun testReadableByteChannel(isBuffer: Boolean, channel: ReadableByteChannel) { assertTrue(channel.isOpen) val byteBuffer = ByteBuffer.allocate(1024) byteBuffer.position(3) // Cast necessary for Java 8. @@ -137,7 +137,7 @@ class NioTest { assertEquals(23, byteBuffer.position()) assertEquals(23, byteBuffer.limit()) channel.close() - assertEquals(channel is Buffer, channel.isOpen()) // Buffer.close() does nothing. + assertEquals(isBuffer, channel.isOpen()) // Buffer.close() does nothing. byteBuffer.flip() // Cast necessary for Java 8. byteBuffer.position(3) // Cast necessary for Java 8. val data = ByteArray(byteBuffer.remaining()) From 336da548cf09c45bb312a9034028562b5195a939 Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Fri, 9 Jun 2023 16:33:07 +0200 Subject: [PATCH 32/83] Mark buffer getters and emitCompleteSegments as delicate API requiring OptIn --- core/api/kotlinx-io-core.api | 3 +++ core/common/src/Buffer.kt | 2 ++ core/common/src/DelicateIoApi.kt | 15 +++++++++++++++ core/common/src/Sink.kt | 11 ++++++++++- core/common/src/Source.kt | 12 +++++++++--- 5 files changed, 39 insertions(+), 4 deletions(-) create mode 100644 core/common/src/DelicateIoApi.kt diff --git a/core/api/kotlinx-io-core.api b/core/api/kotlinx-io-core.api index 8bae9e659..d131a2a76 100644 --- a/core/api/kotlinx-io-core.api +++ b/core/api/kotlinx-io-core.api @@ -67,6 +67,9 @@ public final class kotlinx/io/CoreKt { public static final fun use (Ljava/io/Closeable;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; } +public abstract interface annotation class kotlinx/io/DelicateIoApi : java/lang/annotation/Annotation { +} + public final class kotlinx/io/JvmCoreKt { public static final fun sink (Ljava/io/File;Z)Lkotlinx/io/RawSink; public static final fun sink (Ljava/io/OutputStream;)Lkotlinx/io/RawSink; diff --git a/core/common/src/Buffer.kt b/core/common/src/Buffer.kt index 81a8be746..ffe64ec50 100644 --- a/core/common/src/Buffer.kt +++ b/core/common/src/Buffer.kt @@ -48,11 +48,13 @@ public expect class Buffer() : Source, Sink { /** * Returns the buffer itself. */ + @DelicateIoApi override val buffer: Buffer /** * This method does not affect the buffer's content as there is no upstream to write data to. */ + @DelicateIoApi override fun emitCompleteSegments(): Buffer /** diff --git a/core/common/src/DelicateIoApi.kt b/core/common/src/DelicateIoApi.kt new file mode 100644 index 000000000..9de4cf637 --- /dev/null +++ b/core/common/src/DelicateIoApi.kt @@ -0,0 +1,15 @@ +/* + * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. + */ + +package kotlinx.io + +@MustBeDocumented +@Retention(AnnotationRetention.BINARY) +@RequiresOptIn( + level = RequiresOptIn.Level.WARNING, + message = "This is a delicate API and its use requires care. " + + "Make sure you fully read and understand documentation of the declaration that is marked as a delicate API." +) +public annotation class DelicateIoApi diff --git a/core/common/src/Sink.kt b/core/common/src/Sink.kt index 4d7ad479a..b72e7bd1e 100644 --- a/core/common/src/Sink.kt +++ b/core/common/src/Sink.kt @@ -36,11 +36,19 @@ package kotlinx.io * - [emitCompleteSegments] writes only a part of data from the buffer. * The latter is aimed to reduce memory footprint by keeping the buffer as small as possible without excessive writes * to the upstream. + * All write operations implicitly calls [emitCompleteSegments]. */ public expect sealed interface Sink : RawSink { /** - * This sink's internal buffer. + * This sink's internal buffer. It contains data written to the sink, but not yet flushed to the upstream. + * + * Incorrect use of the buffer may cause data loss or unexpected data being sent to the upstream. + * Consider using alternative APIs to write data into the sink, if possible: + * - write data into separate [Buffer] instance and write that buffer into the sink and then flush the sink to + * ensure that the upstream will receive complete data; + * - implement [RawSink] and wrap an upstream sink into it to intercept data being written. */ + @DelicateIoApi public val buffer: Buffer /** @@ -129,5 +137,6 @@ public expect sealed interface Sink : RawSink { * Typically, application code will not need to call this: it is only necessary when * application code writes directly to this [buffer]. */ + @DelicateIoApi public fun emitCompleteSegments(): Sink } diff --git a/core/common/src/Source.kt b/core/common/src/Source.kt index 855517c7f..5b1ac41f4 100644 --- a/core/common/src/Source.kt +++ b/core/common/src/Source.kt @@ -27,18 +27,24 @@ package kotlinx.io * [Source] is the main `kotlinx-io` interface to read data in client's code, * any [RawSource] could be converted into [Source] using [RawSource.buffer]. * - * Depending on a kind of downstream and the number of bytes read, buffering may improve the performance by hiding + * Depending on the kind of downstream and the number of bytes read, buffering may improve the performance by hiding * the latency of small reads. * * The buffer is refilled on reads as necessary, but it is also possible to ensure it contains enough data * using [require] or [request]. - * [Sink] also allows to skip unneeded prefix of data using [skip] and + * [Sink] also allows skipping unneeded prefix of data using [skip] and * provides look ahead into incoming data, buffering as much as necessary, using [peek]. */ public expect sealed interface Source : RawSource { /** - * This source's internal buffer. + * This source's internal buffer. It contains data fetched from the downstream, but not yet consumed by the upstream. + * + * Incorrect use of the buffer may cause data loss or unexpected data being read by the upstream. + * Consider using alternative APIs to read data from the source, if possible: + * - use [peek] for lookahead into a source; + * - implement [RawSource] and wrap a downstream source into it to intercept data being read. */ + @DelicateIoApi public val buffer: Buffer /** From 33545dcb7bef31e0cdd3d573a4f9b274610dd780 Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Fri, 9 Jun 2023 17:03:39 +0200 Subject: [PATCH 33/83] Source::readFully should accept RawSink instead of Buffer --- core/api/kotlinx-io-core.api | 4 ++-- core/common/src/Source.kt | 4 +++- core/common/src/internal/-Buffer.kt | 2 +- core/common/src/internal/-RealBufferedSource.kt | 6 ++++-- core/jvm/src/Buffer.kt | 2 +- core/jvm/src/RealSource.kt | 2 +- core/jvm/src/Source.kt | 2 +- core/native/src/Buffer.kt | 2 +- core/native/src/RealSource.kt | 2 +- core/native/src/Source.kt | 2 +- 10 files changed, 16 insertions(+), 12 deletions(-) diff --git a/core/api/kotlinx-io-core.api b/core/api/kotlinx-io-core.api index d131a2a76..4147e5e0e 100644 --- a/core/api/kotlinx-io-core.api +++ b/core/api/kotlinx-io-core.api @@ -24,7 +24,7 @@ public final class kotlinx/io/Buffer : java/lang/Cloneable, kotlinx/io/Sink, kot public fun read ([BII)I public fun readAll (Lkotlinx/io/RawSink;)J public fun readByte ()B - public fun readFully (Lkotlinx/io/Buffer;J)V + public fun readFully (Lkotlinx/io/RawSink;J)V public fun readInt ()I public fun readLong ()J public fun readShort ()S @@ -141,7 +141,7 @@ public abstract interface class kotlinx/io/Source : kotlinx/io/RawSource { public abstract fun read ([BII)I public abstract fun readAll (Lkotlinx/io/RawSink;)J public abstract fun readByte ()B - public abstract fun readFully (Lkotlinx/io/Buffer;J)V + public abstract fun readFully (Lkotlinx/io/RawSink;J)V public abstract fun readInt ()I public abstract fun readLong ()J public abstract fun readShort ()S diff --git a/core/common/src/Source.kt b/core/common/src/Source.kt index 5b1ac41f4..6c8789553 100644 --- a/core/common/src/Source.kt +++ b/core/common/src/Source.kt @@ -51,6 +51,8 @@ public expect sealed interface Source : RawSource { * Returns true if there are no more bytes in this source. * * The call of this method will block until there are bytes to read or the source is definitely exhausted. + * + * @throws ??? when the source is closed. */ public fun exhausted(): Boolean @@ -138,7 +140,7 @@ public expect sealed interface Source : RawSource { * @throws IllegalArgumentException when [byteCount] is negative. * @throws EOFException when the requested number of bytes cannot be read. */ - public fun readFully(sink: Buffer, byteCount: Long) + public fun readFully(sink: RawSink, byteCount: Long) /** * Removes all bytes from this source, writes them to [sink], and returns the total number of bytes diff --git a/core/common/src/internal/-Buffer.kt b/core/common/src/internal/-Buffer.kt index 8ce3d22bc..830c21838 100644 --- a/core/common/src/internal/-Buffer.kt +++ b/core/common/src/internal/-Buffer.kt @@ -330,7 +330,7 @@ internal inline fun Buffer.commonRead(sink: ByteArray, offset: Int, byteCount: I internal const val OVERFLOW_ZONE = Long.MIN_VALUE / 10L internal const val OVERFLOW_DIGIT_START = Long.MIN_VALUE % 10L + 1 -internal inline fun Buffer.commonReadFully(sink: Buffer, byteCount: Long) { +internal inline fun Buffer.commonReadFully(sink: RawSink, byteCount: Long) { if (size < byteCount) { sink.write(this, size) // Exhaust ourselves. throw EOFException() diff --git a/core/common/src/internal/-RealBufferedSource.kt b/core/common/src/internal/-RealBufferedSource.kt index 61c09391d..2d372e176 100644 --- a/core/common/src/internal/-RealBufferedSource.kt +++ b/core/common/src/internal/-RealBufferedSource.kt @@ -92,12 +92,14 @@ internal inline fun RealSource.commonRead(sink: ByteArray, offset: Int, byteCoun return buffer.read(sink, offset, toRead) } -internal inline fun RealSource.commonReadFully(sink: Buffer, byteCount: Long) { +internal inline fun RealSource.commonReadFully(sink: RawSink, byteCount: Long) { try { require(byteCount) } catch (e: EOFException) { // The underlying source is exhausted. Copy the bytes we got before rethrowing. - sink.writeAll(buffer) + //sink.writeAll(buffer) + // TODO: it seems to be incorrect. + sink.write(buffer, buffer.size) throw e } diff --git a/core/jvm/src/Buffer.kt b/core/jvm/src/Buffer.kt index 3da4cec58..a75523704 100644 --- a/core/jvm/src/Buffer.kt +++ b/core/jvm/src/Buffer.kt @@ -70,7 +70,7 @@ public actual class Buffer : Source, Sink, Cloneable { override fun readLong(): Long = commonReadLong() - override fun readFully(sink: Buffer, byteCount: Long): Unit = commonReadFully(sink, byteCount) + override fun readFully(sink: RawSink, byteCount: Long): Unit = commonReadFully(sink, byteCount) override fun readAll(sink: RawSink): Long = commonReadAll(sink) diff --git a/core/jvm/src/RealSource.kt b/core/jvm/src/RealSource.kt index 43a2e1c5b..1e81e605b 100644 --- a/core/jvm/src/RealSource.kt +++ b/core/jvm/src/RealSource.kt @@ -43,7 +43,7 @@ internal actual class RealSource actual constructor( override fun read(sink: ByteArray, offset: Int, byteCount: Int): Int = commonRead(sink, offset, byteCount) - override fun readFully(sink: Buffer, byteCount: Long): Unit = commonReadFully(sink, byteCount) + override fun readFully(sink: RawSink, byteCount: Long): Unit = commonReadFully(sink, byteCount) override fun readAll(sink: RawSink): Long = commonReadAll(sink) override fun readShort(): Short = commonReadShort() diff --git a/core/jvm/src/Source.kt b/core/jvm/src/Source.kt index 7e15b57fd..e3955a017 100644 --- a/core/jvm/src/Source.kt +++ b/core/jvm/src/Source.kt @@ -47,7 +47,7 @@ public actual sealed interface Source : RawSource { public actual fun read(sink: ByteArray, offset: Int, byteCount: Int): Int - public actual fun readFully(sink: Buffer, byteCount: Long) + public actual fun readFully(sink: RawSink, byteCount: Long) public actual fun readAll(sink: RawSink): Long diff --git a/core/native/src/Buffer.kt b/core/native/src/Buffer.kt index d24f5bee7..8c45d8d14 100644 --- a/core/native/src/Buffer.kt +++ b/core/native/src/Buffer.kt @@ -62,7 +62,7 @@ public actual class Buffer : Source, Sink { override fun readLong(): Long = commonReadLong() - override fun readFully(sink: Buffer, byteCount: Long): Unit = commonReadFully(sink, byteCount) + override fun readFully(sink: RawSink, byteCount: Long): Unit = commonReadFully(sink, byteCount) override fun readAll(sink: RawSink): Long = commonReadAll(sink) diff --git a/core/native/src/RealSource.kt b/core/native/src/RealSource.kt index 5544a6e71..b8e49f632 100644 --- a/core/native/src/RealSource.kt +++ b/core/native/src/RealSource.kt @@ -36,7 +36,7 @@ internal actual class RealSource actual constructor( override fun read(sink: ByteArray, offset: Int, byteCount: Int): Int = commonRead(sink, offset, byteCount) - override fun readFully(sink: Buffer, byteCount: Long): Unit = commonReadFully(sink, byteCount) + override fun readFully(sink: RawSink, byteCount: Long): Unit = commonReadFully(sink, byteCount) override fun readAll(sink: RawSink): Long = commonReadAll(sink) override fun readShort(): Short = commonReadShort() override fun readInt(): Int = commonReadInt() diff --git a/core/native/src/Source.kt b/core/native/src/Source.kt index 2dbf1bb7d..8ed33177b 100644 --- a/core/native/src/Source.kt +++ b/core/native/src/Source.kt @@ -41,7 +41,7 @@ public actual sealed interface Source : RawSource { public actual fun read(sink: ByteArray, offset: Int, byteCount: Int): Int - public actual fun readFully(sink: Buffer, byteCount: Long) + public actual fun readFully(sink: RawSink, byteCount: Long) public actual fun readAll(sink: RawSink): Long From f349aa871e1706c3efdb1bcae823645f3dba92dc Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Fri, 9 Jun 2023 17:11:58 +0200 Subject: [PATCH 34/83] Fixed line reading tests --- core/common/test/AbstractSourceTest.kt | 95 +++++++++++++------------- 1 file changed, 47 insertions(+), 48 deletions(-) diff --git a/core/common/test/AbstractSourceTest.kt b/core/common/test/AbstractSourceTest.kt index 2898cf25b..74312d27f 100644 --- a/core/common/test/AbstractSourceTest.kt +++ b/core/common/test/AbstractSourceTest.kt @@ -1046,62 +1046,61 @@ abstract class AbstractBufferedSourceTest internal constructor( } } - // TODO: fix tests @Test fun readUtf8Line() { - val buf = Buffer().writeUtf8("first line\nsecond line\n") - assertEquals("first line", buf.readUtf8Line()) - assertEquals("second line\n", buf.readUtf8()) - assertEquals(null, buf.readUtf8Line()) + sink.writeUtf8("first line\nsecond line\n").flush() + assertEquals("first line", source.readUtf8Line()) + assertEquals("second line\n", source.readUtf8()) + assertEquals(null, source.readUtf8Line()) - buf.writeUtf8("\nnext line\n") - assertEquals("", buf.readUtf8Line()) - assertEquals("next line", buf.readUtf8Line()) + sink.writeUtf8("\nnext line\n").flush() + assertEquals("", source.readUtf8Line()) + assertEquals("next line", source.readUtf8Line()) - buf.writeUtf8("There is no newline!") - assertEquals("There is no newline!", buf.readUtf8Line()) + sink.writeUtf8("There is no newline!").flush() + assertEquals("There is no newline!", source.readUtf8Line()) - buf.writeUtf8("Wot do u call it?\r\nWindows") - assertEquals("Wot do u call it?", buf.readUtf8Line()) - buf.clear() + sink.writeUtf8("Wot do u call it?\r\nWindows").flush() + assertEquals("Wot do u call it?", source.readUtf8Line()) + source.readAll(blackholeSink()) - buf.writeUtf8("reo\rde\red\n") - assertEquals("reo\rde\red", buf.readUtf8Line()) + sink.writeUtf8("reo\rde\red\n").flush() + assertEquals("reo\rde\red", source.readUtf8Line()) } @Test fun readUtf8LineStrict() { - val buf = Buffer().writeUtf8("first line\nsecond line\n") - assertEquals("first line", buf.readUtf8LineStrict()) - assertEquals("second line\n", buf.readUtf8()) - assertFailsWith { buf.readUtf8LineStrict() } - - buf.writeUtf8("\nnext line\n") - assertEquals("", buf.readUtf8LineStrict()) - assertEquals("next line", buf.readUtf8LineStrict()) - - buf.writeUtf8("There is no newline!") - assertFailsWith { buf.readUtf8LineStrict() } - assertEquals("There is no newline!", buf.readUtf8()) - - buf.writeUtf8("Wot do u call it?\r\nWindows") - assertEquals("Wot do u call it?", buf.readUtf8LineStrict()) - buf.clear() - - buf.writeUtf8("reo\rde\red\n") - assertEquals("reo\rde\red", buf.readUtf8LineStrict()) - - buf.writeUtf8("line\n") - assertFailsWith { buf.readUtf8LineStrict(3) } - assertEquals("line", buf.readUtf8LineStrict(4)) - assertEquals(0, buf.size) - - buf.writeUtf8("line\r\n") - assertFailsWith { buf.readUtf8LineStrict(3) } - assertEquals("line", buf.readUtf8LineStrict(4)) - assertEquals(0, buf.size) - - buf.writeUtf8("line\n") - assertEquals("line", buf.readUtf8LineStrict(5)) - assertEquals(0, buf.size) + sink.writeUtf8("first line\nsecond line\n").flush() + assertEquals("first line", source.readUtf8LineStrict()) + assertEquals("second line\n", source.readUtf8()) + assertFailsWith { source.readUtf8LineStrict() } + + sink.writeUtf8("\nnext line\n").flush() + assertEquals("", source.readUtf8LineStrict()) + assertEquals("next line", source.readUtf8LineStrict()) + + sink.writeUtf8("There is no newline!").flush() + assertFailsWith { source.readUtf8LineStrict() } + assertEquals("There is no newline!", source.readUtf8()) + + sink.writeUtf8("Wot do u call it?\r\nWindows").flush() + assertEquals("Wot do u call it?", source.readUtf8LineStrict()) + source.readAll(blackholeSink()) + + sink.writeUtf8("reo\rde\red\n").flush() + assertEquals("reo\rde\red", source.readUtf8LineStrict()) + + sink.writeUtf8("line\n").flush() + assertFailsWith { source.readUtf8LineStrict(3) } + assertEquals("line", source.readUtf8LineStrict(4)) + assertTrue(source.exhausted()) + + sink.writeUtf8("line\r\n").flush() + assertFailsWith { source.readUtf8LineStrict(3) } + assertEquals("line", source.readUtf8LineStrict(4)) + assertTrue(source.exhausted()) + + sink.writeUtf8("line\n").flush() + assertEquals("line", source.readUtf8LineStrict(5)) + assertTrue(source.exhausted()) } @Test fun readUnsignedByte() { From f48baab4e1a42bb8a43ab69c529375a53aa5dac9 Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Fri, 9 Jun 2023 17:43:13 +0200 Subject: [PATCH 35/83] Cleanup --- core/common/src/Buffer.kt | 2 +- core/common/src/internal/-Buffer.kt | 2 +- core/common/test/CommonRealSinkTest.kt | 1 - core/jvm/src/RawSink.kt | 1 - core/jvm/src/RealSink.kt | 3 --- core/jvm/src/RealSource.kt | 3 --- 6 files changed, 2 insertions(+), 10 deletions(-) diff --git a/core/common/src/Buffer.kt b/core/common/src/Buffer.kt index ffe64ec50..387cb394c 100644 --- a/core/common/src/Buffer.kt +++ b/core/common/src/Buffer.kt @@ -33,7 +33,7 @@ package kotlinx.io * To reduce allocations and speed up the buffer's extension, it may use data segments pooling. * * [Buffer] implements both [Source] and [Sink] and could be used as a source or a sink, - * but unlike regular sinks and sources its [close], [cancel], [flush], [emit], [emitCompleteSegments] + * but unlike regular sinks and sources its [close], [flush], [emit], [emitCompleteSegments] * does not affect buffer's state and [exhausted] only indicates that a buffer is empty. */ public expect class Buffer() : Source, Sink { diff --git a/core/common/src/internal/-Buffer.kt b/core/common/src/internal/-Buffer.kt index 830c21838..ae4a91dff 100644 --- a/core/common/src/internal/-Buffer.kt +++ b/core/common/src/internal/-Buffer.kt @@ -610,7 +610,7 @@ internal inline fun Buffer.commonWrite(source: RawSource, byteCount: Long): Buff internal inline fun Buffer.commonWriteByte(b: Byte): Buffer { val tail = writableSegment(1) - tail.data[tail.limit++] = b.toByte() + tail.data[tail.limit++] = b size += 1L return this } diff --git a/core/common/test/CommonRealSinkTest.kt b/core/common/test/CommonRealSinkTest.kt index f0b1a6269..45b6f7a68 100644 --- a/core/common/test/CommonRealSinkTest.kt +++ b/core/common/test/CommonRealSinkTest.kt @@ -24,7 +24,6 @@ package kotlinx.io import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFailsWith -import kotlin.test.fail /** * Tests solely for the behavior of RealBufferedSink's implementation. For generic diff --git a/core/jvm/src/RawSink.kt b/core/jvm/src/RawSink.kt index 8cc18a2b9..0ac69a121 100644 --- a/core/jvm/src/RawSink.kt +++ b/core/jvm/src/RawSink.kt @@ -22,7 +22,6 @@ package kotlinx.io import java.io.Closeable import java.io.Flushable -import java.io.IOException public actual interface RawSink : Closeable, Flushable { public actual fun write(source: Buffer, byteCount: Long) diff --git a/core/jvm/src/RealSink.kt b/core/jvm/src/RealSink.kt index 679b8b837..5485a3988 100644 --- a/core/jvm/src/RealSink.kt +++ b/core/jvm/src/RealSink.kt @@ -20,9 +20,6 @@ */ package kotlinx.io -import java.io.IOException -import java.io.OutputStream -import java.nio.ByteBuffer import kotlinx.io.internal.* internal actual class RealSink actual constructor( diff --git a/core/jvm/src/RealSource.kt b/core/jvm/src/RealSource.kt index 1e81e605b..569dd9be5 100644 --- a/core/jvm/src/RealSource.kt +++ b/core/jvm/src/RealSource.kt @@ -20,9 +20,6 @@ */ package kotlinx.io -import java.io.IOException -import java.io.InputStream -import java.nio.ByteBuffer import kotlinx.io.internal.* internal actual class RealSource actual constructor( From ddfcd1148d4994d5edf5f45a56f2507f828c3e41 Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Mon, 12 Jun 2023 09:57:37 +0200 Subject: [PATCH 36/83] Annotate methods using delicate API with corresponding OptIn --- core/api/kotlinx-io-core.api | 1 + core/common/src/PeekSource.kt | 1 + core/common/src/SourceExt.kt | 6 ++++ core/common/src/Utf8.kt | 15 ++++++---- core/common/src/internal/-RealBufferedSink.kt | 12 ++++++++ .../src/internal/-RealBufferedSource.kt | 29 ++++++++----------- 6 files changed, 42 insertions(+), 22 deletions(-) diff --git a/core/api/kotlinx-io-core.api b/core/api/kotlinx-io-core.api index 4147e5e0e..6e1a4731a 100644 --- a/core/api/kotlinx-io-core.api +++ b/core/api/kotlinx-io-core.api @@ -172,6 +172,7 @@ public final class kotlinx/io/SourceExtKt { public static final fun readULongLe (Lkotlinx/io/Source;)J public static final fun readUShort (Lkotlinx/io/Source;)S public static final fun readUShortLe (Lkotlinx/io/Source;)S + public static final fun startsWith (Lkotlinx/io/Source;B)Z } public final class kotlinx/io/SourceKt { diff --git a/core/common/src/PeekSource.kt b/core/common/src/PeekSource.kt index ca4fa53a5..61113f75b 100644 --- a/core/common/src/PeekSource.kt +++ b/core/common/src/PeekSource.kt @@ -33,6 +33,7 @@ package kotlinx.io internal class PeekSource( private val upstream: Source ) : RawSource { + @OptIn(DelicateIoApi::class) private val buffer = upstream.buffer private var expectedSegment = buffer.head private var expectedPos = buffer.head?.pos ?: -1 diff --git a/core/common/src/SourceExt.kt b/core/common/src/SourceExt.kt index 16c2d9dd0..a741d4e74 100644 --- a/core/common/src/SourceExt.kt +++ b/core/common/src/SourceExt.kt @@ -46,6 +46,7 @@ internal const val OVERFLOW_DIGIT_START = Long.MIN_VALUE % 10L + 1 * number was not present. * @throws EOFException if the source is exhausted before a call of this method. */ +@OptIn(DelicateIoApi::class) public fun Source.readDecimalLong(): Long { require(1) var b = readByte() @@ -105,6 +106,7 @@ public fun Source.readDecimalLong(): Long { * hexadecimal was not found. * @throws EOFException if the source is exhausted before a call of this method. */ +@OptIn(DelicateIoApi::class) public fun Source.readHexadecimalUnsignedLong(): Long { require(1) var b = readByte() @@ -184,6 +186,7 @@ public fun Source.readByteArray(byteCount: Long): ByteArray { return readByteArrayImpl(byteCount) } +@OptIn(DelicateIoApi::class) @Suppress("NOTHING_TO_INLINE") private inline fun Source.readByteArrayImpl(size: Long): ByteArray { var arraySize = size @@ -276,3 +279,6 @@ public fun Source.readUIntLe(): UInt = readIntLe().toUInt() * @throws EOFException when there are not enough data to read an unsigned long value. */ public fun Source.readULongLe(): ULong = readLongLe().toULong() + +@OptIn(DelicateIoApi::class) +public fun Source.startsWith(byte: Byte): Boolean = request(1) && buffer[0] == byte \ No newline at end of file diff --git a/core/common/src/Utf8.kt b/core/common/src/Utf8.kt index d7849c6d9..b79f58e85 100644 --- a/core/common/src/Utf8.kt +++ b/core/common/src/Utf8.kt @@ -127,6 +127,7 @@ public fun String.utf8Size(beginIndex: Int = 0, endIndex: Int = length): Long { * * @param codePoint the codePoint to be written. */ +@OptIn(DelicateIoApi::class) public fun T.writeUtf8CodePoint(codePoint: Int): T { buffer.commonWriteUtf8CodePoint(codePoint) emitCompleteSegments() @@ -143,6 +144,7 @@ public fun T.writeUtf8CodePoint(codePoint: Int): T { * @throws IndexOutOfBoundsException when [beginIndex] or [endIndex] correspond to a range * out of the current string bounds. */ +@OptIn(DelicateIoApi::class) public fun T.writeUtf8(string: String, beginIndex: Int = 0, endIndex: Int = string.length): T { buffer.commonWriteUtf8(string, beginIndex, endIndex) emitCompleteSegments() @@ -154,6 +156,7 @@ public fun T.writeUtf8(string: String, beginIndex: Int = 0, endIndex: * * Returns the empty string if this source is empty. */ +@OptIn(DelicateIoApi::class) public fun Source.readUtf8(): String { var req: Long = 1 while (request(req)) { @@ -168,7 +171,7 @@ public fun Source.readUtf8(): String { * Returns the empty string if this buffer is empty. */ public fun Buffer.readUtf8(): String { - return commonReadUtf8(buffer.size) + return commonReadUtf8(size) } /** @@ -179,6 +182,7 @@ public fun Buffer.readUtf8(): String { * @throws IllegalArgumentException when [byteCount] is negative. * @throws EOFException when the source is exhausted before reading [byteCount] bytes from it. */ +@OptIn(DelicateIoApi::class) public fun Source.readUtf8(byteCount: Long): String { require(byteCount) return buffer.commonReadUtf8(byteCount) @@ -198,6 +202,7 @@ public fun Source.readUtf8(byteCount: Long): String { * * @throws EOFException when the source is exhausted before a complete code point can be read. */ +@OptIn(DelicateIoApi::class) public fun Source.readUtf8CodePoint(): Int { require(1) @@ -215,7 +220,7 @@ public fun Source.readUtf8CodePoint(): Int { * @see Source.readUtf8CodePoint */ public fun Buffer.readUtf8CodePoint(): Int { - return buffer.commonReadUtf8CodePoint() + return this.commonReadUtf8CodePoint() } /** @@ -237,7 +242,7 @@ public fun Source.readUtf8Line(): String? { newlineSize = 1L break } else if (b == '\r'.code) { - if (peekSource.request(1) && peekSource.buffer[0].toInt() == '\n'.code) { + if (peekSource.startsWith('\n'.code.toByte())) { newlineSize = 2L break } @@ -277,7 +282,7 @@ public fun Source.readUtf8LineStrict(limit: Long = Long.MAX_VALUE): String { newlineSize = 1L break } else if (b == '\r'.code) { - if (peekSource.request(1) && peekSource.buffer[0].toInt() == '\n'.code) { + if (peekSource.startsWith('\n'.code.toByte())) { newlineSize = 2L break } @@ -289,7 +294,7 @@ public fun Source.readUtf8LineStrict(limit: Long = Long.MAX_VALUE): String { val nlCandidate = peekSource.readByte().toInt() if (nlCandidate == '\n'.code) { newlineSize = 1 - } else if (nlCandidate == '\r'.code && peekSource.request(1) && peekSource.readByte().toInt() == '\n'.code) { + } else if (nlCandidate == '\r'.code && peekSource.startsWith('\n'.code.toByte())) { newlineSize = 2 } } diff --git a/core/common/src/internal/-RealBufferedSink.kt b/core/common/src/internal/-RealBufferedSink.kt index 2cbdb7f13..431dd346e 100644 --- a/core/common/src/internal/-RealBufferedSink.kt +++ b/core/common/src/internal/-RealBufferedSink.kt @@ -26,6 +26,7 @@ package kotlinx.io.internal import kotlinx.io.* +@OptIn(DelicateIoApi::class) internal inline fun RealSink.commonWrite(source: Buffer, byteCount: Long) { require(byteCount >= 0) { "byteCount ($byteCount) should not be negative." } check(!closed) { "closed" } @@ -33,6 +34,7 @@ internal inline fun RealSink.commonWrite(source: Buffer, byteCount: Long) { emitCompleteSegments() } +@OptIn(DelicateIoApi::class) internal inline fun RealSink.commonWrite( source: ByteArray, offset: Int, @@ -44,6 +46,7 @@ internal inline fun RealSink.commonWrite( return emitCompleteSegments() } +@OptIn(DelicateIoApi::class) internal inline fun RealSink.commonWriteAll(source: RawSource): Long { var totalBytesRead = 0L while (true) { @@ -55,6 +58,7 @@ internal inline fun RealSink.commonWriteAll(source: RawSource): Long { return totalBytesRead } +@OptIn(DelicateIoApi::class) internal inline fun RealSink.commonWrite(source: RawSource, byteCount: Long): Sink { require(byteCount >= 0) { "byteCount ($byteCount) should not be negative."} var remainingByteCount = byteCount @@ -67,30 +71,35 @@ internal inline fun RealSink.commonWrite(source: RawSource, byteCount: Long): Si return this } +@OptIn(DelicateIoApi::class) internal inline fun RealSink.commonWriteByte(b: Byte): Sink { check(!closed) { "closed" } buffer.writeByte(b) return emitCompleteSegments() } +@OptIn(DelicateIoApi::class) internal inline fun RealSink.commonWriteShort(s: Short): Sink { check(!closed) { "closed" } buffer.writeShort(s) return emitCompleteSegments() } +@OptIn(DelicateIoApi::class) internal inline fun RealSink.commonWriteInt(i: Int): Sink { check(!closed) { "closed" } buffer.writeInt(i) return emitCompleteSegments() } +@OptIn(DelicateIoApi::class) internal inline fun RealSink.commonWriteLong(v: Long): Sink { check(!closed) { "closed" } buffer.writeLong(v) return emitCompleteSegments() } +@OptIn(DelicateIoApi::class) internal inline fun RealSink.commonEmitCompleteSegments(): Sink { check(!closed) { "closed" } val byteCount = buffer.completeSegmentByteCount() @@ -98,6 +107,7 @@ internal inline fun RealSink.commonEmitCompleteSegments(): Sink { return this } +@OptIn(DelicateIoApi::class) internal inline fun RealSink.commonEmit(): Sink { check(!closed) { "closed" } val byteCount = buffer.size @@ -105,6 +115,7 @@ internal inline fun RealSink.commonEmit(): Sink { return this } +@OptIn(DelicateIoApi::class) internal inline fun RealSink.commonFlush() { check(!closed) { "closed" } if (buffer.size > 0L) { @@ -113,6 +124,7 @@ internal inline fun RealSink.commonFlush() { sink.flush() } +@OptIn(DelicateIoApi::class) internal inline fun RealSink.commonClose() { if (closed) return diff --git a/core/common/src/internal/-RealBufferedSource.kt b/core/common/src/internal/-RealBufferedSource.kt index 2d372e176..ca20aa920 100644 --- a/core/common/src/internal/-RealBufferedSource.kt +++ b/core/common/src/internal/-RealBufferedSource.kt @@ -27,6 +27,7 @@ package kotlinx.io.internal import kotlinx.io.* +@OptIn(DelicateIoApi::class) internal inline fun RealSource.commonRead(sink: Buffer, byteCount: Long): Long { require(byteCount >= 0L) { "byteCount < 0: $byteCount" } check(!closed) { "closed" } @@ -40,6 +41,7 @@ internal inline fun RealSource.commonRead(sink: Buffer, byteCount: Long): Long { return buffer.read(sink, toRead) } +@OptIn(DelicateIoApi::class) internal inline fun RealSource.commonExhausted(): Boolean { check(!closed) { "closed" } return buffer.exhausted() && source.read(buffer, Segment.SIZE.toLong()) == -1L @@ -49,6 +51,7 @@ internal inline fun RealSource.commonRequire(byteCount: Long) { if (!request(byteCount)) throw EOFException() } +@OptIn(DelicateIoApi::class) internal inline fun RealSource.commonRequest(byteCount: Long): Boolean { require(byteCount >= 0L) { "byteCount < 0: $byteCount" } check(!closed) { "closed" } @@ -58,28 +61,13 @@ internal inline fun RealSource.commonRequest(byteCount: Long): Boolean { return true } +@OptIn(DelicateIoApi::class) internal inline fun RealSource.commonReadByte(): Byte { require(1) return buffer.readByte() } -internal inline fun RealSource.commonReadFully(sink: ByteArray) { - try { - require(sink.size.toLong()) - } catch (e: EOFException) { - // The underlying source is exhausted. Copy the bytes we got before rethrowing. - var offset = 0 - while (buffer.size > 0L) { - val read = buffer.read(sink, offset, buffer.size.toInt()) - if (read == -1) throw AssertionError() - offset += read - } - throw e - } - - buffer.readFully(sink) -} - +@OptIn(DelicateIoApi::class) internal inline fun RealSource.commonRead(sink: ByteArray, offset: Int, byteCount: Int): Int { checkOffsetAndCount(sink.size.toLong(), offset.toLong(), byteCount.toLong()) @@ -92,6 +80,7 @@ internal inline fun RealSource.commonRead(sink: ByteArray, offset: Int, byteCoun return buffer.read(sink, offset, toRead) } +@OptIn(DelicateIoApi::class) internal inline fun RealSource.commonReadFully(sink: RawSink, byteCount: Long) { try { require(byteCount) @@ -106,6 +95,7 @@ internal inline fun RealSource.commonReadFully(sink: RawSink, byteCount: Long) { buffer.readFully(sink, byteCount) } +@OptIn(DelicateIoApi::class) internal inline fun RealSource.commonReadAll(sink: RawSink): Long { var totalBytesWritten: Long = 0 while (source.read(buffer, Segment.SIZE.toLong()) != -1L) { @@ -122,21 +112,25 @@ internal inline fun RealSource.commonReadAll(sink: RawSink): Long { return totalBytesWritten } +@OptIn(DelicateIoApi::class) internal inline fun RealSource.commonReadShort(): Short { require(2) return buffer.readShort() } +@OptIn(DelicateIoApi::class) internal inline fun RealSource.commonReadInt(): Int { require(4) return buffer.readInt() } +@OptIn(DelicateIoApi::class) internal inline fun RealSource.commonReadLong(): Long { require(8) return buffer.readLong() } +@OptIn(DelicateIoApi::class) internal inline fun RealSource.commonSkip(byteCount: Long) { var remainingByteCount = byteCount check(!closed) { "closed" } @@ -154,6 +148,7 @@ internal inline fun RealSource.commonPeek(): Source { return PeekSource(this).buffer() } +@OptIn(DelicateIoApi::class) internal inline fun RealSource.commonClose() { if (closed) return closed = true From dc35855fa234b1eb3c4421608c04a4945787c1ff Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Mon, 12 Jun 2023 10:33:52 +0200 Subject: [PATCH 37/83] Cleanup comments and docs --- core/common/src/-CommonPlatform.kt | 3 --- core/common/src/Buffer.kt | 2 -- core/common/src/RawSink.kt | 3 --- core/common/src/RawSource.kt | 2 -- core/common/src/Sink.kt | 4 +--- core/common/src/Source.kt | 2 -- core/common/src/SourceExt.kt | 1 - core/common/src/Utf8.kt | 2 +- core/common/src/files/Paths.kt | 20 ++++++++++++------- .../src/internal/-RealBufferedSource.kt | 2 -- core/jvm/src/Buffer.kt | 4 ---- core/jvm/src/JvmCore.kt | 2 -- 12 files changed, 15 insertions(+), 32 deletions(-) diff --git a/core/common/src/-CommonPlatform.kt b/core/common/src/-CommonPlatform.kt index 2612531aa..c1052b42c 100644 --- a/core/common/src/-CommonPlatform.kt +++ b/core/common/src/-CommonPlatform.kt @@ -23,9 +23,6 @@ package kotlinx.io internal expect fun String.asUtf8ToByteArray(): ByteArray -// TODO make internal https://youtrack.jetbrains.com/issue/KT-37316 -// expect class ArrayIndexOutOfBoundsException(message: String?) : IndexOutOfBoundsException - public expect open class IOException(message: String?, cause: Throwable?) : Exception { public constructor(message: String? = null) } diff --git a/core/common/src/Buffer.kt b/core/common/src/Buffer.kt index 387cb394c..0582c8cae 100644 --- a/core/common/src/Buffer.kt +++ b/core/common/src/Buffer.kt @@ -106,12 +106,10 @@ public expect class Buffer() : Source, Sink { */ public fun clear() - // TODO: figure out what this method may actually throw /** * Discards [byteCount]` bytes from the head of this buffer. * * @throws IllegalArgumentException when [byteCount] is negative. - * @throws ??? when [byteCount] exceeds buffer's [size]. */ override fun skip(byteCount: Long) diff --git a/core/common/src/RawSink.kt b/core/common/src/RawSink.kt index 70cc7d379..8071f2eca 100644 --- a/core/common/src/RawSink.kt +++ b/core/common/src/RawSink.kt @@ -32,7 +32,6 @@ package kotlinx.io * */ public expect interface RawSink : Closeable { - // TODO: should it throw EOFException instead? /** * Removes [byteCount] bytes from [source] and appends them to this sink. * @@ -40,7 +39,6 @@ public expect interface RawSink : Closeable { * @param byteCount the number of bytes to write. * * @throws IndexOutOfBoundsException when the [source]'s size is below [byteCount]. - * @throws IOException if the sink was canceled. */ public fun write(source: Buffer, byteCount: Long) @@ -49,7 +47,6 @@ public expect interface RawSink : Closeable { */ public fun flush() - // TODO: what kind of error it is to write into a closed sink? /** * Pushes all buffered bytes to their final destination and releases the resources held by this * sink. It is an error to write a closed sink. It is safe to close a sink more than once. diff --git a/core/common/src/RawSource.kt b/core/common/src/RawSource.kt index cebfb0574..5f40ca4ff 100644 --- a/core/common/src/RawSource.kt +++ b/core/common/src/RawSource.kt @@ -31,7 +31,6 @@ package kotlinx.io * is both more efficient and more convenient. Use [buffer] to wrap any raw source with a buffer. */ public interface RawSource : Closeable { - // TODO: should be throw something if byteCount = 0? /** * Removes at least 1, and up to [byteCount] bytes from this source and appends them to [sink]. * Returns the number of bytes read, or -1 if this source is exhausted. @@ -40,7 +39,6 @@ public interface RawSource : Closeable { * @param byteCount the number of bytes to read. * * @throws IllegalArgumentException when [byteCount] is negative. - * @throws IOException if the source was canceled. */ public fun read(sink: Buffer, byteCount: Long): Long diff --git a/core/common/src/Sink.kt b/core/common/src/Sink.kt index b72e7bd1e..39439e900 100644 --- a/core/common/src/Sink.kt +++ b/core/common/src/Sink.kt @@ -60,8 +60,6 @@ public expect sealed interface Sink : RawSink { * * @throws IndexOutOfBoundsException when a range specified by [offset] and [byteCount] * is out of range of [source] array indices. - * - * @sample kotlinx.io.AbstractSinkTest.writeByteArray */ public fun write(source: ByteArray, offset: Int = 0, byteCount: Int = source.size - offset): Sink @@ -77,7 +75,7 @@ public expect sealed interface Sink : RawSink { * Removes [byteCount] bytes from [source] and write them to this sink. * * If [source] will be exhausted before reading [byteCount] from it then an exception throws on - * attempt to read remaining bytes will be propagated to a caller of this method. + * an attempt to read remaining bytes will be propagated to a caller of this method. * * @param source the source to consume data from. * @param byteCount the number of bytes to read from [source] and to write into this sink. diff --git a/core/common/src/Source.kt b/core/common/src/Source.kt index 6c8789553..5df2abf36 100644 --- a/core/common/src/Source.kt +++ b/core/common/src/Source.kt @@ -51,8 +51,6 @@ public expect sealed interface Source : RawSource { * Returns true if there are no more bytes in this source. * * The call of this method will block until there are bytes to read or the source is definitely exhausted. - * - * @throws ??? when the source is closed. */ public fun exhausted(): Boolean diff --git a/core/common/src/SourceExt.kt b/core/common/src/SourceExt.kt index a741d4e74..093d370aa 100644 --- a/core/common/src/SourceExt.kt +++ b/core/common/src/SourceExt.kt @@ -217,7 +217,6 @@ public fun Source.readFully(sink: ByteArray) { var offset = 0 while (offset < sink.size) { val bytesRead = read(sink, offset) - // TODO: can we read 0 bytes indefinitely? if (bytesRead == -1) { throw EOFException() } diff --git a/core/common/src/Utf8.kt b/core/common/src/Utf8.kt index b79f58e85..799e86abf 100644 --- a/core/common/src/Utf8.kt +++ b/core/common/src/Utf8.kt @@ -138,7 +138,7 @@ public fun T.writeUtf8CodePoint(codePoint: Int): T { * Encodes the characters at [beginIndex] up to [endIndex] from [string] in UTF-8 and writes it to this sink. * * @param string the string to be encoded. - * @param beginIndex the index of a first character to encode, 0 by default. + * @param beginIndex the index of the first character to encode, 0 by default. * @param endIndex the index of a character past to a last character to encode, `string.length` by default. * * @throws IndexOutOfBoundsException when [beginIndex] or [endIndex] correspond to a range diff --git a/core/common/src/files/Paths.kt b/core/common/src/files/Paths.kt index e2e1b56b9..cae515596 100644 --- a/core/common/src/files/Paths.kt +++ b/core/common/src/files/Paths.kt @@ -7,18 +7,24 @@ package kotlinx.io.files import kotlinx.io.* -/* - * The very base skeleton just to play around +/** + * A wrapper around a string representing a file path allowing to read from and write to a + * corresponding file using [Sink] and [Source]. */ - public expect class Path -// Returns Path for the given string without much of a validation +/** + * Returns Path for the given string without much of a validation. + */ public expect fun Path(path: String): Path -// Returns source for the given file or throws if path is not a file or does not exist +/** + * Returns [Source] for the given file or throws if path is not a file or does not exist + */ public expect fun Path.source(): Source -// Returns sink for the given path, creates file if it doesn't exist, throws if it's directory, -// overwrites contents +/** + * Returns [Sink] for the given path, creates file if it doesn't exist, throws if it's a directory, + * overwrites contents. + */ public expect fun Path.sink(): Sink diff --git a/core/common/src/internal/-RealBufferedSource.kt b/core/common/src/internal/-RealBufferedSource.kt index ca20aa920..f533fa8b8 100644 --- a/core/common/src/internal/-RealBufferedSource.kt +++ b/core/common/src/internal/-RealBufferedSource.kt @@ -86,8 +86,6 @@ internal inline fun RealSource.commonReadFully(sink: RawSink, byteCount: Long) { require(byteCount) } catch (e: EOFException) { // The underlying source is exhausted. Copy the bytes we got before rethrowing. - //sink.writeAll(buffer) - // TODO: it seems to be incorrect. sink.write(buffer, buffer.size) throw e } diff --git a/core/jvm/src/Buffer.kt b/core/jvm/src/Buffer.kt index a75523704..d4db07124 100644 --- a/core/jvm/src/Buffer.kt +++ b/core/jvm/src/Buffer.kt @@ -135,8 +135,6 @@ public actual class Buffer : Source, Sink, Cloneable { * Read and exhaust bytes from [input] into this buffer. Stops reading data on [input] exhaustion. * * @param input the stream to read data from. - * - * @throws ??? */ public fun Buffer.readFrom(input: InputStream): Buffer { readFrom(input, Long.MAX_VALUE, true) @@ -184,8 +182,6 @@ private fun Buffer.readFrom(input: InputStream, byteCount: Long, forever: Boolea * * @param out the [OutputStream] to write to. * @param byteCount the number of bytes to be written, [Buffer.size] by default. - * - * @throws ??? */ public fun Buffer.writeTo(out: OutputStream, byteCount: Long = size): Buffer { checkOffsetAndCount(size, 0, byteCount) diff --git a/core/jvm/src/JvmCore.kt b/core/jvm/src/JvmCore.kt index f310c5204..62da3ec20 100644 --- a/core/jvm/src/JvmCore.kt +++ b/core/jvm/src/JvmCore.kt @@ -31,7 +31,6 @@ import java.nio.file.Files import java.nio.file.OpenOption import java.nio.file.Path as NioPath -// TODO: improve test coverage /** * Returns [RawSink] that writes to an output stream. * @@ -70,7 +69,6 @@ private open class OutputStreamSink( override fun toString() = "sink($out)" } -// TODO: improve test coverage /** * Returns [RawSource] that reads from an input stream. * From b82fb82a969d48341d653f481a209cd878a18dec Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Mon, 12 Jun 2023 10:34:07 +0200 Subject: [PATCH 38/83] Improve implementations --- core/common/src/SinkExt.kt | 116 ++++++++++++++++++++++++++-- core/common/src/Utf8.kt | 2 +- core/common/src/files/Paths.kt | 3 +- core/common/src/internal/-Buffer.kt | 4 +- core/jvm/src/JvmCore.kt | 5 +- 5 files changed, 116 insertions(+), 14 deletions(-) diff --git a/core/common/src/SinkExt.kt b/core/common/src/SinkExt.kt index a08688c74..dd2b75458 100644 --- a/core/common/src/SinkExt.kt +++ b/core/common/src/SinkExt.kt @@ -5,6 +5,11 @@ package kotlinx.io +import kotlin.native.concurrent.SharedImmutable + +@SharedImmutable +internal val HEX_DIGIT_BYTES = "0123456789abcdef".asUtf8ToByteArray() + /** * Writes two bytes containing [short], in the little-endian order, to this sink. * @@ -42,9 +47,72 @@ public fun T.writeLongLe(long: Long): T { * * @param long the long to be written. */ +@OptIn(DelicateIoApi::class) public fun T.writeDecimalLong(long: Long): T { - // TODO: optimize - writeUtf8(long.toString()) + var v = long + if (v == 0L) { + // Both a shortcut and required since the following code can't handle zero. + writeByte('0'.code.toByte()) + return this + } + + var negative = false + if (v < 0L) { + v = -v + if (v < 0L) { // Only true for Long.MIN_VALUE. + return writeUtf8("-9223372036854775808") + } + negative = true + } + + // Binary search for character width which favors matching lower numbers. + var width = + if (v < 100000000L) + if (v < 10000L) + if (v < 100L) + if (v < 10L) 1 + else 2 + else if (v < 1000L) 3 + else 4 + else if (v < 1000000L) + if (v < 100000L) 5 + else 6 + else if (v < 10000000L) 7 + else 8 + else if (v < 1000000000000L) + if (v < 10000000000L) + if (v < 1000000000L) 9 + else 10 + else if (v < 100000000000L) 11 + else 12 + else if (v < 1000000000000000L) + if (v < 10000000000000L) 13 + else if (v < 100000000000000L) 14 + else 15 + else if (v < 100000000000000000L) + if (v < 10000000000000000L) 16 + else 17 + else if (v < 1000000000000000000L) 18 + else 19 + if (negative) { + ++width + } + + val tail = buffer.writableSegment(width) + val data = tail.data + var pos = tail.limit + width // We write backwards from right to left. + while (v != 0L) { + val digit = (v % 10).toInt() + data[--pos] = HEX_DIGIT_BYTES[digit] + v /= 10 + } + if (negative) { + data[--pos] = '-'.code.toByte() + } + + tail.limit += width + buffer.size += width.toLong() + emitCompleteSegments() return this } @@ -55,13 +123,49 @@ public fun T.writeDecimalLong(long: Long): T { * * @param long the long to be written. */ +@OptIn(DelicateIoApi::class) public fun T.writeHexadecimalUnsignedLong(long: Long): T { - if (long == 0L) { + var v = long + if (v == 0L) { + // Both a shortcut and required since the following code can't handle zero. writeByte('0'.code.toByte()) - } else { - // TODO: optimize - writeUtf8(long.toHexString()) + return this + } + + // Mask every bit below the most significant bit to a 1 + // http://aggregate.org/MAGIC/#Most%20Significant%201%20Bit + var x = v + x = x or (x ushr 1) + x = x or (x ushr 2) + x = x or (x ushr 4) + x = x or (x ushr 8) + x = x or (x ushr 16) + x = x or (x ushr 32) + + // Count the number of 1s + // http://aggregate.org/MAGIC/#Population%20Count%20(Ones%20Count) + x -= x ushr 1 and 0x5555555555555555 + x = (x ushr 2 and 0x3333333333333333) + (x and 0x3333333333333333) + x = (x ushr 4) + x and 0x0f0f0f0f0f0f0f0f + x += x ushr 8 + x += x ushr 16 + x = (x and 0x3f) + ((x ushr 32) and 0x3f) + + // Round up to the nearest full byte + val width = ((x + 3) / 4).toInt() + + val tail = buffer.writableSegment(width) + val data = tail.data + var pos = tail.limit + width - 1 + val start = tail.limit + while (pos >= start) { + data[pos] = HEX_DIGIT_BYTES[(v and 0xF).toInt()] + v = v ushr 4 + pos-- } + tail.limit += width + buffer.size += width.toLong() + emitCompleteSegments() return this } diff --git a/core/common/src/Utf8.kt b/core/common/src/Utf8.kt index 799e86abf..fed9ccf76 100644 --- a/core/common/src/Utf8.kt +++ b/core/common/src/Utf8.kt @@ -158,7 +158,7 @@ public fun T.writeUtf8(string: String, beginIndex: Int = 0, endIndex: */ @OptIn(DelicateIoApi::class) public fun Source.readUtf8(): String { - var req: Long = 1 + var req: Long = Segment.SIZE.toLong() while (request(req)) { req *= 2 } diff --git a/core/common/src/files/Paths.kt b/core/common/src/files/Paths.kt index cae515596..412ade22b 100644 --- a/core/common/src/files/Paths.kt +++ b/core/common/src/files/Paths.kt @@ -5,7 +5,8 @@ package kotlinx.io.files -import kotlinx.io.* +import kotlinx.io.Sink +import kotlinx.io.Source /** * A wrapper around a string representing a file path allowing to read from and write to a diff --git a/core/common/src/internal/-Buffer.kt b/core/common/src/internal/-Buffer.kt index ae4a91dff..21f2bae2f 100644 --- a/core/common/src/internal/-Buffer.kt +++ b/core/common/src/internal/-Buffer.kt @@ -849,8 +849,8 @@ internal inline fun Buffer.commonString(): String { } } - val text = peek().readUtf8() - val escapedText = data.decodeToString() + val text = data.decodeToString() + val escapedText = text .substring(0, i) .replace("\\", "\\\\") .replace("\n", "\\n") diff --git a/core/jvm/src/JvmCore.kt b/core/jvm/src/JvmCore.kt index 62da3ec20..f03154f3f 100644 --- a/core/jvm/src/JvmCore.kt +++ b/core/jvm/src/JvmCore.kt @@ -21,11 +21,8 @@ package kotlinx.io -import java.io.File -import java.io.FileOutputStream +import java.io.* import java.io.IOException -import java.io.InputStream -import java.io.OutputStream import java.net.Socket import java.nio.file.Files import java.nio.file.OpenOption From 863cf29325ede69b6b5725a7a0256b810a8485a2 Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Mon, 12 Jun 2023 11:14:40 +0200 Subject: [PATCH 39/83] Rename tests --- core/common/test/BufferCommonTest.kt | 68 ----------------- core/common/test/CommonBufferTest.kt | 75 +++++++++++++++++-- ...monKotlinTest.kt => CommonPlatformTest.kt} | 2 +- core/common/test/CommonRealSinkTest.kt | 1 + core/common/test/CommonRealSourceTest.kt | 7 +- .../test/{Utf8KotlinTest.kt => Utf8Test.kt} | 2 +- 6 files changed, 75 insertions(+), 80 deletions(-) rename core/common/test/{CommonKotlinTest.kt => CommonPlatformTest.kt} (98%) rename core/common/test/{Utf8KotlinTest.kt => Utf8Test.kt} (99%) diff --git a/core/common/test/BufferCommonTest.kt b/core/common/test/BufferCommonTest.kt index 6936cf134..2188dab4f 100644 --- a/core/common/test/BufferCommonTest.kt +++ b/core/common/test/BufferCommonTest.kt @@ -25,71 +25,3 @@ import kotlin.test.assertEquals private const val SEGMENT_SIZE = Segment.SIZE -class BufferCommonTest { - @Test fun completeSegmentByteCountOnEmptyBuffer() { - val buffer = Buffer() - assertEquals(0, buffer.completeSegmentByteCount()) - } - - @Test fun completeSegmentByteCountOnBufferWithFullSegments() { - val buffer = Buffer() - buffer.writeUtf8("a".repeat(Segment.SIZE * 4)) - assertEquals((Segment.SIZE * 4).toLong(), buffer.completeSegmentByteCount()) - } - - @Test fun completeSegmentByteCountOnBufferWithIncompleteTailSegment() { - val buffer = Buffer() - buffer.writeUtf8("a".repeat(Segment.SIZE * 4 - 10)) - assertEquals((Segment.SIZE * 3).toLong(), buffer.completeSegmentByteCount()) - } - - @Test - fun cloneDoesNotObserveWritesToOriginal() { - val original = Buffer() - val clone: Buffer = original.copy() - original.writeUtf8("abc") - assertEquals(0, clone.size) - } - - @Test - fun cloneDoesNotObserveReadsFromOriginal() { - val original = Buffer() - original.writeUtf8("abc") - val clone: Buffer = original.copy() - assertEquals("abc", original.readUtf8(3)) - assertEquals(3, clone.size) - assertEquals("ab", clone.readUtf8(2)) - } - - @Test - fun originalDoesNotObserveWritesToClone() { - val original = Buffer() - val clone: Buffer = original.copy() - clone.writeUtf8("abc") - assertEquals(0, original.size) - } - - @Test - fun originalDoesNotObserveReadsFromClone() { - val original = Buffer() - original.writeUtf8("abc") - val clone: Buffer = original.copy() - assertEquals("abc", clone.readUtf8(3)) - assertEquals(3, original.size) - assertEquals("ab", original.readUtf8(2)) - } - - @Test - fun cloneMultipleSegments() { - val original = Buffer() - original.writeUtf8("a".repeat(SEGMENT_SIZE * 3)) - val clone: Buffer = original.copy() - original.writeUtf8("b".repeat(SEGMENT_SIZE * 3)) - clone.writeUtf8("c".repeat(SEGMENT_SIZE * 3)) - - assertEquals("a".repeat(SEGMENT_SIZE * 3) + "b".repeat(SEGMENT_SIZE * 3), - original.readUtf8((SEGMENT_SIZE * 6).toLong())) - assertEquals("a".repeat( SEGMENT_SIZE * 3) + "c".repeat(SEGMENT_SIZE * 3), - clone.readUtf8((SEGMENT_SIZE * 6).toLong())) - } -} diff --git a/core/common/test/CommonBufferTest.kt b/core/common/test/CommonBufferTest.kt index bc40d5d9a..17aca61ed 100644 --- a/core/common/test/CommonBufferTest.kt +++ b/core/common/test/CommonBufferTest.kt @@ -21,11 +21,9 @@ package kotlinx.io import kotlin.random.Random -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertFailsWith -import kotlin.test.assertFalse -import kotlin.test.assertTrue +import kotlin.test.* + +private const val SEGMENT_SIZE = Segment.SIZE /** * Tests solely for the behavior of Buffer's implementation. For generic BufferedSink or @@ -419,4 +417,71 @@ class CommonBufferTest { assertEquals("aaa", source.readUtf8()) assertEquals("aaa", target.readUtf8()) } + + @Test fun completeSegmentByteCountOnEmptyBuffer() { + val buffer = Buffer() + assertEquals(0, buffer.completeSegmentByteCount()) + } + + @Test fun completeSegmentByteCountOnBufferWithFullSegments() { + val buffer = Buffer() + buffer.writeUtf8("a".repeat(Segment.SIZE * 4)) + assertEquals((Segment.SIZE * 4).toLong(), buffer.completeSegmentByteCount()) + } + + @Test fun completeSegmentByteCountOnBufferWithIncompleteTailSegment() { + val buffer = Buffer() + buffer.writeUtf8("a".repeat(Segment.SIZE * 4 - 10)) + assertEquals((Segment.SIZE * 3).toLong(), buffer.completeSegmentByteCount()) + } + + @Test + fun cloneDoesNotObserveWritesToOriginal() { + val original = Buffer() + val clone: Buffer = original.copy() + original.writeUtf8("abc") + assertEquals(0, clone.size) + } + + @Test + fun cloneDoesNotObserveReadsFromOriginal() { + val original = Buffer() + original.writeUtf8("abc") + val clone: Buffer = original.copy() + assertEquals("abc", original.readUtf8(3)) + assertEquals(3, clone.size) + assertEquals("ab", clone.readUtf8(2)) + } + + @Test + fun originalDoesNotObserveWritesToClone() { + val original = Buffer() + val clone: Buffer = original.copy() + clone.writeUtf8("abc") + assertEquals(0, original.size) + } + + @Test + fun originalDoesNotObserveReadsFromClone() { + val original = Buffer() + original.writeUtf8("abc") + val clone: Buffer = original.copy() + assertEquals("abc", clone.readUtf8(3)) + assertEquals(3, original.size) + assertEquals("ab", original.readUtf8(2)) + } + + @Test + fun cloneMultipleSegments() { + val original = Buffer() + original.writeUtf8("a".repeat(SEGMENT_SIZE * 3)) + val clone: Buffer = original.copy() + original.writeUtf8("b".repeat(SEGMENT_SIZE * 3)) + clone.writeUtf8("c".repeat(SEGMENT_SIZE * 3)) + + assertEquals("a".repeat(SEGMENT_SIZE * 3) + "b".repeat(SEGMENT_SIZE * 3), + original.readUtf8((SEGMENT_SIZE * 6).toLong())) + assertEquals("a".repeat( SEGMENT_SIZE * 3) + "c".repeat(SEGMENT_SIZE * 3), + clone.readUtf8((SEGMENT_SIZE * 6).toLong())) + } } diff --git a/core/common/test/CommonKotlinTest.kt b/core/common/test/CommonPlatformTest.kt similarity index 98% rename from core/common/test/CommonKotlinTest.kt rename to core/common/test/CommonPlatformTest.kt index db05e7f44..e80bfa8c6 100644 --- a/core/common/test/CommonKotlinTest.kt +++ b/core/common/test/CommonPlatformTest.kt @@ -24,7 +24,7 @@ package kotlinx.io import kotlin.test.Test import kotlin.test.assertEquals -class CommonKotlinTest { +class CommonPlatformTest { @Test fun sourceBuffer() { val source = Buffer().writeUtf8("a") val buffered = (source as RawSource).buffer() diff --git a/core/common/test/CommonRealSinkTest.kt b/core/common/test/CommonRealSinkTest.kt index 45b6f7a68..840dbd6f2 100644 --- a/core/common/test/CommonRealSinkTest.kt +++ b/core/common/test/CommonRealSinkTest.kt @@ -29,6 +29,7 @@ import kotlin.test.assertFailsWith * Tests solely for the behavior of RealBufferedSink's implementation. For generic * BufferedSink behavior use BufferedSinkTest. */ +@OptIn(DelicateIoApi::class) class CommonRealSinkTest { @Test fun bufferedSinkEmitsTailWhenItIsComplete() { val sink = Buffer() diff --git a/core/common/test/CommonRealSourceTest.kt b/core/common/test/CommonRealSourceTest.kt index 9782a3466..94fb8ae2a 100644 --- a/core/common/test/CommonRealSourceTest.kt +++ b/core/common/test/CommonRealSourceTest.kt @@ -27,8 +27,9 @@ import kotlin.test.assertFailsWith /** * Tests solely for the behavior of RealBufferedSource's implementation. For generic - * BufferedSource behavior use BufferedSourceTest. + * BufferedSource behavior, use BufferedSourceTest. */ +@OptIn(DelicateIoApi::class) class CommonRealSourceTest { @Test fun indexOfStopsReadingAtLimit() { val buffer = Buffer().writeUtf8("abcdef") @@ -130,10 +131,6 @@ class CommonRealSourceTest { assertFailsWith { bufferedSource.readByte() } - -// assertFailsWith { -// bufferedSource.readByteString(10) -// } } /** diff --git a/core/common/test/Utf8KotlinTest.kt b/core/common/test/Utf8Test.kt similarity index 99% rename from core/common/test/Utf8KotlinTest.kt rename to core/common/test/Utf8Test.kt index 27a29a02a..99de2305d 100644 --- a/core/common/test/Utf8KotlinTest.kt +++ b/core/common/test/Utf8Test.kt @@ -26,7 +26,7 @@ import kotlinx.io.internal.commonAsUtf8ToByteArray import kotlinx.io.internal.processUtf8CodePoints import kotlin.test.* -class Utf8KotlinTest { +class Utf8Test { @Test fun oneByteCharacters() { assertEncoded("00", 0x00) // Smallest 1-byte character. assertEncoded("20", ' '.code) From 08e7cd2e7e1d5cb09f7d49b03b2745e831d169a8 Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Mon, 12 Jun 2023 11:19:26 +0200 Subject: [PATCH 40/83] Cleanup --- core/common/src/SourceExt.kt | 2 +- core/common/src/Utf8.kt | 2 +- core/common/test/BufferCommonTest.kt | 27 -------------------------- core/common/test/util.kt | 2 +- core/jvm/src/-JvmPlatform.kt | 3 --- core/jvm/src/Buffer.kt | 2 +- core/jvm/src/Sink.kt | 2 +- core/jvm/src/Source.kt | 2 +- core/jvm/test/AbstractSinkTestJVM.kt | 6 ++++-- core/jvm/test/AbstractSourceTestJVM.kt | 7 +++++-- core/jvm/test/BufferTest.kt | 6 ++++-- core/jvm/test/JvmPlatformTest.kt | 2 +- core/jvm/test/utilJVM.kt | 5 +++-- core/native/src/-NonJvmPlatform.kt | 6 +----- 14 files changed, 24 insertions(+), 50 deletions(-) delete mode 100644 core/common/test/BufferCommonTest.kt diff --git a/core/common/src/SourceExt.kt b/core/common/src/SourceExt.kt index 093d370aa..d2ba07835 100644 --- a/core/common/src/SourceExt.kt +++ b/core/common/src/SourceExt.kt @@ -280,4 +280,4 @@ public fun Source.readUIntLe(): UInt = readIntLe().toUInt() public fun Source.readULongLe(): ULong = readLongLe().toULong() @OptIn(DelicateIoApi::class) -public fun Source.startsWith(byte: Byte): Boolean = request(1) && buffer[0] == byte \ No newline at end of file +public fun Source.startsWith(byte: Byte): Boolean = request(1) && buffer[0] == byte diff --git a/core/common/src/Utf8.kt b/core/common/src/Utf8.kt index fed9ccf76..a820dca9c 100644 --- a/core/common/src/Utf8.kt +++ b/core/common/src/Utf8.kt @@ -302,4 +302,4 @@ public fun Source.readUtf8LineStrict(limit: Long = Long.MAX_VALUE): String { val line = readUtf8(offset) skip(newlineSize) return line -} \ No newline at end of file +} diff --git a/core/common/test/BufferCommonTest.kt b/core/common/test/BufferCommonTest.kt deleted file mode 100644 index 2188dab4f..000000000 --- a/core/common/test/BufferCommonTest.kt +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright 2017-2023 JetBrains s.r.o. and respective authors and developers. - * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENCE file. - */ - -/* - * Copyright (C) 2019 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 kotlinx.io - -import kotlin.test.Test -import kotlin.test.assertEquals - -private const val SEGMENT_SIZE = Segment.SIZE - diff --git a/core/common/test/util.kt b/core/common/test/util.kt index 11d956eca..f6d5569eb 100644 --- a/core/common/test/util.kt +++ b/core/common/test/util.kt @@ -104,4 +104,4 @@ fun Char.repeat(count: Int): String { fun assertArrayEquals(a: ByteArray, b: ByteArray) { assertEquals(a.contentToString(), b.contentToString()) -} \ No newline at end of file +} diff --git a/core/jvm/src/-JvmPlatform.kt b/core/jvm/src/-JvmPlatform.kt index 73c458591..ba398de61 100644 --- a/core/jvm/src/-JvmPlatform.kt +++ b/core/jvm/src/-JvmPlatform.kt @@ -23,9 +23,6 @@ package kotlinx.io internal actual fun String.asUtf8ToByteArray(): ByteArray = toByteArray(Charsets.UTF_8) -// TODO remove if https://youtrack.jetbrains.com/issue/KT-20641 provides a better solution -// actual typealias ArrayIndexOutOfBoundsException = java.lang.ArrayIndexOutOfBoundsException - public actual typealias IOException = java.io.IOException public actual typealias EOFException = java.io.EOFException diff --git a/core/jvm/src/Buffer.kt b/core/jvm/src/Buffer.kt index d4db07124..581209ad6 100644 --- a/core/jvm/src/Buffer.kt +++ b/core/jvm/src/Buffer.kt @@ -302,4 +302,4 @@ public fun Buffer.channel(): ByteChannel = object : ByteChannel { override fun close() {} override fun isOpen(): Boolean = true -} \ No newline at end of file +} diff --git a/core/jvm/src/Sink.kt b/core/jvm/src/Sink.kt index 4e09173dc..ceede365f 100644 --- a/core/jvm/src/Sink.kt +++ b/core/jvm/src/Sink.kt @@ -140,4 +140,4 @@ public fun Sink.channel(): WritableByteChannel { return this@channel.write(source) } } -} \ No newline at end of file +} diff --git a/core/jvm/src/Source.kt b/core/jvm/src/Source.kt index e3955a017..563484e70 100644 --- a/core/jvm/src/Source.kt +++ b/core/jvm/src/Source.kt @@ -171,4 +171,4 @@ public fun Source.channel(): ReadableByteChannel { override fun read(sink: ByteBuffer): Int = this@channel.read(sink) } -} \ No newline at end of file +} diff --git a/core/jvm/test/AbstractSinkTestJVM.kt b/core/jvm/test/AbstractSinkTestJVM.kt index db2d4d9bd..571e3d101 100644 --- a/core/jvm/test/AbstractSinkTestJVM.kt +++ b/core/jvm/test/AbstractSinkTestJVM.kt @@ -24,7 +24,9 @@ package kotlinx.io import java.io.OutputStream import java.nio.ByteBuffer import java.nio.charset.Charset -import kotlin.test.* +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith import kotlin.text.Charsets.UTF_8 private const val SEGMENT_SIZE = Segment.SIZE @@ -137,4 +139,4 @@ abstract class AbstractSinkTestJVM internal constructor(factory: SinkFactory) { sink.flush() assertArrayEquals("ranə".toByteArray(UTF_8), data.readByteArray()) } -} \ No newline at end of file +} diff --git a/core/jvm/test/AbstractSourceTestJVM.kt b/core/jvm/test/AbstractSourceTestJVM.kt index 74890261c..815b62eee 100644 --- a/core/jvm/test/AbstractSourceTestJVM.kt +++ b/core/jvm/test/AbstractSourceTestJVM.kt @@ -25,7 +25,10 @@ import java.io.InputStream import java.nio.Buffer import java.nio.ByteBuffer import java.nio.charset.Charset -import kotlin.test.* +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith +import kotlin.test.assertTrue private const val SEGMENT_SIZE = Segment.SIZE @@ -249,4 +252,4 @@ abstract class AbstractSourceTestJVM(private val factory: SourceFactory) { } assertEquals("abc", source.readUtf8()) // The read shouldn't consume any data. } -} \ No newline at end of file +} diff --git a/core/jvm/test/BufferTest.kt b/core/jvm/test/BufferTest.kt index 12dcab2bb..78bf288a4 100644 --- a/core/jvm/test/BufferTest.kt +++ b/core/jvm/test/BufferTest.kt @@ -20,11 +20,13 @@ */ package kotlinx.io -import kotlin.test.* import java.io.ByteArrayInputStream import java.io.ByteArrayOutputStream import java.io.InputStream import java.util.* +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith import kotlin.text.Charsets.UTF_8 private const val SEGMENT_SIZE = Segment.SIZE @@ -223,4 +225,4 @@ class BufferTest { assertEquals("par", target.readUtf8()) assertEquals("ty", source.readUtf8()) } -} \ No newline at end of file +} diff --git a/core/jvm/test/JvmPlatformTest.kt b/core/jvm/test/JvmPlatformTest.kt index 37063e055..02201d2a7 100644 --- a/core/jvm/test/JvmPlatformTest.kt +++ b/core/jvm/test/JvmPlatformTest.kt @@ -132,4 +132,4 @@ class JvmPlatformTest { source.read(buffer, 1L) assertEquals(buffer.readUtf8(), "a") } -} \ No newline at end of file +} diff --git a/core/jvm/test/utilJVM.kt b/core/jvm/test/utilJVM.kt index 2c14a3d48..6aae75a50 100644 --- a/core/jvm/test/utilJVM.kt +++ b/core/jvm/test/utilJVM.kt @@ -4,7 +4,8 @@ */ package kotlinx.io -import java.nio.file.* +import java.nio.file.Files +import java.nio.file.Paths import kotlin.test.assertEquals import kotlin.test.assertTrue @@ -23,4 +24,4 @@ fun assertNoEmptySegments(buffer: Buffer) { fun assertByteArrayEquals(expectedUtf8: String, b: ByteArray) { assertEquals(expectedUtf8, b.toString(Charsets.UTF_8)) -} \ No newline at end of file +} diff --git a/core/native/src/-NonJvmPlatform.kt b/core/native/src/-NonJvmPlatform.kt index 5a9860468..c5c9ce398 100644 --- a/core/native/src/-NonJvmPlatform.kt +++ b/core/native/src/-NonJvmPlatform.kt @@ -20,14 +20,10 @@ */ package kotlinx.io -import kotlinx.io.internal.* +import kotlinx.io.internal.commonAsUtf8ToByteArray internal actual fun String.asUtf8ToByteArray(): ByteArray = commonAsUtf8ToByteArray() -//actual open class ArrayIndexOutOfBoundsException actual constructor( -// message: String? -//) : IndexOutOfBoundsException(message) - public actual open class IOException actual constructor( message: String?, cause: Throwable? From a9d113ee399100fbf53865fd84efd4bfb8129d8f Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Mon, 12 Jun 2023 14:38:28 +0200 Subject: [PATCH 41/83] Fixed imports --- benchmarks/src/jvmMain/kotlin/ByteBufferBenchmarks.kt | 7 +++++-- benchmarks/src/jvmMain/kotlin/StreamBenchmarks.kt | 8 +++++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/benchmarks/src/jvmMain/kotlin/ByteBufferBenchmarks.kt b/benchmarks/src/jvmMain/kotlin/ByteBufferBenchmarks.kt index af9b94182..51fe5053e 100644 --- a/benchmarks/src/jvmMain/kotlin/ByteBufferBenchmarks.kt +++ b/benchmarks/src/jvmMain/kotlin/ByteBufferBenchmarks.kt @@ -5,10 +5,13 @@ package kotlinx.io.benchmarks +import kotlinx.benchmark.Benchmark +import kotlinx.benchmark.Param +import kotlinx.benchmark.Setup +import kotlinx.io.read +import kotlinx.io.write import java.nio.ByteBuffer -import kotlinx.benchmark.* - open class ByteBufferReadWrite: BufferRWBenchmarkBase() { private var inputBuffer = ByteBuffer.allocate(0) private var outputBuffer = ByteBuffer.allocate(0) diff --git a/benchmarks/src/jvmMain/kotlin/StreamBenchmarks.kt b/benchmarks/src/jvmMain/kotlin/StreamBenchmarks.kt index 151ac2878..55ee1391d 100644 --- a/benchmarks/src/jvmMain/kotlin/StreamBenchmarks.kt +++ b/benchmarks/src/jvmMain/kotlin/StreamBenchmarks.kt @@ -5,7 +5,13 @@ package kotlinx.io.benchmarks -import kotlinx.benchmark.* +import kotlinx.benchmark.Benchmark +import kotlinx.benchmark.Blackhole +import kotlinx.benchmark.Param +import kotlinx.benchmark.Setup +import kotlinx.io.inputStream +import kotlinx.io.outputStream +import kotlinx.io.readFully open class InputStreamByteRead: BufferRWBenchmarkBase() { private val stream = buffer.inputStream() From e55e9b1ce57662977d439c7c2dd81fde11ed8e27 Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Mon, 12 Jun 2023 17:08:14 +0200 Subject: [PATCH 42/83] Don't override equals/hashCode for Buffer --- core/api/kotlinx-io-core.api | 2 -- core/common/test/CommonBufferTest.kt | 41 ++++++---------------------- core/jvm/src/Buffer.kt | 4 --- core/native/src/Buffer.kt | 4 --- 4 files changed, 9 insertions(+), 42 deletions(-) diff --git a/core/api/kotlinx-io-core.api b/core/api/kotlinx-io-core.api index 6e1a4731a..66c577c28 100644 --- a/core/api/kotlinx-io-core.api +++ b/core/api/kotlinx-io-core.api @@ -12,13 +12,11 @@ public final class kotlinx/io/Buffer : java/lang/Cloneable, kotlinx/io/Sink, kot public synthetic fun emit ()Lkotlinx/io/Sink; public fun emitCompleteSegments ()Lkotlinx/io/Buffer; public synthetic fun emitCompleteSegments ()Lkotlinx/io/Sink; - public fun equals (Ljava/lang/Object;)Z public fun exhausted ()Z public fun flush ()V public final fun get (J)B public fun getBuffer ()Lkotlinx/io/Buffer; public final fun getSize ()J - public fun hashCode ()I public fun peek ()Lkotlinx/io/Source; public fun read (Lkotlinx/io/Buffer;J)J public fun read ([BII)I diff --git a/core/common/test/CommonBufferTest.kt b/core/common/test/CommonBufferTest.kt index 17aca61ed..2b7a98fc1 100644 --- a/core/common/test/CommonBufferTest.kt +++ b/core/common/test/CommonBufferTest.kt @@ -20,8 +20,10 @@ */ package kotlinx.io -import kotlin.random.Random -import kotlin.test.* +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith +import kotlin.test.assertTrue private const val SEGMENT_SIZE = Segment.SIZE @@ -273,41 +275,16 @@ class CommonBufferTest { assertEquals("ab", sink.readUtf8(2)) } - @Suppress("ReplaceAssertBooleanWithAssertEquality") - @Test fun equalsAndHashCodeEmpty() { - val a = Buffer() - val b = Buffer() - assertTrue(a == b) - assertTrue(a.hashCode() == b.hashCode()) - } - - @Suppress("ReplaceAssertBooleanWithAssertEquality") + // Buffer don't override equals and hashCode @Test fun equalsAndHashCode() { val a = Buffer().writeUtf8("dog") + assertEquals(a, a) + val b = Buffer().writeUtf8("hotdog") - assertFalse(a == b) - assertFalse(a.hashCode() == b.hashCode()) + assertTrue(a != b) b.readUtf8(3) // Leaves b containing 'dog'. - assertTrue(a == b) - assertTrue(a.hashCode() == b.hashCode()) - } - - @Suppress("ReplaceAssertBooleanWithAssertEquality") - @Test fun equalsAndHashCodeSpanningSegments() { - val data = ByteArray(1024 * 1024) - val dice = Random(0) - dice.nextBytes(data) - - val a = bufferWithRandomSegmentLayout(dice, data) - val b = bufferWithRandomSegmentLayout(dice, data) - assertTrue(a == b) - assertTrue(a.hashCode() == b.hashCode()) - - data[data.size / 2]++ // Change a single byte. - val c = bufferWithRandomSegmentLayout(dice, data) - assertFalse(a == c) - assertFalse(a.hashCode() == c.hashCode()) + assertTrue(a != b) } /** diff --git a/core/jvm/src/Buffer.kt b/core/jvm/src/Buffer.kt index 581209ad6..9d5db7f71 100644 --- a/core/jvm/src/Buffer.kt +++ b/core/jvm/src/Buffer.kt @@ -111,10 +111,6 @@ public actual class Buffer : Source, Sink, Cloneable { actual override fun close(): Unit = Unit - override fun equals(other: Any?): Boolean = commonEquals(other) - - override fun hashCode(): Int = commonHashCode() - /** * Returns a human-readable string that describes the contents of this buffer. Typically, this * is a string like `[text=Hello]` or `[hex=0000ffff]`. diff --git a/core/native/src/Buffer.kt b/core/native/src/Buffer.kt index 8c45d8d14..d035d32d9 100644 --- a/core/native/src/Buffer.kt +++ b/core/native/src/Buffer.kt @@ -100,10 +100,6 @@ public actual class Buffer : Source, Sink { actual override fun close(): Unit = Unit - override fun equals(other: Any?): Boolean = commonEquals(other) - - override fun hashCode(): Int = commonHashCode() - override fun toString(): String = commonString() public actual fun copy(): Buffer = commonCopy() From d55e78a24f8f59bef63ed659b475d51b5dc80c04 Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Thu, 15 Jun 2023 10:06:46 +0200 Subject: [PATCH 43/83] Enable source /w options test --- core/jvm/test/JvmPlatformTest.kt | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/core/jvm/test/JvmPlatformTest.kt b/core/jvm/test/JvmPlatformTest.kt index 02201d2a7..42def7162 100644 --- a/core/jvm/test/JvmPlatformTest.kt +++ b/core/jvm/test/JvmPlatformTest.kt @@ -25,7 +25,10 @@ import org.junit.jupiter.api.io.TempDir import java.io.ByteArrayInputStream import java.io.ByteArrayOutputStream import java.io.File +import java.io.IOException import java.net.Socket +import java.nio.file.Files +import java.nio.file.LinkOption import java.nio.file.StandardOpenOption import kotlin.test.* @@ -102,14 +105,25 @@ class JvmPlatformTest { assertEquals(buffer.readUtf8(), "a") } - @Ignore("Not sure how to test this") @Test fun pathSourceWithOptions() { val folder = File(tempDir, "folder") folder.mkdir() + val file = File(folder, "new.txt") - file.toPath().source(StandardOpenOption.CREATE_NEW) - // This still throws NoSuchFileException... + assertTrue(file.createNewFile()) + val link = File(folder, "link.txt") + try { + Files.createSymbolicLink(link.toPath(), file.toPath()) + } catch (e: UnsupportedOperationException) { + // the FS does not support symlinks + return + } + + assertFailsWith { + link.toPath().source(LinkOption.NOFOLLOW_LINKS).buffer().readUtf8Line() + } + assertNull(link.toPath().source().buffer().readUtf8Line()) } @Test fun socketSink() { From d3b917958a822f06f921f57d248637dbcdd5f1dc Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Thu, 15 Jun 2023 10:07:16 +0200 Subject: [PATCH 44/83] Throw IllegalArgumentException instead of IOOBE --- core/common/src/-Util.kt | 2 +- core/common/src/Buffer.kt | 4 ++-- core/common/src/RawSink.kt | 5 ++++- core/common/src/RawSource.kt | 1 + core/common/src/Sink.kt | 21 ++++++++++++++++++++- core/common/src/SinkExt.kt | 26 +++++++++++++++++++++++++- core/common/src/Source.kt | 18 +++++++++++++++++- core/common/src/SourceExt.kt | 19 +++++++++++++++++++ core/common/src/Utf8.kt | 16 ++++++++++++++-- core/common/test/AbstractSinkTest.kt | 22 +++++++++++----------- core/common/test/AbstractSourceTest.kt | 6 +++--- core/common/test/CommonBufferTest.kt | 2 +- core/jvm/src/Buffer.kt | 8 +++++++- core/jvm/src/Sink.kt | 5 ++++- core/jvm/src/Source.kt | 6 ++++++ core/jvm/test/AbstractSinkTestJVM.kt | 2 +- core/jvm/test/AbstractSourceTestJVM.kt | 2 +- 17 files changed, 137 insertions(+), 28 deletions(-) diff --git a/core/common/src/-Util.kt b/core/common/src/-Util.kt index 3c14fa723..d16818329 100644 --- a/core/common/src/-Util.kt +++ b/core/common/src/-Util.kt @@ -31,7 +31,7 @@ internal val HEX_DIGIT_CHARS = internal fun checkOffsetAndCount(size: Long, offset: Long, byteCount: Long) { if (offset or byteCount < 0 || offset > size || size - offset < byteCount) { - throw IndexOutOfBoundsException("size=$size offset=$offset byteCount=$byteCount") + throw IllegalArgumentException("size=$size offset=$offset byteCount=$byteCount") } } diff --git a/core/common/src/Buffer.kt b/core/common/src/Buffer.kt index 0582c8cae..a0f6c8873 100644 --- a/core/common/src/Buffer.kt +++ b/core/common/src/Buffer.kt @@ -74,7 +74,7 @@ public expect class Buffer() : Source, Sink { * @param offset the offset to the first byte of data in this buffer to start copying from. * @param byteCount the number of bytes to copy. * - * @throws IndexOutOfBoundsException when [offset] and [byteCount] correspond to a range out of this buffer bounds. + * @throws IllegalArgumentException when [offset] and [byteCount] correspond to a range out of this buffer bounds. */ public fun copyTo( out: Buffer, @@ -95,7 +95,7 @@ public expect class Buffer() : Source, Sink { * Use of this method may expose significant performance penalties and it's not recommended to use it * for sequential access to a range of bytes within the buffer. * - * @throws IndexOutOfBoundsException when [pos] is out of this buffer's bounds. + * @throws IllegalArgumentException when [pos] is out of this buffer's bounds. */ public operator fun get(pos: Long): Byte diff --git a/core/common/src/RawSink.kt b/core/common/src/RawSink.kt index 8071f2eca..b21fc068f 100644 --- a/core/common/src/RawSink.kt +++ b/core/common/src/RawSink.kt @@ -38,12 +38,15 @@ public expect interface RawSink : Closeable { * @param source the source to read data from. * @param byteCount the number of bytes to write. * - * @throws IndexOutOfBoundsException when the [source]'s size is below [byteCount]. + * @throws IllegalArgumentException when the [source]'s size is below [byteCount] or [byteCount] is negative. + * @throws IllegalStateException when the sink is closed. */ public fun write(source: Buffer, byteCount: Long) /** * Pushes all buffered bytes to their final destination. + * + * @throws IllegalStateException when the sink is closed. */ public fun flush() diff --git a/core/common/src/RawSource.kt b/core/common/src/RawSource.kt index 5f40ca4ff..22ff3ea90 100644 --- a/core/common/src/RawSource.kt +++ b/core/common/src/RawSource.kt @@ -39,6 +39,7 @@ public interface RawSource : Closeable { * @param byteCount the number of bytes to read. * * @throws IllegalArgumentException when [byteCount] is negative. + * @throws IllegalStateException when the source is closed. */ public fun read(sink: Buffer, byteCount: Long): Long diff --git a/core/common/src/Sink.kt b/core/common/src/Sink.kt index 39439e900..f81b31c6e 100644 --- a/core/common/src/Sink.kt +++ b/core/common/src/Sink.kt @@ -58,8 +58,9 @@ public expect sealed interface Sink : RawSink { * @param offset the beginning of data within the [source], 0 by default. * @param byteCount the number of bytes to write, size of the [source] subarray starting at [offset] by default. * - * @throws IndexOutOfBoundsException when a range specified by [offset] and [byteCount] + * @throws IllegalArgumentException when a range specified by [offset] and [byteCount] * is out of range of [source] array indices. + * @throws IllegalStateException when the sink is closed. */ public fun write(source: ByteArray, offset: Int = 0, byteCount: Int = source.size - offset): Sink @@ -68,6 +69,9 @@ public expect sealed interface Sink : RawSink { * Returns the number of bytes read which will be 0 if [source] is exhausted. * * @param source the source to consume data from. + * + * @throws IllegalStateException when the sink or [source] is closed. + * */ public fun writeAll(source: RawSource): Long @@ -81,6 +85,7 @@ public expect sealed interface Sink : RawSink { * @param byteCount the number of bytes to read from [source] and to write into this sink. * * @throws IllegalArgumentException when [byteCount] is negative. + * @throws IllegalStateException when the sink or [source] is closed. */ public fun write(source: RawSource, byteCount: Long): Sink @@ -88,6 +93,8 @@ public expect sealed interface Sink : RawSink { * Writes a byte to this sink. * * @param byte the byte to be written. + * + * @throws IllegalStateException when the sink is closed. */ public fun writeByte(byte: Byte): Sink @@ -95,6 +102,8 @@ public expect sealed interface Sink : RawSink { * Writes two bytes containing [short], in the big-endian order, to this sink. * * @param short the short integer to be written. + * + * @throws IllegalStateException when the sink is closed. */ public fun writeShort(short: Short): Sink @@ -102,6 +111,8 @@ public expect sealed interface Sink : RawSink { * Writes four bytes containing [int], in the big-endian order, to this sink. * * @param int the integer to be written. + * + * @throws IllegalStateException when the sink is closed. */ public fun writeInt(int: Int): Sink @@ -109,12 +120,16 @@ public expect sealed interface Sink : RawSink { * Writes eight bytes containing [long], in the big-endian order, to this sink. * * @param long the long integer to be written. + * + * @throws IllegalStateException when the sink is closed. */ public fun writeLong(long: Long): Sink /** * Writes all buffered data to the underlying sink, if one exists. * Then the underlying sink is explicitly flushed. + * + * @throws IllegalStateException when the sink is closed. */ override fun flush() @@ -124,6 +139,8 @@ public expect sealed interface Sink : RawSink { * * This method behaves like [flush], but has weaker guarantees. * Call this method before a buffered sink goes out of scope so that its data can reach its destination. + * + * @throws IllegalStateException when the sink is closed. */ public fun emit(): Sink @@ -134,6 +151,8 @@ public expect sealed interface Sink : RawSink { * Use this to limit the memory held in the buffer to a single segment. * Typically, application code will not need to call this: it is only necessary when * application code writes directly to this [buffer]. + * + * @throws IllegalStateException when the sink is closed. */ @DelicateIoApi public fun emitCompleteSegments(): Sink diff --git a/core/common/src/SinkExt.kt b/core/common/src/SinkExt.kt index dd2b75458..89700016a 100644 --- a/core/common/src/SinkExt.kt +++ b/core/common/src/SinkExt.kt @@ -14,6 +14,8 @@ internal val HEX_DIGIT_BYTES = "0123456789abcdef".asUtf8ToByteArray() * Writes two bytes containing [short], in the little-endian order, to this sink. * * @param short the short integer to be written. + * + * @throws IllegalStateException when the sink is closed. */ public fun T.writeShortLe(short: Short): T { this.writeShort(short.reverseBytes()) @@ -24,6 +26,8 @@ public fun T.writeShortLe(short: Short): T { * Writes four bytes containing [int], in the little-endian order, to this sink. * * @param int the integer to be written. + * + * @throws IllegalStateException when the sink is closed. */ public fun T.writeIntLe(int: Int): T { this.writeInt(int.reverseBytes()) @@ -34,6 +38,8 @@ public fun T.writeIntLe(int: Int): T { * Writes eight bytes containing [long], in the little-endian order, to this sink. * * @param long the long integer to be written. + * + * @throws IllegalStateException when the sink is closed. */ public fun T.writeLongLe(long: Long): T { this.writeLong(long.reverseBytes()) @@ -46,6 +52,8 @@ public fun T.writeLongLe(long: Long): T { * Resulting string will not contain leading zeros, except the `0` value itself. * * @param long the long to be written. + * + * @throws IllegalStateException when the sink is closed. */ @OptIn(DelicateIoApi::class) public fun T.writeDecimalLong(long: Long): T { @@ -122,6 +130,8 @@ public fun T.writeDecimalLong(long: Long): T { * Resulting string will not contain leading zeros, except the `0` value itself. * * @param long the long to be written. + * + * @throws IllegalStateException when the sink is closed. */ @OptIn(DelicateIoApi::class) public fun T.writeHexadecimalUnsignedLong(long: Long): T { @@ -173,6 +183,8 @@ public fun T.writeHexadecimalUnsignedLong(long: Long): T { * Writes am unsigned byte to this sink. * * @param byte the byte to be written. + * + * @throws IllegalStateException when the sink is closed. */ public fun T.writeByte(byte: UByte): T { writeByte(byte.toByte()) @@ -183,6 +195,8 @@ public fun T.writeByte(byte: UByte): T { * Writes two bytes containing [short], in the big-endian order, to this sink. * * @param short the unsigned short integer to be written. + * + * @throws IllegalStateException when the sink is closed. */ public fun T.writeShort(short: UShort): T { writeShort(short.toShort()) @@ -193,6 +207,8 @@ public fun T.writeShort(short: UShort): T { * Writes four bytes containing [int], in the big-endian order, to this sink. * * @param int the unsigned integer to be written. + * + * @throws IllegalStateException when the sink is closed. */ public fun T.writeInt(int: UInt): T { writeInt(int.toInt()) @@ -203,6 +219,8 @@ public fun T.writeInt(int: UInt): T { * Writes eight bytes containing [long], in the big-endian order, to this sink. * * @param long the unsigned long integer to be written. + * + * @throws IllegalStateException when the sink is closed. */ public fun T.writeLong(long: ULong): T { writeLong(long.toLong()) @@ -213,6 +231,8 @@ public fun T.writeLong(long: ULong): T { * Writes two bytes containing [short], in the little-endian order, to this sink. * * @param short the unsigned short integer to be written. + * + * @throws IllegalStateException when the sink is closed. */ public fun T.writeShortLe(short: UShort): T { writeShortLe(short.toShort()) @@ -222,7 +242,9 @@ public fun T.writeShortLe(short: UShort): T { /** * Writes four bytes containing [int], in the little-endian order, to this sink. * - * @param int the unsugned integer to be written. + * @param int the unsigned integer to be written. + * + * @throws IllegalStateException when the sink is closed. */ public fun T.writeIntLe(int: UInt): T { writeIntLe(int.toInt()) @@ -233,6 +255,8 @@ public fun T.writeIntLe(int: UInt): T { * Writes eight bytes containing [long], in the little-endian order, to this sink. * * @param long the unsigned long integer to be written. + * + * @throws IllegalStateException when the sink is closed. */ public fun T.writeLongLe(long: ULong): T { writeLongLe(long.toLong()) diff --git a/core/common/src/Source.kt b/core/common/src/Source.kt index 5df2abf36..df5714ca2 100644 --- a/core/common/src/Source.kt +++ b/core/common/src/Source.kt @@ -51,6 +51,8 @@ public expect sealed interface Source : RawSource { * Returns true if there are no more bytes in this source. * * The call of this method will block until there are bytes to read or the source is definitely exhausted. + * + * @throws IllegalStateException when the source is closed. */ public fun exhausted(): Boolean @@ -64,6 +66,7 @@ public expect sealed interface Source : RawSource { * @param byteCount the number of bytes that the buffer should contain. * * @throws EOFException when the source is exhausted before the required bytes count could be read. + * @throws IllegalStateException when the source is closed. */ public fun require(byteCount: Long) @@ -75,6 +78,8 @@ public expect sealed interface Source : RawSource { * filling the buffer with [byteCount] bytes of data. * * @param byteCount the number of bytes that the buffer should contain. + * + * @throws IllegalStateException when the source is closed. */ public fun request(byteCount: Long): Boolean @@ -82,6 +87,7 @@ public expect sealed interface Source : RawSource { * Removes a byte from this source and returns it. * * @throws EOFException when there are no more bytes to read. + * @throws IllegalStateException when the source is closed. */ public fun readByte(): Byte @@ -89,6 +95,7 @@ public expect sealed interface Source : RawSource { * Removes two bytes from this source and returns a short integer composed of it according to the big-endian order. * * @throws EOFException when there are not enough data to read a short value. + * @throws IllegalStateException when the source is closed. */ public fun readShort(): Short @@ -96,6 +103,7 @@ public expect sealed interface Source : RawSource { * Removes four bytes from this source and returns an integer composed of it according to the big-endian order. * * @throws EOFException when there are not enough data to read an int value. + * @throws IllegalStateException when the source is closed. */ public fun readInt(): Int @@ -103,6 +111,7 @@ public expect sealed interface Source : RawSource { * Removes eight bytes from this source and returns a long integer composed of it according to the big-endian order. * * @throws EOFException when there are not enough data to read a long value. + * @throws IllegalStateException when the source is closed. */ public fun readLong(): Long @@ -112,6 +121,7 @@ public expect sealed interface Source : RawSource { * @param byteCount the number of bytes to be skipped. * * @throws EOFException when the source is exhausted before the requested number of bytes can be skipped. + * @throws IllegalStateException when the source is closed. */ public fun skip(byteCount: Long) @@ -124,8 +134,9 @@ public expect sealed interface Source : RawSource { * @param byteCount the number of bytes that should be written into [sink], * size of the [sink] subarray starting at [offset] by default. * - * @throws IndexOutOfBoundsException when a range specified by [offset] and [byteCount] + * @throws IllegalArgumentException when a range specified by [offset] and [byteCount] * is out of range of [sink] array indices. + * @throws IllegalStateException when the source is closed. */ public fun read(sink: ByteArray, offset: Int = 0, byteCount: Int = sink.size - offset): Int @@ -137,6 +148,7 @@ public expect sealed interface Source : RawSource { * * @throws IllegalArgumentException when [byteCount] is negative. * @throws EOFException when the requested number of bytes cannot be read. + * @throws IllegalStateException when the source or [sink] is closed. */ public fun readFully(sink: RawSink, byteCount: Long) @@ -147,6 +159,8 @@ public expect sealed interface Source : RawSource { * Return 0 if this source is exhausted. * * @param sink the sink to which data will be written from this source. + * + * @throws IllegalStateException when the source or [sink] is closed. */ public fun readAll(sink: RawSink): Long @@ -155,6 +169,8 @@ public expect sealed interface Source : RawSource { * The returned source becomes invalid once this source is next read or closed. * * Peek could be used to lookahead and read the same data multiple times. + * + * @throws IllegalStateException when the source is closed. */ public fun peek(): Source } diff --git a/core/common/src/SourceExt.kt b/core/common/src/SourceExt.kt index d2ba07835..947526c1d 100644 --- a/core/common/src/SourceExt.kt +++ b/core/common/src/SourceExt.kt @@ -9,6 +9,7 @@ package kotlinx.io * Removes two bytes from this source and returns a short integer composed of it according to the little-endian order. * * @throws EOFException when there are not enough data to read a short value. + * @throws IllegalStateException when the source is closed. */ public fun Source.readShortLe(): Short { return readShort().reverseBytes() @@ -18,6 +19,7 @@ public fun Source.readShortLe(): Short { * Removes four bytes from this source and returns an integer composed of it according to the little-endian order. * * @throws EOFException when there are not enough data to read an int value. + * @throws IllegalStateException when the source is closed. */ public fun Source.readIntLe(): Int { return readInt().reverseBytes() @@ -27,6 +29,7 @@ public fun Source.readIntLe(): Int { * Removes eight bytes from this source and returns a long integer composed of it according to the little-endian order. * * @throws EOFException when there are not enough data to read a long value. + * @throws IllegalStateException when the source is closed. */ public fun Source.readLongLe(): Long { return readLong().reverseBytes() @@ -45,6 +48,7 @@ internal const val OVERFLOW_DIGIT_START = Long.MIN_VALUE % 10L + 1 * @throws NumberFormatException if the found digits do not fit into a `long` or a decimal * number was not present. * @throws EOFException if the source is exhausted before a call of this method. + * @throws IllegalStateException when the source is closed. */ @OptIn(DelicateIoApi::class) public fun Source.readDecimalLong(): Long { @@ -105,6 +109,7 @@ public fun Source.readDecimalLong(): Long { * @throws NumberFormatException if the found hexadecimal does not fit into a `long` or * hexadecimal was not found. * @throws EOFException if the source is exhausted before a call of this method. + * @throws IllegalStateException when the source is closed. */ @OptIn(DelicateIoApi::class) public fun Source.readHexadecimalUnsignedLong(): Long { @@ -147,6 +152,8 @@ public fun Source.readHexadecimalUnsignedLong(): Long { * @param b the value to find. * @param fromIndex the start of the range to find [b], inclusive. * @param toIndex the end of the range to find [b], exclusive. + * + * @throws IllegalStateException when the source is closed. */ public fun Source.indexOf(b: Byte, fromIndex: Long = 0L, toIndex: Long = Long.MAX_VALUE): Long { require(fromIndex in 0..toIndex) @@ -168,6 +175,8 @@ public fun Source.indexOf(b: Byte, fromIndex: Long = 0L, toIndex: Long = Long.MA /** * Removes all bytes from this source and returns them as a byte array. + * + * @throws IllegalStateException when the source is closed. */ public fun Source.readByteArray(): ByteArray { return readByteArrayImpl( -1L) @@ -180,6 +189,7 @@ public fun Source.readByteArray(): ByteArray { * * @throws IllegalArgumentException when byteCount is negative. * @throws EOFException when the underlying source is exhausted before [byteCount] bytes of data could be read. + * @throws IllegalStateException when the source is closed. */ public fun Source.readByteArray(byteCount: Long): ByteArray { check(byteCount >= 0) @@ -212,6 +222,7 @@ private inline fun Source.readByteArrayImpl(size: Long): ByteArray { * Removes exactly `sink.length` bytes from this source and copies them into [sink]. * * @throws EOFException when the requested number of bytes cannot be read. + * @throws IllegalStateException when the source is closed. */ public fun Source.readFully(sink: ByteArray) { var offset = 0 @@ -228,6 +239,7 @@ public fun Source.readFully(sink: ByteArray) { * Removes an unsigned byte from this source and returns it. * * @throws EOFException when there are no more bytes to read. + * @throws IllegalStateException when the source is closed. */ public fun Source.readUByte(): UByte = readByte().toUByte() @@ -236,6 +248,7 @@ public fun Source.readUByte(): UByte = readByte().toUByte() * according to the big-endian order. * * @throws EOFException when there are not enough data to read an unsigned short value. + * @throws IllegalStateException when the source is closed. */ public fun Source.readUShort(): UShort = readShort().toUShort() @@ -244,6 +257,7 @@ public fun Source.readUShort(): UShort = readShort().toUShort() * according to the big-endian order. * * @throws EOFException when there are not enough data to read an unsigned int value. + * @throws IllegalStateException when the source is closed. */ public fun Source.readUInt(): UInt = readInt().toUInt() @@ -252,6 +266,7 @@ public fun Source.readUInt(): UInt = readInt().toUInt() * according to the big-endian order. * * @throws EOFException when there are not enough data to read an unsigned long value. + * @throws IllegalStateException when the source is closed. */ public fun Source.readULong(): ULong = readLong().toULong() @@ -260,6 +275,7 @@ public fun Source.readULong(): ULong = readLong().toULong() * according to the little-endian order. * * @throws EOFException when there are not enough data to read an unsigned short value. + * @throws IllegalStateException when the source is closed. */ public fun Source.readUShortLe(): UShort = readShortLe().toUShort() @@ -268,6 +284,7 @@ public fun Source.readUShortLe(): UShort = readShortLe().toUShort() * according to the little-endian order. * * @throws EOFException when there are not enough data to read an unsigned int value. + * @throws IllegalStateException when the source is closed. */ public fun Source.readUIntLe(): UInt = readIntLe().toUInt() @@ -276,8 +293,10 @@ public fun Source.readUIntLe(): UInt = readIntLe().toUInt() * according to the little-endian order. * * @throws EOFException when there are not enough data to read an unsigned long value. + * @throws IllegalStateException when the source is closed. */ public fun Source.readULongLe(): ULong = readLongLe().toULong() +// TODO: add doc @OptIn(DelicateIoApi::class) public fun Source.startsWith(byte: Byte): Boolean = request(1) && buffer[0] == byte diff --git a/core/common/src/Utf8.kt b/core/common/src/Utf8.kt index a820dca9c..90a3ee2c7 100644 --- a/core/common/src/Utf8.kt +++ b/core/common/src/Utf8.kt @@ -80,7 +80,7 @@ import kotlinx.io.internal.commonWriteUtf8CodePoint * @param beginIndex the index of the first character to encode, inclusive. * @param endIndex the index of the character past the last character to encode, exclusive. * - * @throws IndexOutOfBoundsException when [beginIndex] or [endIndex] correspond to a range + * @throws IllegalArgumentException when [beginIndex] or [endIndex] correspond to a range * out of the current string bounds. */ public fun String.utf8Size(beginIndex: Int = 0, endIndex: Int = length): Long { @@ -126,6 +126,8 @@ public fun String.utf8Size(beginIndex: Int = 0, endIndex: Int = length): Long { * Encodes [codePoint] in UTF-8 and writes it to this sink. * * @param codePoint the codePoint to be written. + * + * @throws IllegalStateException when the sink is closed. */ @OptIn(DelicateIoApi::class) public fun T.writeUtf8CodePoint(codePoint: Int): T { @@ -141,8 +143,9 @@ public fun T.writeUtf8CodePoint(codePoint: Int): T { * @param beginIndex the index of the first character to encode, 0 by default. * @param endIndex the index of a character past to a last character to encode, `string.length` by default. * - * @throws IndexOutOfBoundsException when [beginIndex] or [endIndex] correspond to a range + * @throws IllegalArgumentException when [beginIndex] or [endIndex] correspond to a range * out of the current string bounds. + * @throws IllegalStateException when the sink is closed. */ @OptIn(DelicateIoApi::class) public fun T.writeUtf8(string: String, beginIndex: Int = 0, endIndex: Int = string.length): T { @@ -155,6 +158,8 @@ public fun T.writeUtf8(string: String, beginIndex: Int = 0, endIndex: * Removes all bytes from this source, decodes them as UTF-8, and returns the string. * * Returns the empty string if this source is empty. + * + * @throws IllegalStateException when the source is closed. */ @OptIn(DelicateIoApi::class) public fun Source.readUtf8(): String { @@ -181,6 +186,7 @@ public fun Buffer.readUtf8(): String { * * @throws IllegalArgumentException when [byteCount] is negative. * @throws EOFException when the source is exhausted before reading [byteCount] bytes from it. + * @throws IllegalStateException when the source is closed. */ @OptIn(DelicateIoApi::class) public fun Source.readUtf8(byteCount: Long): String { @@ -201,6 +207,7 @@ public fun Source.readUtf8(byteCount: Long): String { * encodings (such as `0xc080` for the NUL character in modified UTF-8). * * @throws EOFException when the source is exhausted before a complete code point can be read. + * @throws IllegalStateException when the source is closed. */ @OptIn(DelicateIoApi::class) public fun Source.readUtf8CodePoint(): Int { @@ -229,6 +236,8 @@ public fun Buffer.readUtf8CodePoint(): Int { * * On the end of the stream this method returns null. If the source doesn't end with a line break, then * an implicit line break is assumed. Null is returned once the source is exhausted. + * + * @throws IllegalStateException when the source is closed. */ public fun Source.readUtf8Line(): String? { if (!request(1)) return null @@ -269,8 +278,11 @@ public fun Source.readUtf8Line(): String? { * * @throws EOFException when the source does not contain a string consisting with at most [limit] bytes followed by * line break characters. + * @throws IllegalStateException when the source is closed. + * @throws IllegalArgumentException when [limit] is negative. */ public fun Source.readUtf8LineStrict(limit: Long = Long.MAX_VALUE): String { + require(limit >= 0) { "limit is negative: $limit" } if (!request(1)) throw EOFException() val peekSource = peek() diff --git a/core/common/test/AbstractSinkTest.kt b/core/common/test/AbstractSinkTest.kt index aef71b805..51359e21e 100644 --- a/core/common/test/AbstractSinkTest.kt +++ b/core/common/test/AbstractSinkTest.kt @@ -21,7 +21,9 @@ package kotlinx.io -import kotlin.test.* +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith class BufferSinkTest : AbstractSinkTest(SinkFactory.BUFFER) class RealSinkTest : AbstractSinkTest(SinkFactory.REAL_BUFFERED_SINK) @@ -49,12 +51,12 @@ abstract class AbstractSinkTest internal constructor( assertEquals("[hex=000102]", data.toString()) data.clear() - assertFailsWith { + assertFailsWith { sink.write(source, -1, 1) } assertEquals(0, data.size) - assertFailsWith { + assertFailsWith { sink.write(source, 1, source.size) } assertEquals(0, data.size) @@ -219,17 +221,15 @@ abstract class AbstractSinkTest internal constructor( assertEquals("abcd", data.readUtf8()) } - @Ignore // TODO: its unclear how write should behave - @Test fun writeBufferThrowsIOOBE() { + @Test fun writeBufferThrowsIEA() { val source: Buffer = Buffer().writeUtf8("abcd") - assertFailsWith { + assertFailsWith { sink.write(source, 8) } - // Ensure that whatever was available was correctly written. sink.flush() - assertEquals("abcd", data.readUtf8()) + assertEquals("", data.readUtf8()) } @Test fun writeSourceWithNegativeBytesCount() { @@ -343,9 +343,9 @@ abstract class AbstractSinkTest internal constructor( } @Test fun writeUtf8WithInvalidIndexes() { - assertFailsWith { sink.writeUtf8("hello", -1) } - assertFailsWith { sink.writeUtf8("hello", 0, 6) } - assertFailsWith { sink.writeUtf8("hello", 6) } + assertFailsWith { sink.writeUtf8("hello", -1) } + assertFailsWith { sink.writeUtf8("hello", 0, 6) } + assertFailsWith { sink.writeUtf8("hello", 6) } } @Test fun writeUByte() { diff --git a/core/common/test/AbstractSourceTest.kt b/core/common/test/AbstractSourceTest.kt index 74312d27f..095720af6 100644 --- a/core/common/test/AbstractSourceTest.kt +++ b/core/common/test/AbstractSourceTest.kt @@ -461,15 +461,15 @@ abstract class AbstractBufferedSourceTest internal constructor( val sink = ByteArray(4) - assertFailsWith { + assertFailsWith { source.read(sink, 4, 1) } - assertFailsWith { + assertFailsWith { source.read(sink, 1, 4) } - assertFailsWith { + assertFailsWith { source.read(sink, -1, 2) } } diff --git a/core/common/test/CommonBufferTest.kt b/core/common/test/CommonBufferTest.kt index 2b7a98fc1..6a351590d 100644 --- a/core/common/test/CommonBufferTest.kt +++ b/core/common/test/CommonBufferTest.kt @@ -261,7 +261,7 @@ class CommonBufferTest { @Test fun getByteOfEmptyBuffer() { val buffer = Buffer() - assertFailsWith { + assertFailsWith { buffer[0] } } diff --git a/core/jvm/src/Buffer.kt b/core/jvm/src/Buffer.kt index 9d5db7f71..69ecd0e59 100644 --- a/core/jvm/src/Buffer.kt +++ b/core/jvm/src/Buffer.kt @@ -145,6 +145,7 @@ public fun Buffer.readFrom(input: InputStream): Buffer { * @param byteCount the number of bytes read from [input]. * * @throws IOException when [input] exhausted before reading [byteCount] bytes from it. + * @throws IllegalArgumentException when [byteCount] is negative. */ public fun Buffer.readFrom(input: InputStream, byteCount: Long): Buffer { require(byteCount >= 0L) { "byteCount < 0: $byteCount" } @@ -178,6 +179,8 @@ private fun Buffer.readFrom(input: InputStream, byteCount: Long, forever: Boolea * * @param out the [OutputStream] to write to. * @param byteCount the number of bytes to be written, [Buffer.size] by default. + * + * @throws IllegalArgumentException when [byteCount] is negative or exceeds the buffer size. */ public fun Buffer.writeTo(out: OutputStream, byteCount: Long = size): Buffer { checkOffsetAndCount(size, 0, byteCount) @@ -210,7 +213,7 @@ public fun Buffer.writeTo(out: OutputStream, byteCount: Long = size): Buffer { * @param offset the offset to start copying data from, `0` by default. * @param byteCount the number of bytes to copy, all data starting from the [offset] by default. * - * @throws IndexOutOfBoundsException when [byteCount] and [offset] represents a range out of the buffer bounds. + * @throws IllegalArgumentException when [byteCount] and [offset] represents a range out of the buffer bounds. */ public fun Buffer.copyTo( out: OutputStream, @@ -286,6 +289,9 @@ public fun Buffer.readFrom(source: ByteBuffer): Buffer { return this } +/** + * Returns a new [ByteChannel] instance representing this buffer. + */ public fun Buffer.channel(): ByteChannel = object : ByteChannel { override fun read(sink: ByteBuffer): Int = writeTo(sink) diff --git a/core/jvm/src/Sink.kt b/core/jvm/src/Sink.kt index ceede365f..6e34384a7 100644 --- a/core/jvm/src/Sink.kt +++ b/core/jvm/src/Sink.kt @@ -59,7 +59,8 @@ public actual sealed interface Sink : RawSink { * @param beginIndex the index of the first character to encode, inclusive, 0 by default. * @param endIndex the index of the last character to encode, exclusive, `string.size` by default. * - * @throws IndexOutOfBoundsException when [beginIndex] and [endIndex] correspond to a range out of [string] bounds. + * @throws IllegalArgumentException when [beginIndex] and [endIndex] correspond to a range out of [string] bounds. + * @throws IllegalStateException when the sink is closed. */ public fun T.writeString(string: String, charset: Charset, beginIndex: Int = 0, endIndex: Int = string.length): T { require(beginIndex >= 0) { "beginIndex < 0: $beginIndex" } @@ -110,6 +111,8 @@ public fun Sink.outputStream(): OutputStream { * Writes data from the [source] into this sink and returns the number of bytes written. * * @param source the source to read from. + * + * @throws IllegalStateException when the sink is closed. */ public fun Sink.write(source: ByteBuffer): Int { val sizeBefore = buffer.size diff --git a/core/jvm/src/Source.kt b/core/jvm/src/Source.kt index 563484e70..a90062cc3 100644 --- a/core/jvm/src/Source.kt +++ b/core/jvm/src/Source.kt @@ -81,6 +81,8 @@ private fun Buffer.readStringImpl(byteCount: Long, charset: Charset): String { * Decodes whole content of this stream into a string using [charset]. Returns empty string if the source is exhausted. * * @param charset the [Charset] to use for string decoding. + * + * @throws IllegalStateException when the source is closed. */ public fun Source.readString(charset: Charset): String { var req = 1L @@ -97,6 +99,8 @@ public fun Source.readString(charset: Charset): String { * @param charset the [Charset] to use for string decoding. * * @throws EOFException when the source exhausted before [byteCount] bytes could be read from it. + * @throws IllegalStateException when the source is closed. + * @throws IllegalArgumentException if [byteCount] is negative. */ public fun Source.readString(byteCount: Long, charset: Charset): String { require(byteCount) @@ -143,6 +147,8 @@ public fun Source.inputStream(): InputStream { * Reads data from this source into [sink]. * * @param sink the sink to write the data to. + * + * @throws IllegalStateException when the source is closed. */ public fun Source.read(sink: ByteBuffer): Int { if (buffer.size == 0L) { diff --git a/core/jvm/test/AbstractSinkTestJVM.kt b/core/jvm/test/AbstractSinkTestJVM.kt index 571e3d101..7e6009cad 100644 --- a/core/jvm/test/AbstractSinkTestJVM.kt +++ b/core/jvm/test/AbstractSinkTestJVM.kt @@ -51,7 +51,7 @@ abstract class AbstractSinkTestJVM internal constructor(factory: SinkFactory) { @Test fun outputStreamBounds() { val out: OutputStream = sink.outputStream() - assertFailsWith { + assertFailsWith { out.write(ByteArray(100), 50, 51) } } diff --git a/core/jvm/test/AbstractSourceTestJVM.kt b/core/jvm/test/AbstractSourceTestJVM.kt index 815b62eee..8e600e8e4 100644 --- a/core/jvm/test/AbstractSourceTestJVM.kt +++ b/core/jvm/test/AbstractSourceTestJVM.kt @@ -116,7 +116,7 @@ abstract class AbstractSourceTestJVM(private val factory: SourceFactory) { sink.writeUtf8("a".repeat(100)) sink.emit() val input: InputStream = source.inputStream() - assertFailsWith { + assertFailsWith { input.read(ByteArray(100), 50, 51) } } From 2f73fd580c3017b450742ea33987c302f0422dec Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Fri, 16 Jun 2023 11:31:41 +0200 Subject: [PATCH 45/83] Don't return a receiver from write methods --- benchmarks/src/commonMain/kotlin/BufferOps.kt | 34 ++- .../jvmMain/kotlin/SegmentPoolBenchmarkMT.kt | 5 +- core/api/kotlinx-io-core.api | 88 +++--- core/common/src/Buffer.kt | 18 +- core/common/src/Sink.kt | 16 +- core/common/src/SinkExt.kt | 43 ++- core/common/src/SourceExt.kt | 17 +- core/common/src/Utf8.kt | 6 +- core/common/src/internal/-Buffer.kt | 35 +-- core/common/src/internal/-RealBufferedSink.kt | 29 +- core/common/test/AbstractSinkTest.kt | 47 +++- core/common/test/AbstractSourceTest.kt | 254 +++++++++++------- core/common/test/CommonBufferTest.kt | 34 +-- core/common/test/CommonPlatformTest.kt | 4 +- core/common/test/CommonRealSinkTest.kt | 14 +- core/common/test/CommonRealSourceTest.kt | 14 +- core/common/test/util.kt | 38 --- core/jvm/src/Buffer.kt | 28 +- core/jvm/src/RealSink.kt | 2 +- core/jvm/src/Sink.kt | 19 +- core/jvm/test/BufferTest.kt | 6 +- core/jvm/test/JvmPlatformTest.kt | 12 +- core/native/src/Buffer.kt | 18 +- core/native/src/RealSink.kt | 2 +- core/native/src/Sink.kt | 16 +- 25 files changed, 411 insertions(+), 388 deletions(-) diff --git a/benchmarks/src/commonMain/kotlin/BufferOps.kt b/benchmarks/src/commonMain/kotlin/BufferOps.kt index eb0b1e9df..9860b73f5 100644 --- a/benchmarks/src/commonMain/kotlin/BufferOps.kt +++ b/benchmarks/src/commonMain/kotlin/BufferOps.kt @@ -97,18 +97,21 @@ open class DecimalLongBenchmark: BufferRWBenchmarkBase() { var value = 0L override fun padding(): ByteArray { - val tmpBuffer = Buffer() - while (tmpBuffer.size < minGap) { - // use space as a delimiter between consecutive decimal values - tmpBuffer.writeDecimalLong(value).writeByte(' '.code.toByte()) + return with (Buffer()) { + while (size < minGap) { + writeDecimalLong(value) + // use space as a delimiter between consecutive decimal values + writeByte(' '.code.toByte()) + } + readByteArray() } - return tmpBuffer.readByteArray() } @Benchmark fun benchmark(): Long { // use space as a delimiter between consecutive decimal values - buffer.writeDecimalLong(value).writeByte(' '.code.toByte()) + buffer.writeDecimalLong(value) + buffer.writeByte(' '.code.toByte()) val l = buffer.readDecimalLong() buffer.readByte() // consume the delimiter return l @@ -120,16 +123,19 @@ open class HexadecimalLongBenchmark: BufferRWBenchmarkBase() { var value = 0L override fun padding(): ByteArray { - val tmpBuffer = Buffer() - while (tmpBuffer.size < minGap) { - tmpBuffer.writeHexadecimalUnsignedLong(value).writeByte(' '.code.toByte()) + return with(Buffer()) { + while (size < minGap) { + writeHexadecimalUnsignedLong(value) + writeByte(' '.code.toByte()) + } + readByteArray() } - return tmpBuffer.readByteArray() } @Benchmark fun benchmark(): Long { - buffer.writeHexadecimalUnsignedLong(value).writeByte(' '.code.toByte()) + buffer.writeHexadecimalUnsignedLong(value) + buffer.writeByte(' '.code.toByte()) val l = buffer.readHexadecimalUnsignedLong() buffer.readByte() return l @@ -317,7 +323,11 @@ open class IndexOfBenchmark { if (valueOffset >= 0) array[valueOffset] = VALUE_TO_FIND val padding = ByteArray(paddingSize) - buffer.write(padding).write(array).skip(paddingSize.toLong()) + with(buffer) { + write(padding) + write(array) + skip(paddingSize.toLong()) + } } @Benchmark diff --git a/benchmarks/src/jvmMain/kotlin/SegmentPoolBenchmarkMT.kt b/benchmarks/src/jvmMain/kotlin/SegmentPoolBenchmarkMT.kt index 166288a54..54141db50 100644 --- a/benchmarks/src/jvmMain/kotlin/SegmentPoolBenchmarkMT.kt +++ b/benchmarks/src/jvmMain/kotlin/SegmentPoolBenchmarkMT.kt @@ -5,15 +5,16 @@ package kotlinx.io.benchmarks -import kotlinx.io.* import kotlinx.benchmark.* +import kotlinx.io.* import org.openjdk.jmh.annotations.Group import org.openjdk.jmh.annotations.GroupThreads @State(Scope.Benchmark) open class SegmentPoolBenchmarkMT { private fun testCycle(): Buffer { - val buffer = Buffer().writeByte(0) + val buffer = Buffer() + buffer.writeByte(0) buffer.clear() return buffer } diff --git a/core/api/kotlinx-io-core.api b/core/api/kotlinx-io-core.api index 66c577c28..a3b43d5cf 100644 --- a/core/api/kotlinx-io-core.api +++ b/core/api/kotlinx-io-core.api @@ -6,12 +6,10 @@ public final class kotlinx/io/Buffer : java/lang/Cloneable, kotlinx/io/Sink, kot public fun close ()V public final fun completeSegmentByteCount ()J public final fun copy ()Lkotlinx/io/Buffer; - public final fun copyTo (Lkotlinx/io/Buffer;JJ)Lkotlinx/io/Buffer; - public static synthetic fun copyTo$default (Lkotlinx/io/Buffer;Lkotlinx/io/Buffer;JJILjava/lang/Object;)Lkotlinx/io/Buffer; - public fun emit ()Lkotlinx/io/Buffer; - public synthetic fun emit ()Lkotlinx/io/Sink; - public fun emitCompleteSegments ()Lkotlinx/io/Buffer; - public synthetic fun emitCompleteSegments ()Lkotlinx/io/Sink; + public final fun copyTo (Lkotlinx/io/Buffer;JJ)V + public static synthetic fun copyTo$default (Lkotlinx/io/Buffer;Lkotlinx/io/Buffer;JJILjava/lang/Object;)V + public fun emit ()V + public fun emitCompleteSegments ()V public fun exhausted ()Z public fun flush ()V public final fun get (J)B @@ -31,31 +29,25 @@ public final class kotlinx/io/Buffer : java/lang/Cloneable, kotlinx/io/Sink, kot public fun skip (J)V public fun toString ()Ljava/lang/String; public fun write (Lkotlinx/io/Buffer;J)V - public fun write (Lkotlinx/io/RawSource;J)Lkotlinx/io/Buffer; - public synthetic fun write (Lkotlinx/io/RawSource;J)Lkotlinx/io/Sink; - public fun write ([BII)Lkotlinx/io/Buffer; - public synthetic fun write ([BII)Lkotlinx/io/Sink; + public fun write (Lkotlinx/io/RawSource;J)V + public fun write ([BII)V public fun writeAll (Lkotlinx/io/RawSource;)J - public fun writeByte (B)Lkotlinx/io/Buffer; - public synthetic fun writeByte (B)Lkotlinx/io/Sink; - public fun writeInt (I)Lkotlinx/io/Buffer; - public synthetic fun writeInt (I)Lkotlinx/io/Sink; - public fun writeLong (J)Lkotlinx/io/Buffer; - public synthetic fun writeLong (J)Lkotlinx/io/Sink; - public fun writeShort (S)Lkotlinx/io/Buffer; - public synthetic fun writeShort (S)Lkotlinx/io/Sink; + public fun writeByte (B)V + public fun writeInt (I)V + public fun writeLong (J)V + public fun writeShort (S)V } public final class kotlinx/io/BufferKt { public static final fun channel (Lkotlinx/io/Buffer;)Ljava/nio/channels/ByteChannel; - public static final fun copyTo (Lkotlinx/io/Buffer;Ljava/io/OutputStream;JJ)Lkotlinx/io/Buffer; - public static synthetic fun copyTo$default (Lkotlinx/io/Buffer;Ljava/io/OutputStream;JJILjava/lang/Object;)Lkotlinx/io/Buffer; + public static final fun copyTo (Lkotlinx/io/Buffer;Ljava/io/OutputStream;JJ)V + public static synthetic fun copyTo$default (Lkotlinx/io/Buffer;Ljava/io/OutputStream;JJILjava/lang/Object;)V public static final fun readFrom (Lkotlinx/io/Buffer;Ljava/io/InputStream;)Lkotlinx/io/Buffer; public static final fun readFrom (Lkotlinx/io/Buffer;Ljava/io/InputStream;J)Lkotlinx/io/Buffer; public static final fun readFrom (Lkotlinx/io/Buffer;Ljava/nio/ByteBuffer;)Lkotlinx/io/Buffer; - public static final fun writeTo (Lkotlinx/io/Buffer;Ljava/io/OutputStream;J)Lkotlinx/io/Buffer; + public static final fun writeTo (Lkotlinx/io/Buffer;Ljava/io/OutputStream;J)V public static final fun writeTo (Lkotlinx/io/Buffer;Ljava/nio/ByteBuffer;)I - public static synthetic fun writeTo$default (Lkotlinx/io/Buffer;Ljava/io/OutputStream;JILjava/lang/Object;)Lkotlinx/io/Buffer; + public static synthetic fun writeTo$default (Lkotlinx/io/Buffer;Ljava/io/OutputStream;JILjava/lang/Object;)V } public final class kotlinx/io/CoreKt { @@ -92,44 +84,44 @@ public abstract interface class kotlinx/io/RawSource : java/io/Closeable { } public abstract interface class kotlinx/io/Sink : kotlinx/io/RawSink { - public abstract fun emit ()Lkotlinx/io/Sink; - public abstract fun emitCompleteSegments ()Lkotlinx/io/Sink; + public abstract fun emit ()V + public abstract fun emitCompleteSegments ()V public abstract fun flush ()V public abstract fun getBuffer ()Lkotlinx/io/Buffer; - public abstract fun write (Lkotlinx/io/RawSource;J)Lkotlinx/io/Sink; - public abstract fun write ([BII)Lkotlinx/io/Sink; + public abstract fun write (Lkotlinx/io/RawSource;J)V + public abstract fun write ([BII)V public abstract fun writeAll (Lkotlinx/io/RawSource;)J - public abstract fun writeByte (B)Lkotlinx/io/Sink; - public abstract fun writeInt (I)Lkotlinx/io/Sink; - public abstract fun writeLong (J)Lkotlinx/io/Sink; - public abstract fun writeShort (S)Lkotlinx/io/Sink; + public abstract fun writeByte (B)V + public abstract fun writeInt (I)V + public abstract fun writeLong (J)V + public abstract fun writeShort (S)V } public final class kotlinx/io/Sink$DefaultImpls { - public static synthetic fun write$default (Lkotlinx/io/Sink;[BIIILjava/lang/Object;)Lkotlinx/io/Sink; + public static synthetic fun write$default (Lkotlinx/io/Sink;[BIIILjava/lang/Object;)V } public final class kotlinx/io/SinkExtKt { - public static final fun writeByte-EK-6454 (Lkotlinx/io/Sink;B)Lkotlinx/io/Sink; - public static final fun writeDecimalLong (Lkotlinx/io/Sink;J)Lkotlinx/io/Sink; - public static final fun writeHexadecimalUnsignedLong (Lkotlinx/io/Sink;J)Lkotlinx/io/Sink; - public static final fun writeInt-Qn1smSk (Lkotlinx/io/Sink;I)Lkotlinx/io/Sink; - public static final fun writeIntLe (Lkotlinx/io/Sink;I)Lkotlinx/io/Sink; - public static final fun writeIntLe-Qn1smSk (Lkotlinx/io/Sink;I)Lkotlinx/io/Sink; - public static final fun writeLong-2TYgG_w (Lkotlinx/io/Sink;J)Lkotlinx/io/Sink; - public static final fun writeLongLe (Lkotlinx/io/Sink;J)Lkotlinx/io/Sink; - public static final fun writeLongLe-2TYgG_w (Lkotlinx/io/Sink;J)Lkotlinx/io/Sink; - public static final fun writeShort-i8woANY (Lkotlinx/io/Sink;S)Lkotlinx/io/Sink; - public static final fun writeShortLe (Lkotlinx/io/Sink;S)Lkotlinx/io/Sink; - public static final fun writeShortLe-i8woANY (Lkotlinx/io/Sink;S)Lkotlinx/io/Sink; + public static final fun writeByte-EK-6454 (Lkotlinx/io/Sink;B)V + public static final fun writeDecimalLong (Lkotlinx/io/Sink;J)V + public static final fun writeHexadecimalUnsignedLong (Lkotlinx/io/Sink;J)V + public static final fun writeInt-Qn1smSk (Lkotlinx/io/Sink;I)V + public static final fun writeIntLe (Lkotlinx/io/Sink;I)V + public static final fun writeIntLe-Qn1smSk (Lkotlinx/io/Sink;I)V + public static final fun writeLong-2TYgG_w (Lkotlinx/io/Sink;J)V + public static final fun writeLongLe (Lkotlinx/io/Sink;J)V + public static final fun writeLongLe-2TYgG_w (Lkotlinx/io/Sink;J)V + public static final fun writeShort-i8woANY (Lkotlinx/io/Sink;S)V + public static final fun writeShortLe (Lkotlinx/io/Sink;S)V + public static final fun writeShortLe-i8woANY (Lkotlinx/io/Sink;S)V } public final class kotlinx/io/SinkKt { public static final fun channel (Lkotlinx/io/Sink;)Ljava/nio/channels/WritableByteChannel; public static final fun outputStream (Lkotlinx/io/Sink;)Ljava/io/OutputStream; public static final fun write (Lkotlinx/io/Sink;Ljava/nio/ByteBuffer;)I - public static final fun writeString (Lkotlinx/io/Sink;Ljava/lang/String;Ljava/nio/charset/Charset;II)Lkotlinx/io/Sink; - public static synthetic fun writeString$default (Lkotlinx/io/Sink;Ljava/lang/String;Ljava/nio/charset/Charset;IIILjava/lang/Object;)Lkotlinx/io/Sink; + public static final fun writeString (Lkotlinx/io/Sink;Ljava/lang/String;Ljava/nio/charset/Charset;II)V + public static synthetic fun writeString$default (Lkotlinx/io/Sink;Ljava/lang/String;Ljava/nio/charset/Charset;IIILjava/lang/Object;)V } public abstract interface class kotlinx/io/Source : kotlinx/io/RawSource { @@ -192,9 +184,9 @@ public final class kotlinx/io/Utf8Kt { public static synthetic fun readUtf8LineStrict$default (Lkotlinx/io/Source;JILjava/lang/Object;)Ljava/lang/String; public static final fun utf8Size (Ljava/lang/String;II)J public static synthetic fun utf8Size$default (Ljava/lang/String;IIILjava/lang/Object;)J - public static final fun writeUtf8 (Lkotlinx/io/Sink;Ljava/lang/String;II)Lkotlinx/io/Sink; - public static synthetic fun writeUtf8$default (Lkotlinx/io/Sink;Ljava/lang/String;IIILjava/lang/Object;)Lkotlinx/io/Sink; - public static final fun writeUtf8CodePoint (Lkotlinx/io/Sink;I)Lkotlinx/io/Sink; + public static final fun writeUtf8 (Lkotlinx/io/Sink;Ljava/lang/String;II)V + public static synthetic fun writeUtf8$default (Lkotlinx/io/Sink;Ljava/lang/String;IIILjava/lang/Object;)V + public static final fun writeUtf8CodePoint (Lkotlinx/io/Sink;I)V } public final class kotlinx/io/files/Path { diff --git a/core/common/src/Buffer.kt b/core/common/src/Buffer.kt index a0f6c8873..201db0ddd 100644 --- a/core/common/src/Buffer.kt +++ b/core/common/src/Buffer.kt @@ -55,12 +55,12 @@ public expect class Buffer() : Source, Sink { * This method does not affect the buffer's content as there is no upstream to write data to. */ @DelicateIoApi - override fun emitCompleteSegments(): Buffer + override fun emitCompleteSegments() /** * This method does not affect the buffer's content as there is no upstream to write data to. */ - override fun emit(): Buffer + override fun emit() /** * This method does not affect the buffer's content as there is no upstream to write data to. @@ -80,7 +80,7 @@ public expect class Buffer() : Source, Sink { out: Buffer, offset: Long = 0L, byteCount: Long = size - offset - ): Buffer + ) /** * Returns the number of bytes in segments that are fully filled and are no longer writable. @@ -119,17 +119,17 @@ public expect class Buffer() : Source, Sink { */ internal fun writableSegment(minimumCapacity: Int): Segment - override fun write(source: ByteArray, offset: Int, byteCount: Int): Buffer + override fun write(source: ByteArray, offset: Int, byteCount: Int) - override fun write(source: RawSource, byteCount: Long): Buffer + override fun write(source: RawSource, byteCount: Long) - override fun writeByte(byte: Byte): Buffer + override fun writeByte(byte: Byte) - override fun writeShort(short: Short): Buffer + override fun writeShort(short: Short) - override fun writeInt(int: Int): Buffer + override fun writeInt(int: Int) - override fun writeLong(long: Long): Buffer + override fun writeLong(long: Long) /** * Returns a deep copy of this buffer. diff --git a/core/common/src/Sink.kt b/core/common/src/Sink.kt index f81b31c6e..1750fa44c 100644 --- a/core/common/src/Sink.kt +++ b/core/common/src/Sink.kt @@ -62,7 +62,7 @@ public expect sealed interface Sink : RawSink { * is out of range of [source] array indices. * @throws IllegalStateException when the sink is closed. */ - public fun write(source: ByteArray, offset: Int = 0, byteCount: Int = source.size - offset): Sink + public fun write(source: ByteArray, offset: Int = 0, byteCount: Int = source.size - offset) /** * Removes all bytes from [source] and write them to this sink. @@ -87,7 +87,7 @@ public expect sealed interface Sink : RawSink { * @throws IllegalArgumentException when [byteCount] is negative. * @throws IllegalStateException when the sink or [source] is closed. */ - public fun write(source: RawSource, byteCount: Long): Sink + public fun write(source: RawSource, byteCount: Long) /** * Writes a byte to this sink. @@ -96,7 +96,7 @@ public expect sealed interface Sink : RawSink { * * @throws IllegalStateException when the sink is closed. */ - public fun writeByte(byte: Byte): Sink + public fun writeByte(byte: Byte) /** * Writes two bytes containing [short], in the big-endian order, to this sink. @@ -105,7 +105,7 @@ public expect sealed interface Sink : RawSink { * * @throws IllegalStateException when the sink is closed. */ - public fun writeShort(short: Short): Sink + public fun writeShort(short: Short) /** * Writes four bytes containing [int], in the big-endian order, to this sink. @@ -114,7 +114,7 @@ public expect sealed interface Sink : RawSink { * * @throws IllegalStateException when the sink is closed. */ - public fun writeInt(int: Int): Sink + public fun writeInt(int: Int) /** * Writes eight bytes containing [long], in the big-endian order, to this sink. @@ -123,7 +123,7 @@ public expect sealed interface Sink : RawSink { * * @throws IllegalStateException when the sink is closed. */ - public fun writeLong(long: Long): Sink + public fun writeLong(long: Long) /** * Writes all buffered data to the underlying sink, if one exists. @@ -142,7 +142,7 @@ public expect sealed interface Sink : RawSink { * * @throws IllegalStateException when the sink is closed. */ - public fun emit(): Sink + public fun emit() /** * Writes complete segments to the underlying sink, if one exists. @@ -155,5 +155,5 @@ public expect sealed interface Sink : RawSink { * @throws IllegalStateException when the sink is closed. */ @DelicateIoApi - public fun emitCompleteSegments(): Sink + public fun emitCompleteSegments() } diff --git a/core/common/src/SinkExt.kt b/core/common/src/SinkExt.kt index 89700016a..5aaf170da 100644 --- a/core/common/src/SinkExt.kt +++ b/core/common/src/SinkExt.kt @@ -17,9 +17,8 @@ internal val HEX_DIGIT_BYTES = "0123456789abcdef".asUtf8ToByteArray() * * @throws IllegalStateException when the sink is closed. */ -public fun T.writeShortLe(short: Short): T { +public fun T.writeShortLe(short: Short) { this.writeShort(short.reverseBytes()) - return this } /** @@ -29,9 +28,8 @@ public fun T.writeShortLe(short: Short): T { * * @throws IllegalStateException when the sink is closed. */ -public fun T.writeIntLe(int: Int): T { +public fun T.writeIntLe(int: Int) { this.writeInt(int.reverseBytes()) - return this } /** @@ -41,9 +39,8 @@ public fun T.writeIntLe(int: Int): T { * * @throws IllegalStateException when the sink is closed. */ -public fun T.writeLongLe(long: Long): T { +public fun T.writeLongLe(long: Long) { this.writeLong(long.reverseBytes()) - return this } /** @@ -56,19 +53,20 @@ public fun T.writeLongLe(long: Long): T { * @throws IllegalStateException when the sink is closed. */ @OptIn(DelicateIoApi::class) -public fun T.writeDecimalLong(long: Long): T { +public fun T.writeDecimalLong(long: Long) { var v = long if (v == 0L) { // Both a shortcut and required since the following code can't handle zero. writeByte('0'.code.toByte()) - return this + return } var negative = false if (v < 0L) { v = -v if (v < 0L) { // Only true for Long.MIN_VALUE. - return writeUtf8("-9223372036854775808") + writeUtf8("-9223372036854775808") + return } negative = true } @@ -121,7 +119,6 @@ public fun T.writeDecimalLong(long: Long): T { tail.limit += width buffer.size += width.toLong() emitCompleteSegments() - return this } /** @@ -134,12 +131,12 @@ public fun T.writeDecimalLong(long: Long): T { * @throws IllegalStateException when the sink is closed. */ @OptIn(DelicateIoApi::class) -public fun T.writeHexadecimalUnsignedLong(long: Long): T { +public fun T.writeHexadecimalUnsignedLong(long: Long) { var v = long if (v == 0L) { // Both a shortcut and required since the following code can't handle zero. writeByte('0'.code.toByte()) - return this + return } // Mask every bit below the most significant bit to a 1 @@ -176,7 +173,6 @@ public fun T.writeHexadecimalUnsignedLong(long: Long): T { tail.limit += width buffer.size += width.toLong() emitCompleteSegments() - return this } /** @@ -186,9 +182,8 @@ public fun T.writeHexadecimalUnsignedLong(long: Long): T { * * @throws IllegalStateException when the sink is closed. */ -public fun T.writeByte(byte: UByte): T { +public fun T.writeByte(byte: UByte) { writeByte(byte.toByte()) - return this } /** @@ -198,9 +193,8 @@ public fun T.writeByte(byte: UByte): T { * * @throws IllegalStateException when the sink is closed. */ -public fun T.writeShort(short: UShort): T { +public fun T.writeShort(short: UShort) { writeShort(short.toShort()) - return this } /** @@ -210,9 +204,8 @@ public fun T.writeShort(short: UShort): T { * * @throws IllegalStateException when the sink is closed. */ -public fun T.writeInt(int: UInt): T { +public fun T.writeInt(int: UInt) { writeInt(int.toInt()) - return this } /** @@ -222,9 +215,8 @@ public fun T.writeInt(int: UInt): T { * * @throws IllegalStateException when the sink is closed. */ -public fun T.writeLong(long: ULong): T { +public fun T.writeLong(long: ULong) { writeLong(long.toLong()) - return this } /** @@ -234,9 +226,8 @@ public fun T.writeLong(long: ULong): T { * * @throws IllegalStateException when the sink is closed. */ -public fun T.writeShortLe(short: UShort): T { +public fun T.writeShortLe(short: UShort) { writeShortLe(short.toShort()) - return this } /** @@ -246,9 +237,8 @@ public fun T.writeShortLe(short: UShort): T { * * @throws IllegalStateException when the sink is closed. */ -public fun T.writeIntLe(int: UInt): T { +public fun T.writeIntLe(int: UInt) { writeIntLe(int.toInt()) - return this } /** @@ -258,7 +248,6 @@ public fun T.writeIntLe(int: UInt): T { * * @throws IllegalStateException when the sink is closed. */ -public fun T.writeLongLe(long: ULong): T { +public fun T.writeLongLe(long: ULong) { writeLongLe(long.toLong()) - return this } diff --git a/core/common/src/SourceExt.kt b/core/common/src/SourceExt.kt index 947526c1d..4dda8cd8f 100644 --- a/core/common/src/SourceExt.kt +++ b/core/common/src/SourceExt.kt @@ -80,9 +80,13 @@ public fun Source.readDecimalLong(): Long { // Detect when the digit would cause an overflow. if (value < OVERFLOW_ZONE || value == OVERFLOW_ZONE && digit < overflowDigit) { - val buffer = Buffer().writeDecimalLong(value).writeByte(b) - if (!negative) buffer.readByte() // Skip negative sign. - throw NumberFormatException("Number too large: ${buffer.readUtf8()}") + with(Buffer()) { + writeDecimalLong(value) + writeByte(b) + + if (!negative) readByte() // Skip negative sign. + throw NumberFormatException("Number too large: ${readUtf8()}") + } } value = value * 10L + digit seen++ @@ -131,8 +135,11 @@ public fun Source.readHexadecimalUnsignedLong(): Long { else -> break } if (result and -0x1000000000000000L != 0L) { - val buffer = Buffer().writeHexadecimalUnsignedLong(result).writeByte(b) - throw NumberFormatException("Number too large: " + buffer.readUtf8()) + with(Buffer()){ + writeHexadecimalUnsignedLong(result) + writeByte(b) + throw NumberFormatException("Number too large: " + readUtf8()) + } } readByte() // consume byte result = result.shl(4) + bDigit diff --git a/core/common/src/Utf8.kt b/core/common/src/Utf8.kt index 90a3ee2c7..77f1fa25b 100644 --- a/core/common/src/Utf8.kt +++ b/core/common/src/Utf8.kt @@ -130,10 +130,9 @@ public fun String.utf8Size(beginIndex: Int = 0, endIndex: Int = length): Long { * @throws IllegalStateException when the sink is closed. */ @OptIn(DelicateIoApi::class) -public fun T.writeUtf8CodePoint(codePoint: Int): T { +public fun T.writeUtf8CodePoint(codePoint: Int) { buffer.commonWriteUtf8CodePoint(codePoint) emitCompleteSegments() - return this } /** @@ -148,10 +147,9 @@ public fun T.writeUtf8CodePoint(codePoint: Int): T { * @throws IllegalStateException when the sink is closed. */ @OptIn(DelicateIoApi::class) -public fun T.writeUtf8(string: String, beginIndex: Int = 0, endIndex: Int = string.length): T { +public fun T.writeUtf8(string: String, beginIndex: Int = 0, endIndex: Int = string.length) { buffer.commonWriteUtf8(string, beginIndex, endIndex) emitCompleteSegments() - return this } /** diff --git a/core/common/src/internal/-Buffer.kt b/core/common/src/internal/-Buffer.kt index 21f2bae2f..ac6024b25 100644 --- a/core/common/src/internal/-Buffer.kt +++ b/core/common/src/internal/-Buffer.kt @@ -64,9 +64,9 @@ internal inline fun Buffer.commonCopyTo( out: Buffer, offset: Long, byteCount: Long -): Buffer { +) { checkOffsetAndCount(size, offset, byteCount) - if (byteCount == 0L) return this + if (byteCount == 0L) return var currentOffset = offset var remainingByteCount = byteCount @@ -96,8 +96,6 @@ internal inline fun Buffer.commonCopyTo( currentOffset = 0L s = s.next } - - return this } internal inline fun Buffer.commonCompleteSegmentByteCount(): Long { @@ -283,7 +281,7 @@ internal inline fun Buffer.commonWrite( source: ByteArray, offset: Int, byteCount: Int -): Buffer { +) { var currentOffset = offset checkOffsetAndCount(source.size.toLong(), currentOffset.toLong(), byteCount.toLong()) @@ -304,7 +302,6 @@ internal inline fun Buffer.commonWrite( } size += byteCount.toLong() - return this } internal inline fun Buffer.commonRead(sink: ByteArray, offset: Int, byteCount: Int): Int { @@ -327,9 +324,6 @@ internal inline fun Buffer.commonRead(sink: ByteArray, offset: Int, byteCount: I return toCopy } -internal const val OVERFLOW_ZONE = Long.MIN_VALUE / 10L -internal const val OVERFLOW_DIGIT_START = Long.MIN_VALUE % 10L + 1 - internal inline fun Buffer.commonReadFully(sink: RawSink, byteCount: Long) { if (size < byteCount) { sink.write(this, size) // Exhaust ourselves. @@ -445,7 +439,7 @@ internal inline fun Buffer.commonReadUtf8CodePoint(): Int { } } -internal inline fun Buffer.commonWriteUtf8(string: String, beginIndex: Int, endIndex: Int): Buffer { +internal inline fun Buffer.commonWriteUtf8(string: String, beginIndex: Int, endIndex: Int) { checkOffsetAndCount(string.length.toLong(), beginIndex.toLong(), (endIndex - beginIndex).toLong()) //require(beginIndex >= 0) { "beginIndex < 0: $beginIndex" } //require(endIndex >= beginIndex) { "endIndex < beginIndex: $endIndex < $beginIndex" } @@ -533,11 +527,9 @@ internal inline fun Buffer.commonWriteUtf8(string: String, beginIndex: Int, endI } } } - - return this } -internal inline fun Buffer.commonWriteUtf8CodePoint(codePoint: Int): Buffer { +internal inline fun Buffer.commonWriteUtf8CodePoint(codePoint: Int) { when { codePoint < 0x80 -> { // Emit a 7-bit code point with 1 byte. @@ -584,8 +576,6 @@ internal inline fun Buffer.commonWriteUtf8CodePoint(codePoint: Int): Buffer { throw IllegalArgumentException("Unexpected code point: 0x${codePoint.toHexString()}") } } - - return this } internal inline fun Buffer.commonWriteAll(source: RawSource): Long { @@ -598,24 +588,22 @@ internal inline fun Buffer.commonWriteAll(source: RawSource): Long { return totalBytesRead } -internal inline fun Buffer.commonWrite(source: RawSource, byteCount: Long): Buffer { +internal inline fun Buffer.commonWrite(source: RawSource, byteCount: Long) { var remainingByteCount = byteCount while (remainingByteCount > 0L) { val read = source.read(this, remainingByteCount) if (read == -1L) throw EOFException() remainingByteCount -= read } - return this } -internal inline fun Buffer.commonWriteByte(b: Byte): Buffer { +internal inline fun Buffer.commonWriteByte(b: Byte) { val tail = writableSegment(1) tail.data[tail.limit++] = b size += 1L - return this } -internal inline fun Buffer.commonWriteShort(s: Short): Buffer { +internal inline fun Buffer.commonWriteShort(s: Short) { val tail = writableSegment(2) val data = tail.data var limit = tail.limit @@ -623,10 +611,9 @@ internal inline fun Buffer.commonWriteShort(s: Short): Buffer { data[limit++] = (s.toInt() and 0xff).toByte() // ktlint-disable no-multi-spaces tail.limit = limit size += 2L - return this } -internal inline fun Buffer.commonWriteInt(i: Int): Buffer { +internal inline fun Buffer.commonWriteInt(i: Int) { val tail = writableSegment(4) val data = tail.data var limit = tail.limit @@ -636,10 +623,9 @@ internal inline fun Buffer.commonWriteInt(i: Int): Buffer { data[limit++] = (i and 0xff).toByte() // ktlint-disable no-multi-spaces tail.limit = limit size += 4L - return this } -internal inline fun Buffer.commonWriteLong(v: Long): Buffer { +internal inline fun Buffer.commonWriteLong(v: Long) { val tail = writableSegment(8) val data = tail.data var limit = tail.limit @@ -653,7 +639,6 @@ internal inline fun Buffer.commonWriteLong(v: Long): Buffer { data[limit++] = (v and 0xffL).toByte() // ktlint-disable no-multi-spaces tail.limit = limit size += 8L - return this } internal inline fun Buffer.commonWrite(source: Buffer, byteCount: Long) { diff --git a/core/common/src/internal/-RealBufferedSink.kt b/core/common/src/internal/-RealBufferedSink.kt index 431dd346e..c31328574 100644 --- a/core/common/src/internal/-RealBufferedSink.kt +++ b/core/common/src/internal/-RealBufferedSink.kt @@ -39,11 +39,11 @@ internal inline fun RealSink.commonWrite( source: ByteArray, offset: Int, byteCount: Int -): Sink { +) { checkOffsetAndCount(source.size.toLong(), offset.toLong(), byteCount.toLong()) check(!closed) { "closed" } buffer.write(source, offset, byteCount) - return emitCompleteSegments() + emitCompleteSegments() } @OptIn(DelicateIoApi::class) @@ -59,7 +59,7 @@ internal inline fun RealSink.commonWriteAll(source: RawSource): Long { } @OptIn(DelicateIoApi::class) -internal inline fun RealSink.commonWrite(source: RawSource, byteCount: Long): Sink { +internal inline fun RealSink.commonWrite(source: RawSource, byteCount: Long) { require(byteCount >= 0) { "byteCount ($byteCount) should not be negative."} var remainingByteCount = byteCount while (remainingByteCount > 0L) { @@ -68,51 +68,48 @@ internal inline fun RealSink.commonWrite(source: RawSource, byteCount: Long): Si remainingByteCount -= read emitCompleteSegments() } - return this } @OptIn(DelicateIoApi::class) -internal inline fun RealSink.commonWriteByte(b: Byte): Sink { +internal inline fun RealSink.commonWriteByte(b: Byte) { check(!closed) { "closed" } buffer.writeByte(b) - return emitCompleteSegments() + emitCompleteSegments() } @OptIn(DelicateIoApi::class) -internal inline fun RealSink.commonWriteShort(s: Short): Sink { +internal inline fun RealSink.commonWriteShort(s: Short) { check(!closed) { "closed" } buffer.writeShort(s) - return emitCompleteSegments() + emitCompleteSegments() } @OptIn(DelicateIoApi::class) -internal inline fun RealSink.commonWriteInt(i: Int): Sink { +internal inline fun RealSink.commonWriteInt(i: Int) { check(!closed) { "closed" } buffer.writeInt(i) - return emitCompleteSegments() + emitCompleteSegments() } @OptIn(DelicateIoApi::class) -internal inline fun RealSink.commonWriteLong(v: Long): Sink { +internal inline fun RealSink.commonWriteLong(v: Long) { check(!closed) { "closed" } buffer.writeLong(v) - return emitCompleteSegments() + emitCompleteSegments() } @OptIn(DelicateIoApi::class) -internal inline fun RealSink.commonEmitCompleteSegments(): Sink { +internal inline fun RealSink.commonEmitCompleteSegments() { check(!closed) { "closed" } val byteCount = buffer.completeSegmentByteCount() if (byteCount > 0L) sink.write(buffer, byteCount) - return this } @OptIn(DelicateIoApi::class) -internal inline fun RealSink.commonEmit(): Sink { +internal inline fun RealSink.commonEmit() { check(!closed) { "closed" } val byteCount = buffer.size if (byteCount > 0L) sink.write(buffer, byteCount) - return this } @OptIn(DelicateIoApi::class) diff --git a/core/common/test/AbstractSinkTest.kt b/core/common/test/AbstractSinkTest.kt index 51359e21e..add4ac87e 100644 --- a/core/common/test/AbstractSinkTest.kt +++ b/core/common/test/AbstractSinkTest.kt @@ -172,7 +172,8 @@ abstract class AbstractSinkTest internal constructor( } @Test fun writeAll() { - val source = Buffer().writeUtf8("abcdef") + val source = Buffer() + source.writeUtf8("abcdef") assertEquals(6, sink.writeAll(source)) assertEquals(0, source.size) @@ -187,7 +188,8 @@ abstract class AbstractSinkTest internal constructor( } @Test fun writeSource() { - val source = Buffer().writeUtf8("abcdef") + val source = Buffer() + source.writeUtf8("abcdef") // Force resolution of the Source method overload. sink.write(source as RawSource, 4) @@ -210,7 +212,7 @@ abstract class AbstractSinkTest internal constructor( } @Test fun writeSourcePropagatesEof() { - val source: RawSource = Buffer().writeUtf8("abcd") + val source: RawSource = Buffer().also { it.writeUtf8("abcd") } assertFailsWith { sink.write(source, 8) @@ -222,7 +224,8 @@ abstract class AbstractSinkTest internal constructor( } @Test fun writeBufferThrowsIEA() { - val source: Buffer = Buffer().writeUtf8("abcd") + val source: Buffer = Buffer() + source.writeUtf8("abcd") assertFailsWith { sink.write(source, 8) @@ -233,7 +236,8 @@ abstract class AbstractSinkTest internal constructor( } @Test fun writeSourceWithNegativeBytesCount() { - val source = Buffer().writeByte(0) + val source = Buffer() + source.writeByte(0) assertFailsWith { sink.write(source, -1L) @@ -306,7 +310,11 @@ abstract class AbstractSinkTest internal constructor( } private fun assertLongDecimalString(string: String, value: Long) { - sink.writeDecimalLong(value).writeUtf8("zzz").flush() + with (sink) { + writeDecimalLong(value) + writeUtf8("zzz") + flush() + } val expected = "${string}zzz" val actual = data.readUtf8() assertEquals(expected, actual, "$value expected $expected but was $actual") @@ -324,7 +332,11 @@ abstract class AbstractSinkTest internal constructor( } private fun assertLongHexString(value: Long) { - sink.writeHexadecimalUnsignedLong(value).writeUtf8("zzz").flush() + with (sink) { + writeHexadecimalUnsignedLong(value) + writeUtf8("zzz") + flush() + } val expected = "${value.toHexString()}zzz" val actual = data.readUtf8() assertEquals(expected, actual, "$value expected $expected but was $actual") @@ -349,37 +361,44 @@ abstract class AbstractSinkTest internal constructor( } @Test fun writeUByte() { - sink.writeByte(0xffu).flush() + sink.writeByte(0xffu) + sink.flush() assertEquals(-1, data.readByte()) } @Test fun writeUShort() { - sink.writeShort(0xffffu).flush() + sink.writeShort(0xffffu) + sink.flush() assertEquals(-1, data.readShort()) } @Test fun writeUShortLe() { - sink.writeShortLe(0x1234u).flush() + sink.writeShortLe(0x1234u) + sink.flush() assertEquals("[hex=3412]", data.toString()) } @Test fun writeUInt() { - sink.writeInt(0xffffffffu).flush() + sink.writeInt(0xffffffffu) + sink.flush() assertEquals(-1, data.readInt()) } @Test fun writeUIntLe() { - sink.writeIntLe(0x12345678u).flush() + sink.writeIntLe(0x12345678u) + sink.flush() assertEquals("[hex=78563412]", data.toString()) } @Test fun writeULong() { - sink.writeLong(0xffffffffffffffffu).flush() + sink.writeLong(0xffffffffffffffffu) + sink.flush() assertEquals(-1, data.readLong()) } @Test fun writeULongLe() { - sink.writeLongLe(0x1234567890abcdefu).flush() + sink.writeLongLe(0x1234567890abcdefu) + sink.flush() assertEquals("[hex=efcdab9078563412]", data.toString()) } } diff --git a/core/common/test/AbstractSourceTest.kt b/core/common/test/AbstractSourceTest.kt index 095720af6..77a92f074 100644 --- a/core/common/test/AbstractSourceTest.kt +++ b/core/common/test/AbstractSourceTest.kt @@ -299,7 +299,7 @@ abstract class AbstractBufferedSourceTest internal constructor( @Test fun readNegativeBytesFromSource() { assertFailsWith { - source.read(Buffer().writeByte(0), -1L) + source.read(Buffer().also { it.writeByte(0) }, -1L) } } @@ -310,7 +310,7 @@ abstract class AbstractBufferedSourceTest internal constructor( source.close() assertFailsWith { - source.read(Buffer().writeByte(0), 1L) + source.read(Buffer().also { it.writeByte(0) }, 1L) } } @@ -350,7 +350,8 @@ abstract class AbstractBufferedSourceTest internal constructor( @Test fun readFullyByteArray() { val data = Buffer() - data.writeUtf8("Hello").writeUtf8("e".repeat(Segment.SIZE)) + data.writeUtf8("Hello") + data.writeUtf8("e".repeat(Segment.SIZE)) val expected = data.copy().readByteArray() sink.write(data, data.size) @@ -598,8 +599,12 @@ abstract class AbstractBufferedSourceTest internal constructor( } @Test fun indexOfByteWithStartOffset() { - sink.writeUtf8("a").writeUtf8("b".repeat(Segment.SIZE)).writeUtf8("c") - sink.emit() + with(sink) { + writeUtf8("a") + writeUtf8("b".repeat(Segment.SIZE)) + writeUtf8("c") + emit() + } assertEquals(-1, source.indexOf('a'.code.toByte(), 1)) assertEquals(15, source.indexOf('b'.code.toByte(), 15)) } @@ -680,15 +685,23 @@ abstract class AbstractBufferedSourceTest internal constructor( } @Test fun request() { - sink.writeUtf8("a").writeUtf8("b".repeat(Segment.SIZE)).writeUtf8("c") - sink.emit() + with (sink) { + writeUtf8("a") + writeUtf8("b".repeat(Segment.SIZE)) + writeUtf8("c") + emit() + } assertTrue(source.request((Segment.SIZE + 2).toLong())) assertFalse(source.request((Segment.SIZE + 3).toLong())) } @Test fun require() { - sink.writeUtf8("a").writeUtf8("b".repeat(Segment.SIZE)).writeUtf8("c") - sink.emit() + with (sink) { + writeUtf8("a") + writeUtf8("b".repeat(Segment.SIZE)) + writeUtf8("c") + emit() + } source.require((Segment.SIZE + 2).toLong()) assertFailsWith { source.require((Segment.SIZE + 3).toLong()) @@ -724,21 +737,27 @@ abstract class AbstractBufferedSourceTest internal constructor( } @Test fun longHexStringAcrossSegment() { - sink.writeUtf8("a".repeat(Segment.SIZE - 8)).writeUtf8("FFFFFFFFFFFFFFFF") - sink.emit() + with (sink) { + writeUtf8("a".repeat(Segment.SIZE - 8)) + writeUtf8("FFFFFFFFFFFFFFFF") + emit() + } source.skip((Segment.SIZE - 8).toLong()) assertEquals(-1, source.readHexadecimalUnsignedLong()) } @Test fun longHexTerminatedByNonDigit() { - sink.writeUtf8("abcd,").emit() + sink.writeUtf8("abcd,") + sink.emit() assertEquals(0xabcdL, source.readHexadecimalUnsignedLong()) } @Test fun longHexAlphabet() { - sink.writeUtf8("7896543210abcdef").emit() + sink.writeUtf8("7896543210abcdef") + sink.emit() assertEquals(0x7896543210abcdefL, source.readHexadecimalUnsignedLong()) - sink.writeUtf8("ABCDEF").emit() + sink.writeUtf8("ABCDEF") + sink.emit() assertEquals(0xabcdefL, source.readHexadecimalUnsignedLong()) } @@ -789,9 +808,12 @@ abstract class AbstractBufferedSourceTest internal constructor( } @Test fun longDecimalStringAcrossSegment() { - sink.writeUtf8("a".repeat(Segment.SIZE - 8)).writeUtf8("1234567890123456") - sink.writeUtf8("zzz") - sink.emit() + with (sink) { + writeUtf8("a".repeat(Segment.SIZE - 8)) + writeUtf8("1234567890123456") + writeUtf8("zzz") + emit() + } source.skip((Segment.SIZE - 8).toLong()) assertEquals(1234567890123456L, source.readDecimalLong()) assertEquals("zzz", source.readUtf8()) @@ -862,39 +884,51 @@ abstract class AbstractBufferedSourceTest internal constructor( } @Test fun codePoints() { - sink.writeByte(0x7f) - sink.emit() - assertEquals(0x7f, source.readUtf8CodePoint().toLong()) - - sink.writeByte(0xdf.toByte()).writeByte(0xbf.toByte()) - sink.emit() - assertEquals(0x07ff, source.readUtf8CodePoint().toLong()) - - sink.writeByte(0xef.toByte()).writeByte(0xbf.toByte()).writeByte(0xbf.toByte()) - sink.emit() - assertEquals(0xffff, source.readUtf8CodePoint().toLong()) - - sink.writeByte(0xf4.toByte()).writeByte(0x8f.toByte()) - .writeByte(0xbf.toByte()).writeByte(0xbf.toByte()) - sink.emit() - assertEquals(0x10ffff, source.readUtf8CodePoint().toLong()) + with (sink) { + writeByte(0x7f) + emit() + assertEquals(0x7f, source.readUtf8CodePoint().toLong()) + + writeByte(0xdf.toByte()) + writeByte(0xbf.toByte()) + emit() + assertEquals(0x07ff, source.readUtf8CodePoint().toLong()) + + writeByte(0xef.toByte()) + writeByte(0xbf.toByte()) + writeByte(0xbf.toByte()) + emit() + assertEquals(0xffff, source.readUtf8CodePoint().toLong()) + + writeByte(0xf4.toByte()) + writeByte(0x8f.toByte()) + writeByte(0xbf.toByte()) + writeByte(0xbf.toByte()) + emit() + assertEquals(0x10ffff, source.readUtf8CodePoint().toLong()) + } } @Test fun codePointsFromExhaustedSource() { - sink.writeByte(0xdf.toByte()) // a second byte is missing - sink.emit() - assertFailsWith { source.readUtf8CodePoint() } - assertEquals(1, source.readByteArray().size) - - sink.writeByte(0xe2.toByte()).writeByte(0x98.toByte()) // a third byte is missing - sink.emit() - assertFailsWith { source.readUtf8CodePoint() } - assertEquals(2, source.readByteArray().size) - - sink.writeByte(0xf0.toByte()).writeByte(0x9f.toByte()).writeByte(0x92.toByte()) // a forth byte is missing - sink.emit() - assertFailsWith { source.readUtf8CodePoint() } - assertEquals(3, source.readByteArray().size) + with (sink) { + writeByte(0xdf.toByte()) // a second byte is missing + emit() + assertFailsWith { source.readUtf8CodePoint() } + assertEquals(1, source.readByteArray().size) + + writeByte(0xe2.toByte()) + writeByte(0x98.toByte()) // a third byte is missing + emit() + assertFailsWith { source.readUtf8CodePoint() } + assertEquals(2, source.readByteArray().size) + + writeByte(0xf0.toByte()) + writeByte(0x9f.toByte()) + writeByte(0x92.toByte()) // a forth byte is missing + emit() + assertFailsWith { source.readUtf8CodePoint() } + assertEquals(3, source.readByteArray().size) + } } @Test fun decimalStringWithManyLeadingZeros() { @@ -1047,68 +1081,83 @@ abstract class AbstractBufferedSourceTest internal constructor( } @Test fun readUtf8Line() { - sink.writeUtf8("first line\nsecond line\n").flush() + sink.writeUtf8("first line\nsecond line\n") + sink.flush() assertEquals("first line", source.readUtf8Line()) assertEquals("second line\n", source.readUtf8()) assertEquals(null, source.readUtf8Line()) - sink.writeUtf8("\nnext line\n").flush() + sink.writeUtf8("\nnext line\n") + sink.flush() assertEquals("", source.readUtf8Line()) assertEquals("next line", source.readUtf8Line()) - sink.writeUtf8("There is no newline!").flush() + sink.writeUtf8("There is no newline!") + sink.flush() assertEquals("There is no newline!", source.readUtf8Line()) - sink.writeUtf8("Wot do u call it?\r\nWindows").flush() + sink.writeUtf8("Wot do u call it?\r\nWindows") + sink.flush() assertEquals("Wot do u call it?", source.readUtf8Line()) source.readAll(blackholeSink()) - sink.writeUtf8("reo\rde\red\n").flush() + sink.writeUtf8("reo\rde\red\n") + sink.flush() assertEquals("reo\rde\red", source.readUtf8Line()) } @Test fun readUtf8LineStrict() { - sink.writeUtf8("first line\nsecond line\n").flush() + sink.writeUtf8("first line\nsecond line\n") + sink.flush() assertEquals("first line", source.readUtf8LineStrict()) assertEquals("second line\n", source.readUtf8()) assertFailsWith { source.readUtf8LineStrict() } - sink.writeUtf8("\nnext line\n").flush() + sink.writeUtf8("\nnext line\n") + sink.flush() assertEquals("", source.readUtf8LineStrict()) assertEquals("next line", source.readUtf8LineStrict()) - sink.writeUtf8("There is no newline!").flush() + sink.writeUtf8("There is no newline!") + sink.flush() assertFailsWith { source.readUtf8LineStrict() } assertEquals("There is no newline!", source.readUtf8()) - sink.writeUtf8("Wot do u call it?\r\nWindows").flush() + sink.writeUtf8("Wot do u call it?\r\nWindows") + sink.flush() assertEquals("Wot do u call it?", source.readUtf8LineStrict()) source.readAll(blackholeSink()) - sink.writeUtf8("reo\rde\red\n").flush() + sink.writeUtf8("reo\rde\red\n") + sink.flush() assertEquals("reo\rde\red", source.readUtf8LineStrict()) - sink.writeUtf8("line\n").flush() + sink.writeUtf8("line\n") + sink.flush() assertFailsWith { source.readUtf8LineStrict(3) } assertEquals("line", source.readUtf8LineStrict(4)) assertTrue(source.exhausted()) - sink.writeUtf8("line\r\n").flush() + sink.writeUtf8("line\r\n") + sink.flush() assertFailsWith { source.readUtf8LineStrict(3) } assertEquals("line", source.readUtf8LineStrict(4)) assertTrue(source.exhausted()) - sink.writeUtf8("line\n").flush() + sink.writeUtf8("line\n") + sink.flush() assertEquals("line", source.readUtf8LineStrict(5)) assertTrue(source.exhausted()) } @Test fun readUnsignedByte() { - sink.writeByte(0) - .writeByte(-1) - .writeByte(-128) - .writeByte(127) - .flush() + with(sink) { + writeByte(0) + writeByte(-1) + writeByte(-128) + writeByte(127) + flush() + } assertEquals(0u, source.readUByte()) assertEquals(255u, source.readUByte()) @@ -1122,11 +1171,13 @@ abstract class AbstractBufferedSourceTest internal constructor( } @Test fun readUnsignedShort() { - sink.writeShort(0) - .writeShort(-1) - .writeShort(-32768) - .writeShort(32767) - .flush() + with (sink) { + writeShort(0) + writeShort(-1) + writeShort(-32768) + writeShort(32767) + flush() + } assertEquals(0u, source.readUShort()) assertEquals(65535u, source.readUShort()) @@ -1136,30 +1187,35 @@ abstract class AbstractBufferedSourceTest internal constructor( } @Test fun readUnsignedShortLe() { - sink.write(byteArrayOf(0x12, 0x34)).flush() + sink.write(byteArrayOf(0x12, 0x34)) + sink.flush() assertEquals(0x3412u, source.readUShortLe()) } @Test fun readTooShortUnsignedShortThrows() { assertFailsWith { source.readUShort() } - sink.writeByte(0).flush() + sink.writeByte(0) + sink.flush() assertFailsWith { source.readUShort() } assertTrue(source.request(1)) } @Test fun readTooShortUnsignedShortLeThrows() { assertFailsWith { source.readUShortLe() } - sink.writeByte(0).flush() + sink.writeByte(0) + sink.flush() assertFailsWith { source.readUShortLe() } assertTrue(source.request(1)) } @Test fun readUnsignedInt() { - sink.writeInt(0) - .writeInt(-1) - .writeInt(Int.MIN_VALUE) - .writeInt(Int.MAX_VALUE) - .flush() + with (sink) { + writeInt(0) + writeInt(-1) + writeInt(Int.MIN_VALUE) + writeInt(Int.MAX_VALUE) + flush() + } assertEquals(0u, source.readUInt()) assertEquals(UInt.MAX_VALUE, source.readUInt()) @@ -1169,38 +1225,47 @@ abstract class AbstractBufferedSourceTest internal constructor( } @Test fun readUnsignedIntLe() { - sink.write(byteArrayOf(0x12, 0x34, 0x56, 0x78)).flush() + sink.write(byteArrayOf(0x12, 0x34, 0x56, 0x78)) + sink.flush() assertEquals(0x78563412u, source.readUIntLe()) } @Test fun readTooShortUnsignedIntThrows() { assertFailsWith { source.readUInt() } - sink.writeByte(0).flush() + sink.writeByte(0) + sink.flush() assertFailsWith { source.readUInt() } - sink.writeByte(0).flush() + sink.writeByte(0) + sink.flush() assertFailsWith { source.readUInt() } - sink.writeByte(0).flush() + sink.writeByte(0) + sink.flush() assertFailsWith { source.readUInt() } assertTrue(source.request(3)) } @Test fun readTooShortUnsignedIntLeThrows() { assertFailsWith { source.readUIntLe() } - sink.writeByte(0).flush() + sink.writeByte(0) + sink.flush() assertFailsWith { source.readUIntLe() } - sink.writeByte(0).flush() + sink.writeByte(0) + sink.flush() assertFailsWith { source.readUIntLe() } - sink.writeByte(0).flush() + sink.writeByte(0) + sink.flush() assertFailsWith { source.readUIntLe() } assertTrue(source.request(3)) } @Test fun readUnsignedLong() { - sink.writeLong(0) - .writeLong(-1) - .writeLong(Long.MIN_VALUE) - .writeLong(Long.MAX_VALUE) - .flush() + with (sink) { + writeLong(0) + writeLong(-1) + writeLong(Long.MIN_VALUE) + writeLong(Long.MAX_VALUE) + flush() + } assertEquals(0u, source.readULong()) assertEquals(ULong.MAX_VALUE, source.readULong()) @@ -1210,14 +1275,16 @@ abstract class AbstractBufferedSourceTest internal constructor( } @Test fun readUnsignedLongLe() { - sink.write(byteArrayOf(0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0xff.toByte())).flush() + sink.write(byteArrayOf(0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0xff.toByte())) + sink.flush() assertEquals(0xff07060504030201u, source.readULongLe()) } @Test fun readTooShortUnsignedLongThrows() { assertFailsWith { source.readULong() } for (i in 0 until 7) { - sink.writeByte(0).flush() + sink.writeByte(0) + sink.flush() assertFailsWith { source.readULong() } } assertTrue(source.request(7)) @@ -1226,7 +1293,8 @@ abstract class AbstractBufferedSourceTest internal constructor( @Test fun readTooShortUnsignedLongLeThrows() { assertFailsWith { source.readULongLe() } for (i in 0 until 7) { - sink.writeByte(0).flush() + sink.writeByte(0) + sink.flush() assertFailsWith { source.readULongLe() } } assertTrue(source.request(7)) diff --git a/core/common/test/CommonBufferTest.kt b/core/common/test/CommonBufferTest.kt index 6a351590d..10e647b24 100644 --- a/core/common/test/CommonBufferTest.kt +++ b/core/common/test/CommonBufferTest.kt @@ -51,17 +51,17 @@ class CommonBufferTest { assertEquals("[size=0]", Buffer().toString()) assertEquals("[text=a\\r\\nb\\nc\\rd\\\\e]", - Buffer().writeUtf8("a\r\nb\nc\rd\\e").toString()) + Buffer().also { it.writeUtf8("a\r\nb\nc\rd\\e") }.toString()) assertEquals("[text=Tyrannosaur]", - Buffer().writeUtf8("Tyrannosaur").toString()) + Buffer().also { it.writeUtf8("Tyrannosaur") }.toString()) assertEquals("[text=təˈranəˌsôr]", - Buffer().write("74c999cb8872616ec999cb8c73c3b472".decodeHex()).toString()) + Buffer().also { it.write("74c999cb8872616ec999cb8c73c3b472".decodeHex()) }.toString()) assertEquals("[hex=0000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000]", - Buffer().write(ByteArray(64)).toString()) + Buffer().also { it.write(ByteArray(64)) }.toString()) } @Test fun multipleSegmentBuffers() { @@ -277,10 +277,10 @@ class CommonBufferTest { // Buffer don't override equals and hashCode @Test fun equalsAndHashCode() { - val a = Buffer().writeUtf8("dog") + val a = Buffer().also { it.writeUtf8("dog") } assertEquals(a, a) - val b = Buffer().writeUtf8("hotdog") + val b = Buffer().also { it.writeUtf8("hotdog") } assertTrue(a != b) b.readUtf8(3) // Leaves b containing 'dog'. @@ -292,17 +292,17 @@ class CommonBufferTest { * data by segment. */ @Test fun readAllWritesAllSegmentsAtOnce() { - val write1 = Buffer().writeUtf8( + val write1 = Buffer() + write1.writeUtf8( 'a'.repeat(Segment.SIZE) + - 'b'.repeat(Segment.SIZE) + - 'c'.repeat(Segment.SIZE) - ) + 'b'.repeat(Segment.SIZE) + + 'c'.repeat(Segment.SIZE)) - val source = Buffer().writeUtf8( + val source = Buffer() + source.writeUtf8( 'a'.repeat(Segment.SIZE) + - 'b'.repeat(Segment.SIZE) + - 'c'.repeat(Segment.SIZE) - ) + 'b'.repeat(Segment.SIZE) + + 'c'.repeat(Segment.SIZE)) val mockSink = MockSink() @@ -312,7 +312,7 @@ class CommonBufferTest { } @Test fun writeAllMultipleSegments() { - val source = Buffer().writeUtf8('a'.repeat(Segment.SIZE * 3)) + val source = Buffer().also { it.writeUtf8('a'.repeat(Segment.SIZE * 3)) } val sink = Buffer() assertEquals((Segment.SIZE * 3).toLong(), sink.writeAll(source)) @@ -381,14 +381,14 @@ class CommonBufferTest { @Test fun copyToEmptySource() { val source = Buffer() - val target = Buffer().writeUtf8("aaa") + val target = Buffer().also { it.writeUtf8("aaa") } source.copyTo(target, 0L, 0L) assertEquals("", source.readUtf8()) assertEquals("aaa", target.readUtf8()) } @Test fun copyToEmptyTarget() { - val source = Buffer().writeUtf8("aaa") + val source = Buffer().also { it.writeUtf8("aaa") } val target = Buffer() source.copyTo(target, 0L, 3L) assertEquals("aaa", source.readUtf8()) diff --git a/core/common/test/CommonPlatformTest.kt b/core/common/test/CommonPlatformTest.kt index e80bfa8c6..63d7c1b8a 100644 --- a/core/common/test/CommonPlatformTest.kt +++ b/core/common/test/CommonPlatformTest.kt @@ -26,7 +26,7 @@ import kotlin.test.assertEquals class CommonPlatformTest { @Test fun sourceBuffer() { - val source = Buffer().writeUtf8("a") + val source = Buffer().also { it.writeUtf8("a") } val buffered = (source as RawSource).buffer() assertEquals(buffered.readUtf8(), "a") assertEquals(source.size, 0L) @@ -42,6 +42,6 @@ class CommonPlatformTest { } @Test fun blackhole() { - blackholeSink().write(Buffer().writeUtf8("a"), 1L) + blackholeSink().write(Buffer().also { it.writeUtf8("a") }, 1L) } } diff --git a/core/common/test/CommonRealSinkTest.kt b/core/common/test/CommonRealSinkTest.kt index 840dbd6f2..d2d6d5e34 100644 --- a/core/common/test/CommonRealSinkTest.kt +++ b/core/common/test/CommonRealSinkTest.kt @@ -166,7 +166,7 @@ class CommonRealSinkTest { val bufferedSink = mockSink.buffer() bufferedSink.buffer.writeUtf8("abc") - assertEquals(3, bufferedSink.writeAll(Buffer().writeUtf8("def"))) + assertEquals(3, bufferedSink.writeAll(Buffer().also { it.writeUtf8("def") })) assertEquals(6, bufferedSink.buffer.size) assertEquals("abcdef", bufferedSink.buffer.readUtf8(6)) @@ -183,13 +183,13 @@ class CommonRealSinkTest { } @Test fun writeAllWritesOneSegmentAtATime() { - val write1 = Buffer().writeUtf8("a".repeat(Segment.SIZE)) - val write2 = Buffer().writeUtf8("b".repeat(Segment.SIZE)) - val write3 = Buffer().writeUtf8("c".repeat(Segment.SIZE)) + val write1 = Buffer().also { it.writeUtf8("a".repeat(Segment.SIZE)) } + val write2 = Buffer().also { it.writeUtf8("b".repeat(Segment.SIZE)) } + val write3 = Buffer().also { it.writeUtf8("c".repeat(Segment.SIZE)) } - val source = Buffer().writeUtf8( - "${"a".repeat(Segment.SIZE)}${"b".repeat(Segment.SIZE)}${"c".repeat(Segment.SIZE)}" - ) + val source = Buffer() + source.writeUtf8( + "${"a".repeat(Segment.SIZE)}${"b".repeat(Segment.SIZE)}${"c".repeat(Segment.SIZE)}") val mockSink = MockSink() val bufferedSink = mockSink.buffer() diff --git a/core/common/test/CommonRealSourceTest.kt b/core/common/test/CommonRealSourceTest.kt index 94fb8ae2a..f93f611fe 100644 --- a/core/common/test/CommonRealSourceTest.kt +++ b/core/common/test/CommonRealSourceTest.kt @@ -32,7 +32,7 @@ import kotlin.test.assertFailsWith @OptIn(DelicateIoApi::class) class CommonRealSourceTest { @Test fun indexOfStopsReadingAtLimit() { - val buffer = Buffer().writeUtf8("abcdef") + val buffer = Buffer().also { it.writeUtf8("abcdef") } val bufferedSource = ( object : RawSource by buffer { override fun read(sink: Buffer, byteCount: Long): Long { @@ -138,13 +138,13 @@ class CommonRealSourceTest { * should buffer a segment, write it, and repeat. */ @Test fun readAllReadsOneSegmentAtATime() { - val write1 = Buffer().writeUtf8("a".repeat(Segment.SIZE)) - val write2 = Buffer().writeUtf8("b".repeat(Segment.SIZE)) - val write3 = Buffer().writeUtf8("c".repeat(Segment.SIZE)) + val write1 = Buffer().also { it.writeUtf8("a".repeat(Segment.SIZE)) } + val write2 = Buffer().also { it.writeUtf8("b".repeat(Segment.SIZE)) } + val write3 = Buffer().also { it.writeUtf8("c".repeat(Segment.SIZE)) } - val source = Buffer().writeUtf8( - "${"a".repeat(Segment.SIZE)}${"b".repeat(Segment.SIZE)}${"c".repeat(Segment.SIZE)}" - ) + val source = Buffer() + source.writeUtf8( + "${"a".repeat(Segment.SIZE)}${"b".repeat(Segment.SIZE)}${"c".repeat(Segment.SIZE)}") val mockSink = MockSink() val bufferedSource = (source as RawSource).buffer() diff --git a/core/common/test/util.kt b/core/common/test/util.kt index f6d5569eb..c50314548 100644 --- a/core/common/test/util.kt +++ b/core/common/test/util.kt @@ -20,7 +20,6 @@ */ package kotlinx.io -import kotlin.random.Random import kotlin.test.assertEquals fun segmentSizes(buffer: Buffer): List { @@ -35,43 +34,6 @@ fun segmentSizes(buffer: Buffer): List { return sizes } -fun bufferWithRandomSegmentLayout(dice: Random, data: ByteArray): Buffer { - val result = Buffer() - - // Writing to result directly will yield packed segments. Instead, write to - // other buffers, then write those buffers to result. - var pos = 0 - var byteCount: Int - while (pos < data.size) { - byteCount = Segment.SIZE / 2 + dice.nextInt(Segment.SIZE / 2) - if (byteCount > data.size - pos) byteCount = data.size - pos - val offset = dice.nextInt(Segment.SIZE - byteCount) - - val segment = Buffer() - segment.write(ByteArray(offset)) - segment.write(data, pos, byteCount) - segment.skip(offset.toLong()) - - result.write(segment, byteCount.toLong()) - pos += byteCount - } - - return result -} - -fun bufferWithSegments(vararg segments: String): Buffer { - val result = Buffer() - for (s in segments) { - val offsetInSegment = if (s.length < Segment.SIZE) (Segment.SIZE - s.length) / 2 else 0 - val buffer = Buffer() - buffer.writeUtf8('_'.repeat(offsetInSegment)) - buffer.writeUtf8(s) - buffer.skip(offsetInSegment.toLong()) - result.write(buffer.copyTo(Buffer()), buffer.size) - } - return result -} - expect fun createTempFile(): String expect fun deleteFile(path: String) diff --git a/core/jvm/src/Buffer.kt b/core/jvm/src/Buffer.kt index 69ecd0e59..a1316ad76 100644 --- a/core/jvm/src/Buffer.kt +++ b/core/jvm/src/Buffer.kt @@ -36,9 +36,9 @@ public actual class Buffer : Source, Sink, Cloneable { actual override val buffer: Buffer get() = this - actual override fun emitCompleteSegments(): Buffer = this // Nowhere to emit to! + actual override fun emitCompleteSegments(): Unit = Unit // Nowhere to emit to! - actual override fun emit(): Buffer = this // Nowhere to emit to! + actual override fun emit(): Unit = Unit // Nowhere to emit to! override fun exhausted(): Boolean = size == 0L @@ -56,7 +56,7 @@ public actual class Buffer : Source, Sink, Cloneable { out: Buffer, offset: Long, byteCount: Long - ): Buffer = commonCopyTo(out, offset, byteCount) + ): Unit = commonCopyTo(out, offset, byteCount) public actual fun completeSegmentByteCount(): Long = commonCompleteSegmentByteCount() @@ -85,20 +85,20 @@ public actual class Buffer : Source, Sink, Cloneable { source: ByteArray, offset: Int, byteCount: Int - ): Buffer = commonWrite(source, offset, byteCount) + ): Unit = commonWrite(source, offset, byteCount) override fun writeAll(source: RawSource): Long = commonWriteAll(source) - actual override fun write(source: RawSource, byteCount: Long): Buffer = + actual override fun write(source: RawSource, byteCount: Long): Unit = commonWrite(source, byteCount) - actual override fun writeByte(byte: Byte): Buffer = commonWriteByte(byte) + actual override fun writeByte(byte: Byte): Unit = commonWriteByte(byte) - actual override fun writeShort(short: Short): Buffer = commonWriteShort(short) + actual override fun writeShort(short: Short): Unit = commonWriteShort(short) - actual override fun writeInt(int: Int): Buffer = commonWriteInt(int) + actual override fun writeInt(int: Int): Unit = commonWriteInt(int) - actual override fun writeLong(long: Long): Buffer = commonWriteLong(long) + actual override fun writeLong(long: Long): Unit = commonWriteLong(long) internal actual fun writableSegment(minimumCapacity: Int): Segment = commonWritableSegment(minimumCapacity) @@ -182,7 +182,7 @@ private fun Buffer.readFrom(input: InputStream, byteCount: Long, forever: Boolea * * @throws IllegalArgumentException when [byteCount] is negative or exceeds the buffer size. */ -public fun Buffer.writeTo(out: OutputStream, byteCount: Long = size): Buffer { +public fun Buffer.writeTo(out: OutputStream, byteCount: Long = size) { checkOffsetAndCount(size, 0, byteCount) var remainingByteCount = byteCount @@ -202,8 +202,6 @@ public fun Buffer.writeTo(out: OutputStream, byteCount: Long = size): Buffer { SegmentPool.recycle(toRecycle) } } - - return this } /** @@ -219,9 +217,9 @@ public fun Buffer.copyTo( out: OutputStream, offset: Long = 0L, byteCount: Long = size - offset -): Buffer { +) { checkOffsetAndCount(size, offset, byteCount) - if (byteCount == 0L) return this + if (byteCount == 0L) return var currentOffset = offset var remainingByteCount = byteCount @@ -242,8 +240,6 @@ public fun Buffer.copyTo( currentOffset = 0L s = s.next } - - return this } /** diff --git a/core/jvm/src/RealSink.kt b/core/jvm/src/RealSink.kt index 5485a3988..fabccd99a 100644 --- a/core/jvm/src/RealSink.kt +++ b/core/jvm/src/RealSink.kt @@ -38,7 +38,7 @@ internal actual class RealSink actual constructor( commonWrite(source, offset, byteCount) override fun writeAll(source: RawSource) = commonWriteAll(source) - override fun write(source: RawSource, byteCount: Long): Sink = commonWrite(source, byteCount) + override fun write(source: RawSource, byteCount: Long) = commonWrite(source, byteCount) override fun writeByte(byte: Byte) = commonWriteByte(byte) override fun writeShort(short: Short) = commonWriteShort(short) override fun writeInt(int: Int) = commonWriteInt(int) diff --git a/core/jvm/src/Sink.kt b/core/jvm/src/Sink.kt index 6e34384a7..fa4b61f0e 100644 --- a/core/jvm/src/Sink.kt +++ b/core/jvm/src/Sink.kt @@ -29,25 +29,25 @@ import java.nio.charset.Charset public actual sealed interface Sink : RawSink { public actual val buffer: Buffer - public actual fun write(source: ByteArray, offset: Int, byteCount: Int): Sink + public actual fun write(source: ByteArray, offset: Int, byteCount: Int) public actual fun writeAll(source: RawSource): Long - public actual fun write(source: RawSource, byteCount: Long): Sink + public actual fun write(source: RawSource, byteCount: Long) - public actual fun writeByte(byte: Byte): Sink + public actual fun writeByte(byte: Byte) - public actual fun writeShort(short: Short): Sink + public actual fun writeShort(short: Short) - public actual fun writeInt(int: Int): Sink + public actual fun writeInt(int: Int) - public actual fun writeLong(long: Long): Sink + public actual fun writeLong(long: Long) actual override fun flush() - public actual fun emit(): Sink + public actual fun emit() - public actual fun emitCompleteSegments(): Sink + public actual fun emitCompleteSegments() } /** @@ -62,14 +62,13 @@ public actual sealed interface Sink : RawSink { * @throws IllegalArgumentException when [beginIndex] and [endIndex] correspond to a range out of [string] bounds. * @throws IllegalStateException when the sink is closed. */ -public fun T.writeString(string: String, charset: Charset, beginIndex: Int = 0, endIndex: Int = string.length): T { +public fun T.writeString(string: String, charset: Charset, beginIndex: Int = 0, endIndex: Int = string.length) { require(beginIndex >= 0) { "beginIndex < 0: $beginIndex" } require(endIndex >= beginIndex) { "endIndex < beginIndex: $endIndex < $beginIndex" } require(endIndex <= string.length) { "endIndex > string.length: $endIndex > ${string.length}" } if (charset == Charsets.UTF_8) return writeUtf8(string, beginIndex, endIndex) val data = string.substring(beginIndex, endIndex).toByteArray(charset) write(data, 0, data.size) - return this } /** diff --git a/core/jvm/test/BufferTest.kt b/core/jvm/test/BufferTest.kt index 78bf288a4..d4ce7254f 100644 --- a/core/jvm/test/BufferTest.kt +++ b/core/jvm/test/BufferTest.kt @@ -59,7 +59,7 @@ class BufferTest { @Test fun copyToStream() { - val buffer = Buffer().writeUtf8("hello, world!") + val buffer = Buffer().also { it.writeUtf8("hello, world!") } val out = ByteArrayOutputStream() buffer.copyTo(out) val outString = String(out.toByteArray(), UTF_8) @@ -81,7 +81,7 @@ class BufferTest { @Test fun writeToStream() { - val buffer = Buffer().writeUtf8("hello, world!") + val buffer = Buffer().also { it.writeUtf8("hello, world!") } val out = ByteArrayOutputStream() buffer.writeTo(out) val outString = String(out.toByteArray(), UTF_8) @@ -101,7 +101,7 @@ class BufferTest { @Test fun readFromSpanningSegments() { val input: InputStream = ByteArrayInputStream("hello, world!".toByteArray(UTF_8)) - val buffer = Buffer().writeUtf8("a".repeat(SEGMENT_SIZE - 10)) + val buffer = Buffer().also { it.writeUtf8("a".repeat(SEGMENT_SIZE - 10)) } buffer.readFrom(input) val out = buffer.readUtf8() assertEquals("a".repeat(SEGMENT_SIZE - 10) + "hello, world!", out) diff --git a/core/jvm/test/JvmPlatformTest.kt b/core/jvm/test/JvmPlatformTest.kt index 42def7162..a8d50f125 100644 --- a/core/jvm/test/JvmPlatformTest.kt +++ b/core/jvm/test/JvmPlatformTest.kt @@ -39,7 +39,7 @@ class JvmPlatformTest { @Test fun outputStreamSink() { val baos = ByteArrayOutputStream() val sink = baos.sink() - sink.write(Buffer().writeUtf8("a"), 1L) + sink.write(Buffer().also { it.writeUtf8("a") }, 1L) assertArrayEquals(baos.toByteArray(), byteArrayOf(0x61)) } @@ -54,7 +54,7 @@ class JvmPlatformTest { @Test fun fileSink() { val file = File(tempDir, "test") file.sink().use { sink -> - sink.write(Buffer().writeUtf8("a"), 1L) + sink.write(Buffer().also { it.writeUtf8("a") }, 1L) } assertEquals(file.readText(), "a") } @@ -63,7 +63,7 @@ class JvmPlatformTest { val file = File(tempDir, "test") file.writeText("a") file.sink(append = true).use { sink -> - sink.write(Buffer().writeUtf8("b"), 1L) + sink.write(Buffer().also { it.writeUtf8("b") }, 1L) } assertEquals(file.readText(), "ab") } @@ -81,7 +81,7 @@ class JvmPlatformTest { @Test fun pathSink() { val file = File(tempDir, "test") file.toPath().sink().use { sink -> - sink.write(Buffer().writeUtf8("a"), 1L) + sink.write(Buffer().also { it.writeUtf8("a") }, 1L) } assertEquals(file.readText(), "a") } @@ -90,7 +90,7 @@ class JvmPlatformTest { val file = File(tempDir, "test") file.writeText("a") file.toPath().sink(StandardOpenOption.APPEND).use { sink -> - sink.write(Buffer().writeUtf8("b"), 1L) + sink.write(Buffer().also { it.writeUtf8("b") }, 1L) } assertEquals(file.readText(), "ab") } @@ -132,7 +132,7 @@ class JvmPlatformTest { override fun getOutputStream() = baos } val sink = socket.sink() - sink.write(Buffer().writeUtf8("a"), 1L) + sink.write(Buffer().also { it.writeUtf8("a") }, 1L) assertArrayEquals(baos.toByteArray(), byteArrayOf(0x61)) } diff --git a/core/native/src/Buffer.kt b/core/native/src/Buffer.kt index d035d32d9..00b206b1a 100644 --- a/core/native/src/Buffer.kt +++ b/core/native/src/Buffer.kt @@ -30,9 +30,9 @@ public actual class Buffer : Source, Sink { actual override val buffer: Buffer get() = this - actual override fun emitCompleteSegments(): Buffer = this // Nowhere to emit to! + actual override fun emitCompleteSegments(): Unit = Unit // Nowhere to emit to! - actual override fun emit(): Buffer = this // Nowhere to emit to! + actual override fun emit(): Unit = Unit // Nowhere to emit to! override fun exhausted(): Boolean = size == 0L @@ -48,7 +48,7 @@ public actual class Buffer : Source, Sink { out: Buffer, offset: Long, byteCount: Long - ): Buffer = commonCopyTo(out, offset, byteCount) + ): Unit = commonCopyTo(out, offset, byteCount) public actual operator fun get(pos: Long): Byte = commonGet(pos) @@ -76,21 +76,21 @@ public actual class Buffer : Source, Sink { internal actual fun writableSegment(minimumCapacity: Int): Segment = commonWritableSegment(minimumCapacity) - actual override fun write(source: ByteArray, offset: Int, byteCount: Int): Buffer = + actual override fun write(source: ByteArray, offset: Int, byteCount: Int): Unit = commonWrite(source, offset, byteCount) override fun writeAll(source: RawSource): Long = commonWriteAll(source) - actual override fun write(source: RawSource, byteCount: Long): Buffer = + actual override fun write(source: RawSource, byteCount: Long): Unit = commonWrite(source, byteCount) - actual override fun writeByte(byte: Byte): Buffer = commonWriteByte(byte) + actual override fun writeByte(byte: Byte): Unit = commonWriteByte(byte) - actual override fun writeShort(short: Short): Buffer = commonWriteShort(short) + actual override fun writeShort(short: Short): Unit = commonWriteShort(short) - actual override fun writeInt(int: Int): Buffer = commonWriteInt(int) + actual override fun writeInt(int: Int): Unit = commonWriteInt(int) - actual override fun writeLong(long: Long): Buffer = commonWriteLong(long) + actual override fun writeLong(long: Long): Unit = commonWriteLong(long) override fun write(source: Buffer, byteCount: Long): Unit = commonWrite(source, byteCount) diff --git a/core/native/src/RealSink.kt b/core/native/src/RealSink.kt index f0374894f..9f105fad8 100644 --- a/core/native/src/RealSink.kt +++ b/core/native/src/RealSink.kt @@ -35,7 +35,7 @@ internal actual class RealSink actual constructor( commonWrite(source, offset, byteCount) override fun writeAll(source: RawSource) = commonWriteAll(source) - override fun write(source: RawSource, byteCount: Long): Sink = commonWrite(source, byteCount) + override fun write(source: RawSource, byteCount: Long) = commonWrite(source, byteCount) override fun writeByte(byte: Byte) = commonWriteByte(byte) override fun writeShort(short: Short) = commonWriteShort(short) override fun writeInt(int: Int) = commonWriteInt(int) diff --git a/core/native/src/Sink.kt b/core/native/src/Sink.kt index 54dfa113f..763a90ba3 100644 --- a/core/native/src/Sink.kt +++ b/core/native/src/Sink.kt @@ -23,21 +23,21 @@ package kotlinx.io public actual sealed interface Sink : RawSink { public actual val buffer: Buffer - public actual fun write(source: ByteArray, offset: Int, byteCount: Int): Sink + public actual fun write(source: ByteArray, offset: Int, byteCount: Int) public actual fun writeAll(source: RawSource): Long - public actual fun write(source: RawSource, byteCount: Long): Sink + public actual fun write(source: RawSource, byteCount: Long) - public actual fun writeByte(byte: Byte): Sink + public actual fun writeByte(byte: Byte) - public actual fun writeShort(short: Short): Sink + public actual fun writeShort(short: Short) - public actual fun writeInt(int: Int): Sink + public actual fun writeInt(int: Int) - public actual fun writeLong(long: Long): Sink + public actual fun writeLong(long: Long) - public actual fun emit(): Sink + public actual fun emit() - public actual fun emitCompleteSegments(): Sink + public actual fun emitCompleteSegments() } From 79c927c890518d2f16284931fe252394a2a7b2ed Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Fri, 16 Jun 2023 12:50:02 +0200 Subject: [PATCH 46/83] Enabled missing targets --- buildSrc/src/main/kotlin/Platforms.kt | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/buildSrc/src/main/kotlin/Platforms.kt b/buildSrc/src/main/kotlin/Platforms.kt index a23bbbd9e..157edd255 100644 --- a/buildSrc/src/main/kotlin/Platforms.kt +++ b/buildSrc/src/main/kotlin/Platforms.kt @@ -19,6 +19,12 @@ fun KotlinMultiplatformExtension.configureNativePlatforms() { watchosArm64() watchosX64() watchosSimulatorArm64() + watchosDeviceArm64() + linuxArm64() + androidNativeArm32() + androidNativeArm64() + androidNativeX64() + androidNativeX86() // Required to generate tests tasks: https://youtrack.jetbrains.com/issue/KT-26547 linuxX64() macosX64() @@ -38,7 +44,8 @@ private val appleTargets = listOf( "watchosArm32", "watchosArm64", "watchosX64", - "watchosSimulatorArm64" + "watchosSimulatorArm64", + "watchosDeviceArm64" ) private val mingwTargets = listOf( @@ -46,10 +53,18 @@ private val mingwTargets = listOf( ) private val linuxTargets = listOf( - "linuxX64" + "linuxX64", + "linuxArm64" ) -val nativeTargets = appleTargets + linuxTargets + mingwTargets +private val androidTargets = listOf( + "androidNativeArm32", + "androidNativeArm64", + "androidNativeX64", + "androidNativeX86" +) + +val nativeTargets = appleTargets + linuxTargets + mingwTargets + androidTargets /** * Creates a source set for a directory that isn't already a built-in platform. Use this to create From 008cc82600d351accb1f8bcfa0110f90899122be Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Fri, 16 Jun 2023 13:00:13 +0200 Subject: [PATCH 47/83] Get rid of SharedImmutable --- core/common/src/-Util.kt | 3 --- core/common/src/SinkExt.kt | 3 --- 2 files changed, 6 deletions(-) diff --git a/core/common/src/-Util.kt b/core/common/src/-Util.kt index d16818329..55b3dca6a 100644 --- a/core/common/src/-Util.kt +++ b/core/common/src/-Util.kt @@ -23,9 +23,6 @@ package kotlinx.io -import kotlin.native.concurrent.SharedImmutable - -@SharedImmutable internal val HEX_DIGIT_CHARS = charArrayOf('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f') diff --git a/core/common/src/SinkExt.kt b/core/common/src/SinkExt.kt index 5aaf170da..4dc52831f 100644 --- a/core/common/src/SinkExt.kt +++ b/core/common/src/SinkExt.kt @@ -5,9 +5,6 @@ package kotlinx.io -import kotlin.native.concurrent.SharedImmutable - -@SharedImmutable internal val HEX_DIGIT_BYTES = "0123456789abcdef".asUtf8ToByteArray() /** From b492ae5303d7a6f5df2678a94eac03cb8b17760a Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Fri, 16 Jun 2023 14:56:07 +0200 Subject: [PATCH 48/83] Get rid of some platform-specific implementations --- core/api/kotlinx-io-core.api | 7 +- core/common/src/Buffer.kt | 586 +++++++++++- core/common/src/RealSink.kt | 131 ++- core/common/src/RealSource.kt | 129 ++- core/common/src/Utf8.kt | 243 ++++- core/common/src/internal/-Buffer.kt | 877 ------------------ core/common/src/internal/-RealBufferedSink.kt | 150 --- .../src/internal/-RealBufferedSource.kt | 157 ---- core/common/test/Utf8Test.kt | 12 +- core/jvm/src/{Buffer.kt => BufferExtJvm.kt} | 100 -- core/jvm/src/RealSink.kt | 54 -- core/jvm/src/RealSource.kt | 56 -- core/native/src/Buffer.kt | 106 --- core/native/src/RealSink.kt | 48 - core/native/src/RealSource.kt | 49 - 15 files changed, 1062 insertions(+), 1643 deletions(-) delete mode 100644 core/common/src/internal/-Buffer.kt delete mode 100644 core/common/src/internal/-RealBufferedSink.kt delete mode 100644 core/common/src/internal/-RealBufferedSource.kt rename core/jvm/src/{Buffer.kt => BufferExtJvm.kt} (66%) delete mode 100644 core/jvm/src/RealSink.kt delete mode 100644 core/jvm/src/RealSource.kt delete mode 100644 core/native/src/Buffer.kt delete mode 100644 core/native/src/RealSink.kt delete mode 100644 core/native/src/RealSource.kt diff --git a/core/api/kotlinx-io-core.api b/core/api/kotlinx-io-core.api index a3b43d5cf..ba8d65b0c 100644 --- a/core/api/kotlinx-io-core.api +++ b/core/api/kotlinx-io-core.api @@ -1,10 +1,7 @@ -public final class kotlinx/io/Buffer : java/lang/Cloneable, kotlinx/io/Sink, kotlinx/io/Source { +public final class kotlinx/io/Buffer : kotlinx/io/Sink, kotlinx/io/Source { public fun ()V public final fun clear ()V - public synthetic fun clone ()Ljava/lang/Object; - public fun clone ()Lkotlinx/io/Buffer; public fun close ()V - public final fun completeSegmentByteCount ()J public final fun copy ()Lkotlinx/io/Buffer; public final fun copyTo (Lkotlinx/io/Buffer;JJ)V public static synthetic fun copyTo$default (Lkotlinx/io/Buffer;Lkotlinx/io/Buffer;JJILjava/lang/Object;)V @@ -38,7 +35,7 @@ public final class kotlinx/io/Buffer : java/lang/Cloneable, kotlinx/io/Sink, kot public fun writeShort (S)V } -public final class kotlinx/io/BufferKt { +public final class kotlinx/io/BufferExtJvmKt { public static final fun channel (Lkotlinx/io/Buffer;)Ljava/nio/channels/ByteChannel; public static final fun copyTo (Lkotlinx/io/Buffer;Ljava/io/OutputStream;JJ)V public static synthetic fun copyTo$default (Lkotlinx/io/Buffer;Ljava/io/OutputStream;JJILjava/lang/Object;)V diff --git a/core/common/src/Buffer.kt b/core/common/src/Buffer.kt index 201db0ddd..12215b5fa 100644 --- a/core/common/src/Buffer.kt +++ b/core/common/src/Buffer.kt @@ -20,6 +20,11 @@ */ package kotlinx.io +import kotlinx.io.internal.REPLACEMENT_CODE_POINT +import kotlinx.io.internal.isIsoControl +import kotlinx.io.internal.processUtf8CodePoints +import kotlin.jvm.JvmField + /** * A collection of bytes in memory. * @@ -36,36 +41,161 @@ package kotlinx.io * but unlike regular sinks and sources its [close], [flush], [emit], [emitCompleteSegments] * does not affect buffer's state and [exhausted] only indicates that a buffer is empty. */ -public expect class Buffer() : Source, Sink { - internal var head: Segment? +public class Buffer : Source, Sink { + @JvmField internal var head: Segment? = null /** * The number of bytes accessible for read from this buffer. */ - public var size: Long + public var size: Long = 0L internal set /** * Returns the buffer itself. */ @DelicateIoApi - override val buffer: Buffer + override val buffer: Buffer = this + override fun exhausted(): Boolean = size == 0L + + override fun require(byteCount: Long) { + if (size < byteCount) throw EOFException() + } + + override fun request(byteCount: Long): Boolean = size >= byteCount + + override fun readByte(): Byte { + if (size == 0L) throw EOFException() + val segment = head!! + var pos = segment.pos + val limit = segment.limit + val data = segment.data + val b = data[pos++] + size -= 1L + if (pos == limit) { + head = segment.pop() + SegmentPool.recycle(segment) + } else { + segment.pos = pos + } + return b + } + + override fun readShort(): Short { + if (size < 2L) throw EOFException() + + val segment = head!! + var pos = segment.pos + val limit = segment.limit + + // If the short is split across multiple segments, delegate to readByte(). + if (limit - pos < 2) { + val s = readByte() and 0xff shl 8 or (readByte() and 0xff) + return s.toShort() + } + + val data = segment.data + val s = data[pos++] and 0xff shl 8 or (data[pos++] and 0xff) + size -= 2L + + if (pos == limit) { + head = segment.pop() + SegmentPool.recycle(segment) + } else { + segment.pos = pos + } + + return s.toShort() + } + + override fun readInt(): Int { + if (size < 4L) throw EOFException() + + val segment = head!! + var pos = segment.pos + val limit = segment.limit + + // If the int is split across multiple segments, delegate to readByte(). + if (limit - pos < 4L) { + return ( + readByte() and 0xff shl 24 + or (readByte() and 0xff shl 16) + or (readByte() and 0xff shl 8) + or (readByte() and 0xff) + ) + } + + val data = segment.data + val i = ( + data[pos++] and 0xff shl 24 + or (data[pos++] and 0xff shl 16) + or (data[pos++] and 0xff shl 8) + or (data[pos++] and 0xff) + ) + size -= 4L + + if (pos == limit) { + head = segment.pop() + SegmentPool.recycle(segment) + } else { + segment.pos = pos + } + + return i + } + + override fun readLong(): Long { + if (size < 8L) throw EOFException() + + val segment = head!! + var pos = segment.pos + val limit = segment.limit + + // If the long is split across multiple segments, delegate to readInt(). + if (limit - pos < 8L) { + return ( + readInt() and 0xffffffffL shl 32 + or (readInt() and 0xffffffffL) + ) + } + + val data = segment.data + val v = ( + data[pos++] and 0xffL shl 56 + or (data[pos++] and 0xffL shl 48) + or (data[pos++] and 0xffL shl 40) + or (data[pos++] and 0xffL shl 32) + or (data[pos++] and 0xffL shl 24) + or (data[pos++] and 0xffL shl 16) + or (data[pos++] and 0xffL shl 8) + or (data[pos++] and 0xffL) + ) + size -= 8L + + if (pos == limit) { + head = segment.pop() + SegmentPool.recycle(segment) + } else { + segment.pos = pos + } + + return v + } /** * This method does not affect the buffer's content as there is no upstream to write data to. */ @DelicateIoApi - override fun emitCompleteSegments() + override fun emitCompleteSegments(): Unit = Unit /** * This method does not affect the buffer's content as there is no upstream to write data to. */ - override fun emit() + override fun emit(): Unit = Unit /** * This method does not affect the buffer's content as there is no upstream to write data to. */ - override fun flush() + override fun flush(): Unit = Unit /** * Copy [byteCount] bytes from this buffer, starting at [offset], to [out] buffer. @@ -80,64 +210,474 @@ public expect class Buffer() : Source, Sink { out: Buffer, offset: Long = 0L, byteCount: Long = size - offset - ) + ) { + checkOffsetAndCount(size, offset, byteCount) + if (byteCount == 0L) return + + var currentOffset = offset + var remainingByteCount = byteCount + + out.size += remainingByteCount + + // Skip segments that we aren't copying from. + var s = head + while (currentOffset >= s!!.limit - s.pos) { + currentOffset -= (s.limit - s.pos).toLong() + s = s.next + } + + // Copy one segment at a time. + while (remainingByteCount > 0L) { + val copy = s!!.sharedCopy() + copy.pos += currentOffset.toInt() + copy.limit = minOf(copy.pos + remainingByteCount.toInt(), copy.limit) + if (out.head == null) { + copy.prev = copy + copy.next = copy.prev + out.head = copy.next + } else { + out.head!!.prev!!.push(copy) + } + remainingByteCount -= (copy.limit - copy.pos).toLong() + currentOffset = 0L + s = s.next + } + } /** * Returns the number of bytes in segments that are fully filled and are no longer writable. * * This is the number of bytes that can be flushed immediately to an underlying sink without harming throughput. */ - public fun completeSegmentByteCount(): Long + internal fun completeSegmentByteCount(): Long { + var result = size + if (result == 0L) return 0L + + // Omit the tail if it's still writable. + val tail = head!!.prev!! + if (tail.limit < Segment.SIZE && tail.owner) { + result -= (tail.limit - tail.pos).toLong() + } + + return result + } /** - * Returns the byte at [pos]. + * Returns the byte at [position]. * * Use of this method may expose significant performance penalties and it's not recommended to use it * for sequential access to a range of bytes within the buffer. * - * @throws IllegalArgumentException when [pos] is out of this buffer's bounds. + * @throws IllegalArgumentException when [position] is out of this buffer's bounds. */ - public operator fun get(pos: Long): Byte + public operator fun get(position: Long): Byte { + checkOffsetAndCount(size, position, 1L) + seek(position) { s, offset -> + return s!!.data[(s.pos + position - offset).toInt()] + } + } /** * Discards all bytes in this buffer. * * Call to this method is equivalent to [skip] with `byteCount = size`. */ - public fun clear() + public fun clear(): Unit = skip(size) /** * Discards [byteCount]` bytes from the head of this buffer. * * @throws IllegalArgumentException when [byteCount] is negative. */ - override fun skip(byteCount: Long) + override fun skip(byteCount: Long) { + var remainingByteCount = byteCount + while (remainingByteCount > 0) { + val head = head ?: throw EOFException() + + val toSkip = minOf(remainingByteCount, head.limit - head.pos).toInt() + size -= toSkip.toLong() + remainingByteCount -= toSkip.toLong() + head.pos += toSkip + + if (head.pos == head.limit) { + this.head = head.pop() + SegmentPool.recycle(head) + } + } + } + + override fun read(sink: ByteArray, offset: Int, byteCount: Int): Int { + checkOffsetAndCount(sink.size.toLong(), offset.toLong(), byteCount.toLong()) + + val s = head ?: return -1 + val toCopy = minOf(byteCount, s.limit - s.pos) + s.data.copyInto( + destination = sink, destinationOffset = offset, startIndex = s.pos, endIndex = s.pos + toCopy + ) + + s.pos += toCopy + size -= toCopy.toLong() + + if (s.pos == s.limit) { + head = s.pop() + SegmentPool.recycle(s) + } + + return toCopy + } + + override fun read(sink: Buffer, byteCount: Long): Long { + require(byteCount >= 0L) { "byteCount < 0: $byteCount" } + if (size == 0L) return -1L + val bytesWritten = if (byteCount > size) size else byteCount + sink.write(this, bytesWritten) + return bytesWritten + } + + override fun readFully(sink: RawSink, byteCount: Long) { + if (size < byteCount) { + sink.write(this, size) // Exhaust ourselves. + throw EOFException() + } + sink.write(this, byteCount) + } + + override fun readAll(sink: RawSink): Long { + val byteCount = size + if (byteCount > 0L) { + sink.write(this, byteCount) + } + return byteCount + } + + override fun peek(): Source = PeekSource(this).buffer() /** * Returns a tail segment that we can write at least `minimumCapacity` * bytes to, creating it if necessary. */ - internal fun writableSegment(minimumCapacity: Int): Segment + internal fun writableSegment(minimumCapacity: Int): Segment { + require(minimumCapacity >= 1 && minimumCapacity <= Segment.SIZE) { "unexpected capacity" } + + if (head == null) { + val result = SegmentPool.take() // Acquire a first segment. + head = result + result.prev = result + result.next = result + return result + } + + var tail = head!!.prev + if (tail!!.limit + minimumCapacity > Segment.SIZE || !tail.owner) { + tail = tail.push(SegmentPool.take()) // Append a new empty segment to fill up. + } + return tail + } + + override fun write(source: ByteArray, offset: Int, byteCount: Int): Unit { + var currentOffset = offset + checkOffsetAndCount(source.size.toLong(), currentOffset.toLong(), byteCount.toLong()) + val limit = currentOffset + byteCount + while (currentOffset < limit) { + val tail = writableSegment(1) + + val toCopy = minOf(limit - currentOffset, Segment.SIZE - tail.limit) + source.copyInto( + destination = tail.data, + destinationOffset = tail.limit, + startIndex = currentOffset, + endIndex = currentOffset + toCopy + ) + + currentOffset += toCopy + tail.limit += toCopy + } + size += byteCount.toLong() + } + + override fun write(source: RawSource, byteCount: Long) { + var remainingByteCount = byteCount + while (remainingByteCount > 0L) { + val read = source.read(this, remainingByteCount) + if (read == -1L) throw EOFException() + remainingByteCount -= read + } + } - override fun write(source: ByteArray, offset: Int, byteCount: Int) + override fun write(source: Buffer, byteCount: Long) { + // Move bytes from the head of the source buffer to the tail of this buffer + // while balancing two conflicting goals: don't waste CPU and don't waste + // memory. + // + // + // Don't waste CPU (ie. don't copy data around). + // + // Copying large amounts of data is expensive. Instead, we prefer to + // reassign entire segments from one buffer to the other. + // + // + // Don't waste memory. + // + // As an invariant, adjacent pairs of segments in a buffer should be at + // least 50% full, except for the head segment and the tail segment. + // + // The head segment cannot maintain the invariant because the application is + // consuming bytes from this segment, decreasing its level. + // + // The tail segment cannot maintain the invariant because the application is + // producing bytes, which may require new nearly-empty tail segments to be + // appended. + // + // + // Moving segments between buffers + // + // When writing one buffer to another, we prefer to reassign entire segments + // over copying bytes into their most compact form. Suppose we have a buffer + // with these segment levels [91%, 61%]. If we append a buffer with a + // single [72%] segment, that yields [91%, 61%, 72%]. No bytes are copied. + // + // Or suppose we have a buffer with these segment levels: [100%, 2%], and we + // want to append it to a buffer with these segment levels [99%, 3%]. This + // operation will yield the following segments: [100%, 2%, 99%, 3%]. That + // is, we do not spend time copying bytes around to achieve more efficient + // memory use like [100%, 100%, 4%]. + // + // When combining buffers, we will compact adjacent buffers when their + // combined level doesn't exceed 100%. For example, when we start with + // [100%, 40%] and append [30%, 80%], the result is [100%, 70%, 80%]. + // + // + // Splitting segments + // + // Occasionally we write only part of a source buffer to a sink buffer. For + // example, given a sink [51%, 91%], we may want to write the first 30% of + // a source [92%, 82%] to it. To simplify, we first transform the source to + // an equivalent buffer [30%, 62%, 82%] and then move the head segment, + // yielding sink [51%, 91%, 30%] and source [62%, 82%]. - override fun write(source: RawSource, byteCount: Long) + require(source !== this) { "source == this" } + require(byteCount >= 0) { "byteCount ($byteCount) should not be negative." } + checkOffsetAndCount(source.size, 0, byteCount) - override fun writeByte(byte: Byte) + var remainingByteCount = byteCount - override fun writeShort(short: Short) + while (remainingByteCount > 0L) { + // Is a prefix of the source's head segment all that we need to move? + if (remainingByteCount < source.head!!.limit - source.head!!.pos) { + val tail = if (head != null) head!!.prev else null + if (tail != null && tail.owner && + remainingByteCount + tail.limit - (if (tail.shared) 0 else tail.pos) <= Segment.SIZE + ) { + // Our existing segments are sufficient. Move bytes from source's head to our tail. + source.head!!.writeTo(tail, remainingByteCount.toInt()) + source.size -= remainingByteCount + size += remainingByteCount + return + } else { + // We're going to need another segment. Split the source's head + // segment in two, then move the first of those two to this buffer. + source.head = source.head!!.split(remainingByteCount.toInt()) + } + } - override fun writeInt(int: Int) + // Remove the source's head segment and append it to our tail. + val segmentToMove = source.head + val movedByteCount = (segmentToMove!!.limit - segmentToMove.pos).toLong() + source.head = segmentToMove.pop() + if (head == null) { + head = segmentToMove + segmentToMove.prev = segmentToMove + segmentToMove.next = segmentToMove.prev + } else { + var tail = head!!.prev + tail = tail!!.push(segmentToMove) + tail.compact() + } + source.size -= movedByteCount + size += movedByteCount + remainingByteCount -= movedByteCount + } + } - override fun writeLong(long: Long) + override fun writeAll(source: RawSource): Long { + var totalBytesRead = 0L + while (true) { + val readCount = source.read(this, Segment.SIZE.toLong()) + if (readCount == -1L) break + totalBytesRead += readCount + } + return totalBytesRead + } + + override fun writeByte(byte: Byte) { + val tail = writableSegment(1) + tail.data[tail.limit++] = byte + size += 1L + } + + override fun writeShort(short: Short) { + val tail = writableSegment(2) + val data = tail.data + var limit = tail.limit + data[limit++] = (short.toInt() ushr 8 and 0xff).toByte() + data[limit++] = (short.toInt() and 0xff).toByte() + tail.limit = limit + size += 2L + } + + override fun writeInt(int: Int) { + val tail = writableSegment(4) + val data = tail.data + var limit = tail.limit + data[limit++] = (int ushr 24 and 0xff).toByte() + data[limit++] = (int ushr 16 and 0xff).toByte() + data[limit++] = (int ushr 8 and 0xff).toByte() + data[limit++] = (int and 0xff).toByte() + tail.limit = limit + size += 4L + } + + override fun writeLong(long: Long) { + val tail = writableSegment(8) + val data = tail.data + var limit = tail.limit + data[limit++] = (long ushr 56 and 0xffL).toByte() + data[limit++] = (long ushr 48 and 0xffL).toByte() + data[limit++] = (long ushr 40 and 0xffL).toByte() + data[limit++] = (long ushr 32 and 0xffL).toByte() + data[limit++] = (long ushr 24 and 0xffL).toByte() + data[limit++] = (long ushr 16 and 0xffL).toByte() + data[limit++] = (long ushr 8 and 0xffL).toByte() + data[limit++] = (long and 0xffL).toByte() + tail.limit = limit + size += 8L + } /** * Returns a deep copy of this buffer. */ - public fun copy(): Buffer + public fun copy(): Buffer { + val result = Buffer() + if (size == 0L) return result + + val head = head!! + val headCopy = head.sharedCopy() + + result.head = headCopy + headCopy.prev = result.head + headCopy.next = headCopy.prev + + var s = head.next + while (s !== head) { + headCopy.prev!!.push(s!!.sharedCopy()) + s = s.next + } + + result.size = size + return result + } /** * This method does not affect the buffer. */ - override fun close() + override fun close(): Unit = Unit + + /** + * Returns a human-readable string that describes the contents of this buffer. Typically, this + * is a string like `[text=Hello]` or `[hex=0000ffff]`. + * + * TODO: update + */ + override fun toString(): String { + // TODO: optimize implementation + if (size == 0L) return "[size=0]" + + val peekSrc = peek() + val data = if (peekSrc.request(128)) { + peekSrc.readByteArray(128) + } else { + peekSrc.readByteArray() + } + val i = codePointIndexToCharIndex(data, 64) + if (i == -1) { + return if (data.size <= 64) { + "[hex=${data.hex()}]" + } else { + "[size=${size} hex=${data.hex(64)}…]" + } + } + + val text = data.decodeToString() + val escapedText = text + .substring(0, i) + .replace("\\", "\\\\") + .replace("\n", "\\n") + .replace("\r", "\\r") + + return if (i < text.length) { + "[size=${data.size} text=$escapedText…]" + } else { + "[text=$escapedText]" + } + } +} + +private fun ByteArray.hex(count: Int = this.size): String { + val builder = StringBuilder(count * 2) + forEach { + builder.append(HEX_DIGIT_CHARS[it.shr(4) and 0x0f]) + builder.append(HEX_DIGIT_CHARS[it and 0x0f]) + } + return builder.toString() +} + + +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'.code && c != '\r'.code && isIsoControl(c)) || + c == REPLACEMENT_CODE_POINT + ) { + return -1 + } + + charCount += if (c < 0x10000) 1 else 2 + } + return charCount +} + +/** + * Invoke `lambda` with the segment and offset at `fromIndex`. Searches from the front or the back + * depending on what's closer to `fromIndex`. + */ +private inline fun Buffer.seek( + fromIndex: Long, + lambda: (Segment?, Long) -> T +): T { + var s: Segment = head ?: return lambda(null, -1L) + + if (size - fromIndex < fromIndex) { + // We're scanning in the back half of this buffer. Find the segment starting at the back. + var offset = size + while (offset > fromIndex) { + s = s.prev!! + offset -= (s.limit - s.pos).toLong() + } + return lambda(s, offset) + } else { + // We're scanning in the front half of this buffer. Find the segment starting at the front. + var offset = 0L + while (true) { + val nextOffset = offset + (s.limit - s.pos) + if (nextOffset > fromIndex) break + s = s.next!! + offset = nextOffset + } + return lambda(s, offset) + } } diff --git a/core/common/src/RealSink.kt b/core/common/src/RealSink.kt index a83c68d41..41c04384d 100644 --- a/core/common/src/RealSink.kt +++ b/core/common/src/RealSink.kt @@ -21,9 +21,132 @@ package kotlinx.io -internal expect class RealSink( - sink: RawSink -) : Sink { +import kotlin.jvm.JvmField + +internal class RealSink( val sink: RawSink - var closed: Boolean +) : Sink { + @JvmField + var closed: Boolean = false + private val bufferField = Buffer() + + @DelicateIoApi + override val buffer: Buffer + get() = bufferField + + @OptIn(DelicateIoApi::class) + override fun write(source: Buffer, byteCount: Long) { + require(byteCount >= 0) { "byteCount ($byteCount) should not be negative." } + check(!closed) { "closed" } + bufferField.write(source, byteCount) + emitCompleteSegments() + } + + @OptIn(DelicateIoApi::class) + override fun write(source: ByteArray, offset: Int, byteCount: Int) { + checkOffsetAndCount(source.size.toLong(), offset.toLong(), byteCount.toLong()) + check(!closed) { "closed" } + bufferField.write(source, offset, byteCount) + emitCompleteSegments() + } + + @OptIn(DelicateIoApi::class) + override fun writeAll(source: RawSource): Long { + var totalBytesRead = 0L + while (true) { + val readCount: Long = source.read(bufferField, Segment.SIZE.toLong()) + if (readCount == -1L) break + totalBytesRead += readCount + emitCompleteSegments() + } + return totalBytesRead + } + + @OptIn(DelicateIoApi::class) + override fun write(source: RawSource, byteCount: Long) { + require(byteCount >= 0) { "byteCount ($byteCount) should not be negative." } + var remainingByteCount = byteCount + while (remainingByteCount > 0L) { + val read = source.read(bufferField, remainingByteCount) + if (read == -1L) throw EOFException() + remainingByteCount -= read + emitCompleteSegments() + } + } + + @OptIn(DelicateIoApi::class) + override fun writeByte(byte: Byte) { + check(!closed) { "closed" } + bufferField.writeByte(byte) + emitCompleteSegments() + } + + @OptIn(DelicateIoApi::class) + override fun writeShort(short: Short) { + check(!closed) { "closed" } + bufferField.writeShort(short) + emitCompleteSegments() + } + + @OptIn(DelicateIoApi::class) + override fun writeInt(int: Int) { + check(!closed) { "closed" } + bufferField.writeInt(int) + emitCompleteSegments() + } + + @OptIn(DelicateIoApi::class) + override fun writeLong(long: Long) { + check(!closed) { "closed" } + bufferField.writeLong(long) + emitCompleteSegments() + } + + @DelicateIoApi + override fun emitCompleteSegments() { + check(!closed) { "closed" } + val byteCount = bufferField.completeSegmentByteCount() + if (byteCount > 0L) sink.write(bufferField, byteCount) + } + + override fun emit() { + check(!closed) { "closed" } + val byteCount = bufferField.size + if (byteCount > 0L) sink.write(bufferField, byteCount) + } + + override fun flush() { + check(!closed) { "closed" } + if (bufferField.size > 0L) { + sink.write(bufferField, bufferField.size) + } + sink.flush() + } + + override fun close() { + if (closed) return + + // Emit buffered data to the underlying sink. If this fails, we still need + // to close the sink; otherwise we risk leaking resources. + var thrown: Throwable? = null + try { + if (bufferField.size > 0) { + sink.write(bufferField, bufferField.size) + } + } catch (e: Throwable) { + thrown = e + } + + try { + sink.close() + } catch (e: Throwable) { + if (thrown == null) thrown = e + } + + closed = true + + if (thrown != null) throw thrown + } + + override fun toString() = "buffer($sink)" } diff --git a/core/common/src/RealSource.kt b/core/common/src/RealSource.kt index 59447424d..3fb6adbf5 100644 --- a/core/common/src/RealSource.kt +++ b/core/common/src/RealSource.kt @@ -21,9 +21,130 @@ package kotlinx.io -internal expect class RealSource( - source: RawSource -) : Source { +import kotlin.jvm.JvmField + +internal class RealSource( val source: RawSource - var closed: Boolean +) : Source { + @JvmField + var closed: Boolean = false + private val bufferField = Buffer() + + @DelicateIoApi + override val buffer: Buffer + get() = bufferField + + override fun read(sink: Buffer, byteCount: Long): Long { + require(byteCount >= 0L) { "byteCount < 0: $byteCount" } + check(!closed) { "closed" } + + if (bufferField.size == 0L) { + val read = source.read(bufferField, Segment.SIZE.toLong()) + if (read == -1L) return -1L + } + + val toRead = minOf(byteCount, bufferField.size) + return bufferField.read(sink, toRead) + } + + override fun exhausted(): Boolean { + check(!closed) { "closed" } + return bufferField.exhausted() && source.read(bufferField, Segment.SIZE.toLong()) == -1L + } + + override fun require(byteCount: Long) { + if (!request(byteCount)) throw EOFException() + } + + override fun request(byteCount: Long): Boolean { + require(byteCount >= 0L) { "byteCount < 0: $byteCount" } + check(!closed) { "closed" } + while (bufferField.size < byteCount) { + if (source.read(bufferField, Segment.SIZE.toLong()) == -1L) return false + } + return true + } + + override fun readByte(): Byte { + require(1) + return bufferField.readByte() + } + + override fun read(sink: ByteArray, offset: Int, byteCount: Int): Int { + checkOffsetAndCount(sink.size.toLong(), offset.toLong(), byteCount.toLong()) + + if (bufferField.size == 0L) { + val read = source.read(bufferField, Segment.SIZE.toLong()) + if (read == -1L) return -1 + } + + val toRead = minOf(byteCount, bufferField.size).toInt() + return bufferField.read(sink, offset, toRead) + } + + override fun readFully(sink: RawSink, byteCount: Long): Unit { + try { + require(byteCount) + } catch (e: EOFException) { + // The underlying source is exhausted. Copy the bytes we got before rethrowing. + sink.write(bufferField, bufferField.size) + throw e + } + bufferField.readFully(sink, byteCount) + } + + override fun readAll(sink: RawSink): Long { + var totalBytesWritten: Long = 0 + while (source.read(bufferField, Segment.SIZE.toLong()) != -1L) { + val emitByteCount = bufferField.completeSegmentByteCount() + if (emitByteCount > 0L) { + totalBytesWritten += emitByteCount + sink.write(bufferField, emitByteCount) + } + } + if (bufferField.size > 0L) { + totalBytesWritten += bufferField.size + sink.write(bufferField, bufferField.size) + } + return totalBytesWritten + } + + override fun readShort(): Short { + require(2) + return bufferField.readShort() + } + + override fun readInt(): Int { + require(4) + return bufferField.readInt() + } + + override fun readLong(): Long { + require(8) + return bufferField.readLong() + } + + override fun skip(byteCount: Long) { + var remainingByteCount = byteCount + check(!closed) { "closed" } + while (remainingByteCount > 0) { + if (bufferField.size == 0L && source.read(bufferField, Segment.SIZE.toLong()) == -1L) { + throw EOFException() + } + val toSkip = minOf(remainingByteCount, bufferField.size) + bufferField.skip(toSkip) + remainingByteCount -= toSkip + } + } + + override fun peek(): Source = PeekSource(this).buffer() + + override fun close() { + if (closed) return + closed = true + source.close() + bufferField.clear() + } + + override fun toString(): String = "buffer($source)" } diff --git a/core/common/src/Utf8.kt b/core/common/src/Utf8.kt index 77f1fa25b..61a54c5f7 100644 --- a/core/common/src/Utf8.kt +++ b/core/common/src/Utf8.kt @@ -69,10 +69,7 @@ package kotlinx.io -import kotlinx.io.internal.commonReadUtf8 -import kotlinx.io.internal.commonReadUtf8CodePoint -import kotlinx.io.internal.commonWriteUtf8 -import kotlinx.io.internal.commonWriteUtf8CodePoint +import kotlinx.io.internal.* /** * Returns the number of bytes used to encode the slice of `string` as UTF-8 when using [Sink.writeUtf8]. @@ -313,3 +310,241 @@ public fun Source.readUtf8LineStrict(limit: Long = Long.MAX_VALUE): String { skip(newlineSize) return line } + +private fun Buffer.commonReadUtf8CodePoint(): Int { + if (size == 0L) throw EOFException() + + val b0 = this[0] + var codePoint: Int + val byteCount: Int + val min: Int + + when { + b0 and 0x80 == 0 -> { + // 0xxxxxxx. + codePoint = b0 and 0x7f + byteCount = 1 // 7 bits (ASCII). + min = 0x0 + } + b0 and 0xe0 == 0xc0 -> { + // 0x110xxxxx + codePoint = b0 and 0x1f + byteCount = 2 // 11 bits (5 + 6). + min = 0x80 + } + b0 and 0xf0 == 0xe0 -> { + // 0x1110xxxx + codePoint = b0 and 0x0f + byteCount = 3 // 16 bits (4 + 6 + 6). + min = 0x800 + } + b0 and 0xf8 == 0xf0 -> { + // 0x11110xxx + codePoint = b0 and 0x07 + byteCount = 4 // 21 bits (3 + 6 + 6 + 6). + min = 0x10000 + } + else -> { + // We expected the first byte of a code point but got something else. + skip(1) + return REPLACEMENT_CODE_POINT + } + } + + if (size < byteCount) { + throw EOFException("size < $byteCount: $size (to read code point prefixed 0x${b0.toHexString()})") + } + + // Read the continuation bytes. If we encounter a non-continuation byte, the sequence consumed + // thus far is truncated and is decoded as the replacement character. That non-continuation byte + // is left in the stream for processing by the next call to readUtf8CodePoint(). + for (i in 1 until byteCount) { + val b = this[i.toLong()] + if (b and 0xc0 == 0x80) { + // 0x10xxxxxx + codePoint = codePoint shl 6 + codePoint = codePoint or (b and 0x3f) + } else { + skip(i.toLong()) + return REPLACEMENT_CODE_POINT + } + } + + skip(byteCount.toLong()) + + return when { + codePoint > 0x10ffff -> { + REPLACEMENT_CODE_POINT // Reject code points larger than the Unicode maximum. + } + codePoint in 0xd800..0xdfff -> { + REPLACEMENT_CODE_POINT // Reject partial surrogates. + } + codePoint < min -> { + REPLACEMENT_CODE_POINT // Reject overlong code points. + } + else -> codePoint + } +} + +private fun Buffer.commonWriteUtf8(string: String, beginIndex: Int, endIndex: Int) { + checkOffsetAndCount(string.length.toLong(), beginIndex.toLong(), (endIndex - beginIndex).toLong()) + //require(beginIndex >= 0) { "beginIndex < 0: $beginIndex" } + //require(endIndex >= beginIndex) { "endIndex < beginIndex: $endIndex < $beginIndex" } + //require(endIndex <= string.length) { "endIndex > string.length: $endIndex > ${string.length}" } + + // Transcode a UTF-16 Java String to UTF-8 bytes. + var i = beginIndex + while (i < endIndex) { + var c = string[i].code + + when { + c < 0x80 -> { + val tail = writableSegment(1) + val data = tail.data + val segmentOffset = tail.limit - i + val runLimit = minOf(endIndex, Segment.SIZE - segmentOffset) + + // Emit a 7-bit character with 1 byte. + data[segmentOffset + i++] = c.toByte() // 0xxxxxxx + + // Fast-path contiguous runs of ASCII characters. This is ugly, but yields a ~4x performance + // improvement over independent calls to writeByte(). + while (i < runLimit) { + c = string[i].code + if (c >= 0x80) break + data[segmentOffset + i++] = c.toByte() // 0xxxxxxx + } + + val runSize = i + segmentOffset - tail.limit // Equivalent to i - (previous i). + tail.limit += runSize + size += runSize.toLong() + } + + c < 0x800 -> { + // Emit a 11-bit character with 2 bytes. + val tail = writableSegment(2) + /* ktlint-disable no-multi-spaces */ + tail.data[tail.limit ] = (c shr 6 or 0xc0).toByte() // 110xxxxx + tail.data[tail.limit + 1] = (c and 0x3f or 0x80).toByte() // 10xxxxxx + /* ktlint-enable no-multi-spaces */ + tail.limit += 2 + size += 2L + i++ + } + + c < 0xd800 || c > 0xdfff -> { + // Emit a 16-bit character with 3 bytes. + val tail = writableSegment(3) + /* ktlint-disable no-multi-spaces */ + tail.data[tail.limit ] = (c shr 12 or 0xe0).toByte() // 1110xxxx + tail.data[tail.limit + 1] = (c shr 6 and 0x3f or 0x80).toByte() // 10xxxxxx + tail.data[tail.limit + 2] = (c and 0x3f or 0x80).toByte() // 10xxxxxx + /* ktlint-enable no-multi-spaces */ + tail.limit += 3 + size += 3L + i++ + } + + else -> { + // c is a surrogate. Make sure it is a high surrogate & that its successor is a low + // surrogate. If not, the UTF-16 is invalid, in which case we emit a replacement + // character. + val low = (if (i + 1 < endIndex) string[i + 1].code else 0) + if (c > 0xdbff || low !in 0xdc00..0xdfff) { + writeByte('?'.code.toByte()) + i++ + } else { + // UTF-16 high surrogate: 110110xxxxxxxxxx (10 bits) + // UTF-16 low surrogate: 110111yyyyyyyyyy (10 bits) + // Unicode code point: 00010000000000000000 + xxxxxxxxxxyyyyyyyyyy (21 bits) + val codePoint = 0x010000 + (c and 0x03ff shl 10 or (low and 0x03ff)) + + // Emit a 21-bit character with 4 bytes. + val tail = writableSegment(4) + /* ktlint-disable no-multi-spaces */ + tail.data[tail.limit ] = (codePoint shr 18 or 0xf0).toByte() // 11110xxx + tail.data[tail.limit + 1] = (codePoint shr 12 and 0x3f or 0x80).toByte() // 10xxxxxx + tail.data[tail.limit + 2] = (codePoint shr 6 and 0x3f or 0x80).toByte() // 10xxyyyy + tail.data[tail.limit + 3] = (codePoint and 0x3f or 0x80).toByte() // 10yyyyyy + /* ktlint-enable no-multi-spaces */ + tail.limit += 4 + size += 4L + i += 2 + } + } + } + } +} + +private fun Buffer.commonWriteUtf8CodePoint(codePoint: Int) { + when { + codePoint < 0x80 -> { + // Emit a 7-bit code point with 1 byte. + writeByte(codePoint.toByte()) + } + codePoint < 0x800 -> { + // Emit a 11-bit code point with 2 bytes. + val tail = writableSegment(2) + /* ktlint-disable no-multi-spaces */ + tail.data[tail.limit ] = (codePoint shr 6 or 0xc0).toByte() // 110xxxxx + tail.data[tail.limit + 1] = (codePoint and 0x3f or 0x80).toByte() // 10xxxxxx + /* ktlint-enable no-multi-spaces */ + tail.limit += 2 + size += 2L + } + codePoint in 0xd800..0xdfff -> { + // Emit a replacement character for a partial surrogate. + writeByte('?'.code.toByte()) + } + codePoint < 0x10000 -> { + // Emit a 16-bit code point with 3 bytes. + val tail = writableSegment(3) + /* ktlint-disable no-multi-spaces */ + tail.data[tail.limit ] = (codePoint shr 12 or 0xe0).toByte() // 1110xxxx + tail.data[tail.limit + 1] = (codePoint shr 6 and 0x3f or 0x80).toByte() // 10xxxxxx + tail.data[tail.limit + 2] = (codePoint and 0x3f or 0x80).toByte() // 10xxxxxx + /* ktlint-enable no-multi-spaces */ + tail.limit += 3 + size += 3L + } + codePoint <= 0x10ffff -> { + // Emit a 21-bit code point with 4 bytes. + val tail = writableSegment(4) + /* ktlint-disable no-multi-spaces */ + tail.data[tail.limit ] = (codePoint shr 18 or 0xf0).toByte() // 11110xxx + tail.data[tail.limit + 1] = (codePoint shr 12 and 0x3f or 0x80).toByte() // 10xxxxxx + tail.data[tail.limit + 2] = (codePoint shr 6 and 0x3f or 0x80).toByte() // 10xxyyyy + tail.data[tail.limit + 3] = (codePoint and 0x3f or 0x80).toByte() // 10yyyyyy + /* ktlint-enable no-multi-spaces */ + tail.limit += 4 + size += 4L + } + else -> { + throw IllegalArgumentException("Unexpected code point: 0x${codePoint.toHexString()}") + } + } +} + +private fun Buffer.commonReadUtf8(byteCount: Long): String { + require(byteCount >= 0 && byteCount <= Int.MAX_VALUE) { "byteCount: $byteCount" } + if (size < byteCount) throw EOFException() + if (byteCount == 0L) return "" + + val s = head!! + if (s.pos + byteCount > s.limit) { + // If the string spans multiple segments, delegate to readBytes(). + + return readByteArray(byteCount).commonToUtf8String() + } + + val result = s.data.commonToUtf8String(s.pos, s.pos + byteCount.toInt()) + s.pos += byteCount.toInt() + size -= byteCount + + if (s.pos == s.limit) { + head = s.pop() + SegmentPool.recycle(s) + } + + return result +} diff --git a/core/common/src/internal/-Buffer.kt b/core/common/src/internal/-Buffer.kt deleted file mode 100644 index ac6024b25..000000000 --- a/core/common/src/internal/-Buffer.kt +++ /dev/null @@ -1,877 +0,0 @@ -/* - * Copyright 2017-2023 JetBrains s.r.o. and respective authors and developers. - * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENCE file. - */ - -/* - * Copyright (C) 2019 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. - */ - -// TODO move to Buffer class: https://youtrack.jetbrains.com/issue/KT-20427 - -@file:Suppress("NOTHING_TO_INLINE") - -package kotlinx.io.internal - -import kotlinx.io.* - -/** - * Invoke `lambda` with the segment and offset at `fromIndex`. Searches from the front or the back - * depending on what's closer to `fromIndex`. - */ -internal inline fun Buffer.seek( - fromIndex: Long, - lambda: (Segment?, Long) -> T -): T { - var s: Segment = head ?: return lambda(null, -1L) - - if (size - fromIndex < fromIndex) { - // We're scanning in the back half of this buffer. Find the segment starting at the back. - var offset = size - while (offset > fromIndex) { - s = s.prev!! - offset -= (s.limit - s.pos).toLong() - } - return lambda(s, offset) - } else { - // We're scanning in the front half of this buffer. Find the segment starting at the front. - var offset = 0L - while (true) { - val nextOffset = offset + (s.limit - s.pos) - if (nextOffset > fromIndex) break - s = s.next!! - offset = nextOffset - } - return lambda(s, offset) - } -} -// 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. - -internal inline fun Buffer.commonCopyTo( - out: Buffer, - offset: Long, - byteCount: Long -) { - checkOffsetAndCount(size, offset, byteCount) - if (byteCount == 0L) return - - var currentOffset = offset - var remainingByteCount = byteCount - - out.size += remainingByteCount - - // Skip segments that we aren't copying from. - var s = head - while (currentOffset >= s!!.limit - s.pos) { - currentOffset -= (s.limit - s.pos).toLong() - s = s.next - } - - // Copy one segment at a time. - while (remainingByteCount > 0L) { - val copy = s!!.sharedCopy() - copy.pos += currentOffset.toInt() - copy.limit = minOf(copy.pos + remainingByteCount.toInt(), copy.limit) - if (out.head == null) { - copy.prev = copy - copy.next = copy.prev - out.head = copy.next - } else { - out.head!!.prev!!.push(copy) - } - remainingByteCount -= (copy.limit - copy.pos).toLong() - currentOffset = 0L - s = s.next - } -} - -internal inline fun Buffer.commonCompleteSegmentByteCount(): Long { - var result = size - if (result == 0L) return 0L - - // Omit the tail if it's still writable. - val tail = head!!.prev!! - if (tail.limit < Segment.SIZE && tail.owner) { - result -= (tail.limit - tail.pos).toLong() - } - - return result -} - -internal inline fun Buffer.commonReadByte(): Byte { - if (size == 0L) throw EOFException() - - val segment = head!! - var pos = segment.pos - val limit = segment.limit - - val data = segment.data - val b = data[pos++] - size -= 1L - - if (pos == limit) { - head = segment.pop() - SegmentPool.recycle(segment) - } else { - segment.pos = pos - } - - return b -} - -internal inline fun Buffer.commonReadShort(): Short { - if (size < 2L) throw EOFException() - - val segment = head!! - var pos = segment.pos - val limit = segment.limit - - // If the short is split across multiple segments, delegate to readByte(). - if (limit - pos < 2) { - val s = readByte() and 0xff shl 8 or (readByte() and 0xff) - return s.toShort() - } - - val data = segment.data - val s = data[pos++] and 0xff shl 8 or (data[pos++] and 0xff) - size -= 2L - - if (pos == limit) { - head = segment.pop() - SegmentPool.recycle(segment) - } else { - segment.pos = pos - } - - return s.toShort() -} - -internal inline fun Buffer.commonReadInt(): Int { - if (size < 4L) throw EOFException() - - val segment = head!! - var pos = segment.pos - val limit = segment.limit - - // If the int is split across multiple segments, delegate to readByte(). - if (limit - pos < 4L) { - return ( - readByte() and 0xff shl 24 - or (readByte() and 0xff shl 16) - or (readByte() and 0xff shl 8) // ktlint-disable no-multi-spaces - or (readByte() and 0xff) - ) - } - - val data = segment.data - val i = ( - data[pos++] and 0xff shl 24 - or (data[pos++] and 0xff shl 16) - or (data[pos++] and 0xff shl 8) - or (data[pos++] and 0xff) - ) - size -= 4L - - if (pos == limit) { - head = segment.pop() - SegmentPool.recycle(segment) - } else { - segment.pos = pos - } - - return i -} - -internal inline fun Buffer.commonReadLong(): Long { - if (size < 8L) throw EOFException() - - val segment = head!! - var pos = segment.pos - val limit = segment.limit - - // If the long is split across multiple segments, delegate to readInt(). - if (limit - pos < 8L) { - return ( - readInt() and 0xffffffffL shl 32 - or (readInt() and 0xffffffffL) - ) - } - - val data = segment.data - val v = ( - data[pos++] and 0xffL shl 56 - or (data[pos++] and 0xffL shl 48) - or (data[pos++] and 0xffL shl 40) - or (data[pos++] and 0xffL shl 32) - or (data[pos++] and 0xffL shl 24) - or (data[pos++] and 0xffL shl 16) - or (data[pos++] and 0xffL shl 8) // ktlint-disable no-multi-spaces - or (data[pos++] and 0xffL) - ) - size -= 8L - - if (pos == limit) { - head = segment.pop() - SegmentPool.recycle(segment) - } else { - segment.pos = pos - } - - return v -} - -internal inline fun Buffer.commonGet(pos: Long): Byte { - checkOffsetAndCount(size, pos, 1L) - seek(pos) { s, offset -> - return s!!.data[(s.pos + pos - offset).toInt()] - } -} - -internal inline fun Buffer.commonClear() = skip(size) - -internal inline fun Buffer.commonSkip(byteCount: Long) { - var remainingByteCount = byteCount - while (remainingByteCount > 0) { - val head = this.head ?: throw EOFException() - - val toSkip = minOf(remainingByteCount, head.limit - head.pos).toInt() - size -= toSkip.toLong() - remainingByteCount -= toSkip.toLong() - head.pos += toSkip - - if (head.pos == head.limit) { - this.head = head.pop() - SegmentPool.recycle(head) - } - } -} - -internal inline fun Buffer.commonWritableSegment(minimumCapacity: Int): Segment { - require(minimumCapacity >= 1 && minimumCapacity <= Segment.SIZE) { "unexpected capacity" } - - if (head == null) { - val result = SegmentPool.take() // Acquire a first segment. - head = result - result.prev = result - result.next = result - return result - } - - var tail = head!!.prev - if (tail!!.limit + minimumCapacity > Segment.SIZE || !tail.owner) { - tail = tail.push(SegmentPool.take()) // Append a new empty segment to fill up. - } - return tail -} - -internal inline fun Buffer.commonWrite( - source: ByteArray, - offset: Int, - byteCount: Int -) { - var currentOffset = offset - checkOffsetAndCount(source.size.toLong(), currentOffset.toLong(), byteCount.toLong()) - - val limit = currentOffset + byteCount - while (currentOffset < limit) { - val tail = writableSegment(1) - - val toCopy = minOf(limit - currentOffset, Segment.SIZE - tail.limit) - source.copyInto( - destination = tail.data, - destinationOffset = tail.limit, - startIndex = currentOffset, - endIndex = currentOffset + toCopy - ) - - currentOffset += toCopy - tail.limit += toCopy - } - - size += byteCount.toLong() -} - -internal inline fun Buffer.commonRead(sink: ByteArray, offset: Int, byteCount: Int): Int { - checkOffsetAndCount(sink.size.toLong(), offset.toLong(), byteCount.toLong()) - - val s = head ?: return -1 - val toCopy = minOf(byteCount, s.limit - s.pos) - s.data.copyInto( - destination = sink, destinationOffset = offset, startIndex = s.pos, endIndex = s.pos + toCopy - ) - - s.pos += toCopy - size -= toCopy.toLong() - - if (s.pos == s.limit) { - head = s.pop() - SegmentPool.recycle(s) - } - - return toCopy -} - -internal inline fun Buffer.commonReadFully(sink: RawSink, byteCount: Long) { - if (size < byteCount) { - sink.write(this, size) // Exhaust ourselves. - throw EOFException() - } - sink.write(this, byteCount) -} - -internal inline fun Buffer.commonReadAll(sink: RawSink): Long { - val byteCount = size - if (byteCount > 0L) { - sink.write(this, byteCount) - } - return byteCount -} - -internal inline fun Buffer.commonReadUtf8(byteCount: Long): String { - require(byteCount >= 0 && byteCount <= Int.MAX_VALUE) { "byteCount: $byteCount" } - if (size < byteCount) throw EOFException() - if (byteCount == 0L) return "" - - val s = head!! - if (s.pos + byteCount > s.limit) { - // If the string spans multiple segments, delegate to readBytes(). - - return readByteArray(byteCount).commonToUtf8String() - } - - val result = s.data.commonToUtf8String(s.pos, s.pos + byteCount.toInt()) - s.pos += byteCount.toInt() - size -= byteCount - - if (s.pos == s.limit) { - head = s.pop() - SegmentPool.recycle(s) - } - - return result -} - -internal inline fun Buffer.commonReadUtf8CodePoint(): Int { - if (size == 0L) throw EOFException() - - val b0 = this[0] - var codePoint: Int - val byteCount: Int - val min: Int - - when { - b0 and 0x80 == 0 -> { - // 0xxxxxxx. - codePoint = b0 and 0x7f - byteCount = 1 // 7 bits (ASCII). - min = 0x0 - } - b0 and 0xe0 == 0xc0 -> { - // 0x110xxxxx - codePoint = b0 and 0x1f - byteCount = 2 // 11 bits (5 + 6). - min = 0x80 - } - b0 and 0xf0 == 0xe0 -> { - // 0x1110xxxx - codePoint = b0 and 0x0f - byteCount = 3 // 16 bits (4 + 6 + 6). - min = 0x800 - } - b0 and 0xf8 == 0xf0 -> { - // 0x11110xxx - codePoint = b0 and 0x07 - byteCount = 4 // 21 bits (3 + 6 + 6 + 6). - min = 0x10000 - } - else -> { - // We expected the first byte of a code point but got something else. - skip(1) - return REPLACEMENT_CODE_POINT - } - } - - if (size < byteCount) { - throw EOFException("size < $byteCount: $size (to read code point prefixed 0x${b0.toHexString()})") - } - - // Read the continuation bytes. If we encounter a non-continuation byte, the sequence consumed - // thus far is truncated and is decoded as the replacement character. That non-continuation byte - // is left in the stream for processing by the next call to readUtf8CodePoint(). - for (i in 1 until byteCount) { - val b = this[i.toLong()] - if (b and 0xc0 == 0x80) { - // 0x10xxxxxx - codePoint = codePoint shl 6 - codePoint = codePoint or (b and 0x3f) - } else { - skip(i.toLong()) - return REPLACEMENT_CODE_POINT - } - } - - skip(byteCount.toLong()) - - return when { - codePoint > 0x10ffff -> { - REPLACEMENT_CODE_POINT // Reject code points larger than the Unicode maximum. - } - codePoint in 0xd800..0xdfff -> { - REPLACEMENT_CODE_POINT // Reject partial surrogates. - } - codePoint < min -> { - REPLACEMENT_CODE_POINT // Reject overlong code points. - } - else -> codePoint - } -} - -internal inline fun Buffer.commonWriteUtf8(string: String, beginIndex: Int, endIndex: Int) { - checkOffsetAndCount(string.length.toLong(), beginIndex.toLong(), (endIndex - beginIndex).toLong()) - //require(beginIndex >= 0) { "beginIndex < 0: $beginIndex" } - //require(endIndex >= beginIndex) { "endIndex < beginIndex: $endIndex < $beginIndex" } - //require(endIndex <= string.length) { "endIndex > string.length: $endIndex > ${string.length}" } - - // Transcode a UTF-16 Java String to UTF-8 bytes. - var i = beginIndex - while (i < endIndex) { - var c = string[i].code - - when { - c < 0x80 -> { - val tail = writableSegment(1) - val data = tail.data - val segmentOffset = tail.limit - i - val runLimit = minOf(endIndex, Segment.SIZE - segmentOffset) - - // Emit a 7-bit character with 1 byte. - data[segmentOffset + i++] = c.toByte() // 0xxxxxxx - - // Fast-path contiguous runs of ASCII characters. This is ugly, but yields a ~4x performance - // improvement over independent calls to writeByte(). - while (i < runLimit) { - c = string[i].code - if (c >= 0x80) break - data[segmentOffset + i++] = c.toByte() // 0xxxxxxx - } - - val runSize = i + segmentOffset - tail.limit // Equivalent to i - (previous i). - tail.limit += runSize - size += runSize.toLong() - } - - c < 0x800 -> { - // Emit a 11-bit character with 2 bytes. - val tail = writableSegment(2) - /* ktlint-disable no-multi-spaces */ - tail.data[tail.limit ] = (c shr 6 or 0xc0).toByte() // 110xxxxx - tail.data[tail.limit + 1] = (c and 0x3f or 0x80).toByte() // 10xxxxxx - /* ktlint-enable no-multi-spaces */ - tail.limit += 2 - size += 2L - i++ - } - - c < 0xd800 || c > 0xdfff -> { - // Emit a 16-bit character with 3 bytes. - val tail = writableSegment(3) - /* ktlint-disable no-multi-spaces */ - tail.data[tail.limit ] = (c shr 12 or 0xe0).toByte() // 1110xxxx - tail.data[tail.limit + 1] = (c shr 6 and 0x3f or 0x80).toByte() // 10xxxxxx - tail.data[tail.limit + 2] = (c and 0x3f or 0x80).toByte() // 10xxxxxx - /* ktlint-enable no-multi-spaces */ - tail.limit += 3 - size += 3L - i++ - } - - else -> { - // c is a surrogate. Make sure it is a high surrogate & that its successor is a low - // surrogate. If not, the UTF-16 is invalid, in which case we emit a replacement - // character. - val low = (if (i + 1 < endIndex) string[i + 1].code else 0) - if (c > 0xdbff || low !in 0xdc00..0xdfff) { - writeByte('?'.code.toByte()) - i++ - } else { - // UTF-16 high surrogate: 110110xxxxxxxxxx (10 bits) - // UTF-16 low surrogate: 110111yyyyyyyyyy (10 bits) - // Unicode code point: 00010000000000000000 + xxxxxxxxxxyyyyyyyyyy (21 bits) - val codePoint = 0x010000 + (c and 0x03ff shl 10 or (low and 0x03ff)) - - // Emit a 21-bit character with 4 bytes. - val tail = writableSegment(4) - /* ktlint-disable no-multi-spaces */ - tail.data[tail.limit ] = (codePoint shr 18 or 0xf0).toByte() // 11110xxx - tail.data[tail.limit + 1] = (codePoint shr 12 and 0x3f or 0x80).toByte() // 10xxxxxx - tail.data[tail.limit + 2] = (codePoint shr 6 and 0x3f or 0x80).toByte() // 10xxyyyy - tail.data[tail.limit + 3] = (codePoint and 0x3f or 0x80).toByte() // 10yyyyyy - /* ktlint-enable no-multi-spaces */ - tail.limit += 4 - size += 4L - i += 2 - } - } - } - } -} - -internal inline fun Buffer.commonWriteUtf8CodePoint(codePoint: Int) { - when { - codePoint < 0x80 -> { - // Emit a 7-bit code point with 1 byte. - writeByte(codePoint.toByte()) - } - codePoint < 0x800 -> { - // Emit a 11-bit code point with 2 bytes. - val tail = writableSegment(2) - /* ktlint-disable no-multi-spaces */ - tail.data[tail.limit ] = (codePoint shr 6 or 0xc0).toByte() // 110xxxxx - tail.data[tail.limit + 1] = (codePoint and 0x3f or 0x80).toByte() // 10xxxxxx - /* ktlint-enable no-multi-spaces */ - tail.limit += 2 - size += 2L - } - codePoint in 0xd800..0xdfff -> { - // Emit a replacement character for a partial surrogate. - writeByte('?'.code.toByte()) - } - codePoint < 0x10000 -> { - // Emit a 16-bit code point with 3 bytes. - val tail = writableSegment(3) - /* ktlint-disable no-multi-spaces */ - tail.data[tail.limit ] = (codePoint shr 12 or 0xe0).toByte() // 1110xxxx - tail.data[tail.limit + 1] = (codePoint shr 6 and 0x3f or 0x80).toByte() // 10xxxxxx - tail.data[tail.limit + 2] = (codePoint and 0x3f or 0x80).toByte() // 10xxxxxx - /* ktlint-enable no-multi-spaces */ - tail.limit += 3 - size += 3L - } - codePoint <= 0x10ffff -> { - // Emit a 21-bit code point with 4 bytes. - val tail = writableSegment(4) - /* ktlint-disable no-multi-spaces */ - tail.data[tail.limit ] = (codePoint shr 18 or 0xf0).toByte() // 11110xxx - tail.data[tail.limit + 1] = (codePoint shr 12 and 0x3f or 0x80).toByte() // 10xxxxxx - tail.data[tail.limit + 2] = (codePoint shr 6 and 0x3f or 0x80).toByte() // 10xxyyyy - tail.data[tail.limit + 3] = (codePoint and 0x3f or 0x80).toByte() // 10yyyyyy - /* ktlint-enable no-multi-spaces */ - tail.limit += 4 - size += 4L - } - else -> { - throw IllegalArgumentException("Unexpected code point: 0x${codePoint.toHexString()}") - } - } -} - -internal inline fun Buffer.commonWriteAll(source: RawSource): Long { - var totalBytesRead = 0L - while (true) { - val readCount = source.read(this, Segment.SIZE.toLong()) - if (readCount == -1L) break - totalBytesRead += readCount - } - return totalBytesRead -} - -internal inline fun Buffer.commonWrite(source: RawSource, byteCount: Long) { - var remainingByteCount = byteCount - while (remainingByteCount > 0L) { - val read = source.read(this, remainingByteCount) - if (read == -1L) throw EOFException() - remainingByteCount -= read - } -} - -internal inline fun Buffer.commonWriteByte(b: Byte) { - val tail = writableSegment(1) - tail.data[tail.limit++] = b - size += 1L -} - -internal inline fun Buffer.commonWriteShort(s: Short) { - val tail = writableSegment(2) - val data = tail.data - var limit = tail.limit - data[limit++] = (s.toInt() ushr 8 and 0xff).toByte() - data[limit++] = (s.toInt() and 0xff).toByte() // ktlint-disable no-multi-spaces - tail.limit = limit - size += 2L -} - -internal inline fun Buffer.commonWriteInt(i: Int) { - val tail = writableSegment(4) - val data = tail.data - var limit = tail.limit - data[limit++] = (i ushr 24 and 0xff).toByte() - data[limit++] = (i ushr 16 and 0xff).toByte() - data[limit++] = (i ushr 8 and 0xff).toByte() // ktlint-disable no-multi-spaces - data[limit++] = (i and 0xff).toByte() // ktlint-disable no-multi-spaces - tail.limit = limit - size += 4L -} - -internal inline fun Buffer.commonWriteLong(v: Long) { - val tail = writableSegment(8) - val data = tail.data - var limit = tail.limit - data[limit++] = (v ushr 56 and 0xffL).toByte() - data[limit++] = (v ushr 48 and 0xffL).toByte() - data[limit++] = (v ushr 40 and 0xffL).toByte() - data[limit++] = (v ushr 32 and 0xffL).toByte() - data[limit++] = (v ushr 24 and 0xffL).toByte() - data[limit++] = (v ushr 16 and 0xffL).toByte() - data[limit++] = (v ushr 8 and 0xffL).toByte() // ktlint-disable no-multi-spaces - data[limit++] = (v and 0xffL).toByte() // ktlint-disable no-multi-spaces - tail.limit = limit - size += 8L -} - -internal inline fun Buffer.commonWrite(source: Buffer, byteCount: Long) { - // Move bytes from the head of the source buffer to the tail of this buffer - // while balancing two conflicting goals: don't waste CPU and don't waste - // memory. - // - // - // Don't waste CPU (ie. don't copy data around). - // - // Copying large amounts of data is expensive. Instead, we prefer to - // reassign entire segments from one buffer to the other. - // - // - // Don't waste memory. - // - // As an invariant, adjacent pairs of segments in a buffer should be at - // least 50% full, except for the head segment and the tail segment. - // - // The head segment cannot maintain the invariant because the application is - // consuming bytes from this segment, decreasing its level. - // - // The tail segment cannot maintain the invariant because the application is - // producing bytes, which may require new nearly-empty tail segments to be - // appended. - // - // - // Moving segments between buffers - // - // When writing one buffer to another, we prefer to reassign entire segments - // over copying bytes into their most compact form. Suppose we have a buffer - // with these segment levels [91%, 61%]. If we append a buffer with a - // single [72%] segment, that yields [91%, 61%, 72%]. No bytes are copied. - // - // Or suppose we have a buffer with these segment levels: [100%, 2%], and we - // want to append it to a buffer with these segment levels [99%, 3%]. This - // operation will yield the following segments: [100%, 2%, 99%, 3%]. That - // is, we do not spend time copying bytes around to achieve more efficient - // memory use like [100%, 100%, 4%]. - // - // When combining buffers, we will compact adjacent buffers when their - // combined level doesn't exceed 100%. For example, when we start with - // [100%, 40%] and append [30%, 80%], the result is [100%, 70%, 80%]. - // - // - // Splitting segments - // - // Occasionally we write only part of a source buffer to a sink buffer. For - // example, given a sink [51%, 91%], we may want to write the first 30% of - // a source [92%, 82%] to it. To simplify, we first transform the source to - // an equivalent buffer [30%, 62%, 82%] and then move the head segment, - // yielding sink [51%, 91%, 30%] and source [62%, 82%]. - - require(source !== this) { "source == this" } - require(byteCount >= 0) { "byteCount ($byteCount) should not be negative." } - checkOffsetAndCount(source.size, 0, byteCount) - - var remainingByteCount = byteCount - - while (remainingByteCount > 0L) { - // Is a prefix of the source's head segment all that we need to move? - if (remainingByteCount < source.head!!.limit - source.head!!.pos) { - val tail = if (head != null) head!!.prev else null - if (tail != null && tail.owner && - remainingByteCount + tail.limit - (if (tail.shared) 0 else tail.pos) <= Segment.SIZE - ) { - // Our existing segments are sufficient. Move bytes from source's head to our tail. - source.head!!.writeTo(tail, remainingByteCount.toInt()) - source.size -= remainingByteCount - size += remainingByteCount - return - } else { - // We're going to need another segment. Split the source's head - // segment in two, then move the first of those two to this buffer. - source.head = source.head!!.split(remainingByteCount.toInt()) - } - } - - // Remove the source's head segment and append it to our tail. - val segmentToMove = source.head - val movedByteCount = (segmentToMove!!.limit - segmentToMove.pos).toLong() - source.head = segmentToMove.pop() - if (head == null) { - head = segmentToMove - segmentToMove.prev = segmentToMove - segmentToMove.next = segmentToMove.prev - } else { - var tail = head!!.prev - tail = tail!!.push(segmentToMove) - tail.compact() - } - source.size -= movedByteCount - size += movedByteCount - remainingByteCount -= movedByteCount - } -} - -internal inline fun Buffer.commonRead(sink: Buffer, byteCount: Long): Long { - require(byteCount >= 0L) { "byteCount < 0: $byteCount" } - if (size == 0L) return -1L - val bytesWritten = if (byteCount > size) size else byteCount - sink.write(this, bytesWritten) - return bytesWritten -} - -internal inline fun Buffer.commonEquals(other: Any?): Boolean { - if (this === other) return true - if (other !is Buffer) return false - if (size != other.size) return false - if (size == 0L) return true // Both buffers are empty. - - var sa = this.head!! - var sb = other.head!! - var posA = sa.pos - var posB = sb.pos - - var pos = 0L - var count: Long - while (pos < size) { - count = minOf(sa.limit - posA, sb.limit - posB).toLong() - - for (i in 0L until count) { - if (sa.data[posA++] != sb.data[posB++]) return false - } - - if (posA == sa.limit) { - sa = sa.next!! - posA = sa.pos - } - - if (posB == sb.limit) { - sb = sb.next!! - posB = sb.pos - } - pos += count - } - - return true -} - -internal inline fun Buffer.commonHashCode(): Int { - var s = head ?: return 0 - var result = 1 - do { - var pos = s.pos - val limit = s.limit - while (pos < limit) { - result = 31 * result + s.data[pos] - pos++ - } - s = s.next!! - } while (s !== head) - return result -} - -internal inline fun Buffer.commonCopy(): Buffer { - val result = Buffer() - if (size == 0L) return result - - val head = head!! - val headCopy = head.sharedCopy() - - result.head = headCopy - headCopy.prev = result.head - headCopy.next = headCopy.prev - - var s = head.next - while (s !== head) { - headCopy.prev!!.push(s!!.sharedCopy()) - s = s.next - } - - result.size = size - return result -} - -// TODO: optimize implementation -internal inline fun Buffer.commonString(): String { - if (size == 0L) return "[size=0]" - - val peekSrc = peek() - val data = if (peekSrc.request(128)) { - peekSrc.readByteArray(128) - } else { - peekSrc.readByteArray() - } - val i = codePointIndexToCharIndex(data, 64) - if (i == -1) { - return if (data.size <= 64) { - "[hex=${data.hex()}]" - } else { - "[size=${size} hex=${data.hex(64)}…]" - } - } - - val text = data.decodeToString() - val escapedText = text - .substring(0, i) - .replace("\\", "\\\\") - .replace("\n", "\\n") - .replace("\r", "\\r") - - return if (i < text.length) { - "[size=${data.size} text=$escapedText…]" - } else { - "[text=$escapedText]" - } -} - -private fun ByteArray.hex(count: Int = this.size): String { - val builder = StringBuilder(count * 2) - forEach { - builder.append(HEX_DIGIT_CHARS[it.shr(4) and 0x0f]) - builder.append(HEX_DIGIT_CHARS[it and 0x0f]) - } - return builder.toString() -} - -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'.code && c != '\r'.code && isIsoControl(c)) || - c == REPLACEMENT_CODE_POINT - ) { - return -1 - } - - charCount += if (c < 0x10000) 1 else 2 - } - return charCount -} diff --git a/core/common/src/internal/-RealBufferedSink.kt b/core/common/src/internal/-RealBufferedSink.kt deleted file mode 100644 index c31328574..000000000 --- a/core/common/src/internal/-RealBufferedSink.kt +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Copyright 2017-2023 JetBrains s.r.o. and respective authors and developers. - * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENCE file. - */ - -/* - * Copyright (C) 2019 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. - */ - -// TODO move to RealBufferedSink class: https://youtrack.jetbrains.com/issue/KT-20427 -@file:Suppress("NOTHING_TO_INLINE") - -package kotlinx.io.internal - -import kotlinx.io.* - -@OptIn(DelicateIoApi::class) -internal inline fun RealSink.commonWrite(source: Buffer, byteCount: Long) { - require(byteCount >= 0) { "byteCount ($byteCount) should not be negative." } - check(!closed) { "closed" } - buffer.write(source, byteCount) - emitCompleteSegments() -} - -@OptIn(DelicateIoApi::class) -internal inline fun RealSink.commonWrite( - source: ByteArray, - offset: Int, - byteCount: Int -) { - checkOffsetAndCount(source.size.toLong(), offset.toLong(), byteCount.toLong()) - check(!closed) { "closed" } - buffer.write(source, offset, byteCount) - emitCompleteSegments() -} - -@OptIn(DelicateIoApi::class) -internal inline fun RealSink.commonWriteAll(source: RawSource): Long { - var totalBytesRead = 0L - while (true) { - val readCount: Long = source.read(buffer, Segment.SIZE.toLong()) - if (readCount == -1L) break - totalBytesRead += readCount - emitCompleteSegments() - } - return totalBytesRead -} - -@OptIn(DelicateIoApi::class) -internal inline fun RealSink.commonWrite(source: RawSource, byteCount: Long) { - require(byteCount >= 0) { "byteCount ($byteCount) should not be negative."} - var remainingByteCount = byteCount - while (remainingByteCount > 0L) { - val read = source.read(buffer, remainingByteCount) - if (read == -1L) throw EOFException() - remainingByteCount -= read - emitCompleteSegments() - } -} - -@OptIn(DelicateIoApi::class) -internal inline fun RealSink.commonWriteByte(b: Byte) { - check(!closed) { "closed" } - buffer.writeByte(b) - emitCompleteSegments() -} - -@OptIn(DelicateIoApi::class) -internal inline fun RealSink.commonWriteShort(s: Short) { - check(!closed) { "closed" } - buffer.writeShort(s) - emitCompleteSegments() -} - -@OptIn(DelicateIoApi::class) -internal inline fun RealSink.commonWriteInt(i: Int) { - check(!closed) { "closed" } - buffer.writeInt(i) - emitCompleteSegments() -} - -@OptIn(DelicateIoApi::class) -internal inline fun RealSink.commonWriteLong(v: Long) { - check(!closed) { "closed" } - buffer.writeLong(v) - emitCompleteSegments() -} - -@OptIn(DelicateIoApi::class) -internal inline fun RealSink.commonEmitCompleteSegments() { - check(!closed) { "closed" } - val byteCount = buffer.completeSegmentByteCount() - if (byteCount > 0L) sink.write(buffer, byteCount) -} - -@OptIn(DelicateIoApi::class) -internal inline fun RealSink.commonEmit() { - check(!closed) { "closed" } - val byteCount = buffer.size - if (byteCount > 0L) sink.write(buffer, byteCount) -} - -@OptIn(DelicateIoApi::class) -internal inline fun RealSink.commonFlush() { - check(!closed) { "closed" } - if (buffer.size > 0L) { - sink.write(buffer, buffer.size) - } - sink.flush() -} - -@OptIn(DelicateIoApi::class) -internal inline fun RealSink.commonClose() { - if (closed) return - - // Emit buffered data to the underlying sink. If this fails, we still need - // to close the sink; otherwise we risk leaking resources. - var thrown: Throwable? = null - try { - if (buffer.size > 0) { - sink.write(buffer, buffer.size) - } - } catch (e: Throwable) { - thrown = e - } - - try { - sink.close() - } catch (e: Throwable) { - if (thrown == null) thrown = e - } - - closed = true - - if (thrown != null) throw thrown -} - -internal inline fun RealSink.commonToString() = "buffer($sink)" diff --git a/core/common/src/internal/-RealBufferedSource.kt b/core/common/src/internal/-RealBufferedSource.kt deleted file mode 100644 index f533fa8b8..000000000 --- a/core/common/src/internal/-RealBufferedSource.kt +++ /dev/null @@ -1,157 +0,0 @@ -/* - * Copyright 2017-2023 JetBrains s.r.o. and respective authors and developers. - * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENCE file. - */ - -/* - * Copyright (C) 2019 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. - */ - -// TODO move to RealBufferedSource class: https://youtrack.jetbrains.com/issue/KT-20427 - -@file:Suppress("NOTHING_TO_INLINE") - -package kotlinx.io.internal - -import kotlinx.io.* - -@OptIn(DelicateIoApi::class) -internal inline fun RealSource.commonRead(sink: Buffer, byteCount: Long): Long { - require(byteCount >= 0L) { "byteCount < 0: $byteCount" } - check(!closed) { "closed" } - - if (buffer.size == 0L) { - val read = source.read(buffer, Segment.SIZE.toLong()) - if (read == -1L) return -1L - } - - val toRead = minOf(byteCount, buffer.size) - return buffer.read(sink, toRead) -} - -@OptIn(DelicateIoApi::class) -internal inline fun RealSource.commonExhausted(): Boolean { - check(!closed) { "closed" } - return buffer.exhausted() && source.read(buffer, Segment.SIZE.toLong()) == -1L -} - -internal inline fun RealSource.commonRequire(byteCount: Long) { - if (!request(byteCount)) throw EOFException() -} - -@OptIn(DelicateIoApi::class) -internal inline fun RealSource.commonRequest(byteCount: Long): Boolean { - require(byteCount >= 0L) { "byteCount < 0: $byteCount" } - check(!closed) { "closed" } - while (buffer.size < byteCount) { - if (source.read(buffer, Segment.SIZE.toLong()) == -1L) return false - } - return true -} - -@OptIn(DelicateIoApi::class) -internal inline fun RealSource.commonReadByte(): Byte { - require(1) - return buffer.readByte() -} - -@OptIn(DelicateIoApi::class) -internal inline fun RealSource.commonRead(sink: ByteArray, offset: Int, byteCount: Int): Int { - checkOffsetAndCount(sink.size.toLong(), offset.toLong(), byteCount.toLong()) - - if (buffer.size == 0L) { - val read = source.read(buffer, Segment.SIZE.toLong()) - if (read == -1L) return -1 - } - - val toRead = minOf(byteCount, buffer.size).toInt() - return buffer.read(sink, offset, toRead) -} - -@OptIn(DelicateIoApi::class) -internal inline fun RealSource.commonReadFully(sink: RawSink, byteCount: Long) { - try { - require(byteCount) - } catch (e: EOFException) { - // The underlying source is exhausted. Copy the bytes we got before rethrowing. - sink.write(buffer, buffer.size) - throw e - } - - buffer.readFully(sink, byteCount) -} - -@OptIn(DelicateIoApi::class) -internal inline fun RealSource.commonReadAll(sink: RawSink): Long { - var totalBytesWritten: Long = 0 - while (source.read(buffer, Segment.SIZE.toLong()) != -1L) { - val emitByteCount = buffer.completeSegmentByteCount() - if (emitByteCount > 0L) { - totalBytesWritten += emitByteCount - sink.write(buffer, emitByteCount) - } - } - if (buffer.size > 0L) { - totalBytesWritten += buffer.size - sink.write(buffer, buffer.size) - } - return totalBytesWritten -} - -@OptIn(DelicateIoApi::class) -internal inline fun RealSource.commonReadShort(): Short { - require(2) - return buffer.readShort() -} - -@OptIn(DelicateIoApi::class) -internal inline fun RealSource.commonReadInt(): Int { - require(4) - return buffer.readInt() -} - -@OptIn(DelicateIoApi::class) -internal inline fun RealSource.commonReadLong(): Long { - require(8) - return buffer.readLong() -} - -@OptIn(DelicateIoApi::class) -internal inline fun RealSource.commonSkip(byteCount: Long) { - var remainingByteCount = byteCount - check(!closed) { "closed" } - while (remainingByteCount > 0) { - if (buffer.size == 0L && source.read(buffer, Segment.SIZE.toLong()) == -1L) { - throw EOFException() - } - val toSkip = minOf(remainingByteCount, buffer.size) - buffer.skip(toSkip) - remainingByteCount -= toSkip - } -} - -internal inline fun RealSource.commonPeek(): Source { - return PeekSource(this).buffer() -} - -@OptIn(DelicateIoApi::class) -internal inline fun RealSource.commonClose() { - if (closed) return - closed = true - source.close() - buffer.clear() -} - -internal inline fun RealSource.commonToString() = "buffer($source)" diff --git a/core/common/test/Utf8Test.kt b/core/common/test/Utf8Test.kt index 99de2305d..f92da04c8 100644 --- a/core/common/test/Utf8Test.kt +++ b/core/common/test/Utf8Test.kt @@ -299,18 +299,18 @@ class Utf8Test { } private fun Buffer.assertCodePointEncoded(expectedHex: String, codePoint: Int) { - buffer.writeUtf8CodePoint(codePoint) - assertArrayEquals(expectedHex.decodeHex(), buffer.readByteArray()) + writeUtf8CodePoint(codePoint) + assertArrayEquals(expectedHex.decodeHex(), readByteArray()) } private fun Buffer.assertCodePointDecoded(expectedCodePoint: Int, hex: String) { - buffer.write(hex.decodeHex()) - assertEquals(expectedCodePoint, buffer.readUtf8CodePoint()) + write(hex.decodeHex()) + assertEquals(expectedCodePoint, readUtf8CodePoint()) } private fun Buffer.assertUtf8StringEncoded(expectedHex: String, string: String) { - buffer.writeUtf8(string) - assertArrayEquals(expectedHex.decodeHex(), buffer.readByteArray()) + writeUtf8(string) + assertArrayEquals(expectedHex.decodeHex(), readByteArray()) } private fun assertStringEncoded(hex: String, string: String) { diff --git a/core/jvm/src/Buffer.kt b/core/jvm/src/BufferExtJvm.kt similarity index 66% rename from core/jvm/src/Buffer.kt rename to core/jvm/src/BufferExtJvm.kt index a1316ad76..c9fdda22e 100644 --- a/core/jvm/src/Buffer.kt +++ b/core/jvm/src/BufferExtJvm.kt @@ -20,7 +20,6 @@ */ package kotlinx.io -import kotlinx.io.internal.* import java.io.EOFException import java.io.IOException import java.io.InputStream @@ -28,105 +27,6 @@ import java.io.OutputStream import java.nio.ByteBuffer import java.nio.channels.ByteChannel -public actual class Buffer : Source, Sink, Cloneable { - @JvmField internal actual var head: Segment? = null - - public actual var size: Long = 0L - internal set - - actual override val buffer: Buffer get() = this - - actual override fun emitCompleteSegments(): Unit = Unit // Nowhere to emit to! - - actual override fun emit(): Unit = Unit // Nowhere to emit to! - - override fun exhausted(): Boolean = size == 0L - - override fun require(byteCount: Long) { - if (size < byteCount) throw EOFException() - } - - override fun request(byteCount: Long): Boolean = size >= byteCount - - override fun peek(): Source { - return PeekSource(this).buffer() - } - - public actual fun copyTo( - out: Buffer, - offset: Long, - byteCount: Long - ): Unit = commonCopyTo(out, offset, byteCount) - - public actual fun completeSegmentByteCount(): Long = commonCompleteSegmentByteCount() - - override fun readByte(): Byte = commonReadByte() - - public actual operator fun get(pos: Long): Byte = commonGet(pos) - - override fun readShort(): Short = commonReadShort() - - override fun readInt(): Int = commonReadInt() - - override fun readLong(): Long = commonReadLong() - - override fun readFully(sink: RawSink, byteCount: Long): Unit = commonReadFully(sink, byteCount) - - override fun readAll(sink: RawSink): Long = commonReadAll(sink) - - override fun read(sink: ByteArray, offset: Int, byteCount: Int): Int = - commonRead(sink, offset, byteCount) - - public actual fun clear(): Unit = commonClear() - - public actual override fun skip(byteCount: Long): Unit = commonSkip(byteCount) - - actual override fun write( - source: ByteArray, - offset: Int, - byteCount: Int - ): Unit = commonWrite(source, offset, byteCount) - - override fun writeAll(source: RawSource): Long = commonWriteAll(source) - - actual override fun write(source: RawSource, byteCount: Long): Unit = - commonWrite(source, byteCount) - - actual override fun writeByte(byte: Byte): Unit = commonWriteByte(byte) - - actual override fun writeShort(short: Short): Unit = commonWriteShort(short) - - actual override fun writeInt(int: Int): Unit = commonWriteInt(int) - - actual override fun writeLong(long: Long): Unit = commonWriteLong(long) - - internal actual fun writableSegment(minimumCapacity: Int): Segment = - commonWritableSegment(minimumCapacity) - - override fun write(source: Buffer, byteCount: Long): Unit = commonWrite(source, byteCount) - - override fun read(sink: Buffer, byteCount: Long): Long = commonRead(sink, byteCount) - - public actual override fun flush(): Unit = Unit - - actual override fun close(): Unit = Unit - - /** - * Returns a human-readable string that describes the contents of this buffer. Typically, this - * is a string like `[text=Hello]` or `[hex=0000ffff]`. - */ - override fun toString(): String = commonString() - - public actual fun copy(): Buffer = commonCopy() - - /** - * Returns a deep copy of this buffer. - * - * This method is equivalent to [copy]. - */ - public override fun clone(): Buffer = copy() -} - /** * Read and exhaust bytes from [input] into this buffer. Stops reading data on [input] exhaustion. * diff --git a/core/jvm/src/RealSink.kt b/core/jvm/src/RealSink.kt deleted file mode 100644 index fabccd99a..000000000 --- a/core/jvm/src/RealSink.kt +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2017-2023 JetBrains s.r.o. and respective authors and developers. - * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENCE file. - */ - -/* - * Copyright (C) 2014 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 kotlinx.io - -import kotlinx.io.internal.* - -internal actual class RealSink actual constructor( - @JvmField actual val sink: RawSink -) : Sink { - @JvmField val bufferField = Buffer() - @JvmField actual var closed: Boolean = false - - @Suppress("OVERRIDE_BY_INLINE") // Prevent internal code from calling the getter. - override val buffer: Buffer - inline get() = bufferField - - override fun write(source: Buffer, byteCount: Long) = commonWrite(source, byteCount) - - override fun write(source: ByteArray, offset: Int, byteCount: Int) = - commonWrite(source, offset, byteCount) - - override fun writeAll(source: RawSource) = commonWriteAll(source) - override fun write(source: RawSource, byteCount: Long) = commonWrite(source, byteCount) - override fun writeByte(byte: Byte) = commonWriteByte(byte) - override fun writeShort(short: Short) = commonWriteShort(short) - override fun writeInt(int: Int) = commonWriteInt(int) - override fun writeLong(long: Long) = commonWriteLong(long) - override fun emitCompleteSegments() = commonEmitCompleteSegments() - override fun emit() = commonEmit() - - override fun flush() = commonFlush() - - override fun close() = commonClose() - - override fun toString() = commonToString() -} diff --git a/core/jvm/src/RealSource.kt b/core/jvm/src/RealSource.kt deleted file mode 100644 index 569dd9be5..000000000 --- a/core/jvm/src/RealSource.kt +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2017-2023 JetBrains s.r.o. and respective authors and developers. - * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENCE file. - */ - -/* - * Copyright (C) 2014 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 kotlinx.io - -import kotlinx.io.internal.* - -internal actual class RealSource actual constructor( - @JvmField actual val source: RawSource -) : Source { - @JvmField val bufferField = Buffer() - @JvmField actual var closed: Boolean = false - - @Suppress("OVERRIDE_BY_INLINE") // Prevent internal code from calling the getter. - override val buffer: Buffer - inline get() = bufferField - - override fun read(sink: Buffer, byteCount: Long): Long = commonRead(sink, byteCount) - override fun exhausted(): Boolean = commonExhausted() - override fun require(byteCount: Long): Unit = commonRequire(byteCount) - override fun request(byteCount: Long): Boolean = commonRequest(byteCount) - override fun readByte(): Byte = commonReadByte() - override fun read(sink: ByteArray, offset: Int, byteCount: Int): Int = - commonRead(sink, offset, byteCount) - - override fun readFully(sink: RawSink, byteCount: Long): Unit = commonReadFully(sink, byteCount) - override fun readAll(sink: RawSink): Long = commonReadAll(sink) - - override fun readShort(): Short = commonReadShort() - override fun readInt(): Int = commonReadInt() - override fun readLong(): Long = commonReadLong() - override fun skip(byteCount: Long): Unit = commonSkip(byteCount) - - override fun peek(): Source = commonPeek() - - override fun close(): Unit = commonClose() - - override fun toString(): String = commonToString() -} diff --git a/core/native/src/Buffer.kt b/core/native/src/Buffer.kt deleted file mode 100644 index 00b206b1a..000000000 --- a/core/native/src/Buffer.kt +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright 2017-2023 JetBrains s.r.o. and respective authors and developers. - * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENCE file. - */ - -/* - * Copyright (C) 2019 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 kotlinx.io - -import kotlinx.io.internal.* - -public actual class Buffer : Source, Sink { - internal actual var head: Segment? = null - - public actual var size: Long = 0L - internal set - - actual override val buffer: Buffer get() = this - - actual override fun emitCompleteSegments(): Unit = Unit // Nowhere to emit to! - - actual override fun emit(): Unit = Unit // Nowhere to emit to! - - override fun exhausted(): Boolean = size == 0L - - override fun require(byteCount: Long) { - if (size < byteCount) throw EOFException(null) - } - - override fun request(byteCount: Long): Boolean = size >= byteCount - - override fun peek(): Source = PeekSource(this).buffer() - - public actual fun copyTo( - out: Buffer, - offset: Long, - byteCount: Long - ): Unit = commonCopyTo(out, offset, byteCount) - - public actual operator fun get(pos: Long): Byte = commonGet(pos) - - public actual fun completeSegmentByteCount(): Long = commonCompleteSegmentByteCount() - - override fun readByte(): Byte = commonReadByte() - - override fun readShort(): Short = commonReadShort() - - override fun readInt(): Int = commonReadInt() - - override fun readLong(): Long = commonReadLong() - - override fun readFully(sink: RawSink, byteCount: Long): Unit = commonReadFully(sink, byteCount) - - override fun readAll(sink: RawSink): Long = commonReadAll(sink) - - override fun read(sink: ByteArray, offset: Int, byteCount: Int): Int = - commonRead(sink, offset, byteCount) - - public actual fun clear(): Unit = commonClear() - - actual override fun skip(byteCount: Long): Unit = commonSkip(byteCount) - - internal actual fun writableSegment(minimumCapacity: Int): Segment = - commonWritableSegment(minimumCapacity) - - actual override fun write(source: ByteArray, offset: Int, byteCount: Int): Unit = - commonWrite(source, offset, byteCount) - - override fun writeAll(source: RawSource): Long = commonWriteAll(source) - - actual override fun write(source: RawSource, byteCount: Long): Unit = - commonWrite(source, byteCount) - - actual override fun writeByte(byte: Byte): Unit = commonWriteByte(byte) - - actual override fun writeShort(short: Short): Unit = commonWriteShort(short) - - actual override fun writeInt(int: Int): Unit = commonWriteInt(int) - - actual override fun writeLong(long: Long): Unit = commonWriteLong(long) - - override fun write(source: Buffer, byteCount: Long): Unit = commonWrite(source, byteCount) - - override fun read(sink: Buffer, byteCount: Long): Long = commonRead(sink, byteCount) - - actual override fun flush(): Unit = Unit - - actual override fun close(): Unit = Unit - - override fun toString(): String = commonString() - - public actual fun copy(): Buffer = commonCopy() -} diff --git a/core/native/src/RealSink.kt b/core/native/src/RealSink.kt deleted file mode 100644 index 9f105fad8..000000000 --- a/core/native/src/RealSink.kt +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2017-2023 JetBrains s.r.o. and respective authors and developers. - * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENCE file. - */ - -/* - * Copyright (C) 2019 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 kotlinx.io - -import kotlinx.io.internal.* - -internal actual class RealSink actual constructor( - actual val sink: RawSink -) : Sink { - actual var closed: Boolean = false - override val buffer = Buffer() - - override fun write(source: Buffer, byteCount: Long) = commonWrite(source, byteCount) - - override fun write(source: ByteArray, offset: Int, byteCount: Int) = - commonWrite(source, offset, byteCount) - - override fun writeAll(source: RawSource) = commonWriteAll(source) - override fun write(source: RawSource, byteCount: Long) = commonWrite(source, byteCount) - override fun writeByte(byte: Byte) = commonWriteByte(byte) - override fun writeShort(short: Short) = commonWriteShort(short) - override fun writeInt(int: Int) = commonWriteInt(int) - override fun writeLong(long: Long) = commonWriteLong(long) - override fun emitCompleteSegments() = commonEmitCompleteSegments() - override fun emit() = commonEmit() - override fun flush() = commonFlush() - override fun close() = commonClose() - override fun toString() = commonToString() -} diff --git a/core/native/src/RealSource.kt b/core/native/src/RealSource.kt deleted file mode 100644 index b8e49f632..000000000 --- a/core/native/src/RealSource.kt +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2017-2023 JetBrains s.r.o. and respective authors and developers. - * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENCE file. - */ - -/* - * Copyright (C) 2014 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 kotlinx.io - -import kotlinx.io.internal.* - -internal actual class RealSource actual constructor( - actual val source: RawSource -) : Source { - actual var closed: Boolean = false - override val buffer: Buffer = Buffer() - - override fun read(sink: Buffer, byteCount: Long): Long = commonRead(sink, byteCount) - override fun exhausted(): Boolean = commonExhausted() - override fun require(byteCount: Long): Unit = commonRequire(byteCount) - override fun request(byteCount: Long): Boolean = commonRequest(byteCount) - override fun readByte(): Byte = commonReadByte() - override fun read(sink: ByteArray, offset: Int, byteCount: Int): Int = - commonRead(sink, offset, byteCount) - - override fun readFully(sink: RawSink, byteCount: Long): Unit = commonReadFully(sink, byteCount) - override fun readAll(sink: RawSink): Long = commonReadAll(sink) - override fun readShort(): Short = commonReadShort() - override fun readInt(): Int = commonReadInt() - override fun readLong(): Long = commonReadLong() - override fun skip(byteCount: Long): Unit = commonSkip(byteCount) - - override fun peek(): Source = commonPeek() - override fun close(): Unit = commonClose() - override fun toString(): String = commonToString() -} From cfe3f40e2fddd263b827050ba8d621dfdc07100f Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Fri, 16 Jun 2023 15:09:01 +0200 Subject: [PATCH 49/83] Implement AutoClosable from stdlib instead of custom Closable interface --- core/api/kotlinx-io-core.api | 5 ++--- core/common/src/-CommonPlatform.kt | 14 +++++++------- core/common/src/Core.kt | 24 ------------------------ core/common/src/RawSink.kt | 3 ++- core/common/src/RawSource.kt | 3 ++- core/common/test/files/SmokeFileTest.kt | 11 +++++++++-- core/jvm/src/-JvmPlatform.kt | 2 -- core/jvm/src/RawSink.kt | 4 ++-- core/native/src/-NonJvmPlatform.kt | 4 ---- core/native/src/RawSink.kt | 3 ++- 10 files changed, 26 insertions(+), 47 deletions(-) diff --git a/core/api/kotlinx-io-core.api b/core/api/kotlinx-io-core.api index ba8d65b0c..c0fb40fc8 100644 --- a/core/api/kotlinx-io-core.api +++ b/core/api/kotlinx-io-core.api @@ -51,7 +51,6 @@ public final class kotlinx/io/CoreKt { public static final fun blackholeSink ()Lkotlinx/io/RawSink; public static final fun buffer (Lkotlinx/io/RawSink;)Lkotlinx/io/Sink; public static final fun buffer (Lkotlinx/io/RawSource;)Lkotlinx/io/Source; - public static final fun use (Ljava/io/Closeable;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; } public abstract interface annotation class kotlinx/io/DelicateIoApi : java/lang/annotation/Annotation { @@ -69,13 +68,13 @@ public final class kotlinx/io/JvmCoreKt { public static final fun source (Ljava/nio/file/Path;[Ljava/nio/file/OpenOption;)Lkotlinx/io/RawSource; } -public abstract interface class kotlinx/io/RawSink : java/io/Closeable, java/io/Flushable { +public abstract interface class kotlinx/io/RawSink : java/io/Flushable, java/lang/AutoCloseable { public abstract fun close ()V public abstract fun flush ()V public abstract fun write (Lkotlinx/io/Buffer;J)V } -public abstract interface class kotlinx/io/RawSource : java/io/Closeable { +public abstract interface class kotlinx/io/RawSource : java/lang/AutoCloseable { public abstract fun close ()V public abstract fun read (Lkotlinx/io/Buffer;J)J } diff --git a/core/common/src/-CommonPlatform.kt b/core/common/src/-CommonPlatform.kt index c1052b42c..9cff739ec 100644 --- a/core/common/src/-CommonPlatform.kt +++ b/core/common/src/-CommonPlatform.kt @@ -29,10 +29,10 @@ public expect open class IOException(message: String?, cause: Throwable?) : Exce public expect open class EOFException(message: String? = null) : IOException -public expect interface Closeable { - /** - * Closes this object and releases the resources it holds. It is an error to use an object after - * it has been closed. It is safe to close an object more than once. - */ - public fun close() -} + +// There is no actual AutoCloseable on JVM (https://youtrack.jetbrains.com/issue/KT-55777), +// but on JVM we have to explicitly implement by RawSink and the compiler does not allow that. +// This is a workaround that should be removed as soon as stdlib will support AutoCloseable +// actual typealias on JVM. +@OptIn(ExperimentalStdlibApi::class) +internal typealias AutoCloseableAlias = AutoCloseable \ No newline at end of file diff --git a/core/common/src/Core.kt b/core/common/src/Core.kt index 3a9bd190e..6b44e9046 100644 --- a/core/common/src/Core.kt +++ b/core/common/src/Core.kt @@ -43,27 +43,3 @@ private class BlackholeSink : RawSink { override fun flush() {} override fun close() {} } - -/** - * Execute [block] then close this. This will be closed even if [block] throws. - */ -public inline fun T.use(block: (T) -> R): R { - var result: R? = null - var thrown: Throwable? = null - - try { - result = block(this) - } catch (t: Throwable) { - thrown = t - } - - try { - this?.close() - } catch (t: Throwable) { - if (thrown == null) thrown = t - else thrown.addSuppressed(t) - } - - if (thrown != null) throw thrown - return result!! -} diff --git a/core/common/src/RawSink.kt b/core/common/src/RawSink.kt index b21fc068f..2fff51a28 100644 --- a/core/common/src/RawSink.kt +++ b/core/common/src/RawSink.kt @@ -31,7 +31,8 @@ package kotlinx.io * is both more efficient and more convenient. Use [buffer] to wrap any raw sink with a buffer. * */ -public expect interface RawSink : Closeable { +@OptIn(ExperimentalStdlibApi::class) +public expect interface RawSink : AutoCloseableAlias { /** * Removes [byteCount] bytes from [source] and appends them to this sink. * diff --git a/core/common/src/RawSource.kt b/core/common/src/RawSource.kt index 22ff3ea90..45e0bbdb2 100644 --- a/core/common/src/RawSource.kt +++ b/core/common/src/RawSource.kt @@ -30,7 +30,8 @@ package kotlinx.io * Most applications shouldn't operate on a raw source directly, but rather on a buffered [Source] which * is both more efficient and more convenient. Use [buffer] to wrap any raw source with a buffer. */ -public interface RawSource : Closeable { +@OptIn(ExperimentalStdlibApi::class) +public interface RawSource : AutoCloseableAlias { /** * Removes at least 1, and up to [byteCount] bytes from this source and appends them to [sink]. * Returns the number of bytes read, or -1 if this source is exhausted. diff --git a/core/common/test/files/SmokeFileTest.kt b/core/common/test/files/SmokeFileTest.kt index 7b3e03e7d..110033fc8 100644 --- a/core/common/test/files/SmokeFileTest.kt +++ b/core/common/test/files/SmokeFileTest.kt @@ -5,8 +5,14 @@ package kotlinx.io.files -import kotlinx.io.* -import kotlin.test.* +import kotlinx.io.createTempFile +import kotlinx.io.deleteFile +import kotlinx.io.readUtf8Line +import kotlinx.io.writeUtf8 +import kotlin.test.AfterTest +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertEquals class SmokeFileTest { private var tempFile: String? = null @@ -21,6 +27,7 @@ class SmokeFileTest { deleteFile(tempFile!!) } + @OptIn(ExperimentalStdlibApi::class) @Test fun testBasicFile() { val path = Path(tempFile!!) diff --git a/core/jvm/src/-JvmPlatform.kt b/core/jvm/src/-JvmPlatform.kt index ba398de61..68f383172 100644 --- a/core/jvm/src/-JvmPlatform.kt +++ b/core/jvm/src/-JvmPlatform.kt @@ -26,5 +26,3 @@ internal actual fun String.asUtf8ToByteArray(): ByteArray = toByteArray(Charsets public actual typealias IOException = java.io.IOException public actual typealias EOFException = java.io.EOFException - -public actual typealias Closeable = java.io.Closeable diff --git a/core/jvm/src/RawSink.kt b/core/jvm/src/RawSink.kt index 0ac69a121..278090beb 100644 --- a/core/jvm/src/RawSink.kt +++ b/core/jvm/src/RawSink.kt @@ -20,10 +20,10 @@ */ package kotlinx.io -import java.io.Closeable import java.io.Flushable -public actual interface RawSink : Closeable, Flushable { +@OptIn(ExperimentalStdlibApi::class) +public actual interface RawSink : AutoCloseableAlias, Flushable { public actual fun write(source: Buffer, byteCount: Long) actual override fun flush() diff --git a/core/native/src/-NonJvmPlatform.kt b/core/native/src/-NonJvmPlatform.kt index c5c9ce398..a4762a276 100644 --- a/core/native/src/-NonJvmPlatform.kt +++ b/core/native/src/-NonJvmPlatform.kt @@ -32,7 +32,3 @@ public actual open class IOException actual constructor( } public actual open class EOFException actual constructor(message: String?) : IOException(message) - -public actual interface Closeable { - public actual fun close() -} diff --git a/core/native/src/RawSink.kt b/core/native/src/RawSink.kt index d4ed38c5c..6f9287f1d 100644 --- a/core/native/src/RawSink.kt +++ b/core/native/src/RawSink.kt @@ -20,7 +20,8 @@ */ package kotlinx.io -public actual interface RawSink : Closeable { +@OptIn(ExperimentalStdlibApi::class) +public actual interface RawSink : AutoCloseableAlias { public actual fun write(source: Buffer, byteCount: Long) public actual fun flush() From b404e721c7c50822a7d143f93d618ee83b14bea1 Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Fri, 16 Jun 2023 15:15:04 +0200 Subject: [PATCH 50/83] Simplified receiver decl for Sink's extensions --- core/common/src/SinkExt.kt | 24 ++++++++++++------------ core/common/src/Utf8.kt | 4 ++-- core/jvm/src/Sink.kt | 2 +- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/core/common/src/SinkExt.kt b/core/common/src/SinkExt.kt index 4dc52831f..9cf098f28 100644 --- a/core/common/src/SinkExt.kt +++ b/core/common/src/SinkExt.kt @@ -14,7 +14,7 @@ internal val HEX_DIGIT_BYTES = "0123456789abcdef".asUtf8ToByteArray() * * @throws IllegalStateException when the sink is closed. */ -public fun T.writeShortLe(short: Short) { +public fun Sink.writeShortLe(short: Short) { this.writeShort(short.reverseBytes()) } @@ -25,7 +25,7 @@ public fun T.writeShortLe(short: Short) { * * @throws IllegalStateException when the sink is closed. */ -public fun T.writeIntLe(int: Int) { +public fun Sink.writeIntLe(int: Int) { this.writeInt(int.reverseBytes()) } @@ -36,7 +36,7 @@ public fun T.writeIntLe(int: Int) { * * @throws IllegalStateException when the sink is closed. */ -public fun T.writeLongLe(long: Long) { +public fun Sink.writeLongLe(long: Long) { this.writeLong(long.reverseBytes()) } @@ -50,7 +50,7 @@ public fun T.writeLongLe(long: Long) { * @throws IllegalStateException when the sink is closed. */ @OptIn(DelicateIoApi::class) -public fun T.writeDecimalLong(long: Long) { +public fun Sink.writeDecimalLong(long: Long) { var v = long if (v == 0L) { // Both a shortcut and required since the following code can't handle zero. @@ -128,7 +128,7 @@ public fun T.writeDecimalLong(long: Long) { * @throws IllegalStateException when the sink is closed. */ @OptIn(DelicateIoApi::class) -public fun T.writeHexadecimalUnsignedLong(long: Long) { +public fun Sink.writeHexadecimalUnsignedLong(long: Long) { var v = long if (v == 0L) { // Both a shortcut and required since the following code can't handle zero. @@ -179,7 +179,7 @@ public fun T.writeHexadecimalUnsignedLong(long: Long) { * * @throws IllegalStateException when the sink is closed. */ -public fun T.writeByte(byte: UByte) { +public fun Sink.writeByte(byte: UByte) { writeByte(byte.toByte()) } @@ -190,7 +190,7 @@ public fun T.writeByte(byte: UByte) { * * @throws IllegalStateException when the sink is closed. */ -public fun T.writeShort(short: UShort) { +public fun Sink.writeShort(short: UShort) { writeShort(short.toShort()) } @@ -201,7 +201,7 @@ public fun T.writeShort(short: UShort) { * * @throws IllegalStateException when the sink is closed. */ -public fun T.writeInt(int: UInt) { +public fun Sink.writeInt(int: UInt) { writeInt(int.toInt()) } @@ -212,7 +212,7 @@ public fun T.writeInt(int: UInt) { * * @throws IllegalStateException when the sink is closed. */ -public fun T.writeLong(long: ULong) { +public fun Sink.writeLong(long: ULong) { writeLong(long.toLong()) } @@ -223,7 +223,7 @@ public fun T.writeLong(long: ULong) { * * @throws IllegalStateException when the sink is closed. */ -public fun T.writeShortLe(short: UShort) { +public fun Sink.writeShortLe(short: UShort) { writeShortLe(short.toShort()) } @@ -234,7 +234,7 @@ public fun T.writeShortLe(short: UShort) { * * @throws IllegalStateException when the sink is closed. */ -public fun T.writeIntLe(int: UInt) { +public fun Sink.writeIntLe(int: UInt) { writeIntLe(int.toInt()) } @@ -245,6 +245,6 @@ public fun T.writeIntLe(int: UInt) { * * @throws IllegalStateException when the sink is closed. */ -public fun T.writeLongLe(long: ULong) { +public fun Sink.writeLongLe(long: ULong) { writeLongLe(long.toLong()) } diff --git a/core/common/src/Utf8.kt b/core/common/src/Utf8.kt index 61a54c5f7..ee01ae3a7 100644 --- a/core/common/src/Utf8.kt +++ b/core/common/src/Utf8.kt @@ -127,7 +127,7 @@ public fun String.utf8Size(beginIndex: Int = 0, endIndex: Int = length): Long { * @throws IllegalStateException when the sink is closed. */ @OptIn(DelicateIoApi::class) -public fun T.writeUtf8CodePoint(codePoint: Int) { +public fun Sink.writeUtf8CodePoint(codePoint: Int) { buffer.commonWriteUtf8CodePoint(codePoint) emitCompleteSegments() } @@ -144,7 +144,7 @@ public fun T.writeUtf8CodePoint(codePoint: Int) { * @throws IllegalStateException when the sink is closed. */ @OptIn(DelicateIoApi::class) -public fun T.writeUtf8(string: String, beginIndex: Int = 0, endIndex: Int = string.length) { +public fun Sink.writeUtf8(string: String, beginIndex: Int = 0, endIndex: Int = string.length) { buffer.commonWriteUtf8(string, beginIndex, endIndex) emitCompleteSegments() } diff --git a/core/jvm/src/Sink.kt b/core/jvm/src/Sink.kt index fa4b61f0e..1c8e11de6 100644 --- a/core/jvm/src/Sink.kt +++ b/core/jvm/src/Sink.kt @@ -62,7 +62,7 @@ public actual sealed interface Sink : RawSink { * @throws IllegalArgumentException when [beginIndex] and [endIndex] correspond to a range out of [string] bounds. * @throws IllegalStateException when the sink is closed. */ -public fun T.writeString(string: String, charset: Charset, beginIndex: Int = 0, endIndex: Int = string.length) { +public fun Sink.writeString(string: String, charset: Charset, beginIndex: Int = 0, endIndex: Int = string.length) { require(beginIndex >= 0) { "beginIndex < 0: $beginIndex" } require(endIndex >= beginIndex) { "endIndex < beginIndex: $endIndex < $beginIndex" } require(endIndex <= string.length) { "endIndex > string.length: $endIndex > ${string.length}" } From c6a93950f12b04d0edaf60d7f0265d44e1e2a168 Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Fri, 16 Jun 2023 15:23:26 +0200 Subject: [PATCH 51/83] Make Source and Sink regular interfaces, not expects --- core/api/kotlinx-io-core.api | 32 +++++++------- core/common/src/Sink.kt | 2 +- core/common/src/Source.kt | 2 +- core/common/test/AbstractSourceTest.kt | 4 ++ core/jvm/src/{Sink.kt => SinkExtJvm.kt} | 26 +---------- core/jvm/src/{Source.kt => SourceExtJvm.kt} | 32 ++------------ core/native/src/Sink.kt | 43 ------------------ core/native/src/Source.kt | 49 --------------------- 8 files changed, 28 insertions(+), 162 deletions(-) rename core/jvm/src/{Sink.kt => SinkExtJvm.kt} (88%) rename core/jvm/src/{Source.kt => SourceExtJvm.kt} (87%) delete mode 100644 core/native/src/Sink.kt delete mode 100644 core/native/src/Source.kt diff --git a/core/api/kotlinx-io-core.api b/core/api/kotlinx-io-core.api index c0fb40fc8..85a8be629 100644 --- a/core/api/kotlinx-io-core.api +++ b/core/api/kotlinx-io-core.api @@ -97,6 +97,14 @@ public final class kotlinx/io/Sink$DefaultImpls { public static synthetic fun write$default (Lkotlinx/io/Sink;[BIIILjava/lang/Object;)V } +public final class kotlinx/io/SinkExtJvmKt { + public static final fun channel (Lkotlinx/io/Sink;)Ljava/nio/channels/WritableByteChannel; + public static final fun outputStream (Lkotlinx/io/Sink;)Ljava/io/OutputStream; + public static final fun write (Lkotlinx/io/Sink;Ljava/nio/ByteBuffer;)I + public static final fun writeString (Lkotlinx/io/Sink;Ljava/lang/String;Ljava/nio/charset/Charset;II)V + public static synthetic fun writeString$default (Lkotlinx/io/Sink;Ljava/lang/String;Ljava/nio/charset/Charset;IIILjava/lang/Object;)V +} + public final class kotlinx/io/SinkExtKt { public static final fun writeByte-EK-6454 (Lkotlinx/io/Sink;B)V public static final fun writeDecimalLong (Lkotlinx/io/Sink;J)V @@ -112,14 +120,6 @@ public final class kotlinx/io/SinkExtKt { public static final fun writeShortLe-i8woANY (Lkotlinx/io/Sink;S)V } -public final class kotlinx/io/SinkKt { - public static final fun channel (Lkotlinx/io/Sink;)Ljava/nio/channels/WritableByteChannel; - public static final fun outputStream (Lkotlinx/io/Sink;)Ljava/io/OutputStream; - public static final fun write (Lkotlinx/io/Sink;Ljava/nio/ByteBuffer;)I - public static final fun writeString (Lkotlinx/io/Sink;Ljava/lang/String;Ljava/nio/charset/Charset;II)V - public static synthetic fun writeString$default (Lkotlinx/io/Sink;Ljava/lang/String;Ljava/nio/charset/Charset;IIILjava/lang/Object;)V -} - public abstract interface class kotlinx/io/Source : kotlinx/io/RawSource { public abstract fun exhausted ()Z public abstract fun getBuffer ()Lkotlinx/io/Buffer; @@ -140,6 +140,14 @@ public final class kotlinx/io/Source$DefaultImpls { public static synthetic fun read$default (Lkotlinx/io/Source;[BIIILjava/lang/Object;)I } +public final class kotlinx/io/SourceExtJvmKt { + public static final fun channel (Lkotlinx/io/Source;)Ljava/nio/channels/ReadableByteChannel; + public static final fun inputStream (Lkotlinx/io/Source;)Ljava/io/InputStream; + public static final fun read (Lkotlinx/io/Source;Ljava/nio/ByteBuffer;)I + public static final fun readString (Lkotlinx/io/Source;JLjava/nio/charset/Charset;)Ljava/lang/String; + public static final fun readString (Lkotlinx/io/Source;Ljava/nio/charset/Charset;)Ljava/lang/String; +} + public final class kotlinx/io/SourceExtKt { public static final fun indexOf (Lkotlinx/io/Source;BJJ)J public static synthetic fun indexOf$default (Lkotlinx/io/Source;BJJILjava/lang/Object;)J @@ -161,14 +169,6 @@ public final class kotlinx/io/SourceExtKt { public static final fun startsWith (Lkotlinx/io/Source;B)Z } -public final class kotlinx/io/SourceKt { - public static final fun channel (Lkotlinx/io/Source;)Ljava/nio/channels/ReadableByteChannel; - public static final fun inputStream (Lkotlinx/io/Source;)Ljava/io/InputStream; - public static final fun read (Lkotlinx/io/Source;Ljava/nio/ByteBuffer;)I - public static final fun readString (Lkotlinx/io/Source;JLjava/nio/charset/Charset;)Ljava/lang/String; - public static final fun readString (Lkotlinx/io/Source;Ljava/nio/charset/Charset;)Ljava/lang/String; -} - public final class kotlinx/io/Utf8Kt { public static final fun readUtf8 (Lkotlinx/io/Buffer;)Ljava/lang/String; public static final fun readUtf8 (Lkotlinx/io/Source;)Ljava/lang/String; diff --git a/core/common/src/Sink.kt b/core/common/src/Sink.kt index 1750fa44c..a91bed217 100644 --- a/core/common/src/Sink.kt +++ b/core/common/src/Sink.kt @@ -38,7 +38,7 @@ package kotlinx.io * to the upstream. * All write operations implicitly calls [emitCompleteSegments]. */ -public expect sealed interface Sink : RawSink { +public sealed interface Sink : RawSink { /** * This sink's internal buffer. It contains data written to the sink, but not yet flushed to the upstream. * diff --git a/core/common/src/Source.kt b/core/common/src/Source.kt index df5714ca2..e1c0d6e2e 100644 --- a/core/common/src/Source.kt +++ b/core/common/src/Source.kt @@ -35,7 +35,7 @@ package kotlinx.io * [Sink] also allows skipping unneeded prefix of data using [skip] and * provides look ahead into incoming data, buffering as much as necessary, using [peek]. */ -public expect sealed interface Source : RawSource { +public sealed interface Source : RawSource { /** * This source's internal buffer. It contains data fetched from the downstream, but not yet consumed by the upstream. * diff --git a/core/common/test/AbstractSourceTest.kt b/core/common/test/AbstractSourceTest.kt index 77a92f074..2699e55b5 100644 --- a/core/common/test/AbstractSourceTest.kt +++ b/core/common/test/AbstractSourceTest.kt @@ -260,6 +260,7 @@ abstract class AbstractBufferedSourceTest internal constructor( } } + @OptIn(DelicateIoApi::class) @Test fun readAll() { source.buffer.writeUtf8("abc") sink.writeUtf8("def") @@ -1015,6 +1016,7 @@ abstract class AbstractBufferedSourceTest internal constructor( assertEquals("Peek source is invalid because upstream source was used", e.message) } + @OptIn(DelicateIoApi::class) @Test fun peekSegmentThenInvalid() { sink.writeUtf8("abc") sink.writeUtf8("d".repeat(2 * Segment.SIZE)) @@ -1036,6 +1038,7 @@ abstract class AbstractBufferedSourceTest internal constructor( assertEquals("Peek source is invalid because upstream source was used", e.message) } + @OptIn(DelicateIoApi::class) @Test fun peekDoesntReadTooMuch() { // 6 bytes in source's buffer plus 3 bytes upstream. sink.writeUtf8("abcdef") @@ -1069,6 +1072,7 @@ abstract class AbstractBufferedSourceTest internal constructor( assertEquals("ghi", peek.readUtf8(3L)) } + @OptIn(DelicateIoApi::class) @Test fun factorySegmentSizes() { sink.writeUtf8("abc") sink.emit() diff --git a/core/jvm/src/Sink.kt b/core/jvm/src/SinkExtJvm.kt similarity index 88% rename from core/jvm/src/Sink.kt rename to core/jvm/src/SinkExtJvm.kt index 1c8e11de6..13f299cac 100644 --- a/core/jvm/src/Sink.kt +++ b/core/jvm/src/SinkExtJvm.kt @@ -26,30 +26,6 @@ import java.nio.ByteBuffer import java.nio.channels.WritableByteChannel import java.nio.charset.Charset -public actual sealed interface Sink : RawSink { - public actual val buffer: Buffer - - public actual fun write(source: ByteArray, offset: Int, byteCount: Int) - - public actual fun writeAll(source: RawSource): Long - - public actual fun write(source: RawSource, byteCount: Long) - - public actual fun writeByte(byte: Byte) - - public actual fun writeShort(short: Short) - - public actual fun writeInt(int: Int) - - public actual fun writeLong(long: Long) - - actual override fun flush() - - public actual fun emit() - - public actual fun emitCompleteSegments() -} - /** * Encodes substring of [string] starting at [beginIndex] and ending at [endIndex] using [charset] * and writes into this sink. @@ -74,6 +50,7 @@ public fun Sink.writeString(string: String, charset: Charset, beginIndex: Int = /** * Returns an output stream that writes to this sink. Closing the stream will also close this sink. */ +@OptIn(DelicateIoApi::class) public fun Sink.outputStream(): OutputStream { val isClosed: () -> Boolean = when (this) { is RealSink -> this::closed @@ -113,6 +90,7 @@ public fun Sink.outputStream(): OutputStream { * * @throws IllegalStateException when the sink is closed. */ +@OptIn(DelicateIoApi::class) public fun Sink.write(source: ByteBuffer): Int { val sizeBefore = buffer.size buffer.readFrom(source) diff --git a/core/jvm/src/Source.kt b/core/jvm/src/SourceExtJvm.kt similarity index 87% rename from core/jvm/src/Source.kt rename to core/jvm/src/SourceExtJvm.kt index a90062cc3..02d45189d 100644 --- a/core/jvm/src/Source.kt +++ b/core/jvm/src/SourceExtJvm.kt @@ -26,34 +26,6 @@ import java.nio.ByteBuffer import java.nio.channels.ReadableByteChannel import java.nio.charset.Charset -public actual sealed interface Source : RawSource { - public actual val buffer: Buffer - - public actual fun exhausted(): Boolean - - public actual fun require(byteCount: Long) - - public actual fun request(byteCount: Long): Boolean - - public actual fun readByte(): Byte - - public actual fun readShort(): Short - - public actual fun readInt(): Int - - public actual fun readLong(): Long - - public actual fun skip(byteCount: Long) - - public actual fun read(sink: ByteArray, offset: Int, byteCount: Int): Int - - public actual fun readFully(sink: RawSink, byteCount: Long) - - public actual fun readAll(sink: RawSink): Long - - public actual fun peek(): Source -} - private fun Buffer.readStringImpl(byteCount: Long, charset: Charset): String { require(byteCount >= 0 && byteCount <= Integer.MAX_VALUE) { "byteCount: $byteCount" } if (size < byteCount) throw EOFException() @@ -84,6 +56,7 @@ private fun Buffer.readStringImpl(byteCount: Long, charset: Charset): String { * * @throws IllegalStateException when the source is closed. */ +@OptIn(DelicateIoApi::class) public fun Source.readString(charset: Charset): String { var req = 1L while (request(req)) { @@ -102,6 +75,7 @@ public fun Source.readString(charset: Charset): String { * @throws IllegalStateException when the source is closed. * @throws IllegalArgumentException if [byteCount] is negative. */ +@OptIn(DelicateIoApi::class) public fun Source.readString(byteCount: Long, charset: Charset): String { require(byteCount) return buffer.readStringImpl(byteCount, charset) @@ -110,6 +84,7 @@ public fun Source.readString(byteCount: Long, charset: Charset): String { /** * Returns an input stream that reads from this source. Closing the stream will also close this source. */ +@OptIn(DelicateIoApi::class) public fun Source.inputStream(): InputStream { val isClosed: () -> Boolean = when (this) { is RealSource -> this::closed @@ -150,6 +125,7 @@ public fun Source.inputStream(): InputStream { * * @throws IllegalStateException when the source is closed. */ +@OptIn(DelicateIoApi::class) public fun Source.read(sink: ByteBuffer): Int { if (buffer.size == 0L) { request(Segment.SIZE.toLong()) diff --git a/core/native/src/Sink.kt b/core/native/src/Sink.kt deleted file mode 100644 index 763a90ba3..000000000 --- a/core/native/src/Sink.kt +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2017-2023 JetBrains s.r.o. and respective authors and developers. - * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENCE file. - */ - -/* - * Copyright (C) 2019 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 kotlinx.io - -public actual sealed interface Sink : RawSink { - public actual val buffer: Buffer - - public actual fun write(source: ByteArray, offset: Int, byteCount: Int) - - public actual fun writeAll(source: RawSource): Long - - public actual fun write(source: RawSource, byteCount: Long) - - public actual fun writeByte(byte: Byte) - - public actual fun writeShort(short: Short) - - public actual fun writeInt(int: Int) - - public actual fun writeLong(long: Long) - - public actual fun emit() - - public actual fun emitCompleteSegments() -} diff --git a/core/native/src/Source.kt b/core/native/src/Source.kt deleted file mode 100644 index 8ed33177b..000000000 --- a/core/native/src/Source.kt +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2017-2023 JetBrains s.r.o. and respective authors and developers. - * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENCE file. - */ - -/* - * Copyright (C) 2019 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 kotlinx.io - -public actual sealed interface Source : RawSource { - public actual val buffer: Buffer - - public actual fun exhausted(): Boolean - - public actual fun require(byteCount: Long) - - public actual fun request(byteCount: Long): Boolean - - public actual fun readByte(): Byte - - public actual fun readShort(): Short - - public actual fun readInt(): Int - - public actual fun readLong(): Long - - public actual fun skip(byteCount: Long) - - public actual fun read(sink: ByteArray, offset: Int, byteCount: Int): Int - - public actual fun readFully(sink: RawSink, byteCount: Long) - - public actual fun readAll(sink: RawSink): Long - - public actual fun peek(): Source -} From 29845d611f2a94ffa58a46b021edfcc77f64afe8 Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Fri, 16 Jun 2023 15:27:43 +0200 Subject: [PATCH 52/83] Update Buffer::toString doc --- core/common/src/Buffer.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/common/src/Buffer.kt b/core/common/src/Buffer.kt index 12215b5fa..16598ce8b 100644 --- a/core/common/src/Buffer.kt +++ b/core/common/src/Buffer.kt @@ -583,10 +583,10 @@ public class Buffer : Source, Sink { override fun close(): Unit = Unit /** - * Returns a human-readable string that describes the contents of this buffer. Typically, this - * is a string like `[text=Hello]` or `[hex=0000ffff]`. - * - * TODO: update + * Returns a human-readable string that describes the contents of this buffer. For buffers containing + * few bytes, this is a string like `[text=Hello]` or `[hex=0000ffff]`. However, if the buffer is too large, + * a string will contain its size and only a prefix of data, like `[size=1024 hex=01234…]`. Thus, the string could not + * be used to compare buffers or verify buffer's content. */ override fun toString(): String { // TODO: optimize implementation From ef49ac5929d852f57e29bdc87392ca5e8f6ddca2 Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Fri, 16 Jun 2023 16:43:28 +0200 Subject: [PATCH 53/83] Make public API intended for internal use less accessible --- core/api/kotlinx-io-core.api | 8 ++- core/common/src/Annotations.kt | 41 ++++++++++++++ core/common/src/Buffer.kt | 8 +-- core/common/src/DelicateIoApi.kt | 15 ----- core/common/src/PeekSource.kt | 2 +- core/common/src/RealSink.kt | 29 ++++------ core/common/src/RealSource.kt | 2 +- core/common/src/Sink.kt | 27 +++++---- core/common/src/SinkExt.kt | 72 ++++++++++++++++-------- core/common/src/Source.kt | 2 +- core/common/src/SourceExt.kt | 20 ++++--- core/common/src/Utf8.kt | 30 +++------- core/common/test/AbstractSourceTest.kt | 8 +-- core/common/test/CommonRealSinkTest.kt | 4 +- core/common/test/CommonRealSourceTest.kt | 2 +- core/common/test/DelicateApiTest.kt | 41 ++++++++++++++ core/jvm/src/SinkExtJvm.kt | 10 ++-- core/jvm/src/SourceExtJvm.kt | 8 +-- 18 files changed, 205 insertions(+), 124 deletions(-) create mode 100644 core/common/src/Annotations.kt delete mode 100644 core/common/src/DelicateIoApi.kt create mode 100644 core/common/test/DelicateApiTest.kt diff --git a/core/api/kotlinx-io-core.api b/core/api/kotlinx-io-core.api index 85a8be629..a204e6ebd 100644 --- a/core/api/kotlinx-io-core.api +++ b/core/api/kotlinx-io-core.api @@ -6,12 +6,12 @@ public final class kotlinx/io/Buffer : kotlinx/io/Sink, kotlinx/io/Source { public final fun copyTo (Lkotlinx/io/Buffer;JJ)V public static synthetic fun copyTo$default (Lkotlinx/io/Buffer;Lkotlinx/io/Buffer;JJILjava/lang/Object;)V public fun emit ()V - public fun emitCompleteSegments ()V public fun exhausted ()Z public fun flush ()V public final fun get (J)B public fun getBuffer ()Lkotlinx/io/Buffer; public final fun getSize ()J + public fun hintEmit ()V public fun peek ()Lkotlinx/io/Source; public fun read (Lkotlinx/io/Buffer;J)J public fun read ([BII)I @@ -56,6 +56,9 @@ public final class kotlinx/io/CoreKt { public abstract interface annotation class kotlinx/io/DelicateIoApi : java/lang/annotation/Annotation { } +public abstract interface annotation class kotlinx/io/InternalIoApi : java/lang/annotation/Annotation { +} + public final class kotlinx/io/JvmCoreKt { public static final fun sink (Ljava/io/File;Z)Lkotlinx/io/RawSink; public static final fun sink (Ljava/io/OutputStream;)Lkotlinx/io/RawSink; @@ -81,9 +84,9 @@ public abstract interface class kotlinx/io/RawSource : java/lang/AutoCloseable { public abstract interface class kotlinx/io/Sink : kotlinx/io/RawSink { public abstract fun emit ()V - public abstract fun emitCompleteSegments ()V public abstract fun flush ()V public abstract fun getBuffer ()Lkotlinx/io/Buffer; + public abstract fun hintEmit ()V public abstract fun write (Lkotlinx/io/RawSource;J)V public abstract fun write ([BII)V public abstract fun writeAll (Lkotlinx/io/RawSource;)J @@ -118,6 +121,7 @@ public final class kotlinx/io/SinkExtKt { public static final fun writeShort-i8woANY (Lkotlinx/io/Sink;S)V public static final fun writeShortLe (Lkotlinx/io/Sink;S)V public static final fun writeShortLe-i8woANY (Lkotlinx/io/Sink;S)V + public static final fun writeToInternalBuffer (Lkotlinx/io/Sink;Lkotlin/jvm/functions/Function1;)V } public abstract interface class kotlinx/io/Source : kotlinx/io/RawSource { diff --git a/core/common/src/Annotations.kt b/core/common/src/Annotations.kt new file mode 100644 index 000000000..c4fb69992 --- /dev/null +++ b/core/common/src/Annotations.kt @@ -0,0 +1,41 @@ +/* + * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. + */ + +package kotlinx.io + +/** + * Marks declarations that should be used carefully and in some cases, may cause data corruption or loss. + * + * Consider using other APIs instead when possible. + * Otherwise, make sure to read documentation describing a delicate API. + */ +@MustBeDocumented +@Retention(AnnotationRetention.BINARY) +@RequiresOptIn( + level = RequiresOptIn.Level.WARNING, + message = "This is a delicate API and its use requires care. " + + "Make sure you fully read and understand documentation of the declaration that is marked as a delicate API." +) +public annotation class DelicateIoApi + +/** + * Marks declarations that are **internal** in IO API. + * These declarations may change or be removed without notice, and not intended for public use. + * Incorrect of declarations marked as internal may cause data corruption or loss. + * + * Consider using other APIs instead when possible. + * Otherwise, make sure to read documentation describing + * an internal API. + */ +@MustBeDocumented +@Retention(AnnotationRetention.BINARY) +@RequiresOptIn( + level = RequiresOptIn.Level.ERROR, + message = "This is an internal API and its use requires care. " + + "It is subject to change or removal and is not intended for use outside the library." + + "Make sure you fully read and understand documentation of the declaration that " + + "is marked as an internal API." +) +public annotation class InternalIoApi \ No newline at end of file diff --git a/core/common/src/Buffer.kt b/core/common/src/Buffer.kt index 16598ce8b..099946802 100644 --- a/core/common/src/Buffer.kt +++ b/core/common/src/Buffer.kt @@ -38,7 +38,7 @@ import kotlin.jvm.JvmField * To reduce allocations and speed up the buffer's extension, it may use data segments pooling. * * [Buffer] implements both [Source] and [Sink] and could be used as a source or a sink, - * but unlike regular sinks and sources its [close], [flush], [emit], [emitCompleteSegments] + * but unlike regular sinks and sources its [close], [flush], [emit], [hintEmit] * does not affect buffer's state and [exhausted] only indicates that a buffer is empty. */ public class Buffer : Source, Sink { @@ -53,7 +53,7 @@ public class Buffer : Source, Sink { /** * Returns the buffer itself. */ - @DelicateIoApi + @InternalIoApi override val buffer: Buffer = this override fun exhausted(): Boolean = size == 0L @@ -184,8 +184,8 @@ public class Buffer : Source, Sink { /** * This method does not affect the buffer's content as there is no upstream to write data to. */ - @DelicateIoApi - override fun emitCompleteSegments(): Unit = Unit + @InternalIoApi + override fun hintEmit(): Unit = Unit /** * This method does not affect the buffer's content as there is no upstream to write data to. diff --git a/core/common/src/DelicateIoApi.kt b/core/common/src/DelicateIoApi.kt deleted file mode 100644 index 9de4cf637..000000000 --- a/core/common/src/DelicateIoApi.kt +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. - * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. - */ - -package kotlinx.io - -@MustBeDocumented -@Retention(AnnotationRetention.BINARY) -@RequiresOptIn( - level = RequiresOptIn.Level.WARNING, - message = "This is a delicate API and its use requires care. " + - "Make sure you fully read and understand documentation of the declaration that is marked as a delicate API." -) -public annotation class DelicateIoApi diff --git a/core/common/src/PeekSource.kt b/core/common/src/PeekSource.kt index 61113f75b..9a9627b74 100644 --- a/core/common/src/PeekSource.kt +++ b/core/common/src/PeekSource.kt @@ -33,7 +33,7 @@ package kotlinx.io internal class PeekSource( private val upstream: Source ) : RawSource { - @OptIn(DelicateIoApi::class) + @OptIn(InternalIoApi::class) private val buffer = upstream.buffer private var expectedSegment = buffer.head private var expectedPos = buffer.head?.pos ?: -1 diff --git a/core/common/src/RealSink.kt b/core/common/src/RealSink.kt index 41c04384d..bd80214d8 100644 --- a/core/common/src/RealSink.kt +++ b/core/common/src/RealSink.kt @@ -23,6 +23,7 @@ package kotlinx.io import kotlin.jvm.JvmField +@OptIn(InternalIoApi::class) internal class RealSink( val sink: RawSink ) : Sink { @@ -34,35 +35,31 @@ internal class RealSink( override val buffer: Buffer get() = bufferField - @OptIn(DelicateIoApi::class) override fun write(source: Buffer, byteCount: Long) { require(byteCount >= 0) { "byteCount ($byteCount) should not be negative." } check(!closed) { "closed" } bufferField.write(source, byteCount) - emitCompleteSegments() + hintEmit() } - @OptIn(DelicateIoApi::class) override fun write(source: ByteArray, offset: Int, byteCount: Int) { checkOffsetAndCount(source.size.toLong(), offset.toLong(), byteCount.toLong()) check(!closed) { "closed" } bufferField.write(source, offset, byteCount) - emitCompleteSegments() + hintEmit() } - @OptIn(DelicateIoApi::class) override fun writeAll(source: RawSource): Long { var totalBytesRead = 0L while (true) { val readCount: Long = source.read(bufferField, Segment.SIZE.toLong()) if (readCount == -1L) break totalBytesRead += readCount - emitCompleteSegments() + hintEmit() } return totalBytesRead } - @OptIn(DelicateIoApi::class) override fun write(source: RawSource, byteCount: Long) { require(byteCount >= 0) { "byteCount ($byteCount) should not be negative." } var remainingByteCount = byteCount @@ -70,40 +67,36 @@ internal class RealSink( val read = source.read(bufferField, remainingByteCount) if (read == -1L) throw EOFException() remainingByteCount -= read - emitCompleteSegments() + hintEmit() } } - @OptIn(DelicateIoApi::class) override fun writeByte(byte: Byte) { check(!closed) { "closed" } bufferField.writeByte(byte) - emitCompleteSegments() + hintEmit() } - @OptIn(DelicateIoApi::class) override fun writeShort(short: Short) { check(!closed) { "closed" } bufferField.writeShort(short) - emitCompleteSegments() + hintEmit() } - @OptIn(DelicateIoApi::class) override fun writeInt(int: Int) { check(!closed) { "closed" } bufferField.writeInt(int) - emitCompleteSegments() + hintEmit() } - @OptIn(DelicateIoApi::class) override fun writeLong(long: Long) { check(!closed) { "closed" } bufferField.writeLong(long) - emitCompleteSegments() + hintEmit() } - @DelicateIoApi - override fun emitCompleteSegments() { + @InternalIoApi + override fun hintEmit() { check(!closed) { "closed" } val byteCount = bufferField.completeSegmentByteCount() if (byteCount > 0L) sink.write(bufferField, byteCount) diff --git a/core/common/src/RealSource.kt b/core/common/src/RealSource.kt index 3fb6adbf5..4d4464d5e 100644 --- a/core/common/src/RealSource.kt +++ b/core/common/src/RealSource.kt @@ -30,7 +30,7 @@ internal class RealSource( var closed: Boolean = false private val bufferField = Buffer() - @DelicateIoApi + @InternalIoApi override val buffer: Buffer get() = bufferField diff --git a/core/common/src/Sink.kt b/core/common/src/Sink.kt index a91bed217..d57b414d5 100644 --- a/core/common/src/Sink.kt +++ b/core/common/src/Sink.kt @@ -27,16 +27,17 @@ package kotlinx.io * [Sink] is the main `kotlinx-io` interface to write data in client's code, * any [RawSink] could be turned into [Sink] using [RawSink.buffer]. * - * Depending on a kind of upstream and the number of bytes written, buffering may improve the performance + * Depending on the kind of upstream and the number of bytes written, buffering may improve the performance * by hiding the latency of small writes. * - * Data stored inside the internal buffer could be sent to an upstream using [flush], [emit], or [emitCompleteSegments]: + * Data stored inside the internal buffer could be sent to an upstream using [flush], [emit], or [hintEmit]: * - [flush] writes the whole buffer to an upstream and then flushes the upstream. * - [emit] writes all data from the buffer into the upstream without flushing it. - * - [emitCompleteSegments] writes only a part of data from the buffer. + * - [hintEmit] hints the source that current write operation is now finished and a part of data from the buffer + * may be partially emitted into the upstream. * The latter is aimed to reduce memory footprint by keeping the buffer as small as possible without excessive writes * to the upstream. - * All write operations implicitly calls [emitCompleteSegments]. + * All write operations implicitly calls [hintEmit]. */ public sealed interface Sink : RawSink { /** @@ -47,8 +48,10 @@ public sealed interface Sink : RawSink { * - write data into separate [Buffer] instance and write that buffer into the sink and then flush the sink to * ensure that the upstream will receive complete data; * - implement [RawSink] and wrap an upstream sink into it to intercept data being written. + * + * If there is an actual need to write data directly into the buffer, consider using [Sink.writeToInternalBuffer] instead. */ - @DelicateIoApi + @InternalIoApi public val buffer: Buffer /** @@ -134,7 +137,7 @@ public sealed interface Sink : RawSink { override fun flush() /** - * Writes all buffered data to the underlying sink, if one exists. + * Writes all buffered data to the underlying sink if one exists. * The underlying sink will not be explicitly flushed. * * This method behaves like [flush], but has weaker guarantees. @@ -145,15 +148,19 @@ public sealed interface Sink : RawSink { public fun emit() /** - * Writes complete segments to the underlying sink, if one exists. + * Hints that the buffer may be *partially* emitted (see [emit]) to the underlying sink. * The underlying sink will not be explicitly flushed. + * There are no guarantees that this call will cause emit of buffered data as well as + * there are no guarantees how many bytes will be emitted. * - * Use this to limit the memory held in the buffer to a single segment. * Typically, application code will not need to call this: it is only necessary when * application code writes directly to this [buffer]. + * Use this to limit the memory held in the buffer. + * + * Consider using [Sink.writeToInternalBuffer] for writes into [buffer] followed by [hintEmit] call. * * @throws IllegalStateException when the sink is closed. */ - @DelicateIoApi - public fun emitCompleteSegments() + @InternalIoApi + public fun hintEmit() } diff --git a/core/common/src/SinkExt.kt b/core/common/src/SinkExt.kt index 9cf098f28..efc85672d 100644 --- a/core/common/src/SinkExt.kt +++ b/core/common/src/SinkExt.kt @@ -101,21 +101,22 @@ public fun Sink.writeDecimalLong(long: Long) { ++width } - val tail = buffer.writableSegment(width) - val data = tail.data - var pos = tail.limit + width // We write backwards from right to left. - while (v != 0L) { - val digit = (v % 10).toInt() - data[--pos] = HEX_DIGIT_BYTES[digit] - v /= 10 - } - if (negative) { - data[--pos] = '-'.code.toByte() - } + writeToInternalBuffer { buffer -> + val tail = buffer.writableSegment(width) + val data = tail.data + var pos = tail.limit + width // We write backwards from right to left. + while (v != 0L) { + val digit = (v % 10).toInt() + data[--pos] = HEX_DIGIT_BYTES[digit] + v /= 10 + } + if (negative) { + data[--pos] = '-'.code.toByte() + } - tail.limit += width - buffer.size += width.toLong() - emitCompleteSegments() + tail.limit += width + buffer.size += width.toLong() + } } /** @@ -158,18 +159,19 @@ public fun Sink.writeHexadecimalUnsignedLong(long: Long) { // Round up to the nearest full byte val width = ((x + 3) / 4).toInt() - val tail = buffer.writableSegment(width) - val data = tail.data - var pos = tail.limit + width - 1 - val start = tail.limit - while (pos >= start) { - data[pos] = HEX_DIGIT_BYTES[(v and 0xF).toInt()] - v = v ushr 4 - pos-- + writeToInternalBuffer { buffer -> + val tail = buffer.writableSegment(width) + val data = tail.data + var pos = tail.limit + width - 1 + val start = tail.limit + while (pos >= start) { + data[pos] = HEX_DIGIT_BYTES[(v and 0xF).toInt()] + v = v ushr 4 + pos-- + } + tail.limit += width + buffer.size += width.toLong() } - tail.limit += width - buffer.size += width.toLong() - emitCompleteSegments() } /** @@ -248,3 +250,23 @@ public fun Sink.writeIntLe(int: UInt) { public fun Sink.writeLongLe(long: ULong) { writeLongLe(long.toLong()) } + +/** + * Provides direct access to the sink's internal buffer and hints its emit before exit. + * + * The internal buffer is passed into [lambda], + * and it may be partially emitted to the underlying sink before returning from this method. + * + * Use this method with care as the data within the buffer is not yet emitted to the underlying sink + * and consumption of data from the buffer will cause its loss. + * + * @param lambda the callback accessing internal buffer. + * + * @throws IllegalStateException when the sink is closed. + */ +@DelicateIoApi +@OptIn(InternalIoApi::class) +public inline fun Sink.writeToInternalBuffer(lambda: (Buffer) -> Unit) { + lambda(this.buffer) + this.hintEmit() +} \ No newline at end of file diff --git a/core/common/src/Source.kt b/core/common/src/Source.kt index e1c0d6e2e..08ea250d0 100644 --- a/core/common/src/Source.kt +++ b/core/common/src/Source.kt @@ -44,7 +44,7 @@ public sealed interface Source : RawSource { * - use [peek] for lookahead into a source; * - implement [RawSource] and wrap a downstream source into it to intercept data being read. */ - @DelicateIoApi + @InternalIoApi public val buffer: Buffer /** diff --git a/core/common/src/SourceExt.kt b/core/common/src/SourceExt.kt index 4dda8cd8f..fe07b870d 100644 --- a/core/common/src/SourceExt.kt +++ b/core/common/src/SourceExt.kt @@ -50,7 +50,7 @@ internal const val OVERFLOW_DIGIT_START = Long.MIN_VALUE % 10L + 1 * @throws EOFException if the source is exhausted before a call of this method. * @throws IllegalStateException when the source is closed. */ -@OptIn(DelicateIoApi::class) +@OptIn(InternalIoApi::class) public fun Source.readDecimalLong(): Long { require(1) var b = readByte() @@ -115,7 +115,7 @@ public fun Source.readDecimalLong(): Long { * @throws EOFException if the source is exhausted before a call of this method. * @throws IllegalStateException when the source is closed. */ -@OptIn(DelicateIoApi::class) +@OptIn(InternalIoApi::class) public fun Source.readHexadecimalUnsignedLong(): Long { require(1) var b = readByte() @@ -203,9 +203,8 @@ public fun Source.readByteArray(byteCount: Long): ByteArray { return readByteArrayImpl(byteCount) } -@OptIn(DelicateIoApi::class) -@Suppress("NOTHING_TO_INLINE") -private inline fun Source.readByteArrayImpl(size: Long): ByteArray { +@OptIn(InternalIoApi::class) +private fun Source.readByteArrayImpl(size: Long): ByteArray { var arraySize = size if (size == -1L) { var fetchSize = Int.MAX_VALUE.toLong() @@ -304,6 +303,13 @@ public fun Source.readUIntLe(): UInt = readIntLe().toUInt() */ public fun Source.readULongLe(): ULong = readLongLe().toULong() -// TODO: add doc -@OptIn(DelicateIoApi::class) +/** + * Return `true` if the next byte to be consumed from this source is equal to [byte]. + * Otherwise, return `false` as well as when the source is exhausted. + * + * If there are no buffered data, this call will result in a fetch from the underlying source. + * + * @throws IllegalStateException when the source is closed. + */ +@OptIn(InternalIoApi::class) public fun Source.startsWith(byte: Byte): Boolean = request(1) && buffer[0] == byte diff --git a/core/common/src/Utf8.kt b/core/common/src/Utf8.kt index ee01ae3a7..d87d55814 100644 --- a/core/common/src/Utf8.kt +++ b/core/common/src/Utf8.kt @@ -127,10 +127,8 @@ public fun String.utf8Size(beginIndex: Int = 0, endIndex: Int = length): Long { * @throws IllegalStateException when the sink is closed. */ @OptIn(DelicateIoApi::class) -public fun Sink.writeUtf8CodePoint(codePoint: Int) { - buffer.commonWriteUtf8CodePoint(codePoint) - emitCompleteSegments() -} +public fun Sink.writeUtf8CodePoint(codePoint: Int): Unit = + writeToInternalBuffer { it.commonWriteUtf8CodePoint(codePoint) } /** * Encodes the characters at [beginIndex] up to [endIndex] from [string] in UTF-8 and writes it to this sink. @@ -144,10 +142,8 @@ public fun Sink.writeUtf8CodePoint(codePoint: Int) { * @throws IllegalStateException when the sink is closed. */ @OptIn(DelicateIoApi::class) -public fun Sink.writeUtf8(string: String, beginIndex: Int = 0, endIndex: Int = string.length) { - buffer.commonWriteUtf8(string, beginIndex, endIndex) - emitCompleteSegments() -} +public fun Sink.writeUtf8(string: String, beginIndex: Int = 0, endIndex: Int = string.length): Unit = + writeToInternalBuffer { it.commonWriteUtf8(string, beginIndex, endIndex) } /** * Removes all bytes from this source, decodes them as UTF-8, and returns the string. @@ -156,7 +152,7 @@ public fun Sink.writeUtf8(string: String, beginIndex: Int = 0, endIndex: Int = s * * @throws IllegalStateException when the source is closed. */ -@OptIn(DelicateIoApi::class) +@OptIn(InternalIoApi::class) public fun Source.readUtf8(): String { var req: Long = Segment.SIZE.toLong() while (request(req)) { @@ -183,7 +179,7 @@ public fun Buffer.readUtf8(): String { * @throws EOFException when the source is exhausted before reading [byteCount] bytes from it. * @throws IllegalStateException when the source is closed. */ -@OptIn(DelicateIoApi::class) +@OptIn(InternalIoApi::class) public fun Source.readUtf8(byteCount: Long): String { require(byteCount) return buffer.commonReadUtf8(byteCount) @@ -204,7 +200,7 @@ public fun Source.readUtf8(byteCount: Long): String { * @throws EOFException when the source is exhausted before a complete code point can be read. * @throws IllegalStateException when the source is closed. */ -@OptIn(DelicateIoApi::class) +@OptIn(InternalIoApi::class) public fun Source.readUtf8CodePoint(): Int { require(1) @@ -423,10 +419,8 @@ private fun Buffer.commonWriteUtf8(string: String, beginIndex: Int, endIndex: In c < 0x800 -> { // Emit a 11-bit character with 2 bytes. val tail = writableSegment(2) - /* ktlint-disable no-multi-spaces */ tail.data[tail.limit ] = (c shr 6 or 0xc0).toByte() // 110xxxxx tail.data[tail.limit + 1] = (c and 0x3f or 0x80).toByte() // 10xxxxxx - /* ktlint-enable no-multi-spaces */ tail.limit += 2 size += 2L i++ @@ -435,11 +429,9 @@ private fun Buffer.commonWriteUtf8(string: String, beginIndex: Int, endIndex: In c < 0xd800 || c > 0xdfff -> { // Emit a 16-bit character with 3 bytes. val tail = writableSegment(3) - /* ktlint-disable no-multi-spaces */ tail.data[tail.limit ] = (c shr 12 or 0xe0).toByte() // 1110xxxx tail.data[tail.limit + 1] = (c shr 6 and 0x3f or 0x80).toByte() // 10xxxxxx tail.data[tail.limit + 2] = (c and 0x3f or 0x80).toByte() // 10xxxxxx - /* ktlint-enable no-multi-spaces */ tail.limit += 3 size += 3L i++ @@ -461,12 +453,10 @@ private fun Buffer.commonWriteUtf8(string: String, beginIndex: Int, endIndex: In // Emit a 21-bit character with 4 bytes. val tail = writableSegment(4) - /* ktlint-disable no-multi-spaces */ tail.data[tail.limit ] = (codePoint shr 18 or 0xf0).toByte() // 11110xxx tail.data[tail.limit + 1] = (codePoint shr 12 and 0x3f or 0x80).toByte() // 10xxxxxx tail.data[tail.limit + 2] = (codePoint shr 6 and 0x3f or 0x80).toByte() // 10xxyyyy tail.data[tail.limit + 3] = (codePoint and 0x3f or 0x80).toByte() // 10yyyyyy - /* ktlint-enable no-multi-spaces */ tail.limit += 4 size += 4L i += 2 @@ -485,10 +475,8 @@ private fun Buffer.commonWriteUtf8CodePoint(codePoint: Int) { codePoint < 0x800 -> { // Emit a 11-bit code point with 2 bytes. val tail = writableSegment(2) - /* ktlint-disable no-multi-spaces */ tail.data[tail.limit ] = (codePoint shr 6 or 0xc0).toByte() // 110xxxxx tail.data[tail.limit + 1] = (codePoint and 0x3f or 0x80).toByte() // 10xxxxxx - /* ktlint-enable no-multi-spaces */ tail.limit += 2 size += 2L } @@ -499,23 +487,19 @@ private fun Buffer.commonWriteUtf8CodePoint(codePoint: Int) { codePoint < 0x10000 -> { // Emit a 16-bit code point with 3 bytes. val tail = writableSegment(3) - /* ktlint-disable no-multi-spaces */ tail.data[tail.limit ] = (codePoint shr 12 or 0xe0).toByte() // 1110xxxx tail.data[tail.limit + 1] = (codePoint shr 6 and 0x3f or 0x80).toByte() // 10xxxxxx tail.data[tail.limit + 2] = (codePoint and 0x3f or 0x80).toByte() // 10xxxxxx - /* ktlint-enable no-multi-spaces */ tail.limit += 3 size += 3L } codePoint <= 0x10ffff -> { // Emit a 21-bit code point with 4 bytes. val tail = writableSegment(4) - /* ktlint-disable no-multi-spaces */ tail.data[tail.limit ] = (codePoint shr 18 or 0xf0).toByte() // 11110xxx tail.data[tail.limit + 1] = (codePoint shr 12 and 0x3f or 0x80).toByte() // 10xxxxxx tail.data[tail.limit + 2] = (codePoint shr 6 and 0x3f or 0x80).toByte() // 10xxyyyy tail.data[tail.limit + 3] = (codePoint and 0x3f or 0x80).toByte() // 10yyyyyy - /* ktlint-enable no-multi-spaces */ tail.limit += 4 size += 4L } diff --git a/core/common/test/AbstractSourceTest.kt b/core/common/test/AbstractSourceTest.kt index 2699e55b5..94edb6648 100644 --- a/core/common/test/AbstractSourceTest.kt +++ b/core/common/test/AbstractSourceTest.kt @@ -260,7 +260,7 @@ abstract class AbstractBufferedSourceTest internal constructor( } } - @OptIn(DelicateIoApi::class) + @OptIn(InternalIoApi::class) @Test fun readAll() { source.buffer.writeUtf8("abc") sink.writeUtf8("def") @@ -1016,7 +1016,7 @@ abstract class AbstractBufferedSourceTest internal constructor( assertEquals("Peek source is invalid because upstream source was used", e.message) } - @OptIn(DelicateIoApi::class) + @OptIn(InternalIoApi::class) @Test fun peekSegmentThenInvalid() { sink.writeUtf8("abc") sink.writeUtf8("d".repeat(2 * Segment.SIZE)) @@ -1038,7 +1038,7 @@ abstract class AbstractBufferedSourceTest internal constructor( assertEquals("Peek source is invalid because upstream source was used", e.message) } - @OptIn(DelicateIoApi::class) + @OptIn(InternalIoApi::class) @Test fun peekDoesntReadTooMuch() { // 6 bytes in source's buffer plus 3 bytes upstream. sink.writeUtf8("abcdef") @@ -1072,7 +1072,7 @@ abstract class AbstractBufferedSourceTest internal constructor( assertEquals("ghi", peek.readUtf8(3L)) } - @OptIn(DelicateIoApi::class) + @OptIn(InternalIoApi::class) @Test fun factorySegmentSizes() { sink.writeUtf8("abc") sink.emit() diff --git a/core/common/test/CommonRealSinkTest.kt b/core/common/test/CommonRealSinkTest.kt index d2d6d5e34..2cbcac5fb 100644 --- a/core/common/test/CommonRealSinkTest.kt +++ b/core/common/test/CommonRealSinkTest.kt @@ -29,7 +29,7 @@ import kotlin.test.assertFailsWith * Tests solely for the behavior of RealBufferedSink's implementation. For generic * BufferedSink behavior use BufferedSinkTest. */ -@OptIn(DelicateIoApi::class) +@OptIn(InternalIoApi::class) class CommonRealSinkTest { @Test fun bufferedSinkEmitsTailWhenItIsComplete() { val sink = Buffer() @@ -149,7 +149,7 @@ class CommonRealSinkTest { } assertFailsWith { - bufferedSink.emitCompleteSegments() + bufferedSink.hintEmit() } assertFailsWith { diff --git a/core/common/test/CommonRealSourceTest.kt b/core/common/test/CommonRealSourceTest.kt index f93f611fe..1c4ca3320 100644 --- a/core/common/test/CommonRealSourceTest.kt +++ b/core/common/test/CommonRealSourceTest.kt @@ -29,7 +29,7 @@ import kotlin.test.assertFailsWith * Tests solely for the behavior of RealBufferedSource's implementation. For generic * BufferedSource behavior, use BufferedSourceTest. */ -@OptIn(DelicateIoApi::class) +@OptIn(InternalIoApi::class) class CommonRealSourceTest { @Test fun indexOfStopsReadingAtLimit() { val buffer = Buffer().also { it.writeUtf8("abcdef") } diff --git a/core/common/test/DelicateApiTest.kt b/core/common/test/DelicateApiTest.kt new file mode 100644 index 000000000..104732e95 --- /dev/null +++ b/core/common/test/DelicateApiTest.kt @@ -0,0 +1,41 @@ +/* + * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. + */ + +package kotlinx.io + +import kotlin.test.Test +import kotlin.test.assertEquals + +@OptIn(DelicateIoApi::class) +class DelicateApiTest { + @Test + @OptIn(InternalIoApi::class) + fun testWriteIntoBuffer() { + val sink = Buffer() + val rawSink = sink as RawSink + val bufferedSink = rawSink.buffer() + + bufferedSink.writeToInternalBuffer { + it.writeByte(42) + } + + assertEquals(0, sink.size) + assertEquals(1, bufferedSink.buffer.size) + + bufferedSink.writeToInternalBuffer { + // 1 byte missing for segment to be complete + it.write(ByteArray(Segment.SIZE - 2)) + // skip everything (skip everything but one last byte) + it.skip(Segment.SIZE - 2L) + // this will complete the segment + it.writeByte(0x12) + // this will start a new one + it.writeByte(0x34) + } + + assertArrayEquals(byteArrayOf(0x0, 0x12), sink.readByteArray()) + assertArrayEquals(byteArrayOf(0x34), bufferedSink.buffer.readByteArray()) + } +} \ No newline at end of file diff --git a/core/jvm/src/SinkExtJvm.kt b/core/jvm/src/SinkExtJvm.kt index 13f299cac..fecc8e938 100644 --- a/core/jvm/src/SinkExtJvm.kt +++ b/core/jvm/src/SinkExtJvm.kt @@ -60,14 +60,12 @@ public fun Sink.outputStream(): OutputStream { return object : OutputStream() { override fun write(b: Int) { if (isClosed()) throw IOException("closed") - buffer.writeByte(b.toByte()) - emitCompleteSegments() + writeToInternalBuffer { it.writeByte(b.toByte()) } } override fun write(data: ByteArray, offset: Int, byteCount: Int) { if (isClosed()) throw IOException("closed") - buffer.write(data, offset, byteCount) - emitCompleteSegments() + writeToInternalBuffer { it.write(data, offset, byteCount) } } override fun flush() { @@ -90,12 +88,12 @@ public fun Sink.outputStream(): OutputStream { * * @throws IllegalStateException when the sink is closed. */ -@OptIn(DelicateIoApi::class) +@OptIn(InternalIoApi::class) public fun Sink.write(source: ByteBuffer): Int { val sizeBefore = buffer.size buffer.readFrom(source) val bytesRead = buffer.size - sizeBefore - emitCompleteSegments() + hintEmit() return bytesRead.toInt() } diff --git a/core/jvm/src/SourceExtJvm.kt b/core/jvm/src/SourceExtJvm.kt index 02d45189d..c525ff9c5 100644 --- a/core/jvm/src/SourceExtJvm.kt +++ b/core/jvm/src/SourceExtJvm.kt @@ -56,7 +56,7 @@ private fun Buffer.readStringImpl(byteCount: Long, charset: Charset): String { * * @throws IllegalStateException when the source is closed. */ -@OptIn(DelicateIoApi::class) +@OptIn(InternalIoApi::class) public fun Source.readString(charset: Charset): String { var req = 1L while (request(req)) { @@ -75,7 +75,7 @@ public fun Source.readString(charset: Charset): String { * @throws IllegalStateException when the source is closed. * @throws IllegalArgumentException if [byteCount] is negative. */ -@OptIn(DelicateIoApi::class) +@OptIn(InternalIoApi::class) public fun Source.readString(byteCount: Long, charset: Charset): String { require(byteCount) return buffer.readStringImpl(byteCount, charset) @@ -84,7 +84,7 @@ public fun Source.readString(byteCount: Long, charset: Charset): String { /** * Returns an input stream that reads from this source. Closing the stream will also close this source. */ -@OptIn(DelicateIoApi::class) +@OptIn(InternalIoApi::class) public fun Source.inputStream(): InputStream { val isClosed: () -> Boolean = when (this) { is RealSource -> this::closed @@ -125,7 +125,7 @@ public fun Source.inputStream(): InputStream { * * @throws IllegalStateException when the source is closed. */ -@OptIn(DelicateIoApi::class) +@OptIn(InternalIoApi::class) public fun Source.read(sink: ByteBuffer): Int { if (buffer.size == 0L) { request(Segment.SIZE.toLong()) From d0cff32a2907c5f74f63b21b8e8389000a0084a4 Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Fri, 16 Jun 2023 17:26:45 +0200 Subject: [PATCH 54/83] Fixed pathSourceWithOptions test on Windows --- core/jvm/test/JvmPlatformTest.kt | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/core/jvm/test/JvmPlatformTest.kt b/core/jvm/test/JvmPlatformTest.kt index a8d50f125..730e27e8e 100644 --- a/core/jvm/test/JvmPlatformTest.kt +++ b/core/jvm/test/JvmPlatformTest.kt @@ -107,12 +107,9 @@ class JvmPlatformTest { @Test fun pathSourceWithOptions() { - val folder = File(tempDir, "folder") - folder.mkdir() - - val file = File(folder, "new.txt") + val file = File(tempDir, "new.txt") assertTrue(file.createNewFile()) - val link = File(folder, "link.txt") + val link = File(tempDir, "link.txt") try { Files.createSymbolicLink(link.toPath(), file.toPath()) } catch (e: UnsupportedOperationException) { @@ -121,9 +118,9 @@ class JvmPlatformTest { } assertFailsWith { - link.toPath().source(LinkOption.NOFOLLOW_LINKS).buffer().readUtf8Line() + link.toPath().source(LinkOption.NOFOLLOW_LINKS).use { it.buffer().readUtf8Line() } } - assertNull(link.toPath().source().buffer().readUtf8Line()) + assertNull(link.toPath().source().use { it.buffer().readUtf8Line() }) } @Test fun socketSink() { From a6a0f64eef83998553cec9256a3148260aa6fef5 Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Mon, 19 Jun 2023 11:11:43 +0200 Subject: [PATCH 55/83] Apply new naming conventions --- benchmarks/src/commonMain/kotlin/BufferOps.kt | 2 +- .../jvmMain/kotlin/ByteBufferBenchmarks.kt | 4 +- .../src/jvmMain/kotlin/StreamBenchmarks.kt | 4 +- core/common/src/Buffer.kt | 18 +++++---- core/common/src/PeekSource.kt | 4 +- core/common/src/RawSource.kt | 2 +- core/common/src/RealSink.kt | 6 +-- core/common/src/RealSource.kt | 29 +++++++------- core/common/src/Sink.kt | 2 +- core/common/src/Source.kt | 6 +-- core/common/src/SourceExt.kt | 6 +-- core/common/test/AbstractSinkTest.kt | 8 ++-- core/common/test/AbstractSourceTest.kt | 40 +++++++++---------- core/common/test/CommonBufferTest.kt | 10 ++--- core/common/test/CommonRealSinkTest.kt | 6 +-- core/common/test/CommonRealSourceTest.kt | 6 +-- core/common/test/SourceFactory.kt | 4 +- core/jvm/src/BufferExtJvm.kt | 14 +++---- core/jvm/src/JvmCore.kt | 2 +- core/jvm/src/SinkExtJvm.kt | 2 +- core/jvm/src/SourceExtJvm.kt | 10 ++--- core/jvm/test/AbstractSourceTestJVM.kt | 6 +-- core/jvm/test/BufferTest.kt | 12 +++--- core/jvm/test/JvmPlatformTest.kt | 8 ++-- core/native/src/files/PathsNative.kt | 2 +- 25 files changed, 108 insertions(+), 105 deletions(-) diff --git a/benchmarks/src/commonMain/kotlin/BufferOps.kt b/benchmarks/src/commonMain/kotlin/BufferOps.kt index 9860b73f5..30005b5eb 100644 --- a/benchmarks/src/commonMain/kotlin/BufferOps.kt +++ b/benchmarks/src/commonMain/kotlin/BufferOps.kt @@ -368,7 +368,7 @@ open class BufferReadWriteByteArray: BufferRWBenchmarkBase() { @Benchmark fun benchmark(blackhole: Blackhole) { buffer.write(inputArray) - buffer.readFully(outputArray) + buffer.readTo(outputArray) blackhole.consume(outputArray) } } diff --git a/benchmarks/src/jvmMain/kotlin/ByteBufferBenchmarks.kt b/benchmarks/src/jvmMain/kotlin/ByteBufferBenchmarks.kt index 51fe5053e..d25dbde91 100644 --- a/benchmarks/src/jvmMain/kotlin/ByteBufferBenchmarks.kt +++ b/benchmarks/src/jvmMain/kotlin/ByteBufferBenchmarks.kt @@ -8,7 +8,7 @@ package kotlinx.io.benchmarks import kotlinx.benchmark.Benchmark import kotlinx.benchmark.Param import kotlinx.benchmark.Setup -import kotlinx.io.read +import kotlinx.io.readAtMostTo import kotlinx.io.write import java.nio.ByteBuffer @@ -31,7 +31,7 @@ open class ByteBufferReadWrite: BufferRWBenchmarkBase() { inputBuffer.rewind() outputBuffer.clear() buffer.write(inputBuffer) - while (buffer.read(outputBuffer) > 0) { + while (buffer.readAtMostTo(outputBuffer) > 0) { // do nothing } return outputBuffer diff --git a/benchmarks/src/jvmMain/kotlin/StreamBenchmarks.kt b/benchmarks/src/jvmMain/kotlin/StreamBenchmarks.kt index 55ee1391d..248b2f8a9 100644 --- a/benchmarks/src/jvmMain/kotlin/StreamBenchmarks.kt +++ b/benchmarks/src/jvmMain/kotlin/StreamBenchmarks.kt @@ -11,7 +11,7 @@ import kotlinx.benchmark.Param import kotlinx.benchmark.Setup import kotlinx.io.inputStream import kotlinx.io.outputStream -import kotlinx.io.readFully +import kotlinx.io.readTo open class InputStreamByteRead: BufferRWBenchmarkBase() { private val stream = buffer.inputStream() @@ -67,7 +67,7 @@ open class OutputStreamByteArrayWrite: StreamByteArrayBenchmarkBase() { @Benchmark fun benchmark(blackhole: Blackhole) { stream.write(outputArray) - buffer.readFully(inputArray) + buffer.readTo(inputArray) blackhole.consume(inputArray) } } diff --git a/core/common/src/Buffer.kt b/core/common/src/Buffer.kt index 099946802..76065a8df 100644 --- a/core/common/src/Buffer.kt +++ b/core/common/src/Buffer.kt @@ -306,7 +306,7 @@ public class Buffer : Source, Sink { } } - override fun read(sink: ByteArray, offset: Int, byteCount: Int): Int { + override fun readAtMostTo(sink: ByteArray, offset: Int, byteCount: Int): Int { checkOffsetAndCount(sink.size.toLong(), offset.toLong(), byteCount.toLong()) val s = head ?: return -1 @@ -326,15 +326,16 @@ public class Buffer : Source, Sink { return toCopy } - override fun read(sink: Buffer, byteCount: Long): Long { - require(byteCount >= 0L) { "byteCount < 0: $byteCount" } + override fun readAtMostTo(sink: Buffer, byteCount: Long): Long { + require(byteCount >= 0L) { "byteCount: $byteCount" } if (size == 0L) return -1L val bytesWritten = if (byteCount > size) size else byteCount sink.write(this, bytesWritten) return bytesWritten } - override fun readFully(sink: RawSink, byteCount: Long) { + override fun readTo(sink: RawSink, byteCount: Long) { + require(byteCount >= 0) { "byteCount: $byteCount" } if (size < byteCount) { sink.write(this, size) // Exhaust ourselves. throw EOFException() @@ -342,7 +343,7 @@ public class Buffer : Source, Sink { sink.write(this, byteCount) } - override fun readAll(sink: RawSink): Long { + override fun transferTo(sink: RawSink): Long { val byteCount = size if (byteCount > 0L) { sink.write(this, byteCount) @@ -396,9 +397,10 @@ public class Buffer : Source, Sink { } override fun write(source: RawSource, byteCount: Long) { + require(byteCount >= 0) { "byteCount: $byteCount" } var remainingByteCount = byteCount while (remainingByteCount > 0L) { - val read = source.read(this, remainingByteCount) + val read = source.readAtMostTo(this, remainingByteCount) if (read == -1L) throw EOFException() remainingByteCount -= read } @@ -499,10 +501,10 @@ public class Buffer : Source, Sink { } } - override fun writeAll(source: RawSource): Long { + override fun transferFrom(source: RawSource): Long { var totalBytesRead = 0L while (true) { - val readCount = source.read(this, Segment.SIZE.toLong()) + val readCount = source.readAtMostTo(this, Segment.SIZE.toLong()) if (readCount == -1L) break totalBytesRead += readCount } diff --git a/core/common/src/PeekSource.kt b/core/common/src/PeekSource.kt index 9a9627b74..f94a34d81 100644 --- a/core/common/src/PeekSource.kt +++ b/core/common/src/PeekSource.kt @@ -41,8 +41,8 @@ internal class PeekSource( private var closed = false private var pos = 0L - override fun read(sink: Buffer, byteCount: Long): Long { - require(byteCount >= 0L) { "byteCount < 0: $byteCount" } + override fun readAtMostTo(sink: Buffer, byteCount: Long): Long { + require(byteCount >= 0L) { "byteCount: $byteCount" } check(!closed) { "closed" } // Source becomes invalid if there is an expected Segment and it and the expected position // do not match the current head and head position of the upstream buffer diff --git a/core/common/src/RawSource.kt b/core/common/src/RawSource.kt index 45e0bbdb2..9ecf6d6fa 100644 --- a/core/common/src/RawSource.kt +++ b/core/common/src/RawSource.kt @@ -42,7 +42,7 @@ public interface RawSource : AutoCloseableAlias { * @throws IllegalArgumentException when [byteCount] is negative. * @throws IllegalStateException when the source is closed. */ - public fun read(sink: Buffer, byteCount: Long): Long + public fun readAtMostTo(sink: Buffer, byteCount: Long): Long /** * Closes this source and releases the resources held by this source. It is an error to read a diff --git a/core/common/src/RealSink.kt b/core/common/src/RealSink.kt index bd80214d8..3433593c6 100644 --- a/core/common/src/RealSink.kt +++ b/core/common/src/RealSink.kt @@ -49,10 +49,10 @@ internal class RealSink( hintEmit() } - override fun writeAll(source: RawSource): Long { + override fun transferFrom(source: RawSource): Long { var totalBytesRead = 0L while (true) { - val readCount: Long = source.read(bufferField, Segment.SIZE.toLong()) + val readCount: Long = source.readAtMostTo(bufferField, Segment.SIZE.toLong()) if (readCount == -1L) break totalBytesRead += readCount hintEmit() @@ -64,7 +64,7 @@ internal class RealSink( require(byteCount >= 0) { "byteCount ($byteCount) should not be negative." } var remainingByteCount = byteCount while (remainingByteCount > 0L) { - val read = source.read(bufferField, remainingByteCount) + val read = source.readAtMostTo(bufferField, remainingByteCount) if (read == -1L) throw EOFException() remainingByteCount -= read hintEmit() diff --git a/core/common/src/RealSource.kt b/core/common/src/RealSource.kt index 4d4464d5e..8bb7bbd39 100644 --- a/core/common/src/RealSource.kt +++ b/core/common/src/RealSource.kt @@ -34,22 +34,22 @@ internal class RealSource( override val buffer: Buffer get() = bufferField - override fun read(sink: Buffer, byteCount: Long): Long { + override fun readAtMostTo(sink: Buffer, byteCount: Long): Long { require(byteCount >= 0L) { "byteCount < 0: $byteCount" } check(!closed) { "closed" } if (bufferField.size == 0L) { - val read = source.read(bufferField, Segment.SIZE.toLong()) + val read = source.readAtMostTo(bufferField, Segment.SIZE.toLong()) if (read == -1L) return -1L } val toRead = minOf(byteCount, bufferField.size) - return bufferField.read(sink, toRead) + return bufferField.readAtMostTo(sink, toRead) } override fun exhausted(): Boolean { check(!closed) { "closed" } - return bufferField.exhausted() && source.read(bufferField, Segment.SIZE.toLong()) == -1L + return bufferField.exhausted() && source.readAtMostTo(bufferField, Segment.SIZE.toLong()) == -1L } override fun require(byteCount: Long) { @@ -60,7 +60,7 @@ internal class RealSource( require(byteCount >= 0L) { "byteCount < 0: $byteCount" } check(!closed) { "closed" } while (bufferField.size < byteCount) { - if (source.read(bufferField, Segment.SIZE.toLong()) == -1L) return false + if (source.readAtMostTo(bufferField, Segment.SIZE.toLong()) == -1L) return false } return true } @@ -70,19 +70,20 @@ internal class RealSource( return bufferField.readByte() } - override fun read(sink: ByteArray, offset: Int, byteCount: Int): Int { + override fun readAtMostTo(sink: ByteArray, offset: Int, byteCount: Int): Int { checkOffsetAndCount(sink.size.toLong(), offset.toLong(), byteCount.toLong()) if (bufferField.size == 0L) { - val read = source.read(bufferField, Segment.SIZE.toLong()) + val read = source.readAtMostTo(bufferField, Segment.SIZE.toLong()) if (read == -1L) return -1 } - + val toRead = minOf(endIndex - startIndex, bufferField.size).toInt() + //return bufferField.readAtMostTo(sink, startIndex, startIndex + toRead) val toRead = minOf(byteCount, bufferField.size).toInt() - return bufferField.read(sink, offset, toRead) + return bufferField.readAtMostTo(sink, offset, toRead) } - override fun readFully(sink: RawSink, byteCount: Long): Unit { + override fun readTo(sink: RawSink, byteCount: Long) { try { require(byteCount) } catch (e: EOFException) { @@ -90,12 +91,12 @@ internal class RealSource( sink.write(bufferField, bufferField.size) throw e } - bufferField.readFully(sink, byteCount) + bufferField.readTo(sink, byteCount) } - override fun readAll(sink: RawSink): Long { + override fun transferTo(sink: RawSink): Long { var totalBytesWritten: Long = 0 - while (source.read(bufferField, Segment.SIZE.toLong()) != -1L) { + while (source.readAtMostTo(bufferField, Segment.SIZE.toLong()) != -1L) { val emitByteCount = bufferField.completeSegmentByteCount() if (emitByteCount > 0L) { totalBytesWritten += emitByteCount @@ -128,7 +129,7 @@ internal class RealSource( var remainingByteCount = byteCount check(!closed) { "closed" } while (remainingByteCount > 0) { - if (bufferField.size == 0L && source.read(bufferField, Segment.SIZE.toLong()) == -1L) { + if (bufferField.size == 0L && source.readAtMostTo(bufferField, Segment.SIZE.toLong()) == -1L) { throw EOFException() } val toSkip = minOf(remainingByteCount, bufferField.size) diff --git a/core/common/src/Sink.kt b/core/common/src/Sink.kt index d57b414d5..33e8eff4e 100644 --- a/core/common/src/Sink.kt +++ b/core/common/src/Sink.kt @@ -76,7 +76,7 @@ public sealed interface Sink : RawSink { * @throws IllegalStateException when the sink or [source] is closed. * */ - public fun writeAll(source: RawSource): Long + public fun transferFrom(source: RawSource): Long /** * Removes [byteCount] bytes from [source] and write them to this sink. diff --git a/core/common/src/Source.kt b/core/common/src/Source.kt index 08ea250d0..8b6289428 100644 --- a/core/common/src/Source.kt +++ b/core/common/src/Source.kt @@ -138,7 +138,7 @@ public sealed interface Source : RawSource { * is out of range of [sink] array indices. * @throws IllegalStateException when the source is closed. */ - public fun read(sink: ByteArray, offset: Int = 0, byteCount: Int = sink.size - offset): Int + public fun readAtMostTo(sink: ByteArray, offset: Int = 0, byteCount: Int = sink.size - offset): Int /** * Removes exactly [byteCount] bytes from this source and writes them to [sink]. @@ -150,7 +150,7 @@ public sealed interface Source : RawSource { * @throws EOFException when the requested number of bytes cannot be read. * @throws IllegalStateException when the source or [sink] is closed. */ - public fun readFully(sink: RawSink, byteCount: Long) + public fun readTo(sink: RawSink, byteCount: Long) /** * Removes all bytes from this source, writes them to [sink], and returns the total number of bytes @@ -162,7 +162,7 @@ public sealed interface Source : RawSource { * * @throws IllegalStateException when the source or [sink] is closed. */ - public fun readAll(sink: RawSink): Long + public fun transferTo(sink: RawSink): Long /** * Returns a new [Source] that can read data from this source without consuming it. diff --git a/core/common/src/SourceExt.kt b/core/common/src/SourceExt.kt index fe07b870d..1a7c71468 100644 --- a/core/common/src/SourceExt.kt +++ b/core/common/src/SourceExt.kt @@ -219,7 +219,7 @@ private fun Source.readByteArrayImpl(size: Long): ByteArray { require(size) } val array = ByteArray(arraySize.toInt()) - buffer.readFully(array) + buffer.readTo(array) return array } @@ -230,10 +230,10 @@ private fun Source.readByteArrayImpl(size: Long): ByteArray { * @throws EOFException when the requested number of bytes cannot be read. * @throws IllegalStateException when the source is closed. */ -public fun Source.readFully(sink: ByteArray) { +public fun Source.readTo(sink: ByteArray) { var offset = 0 while (offset < sink.size) { - val bytesRead = read(sink, offset) + val bytesRead = readAtMostTo(sink, offset) if (bytesRead == -1) { throw EOFException() } diff --git a/core/common/test/AbstractSinkTest.kt b/core/common/test/AbstractSinkTest.kt index add4ac87e..f3dbdcff5 100644 --- a/core/common/test/AbstractSinkTest.kt +++ b/core/common/test/AbstractSinkTest.kt @@ -175,7 +175,7 @@ abstract class AbstractSinkTest internal constructor( val source = Buffer() source.writeUtf8("abcdef") - assertEquals(6, sink.writeAll(source)) + assertEquals(6, sink.transferFrom(source)) assertEquals(0, source.size) sink.flush() assertEquals("abcdef", data.readUtf8()) @@ -183,7 +183,7 @@ abstract class AbstractSinkTest internal constructor( @Test fun writeAllExhausted() { val source = Buffer() - assertEquals(0, sink.writeAll(source)) + assertEquals(0, sink.transferFrom(source)) assertEquals(0, source.size) } @@ -200,7 +200,7 @@ abstract class AbstractSinkTest internal constructor( @Test fun writeSourceReadsFully() { val source = object : RawSource by Buffer() { - override fun read(sink: Buffer, byteCount: Long): Long { + override fun readAtMostTo(sink: Buffer, byteCount: Long): Long { sink.writeUtf8("abcd") return 4 } @@ -249,7 +249,7 @@ abstract class AbstractSinkTest internal constructor( // tied to something like a socket which will potentially block trying to read a segment when // ultimately we don't want any data. val source = object : RawSource by Buffer() { - override fun read(sink: Buffer, byteCount: Long): Long { + override fun readAtMostTo(sink: Buffer, byteCount: Long): Long { throw AssertionError() } } diff --git a/core/common/test/AbstractSourceTest.kt b/core/common/test/AbstractSourceTest.kt index 94edb6648..c9c6b7d6b 100644 --- a/core/common/test/AbstractSourceTest.kt +++ b/core/common/test/AbstractSourceTest.kt @@ -267,14 +267,14 @@ abstract class AbstractBufferedSourceTest internal constructor( sink.emit() val sink = Buffer() - assertEquals(6, source.readAll(sink)) + assertEquals(6, source.transferTo(sink)) assertEquals("abcdef", sink.readUtf8()) assertTrue(source.exhausted()) } @Test fun readAllExhausted() { val mockSink = MockSink() - assertEquals(0, source.readAll(mockSink)) + assertEquals(0, source.transferTo(mockSink)) assertTrue(source.exhausted()) mockSink.assertLog() } @@ -282,7 +282,7 @@ abstract class AbstractBufferedSourceTest internal constructor( @Test fun readExhaustedSource() { val sink = Buffer() sink.writeUtf8("a".repeat(10)) - assertEquals(-1, source.read(sink, 10)) + assertEquals(-1, source.readAtMostTo(sink, 10)) assertEquals(10, sink.size) assertTrue(source.exhausted()) } @@ -311,7 +311,7 @@ abstract class AbstractBufferedSourceTest internal constructor( source.close() assertFailsWith { - source.read(Buffer().also { it.writeByte(0) }, 1L) + source.readAtMostTo(Buffer().also { it.writeByte(0) }, 1L) } } @@ -319,7 +319,7 @@ abstract class AbstractBufferedSourceTest internal constructor( sink.writeUtf8("a".repeat(10000)) sink.emit() val sink = Buffer() - source.readFully(sink, 9999) + source.readTo(sink, 9999) assertEquals("a".repeat(9999), sink.readUtf8()) assertEquals("a", source.readUtf8()) } @@ -329,7 +329,7 @@ abstract class AbstractBufferedSourceTest internal constructor( sink.emit() val sink = Buffer() assertFailsWith { - source.readFully(sink, 5) + source.readTo(sink, 5) } // Verify we read all that we could from the source. @@ -339,13 +339,13 @@ abstract class AbstractBufferedSourceTest internal constructor( @Test fun readFullyWithNegativeByteCount() { val sink = Buffer() assertFailsWith { - source.readFully(sink, -1) + source.readTo(sink, -1) } } @Test fun readFullyZeroBytes() { val sink = Buffer() - source.readFully(sink, 0) + source.readTo(sink, 0) assertEquals(0, sink.size) } @@ -359,7 +359,7 @@ abstract class AbstractBufferedSourceTest internal constructor( sink.emit() val sink = ByteArray(Segment.SIZE + 5) - source.readFully(sink) + source.readTo(sink) assertArrayEquals(expected, sink) } @@ -369,7 +369,7 @@ abstract class AbstractBufferedSourceTest internal constructor( val array = ByteArray(6) assertFailsWith { - source.readFully(array) + source.readTo(array) } // Verify we read all that we could from the source. @@ -391,7 +391,7 @@ abstract class AbstractBufferedSourceTest internal constructor( sink.emit() val sink = ByteArray(3) - val read = source.read(sink) + val read = source.readAtMostTo(sink) if (factory.isOneByteAtATime) { assertEquals(1, read.toLong()) val expected = byteArrayOf('a'.code.toByte(), 0, 0) @@ -408,7 +408,7 @@ abstract class AbstractBufferedSourceTest internal constructor( sink.emit() val sink = ByteArray(5) - val read = source.read(sink) + val read = source.readAtMostTo(sink) if (factory.isOneByteAtATime) { assertEquals(1, read.toLong()) val expected = byteArrayOf('a'.code.toByte(), 0, 0, 0, 0) @@ -426,7 +426,7 @@ abstract class AbstractBufferedSourceTest internal constructor( sink.emit() val sink = ByteArray(7) - val read = source.read(sink, 2, 3) + val read = source.readAtMostTo(sink, 2, 3) if (factory.isOneByteAtATime) { assertEquals(1, read.toLong()) val expected = byteArrayOf(0, 0, 'a'.code.toByte(), 0, 0, 0, 0) @@ -444,7 +444,7 @@ abstract class AbstractBufferedSourceTest internal constructor( sink.emit() val sink = ByteArray(7) - val read = source.read(sink, 4) + val read = source.readAtMostTo(sink, 4) if (factory.isOneByteAtATime) { assertEquals(1, read.toLong()) val expected = byteArrayOf(0, 0, 0, 0, 'a'.code.toByte(), 0, 0) @@ -464,15 +464,15 @@ abstract class AbstractBufferedSourceTest internal constructor( val sink = ByteArray(4) assertFailsWith { - source.read(sink, 4, 1) + source.readAtMostTo(sink, 4, 1) } assertFailsWith { - source.read(sink, 1, 4) + source.readAtMostTo(sink, 1, 4) } assertFailsWith { - source.read(sink, -1, 2) + source.readAtMostTo(sink, -1, 2) } } @@ -1027,7 +1027,7 @@ abstract class AbstractBufferedSourceTest internal constructor( // Peek a little data and skip the rest of the upstream source val peek = source.peek() assertEquals("ddd", peek.readUtf8(3)) - source.readAll(blackholeSink()) + source.transferTo(blackholeSink()) // Skip the rest of the buffered data peek.skip(peek.buffer.size) @@ -1103,7 +1103,7 @@ abstract class AbstractBufferedSourceTest internal constructor( sink.writeUtf8("Wot do u call it?\r\nWindows") sink.flush() assertEquals("Wot do u call it?", source.readUtf8Line()) - source.readAll(blackholeSink()) + source.transferTo(blackholeSink()) sink.writeUtf8("reo\rde\red\n") sink.flush() @@ -1130,7 +1130,7 @@ abstract class AbstractBufferedSourceTest internal constructor( sink.writeUtf8("Wot do u call it?\r\nWindows") sink.flush() assertEquals("Wot do u call it?", source.readUtf8LineStrict()) - source.readAll(blackholeSink()) + source.transferTo(blackholeSink()) sink.writeUtf8("reo\rde\red\n") sink.flush() diff --git a/core/common/test/CommonBufferTest.kt b/core/common/test/CommonBufferTest.kt index 10e647b24..92b73b972 100644 --- a/core/common/test/CommonBufferTest.kt +++ b/core/common/test/CommonBufferTest.kt @@ -137,7 +137,7 @@ class CommonBufferTest { for (s in contents) { val source = Buffer() source.writeUtf8(s) - buffer.writeAll(source) + buffer.transferFrom(source) expected.append(s) } val segmentSizes = segmentSizes(buffer) @@ -211,7 +211,7 @@ class CommonBufferTest { val source = Buffer() source.writeUtf8('b'.repeat(15)) - assertEquals(10, source.read(sink, 10)) + assertEquals(10, source.readAtMostTo(sink, 10)) assertEquals(20, sink.size) assertEquals(5, source.size) assertEquals('a'.repeat(10) + 'b'.repeat(10), sink.readUtf8(20)) @@ -224,7 +224,7 @@ class CommonBufferTest { val source = Buffer() source.writeUtf8('b'.repeat(20)) - assertEquals(20, source.read(sink, 25)) + assertEquals(20, source.readAtMostTo(sink, 25)) assertEquals(30, sink.size) assertEquals(0, source.size) assertEquals('a'.repeat(10) + 'b'.repeat(20), sink.readUtf8(30)) @@ -306,7 +306,7 @@ class CommonBufferTest { val mockSink = MockSink() - assertEquals((Segment.SIZE * 3).toLong(), source.readAll(mockSink)) + assertEquals((Segment.SIZE * 3).toLong(), source.transferTo(mockSink)) assertEquals(0, source.size) mockSink.assertLog("write($write1, ${write1.size})") } @@ -315,7 +315,7 @@ class CommonBufferTest { val source = Buffer().also { it.writeUtf8('a'.repeat(Segment.SIZE * 3)) } val sink = Buffer() - assertEquals((Segment.SIZE * 3).toLong(), sink.writeAll(source)) + assertEquals((Segment.SIZE * 3).toLong(), sink.transferFrom(source)) assertEquals(0, source.size) assertEquals('a'.repeat(Segment.SIZE * 3), sink.readUtf8()) } diff --git a/core/common/test/CommonRealSinkTest.kt b/core/common/test/CommonRealSinkTest.kt index 2cbcac5fb..8047e1ec3 100644 --- a/core/common/test/CommonRealSinkTest.kt +++ b/core/common/test/CommonRealSinkTest.kt @@ -166,7 +166,7 @@ class CommonRealSinkTest { val bufferedSink = mockSink.buffer() bufferedSink.buffer.writeUtf8("abc") - assertEquals(3, bufferedSink.writeAll(Buffer().also { it.writeUtf8("def") })) + assertEquals(3, bufferedSink.transferFrom(Buffer().also { it.writeUtf8("def") })) assertEquals(6, bufferedSink.buffer.size) assertEquals("abcdef", bufferedSink.buffer.readUtf8(6)) @@ -177,7 +177,7 @@ class CommonRealSinkTest { val mockSink = MockSink() val bufferedSink = mockSink.buffer() - assertEquals(0, bufferedSink.writeAll(Buffer())) + assertEquals(0, bufferedSink.transferFrom(Buffer())) assertEquals(0, bufferedSink.buffer.size) mockSink.assertLog() // No writes. } @@ -193,7 +193,7 @@ class CommonRealSinkTest { val mockSink = MockSink() val bufferedSink = mockSink.buffer() - assertEquals(Segment.SIZE.toLong() * 3L, bufferedSink.writeAll(source)) + assertEquals(Segment.SIZE.toLong() * 3L, bufferedSink.transferFrom(source)) mockSink.assertLog( "write($write1, ${write1.size})", diff --git a/core/common/test/CommonRealSourceTest.kt b/core/common/test/CommonRealSourceTest.kt index 1c4ca3320..3c72148d0 100644 --- a/core/common/test/CommonRealSourceTest.kt +++ b/core/common/test/CommonRealSourceTest.kt @@ -35,8 +35,8 @@ class CommonRealSourceTest { val buffer = Buffer().also { it.writeUtf8("abcdef") } val bufferedSource = ( object : RawSource by buffer { - override fun read(sink: Buffer, byteCount: Long): Long { - return buffer.read(sink, minOf(1, byteCount)) + override fun readAtMostTo(sink: Buffer, byteCount: Long): Long { + return buffer.readAtMostTo(sink, minOf(1, byteCount)) } } ).buffer() @@ -148,7 +148,7 @@ class CommonRealSourceTest { val mockSink = MockSink() val bufferedSource = (source as RawSource).buffer() - assertEquals(Segment.SIZE.toLong() * 3L, bufferedSource.readAll(mockSink)) + assertEquals(Segment.SIZE.toLong() * 3L, bufferedSource.transferTo(mockSink)) mockSink.assertLog( "write($write1, ${write1.size})", "write($write2, ${write2.size})", diff --git a/core/common/test/SourceFactory.kt b/core/common/test/SourceFactory.kt index 095c683a0..2b798bd59 100644 --- a/core/common/test/SourceFactory.kt +++ b/core/common/test/SourceFactory.kt @@ -76,11 +76,11 @@ interface SourceFactory { return Pipe( buffer, object : RawSource by buffer { - override fun read(sink: Buffer, byteCount: Long): Long { + override fun readAtMostTo(sink: Buffer, byteCount: Long): Long { // Read one byte into a new buffer, then clone it so that the segment is shared. // Shared segments cannot be compacted so we'll get a long chain of short segments. val box = Buffer() - val result = buffer.read(box, minOf(byteCount, 1L)) + val result = buffer.readAtMostTo(box, minOf(byteCount, 1L)) if (result > 0L) sink.write(box.copy(), result) return result } diff --git a/core/jvm/src/BufferExtJvm.kt b/core/jvm/src/BufferExtJvm.kt index c9fdda22e..c90ab232e 100644 --- a/core/jvm/src/BufferExtJvm.kt +++ b/core/jvm/src/BufferExtJvm.kt @@ -32,7 +32,7 @@ import java.nio.channels.ByteChannel * * @param input the stream to read data from. */ -public fun Buffer.readFrom(input: InputStream): Buffer { +public fun Buffer.transferFrom(input: InputStream): Buffer { readFrom(input, Long.MAX_VALUE, true) return this } @@ -47,8 +47,8 @@ public fun Buffer.readFrom(input: InputStream): Buffer { * @throws IOException when [input] exhausted before reading [byteCount] bytes from it. * @throws IllegalArgumentException when [byteCount] is negative. */ -public fun Buffer.readFrom(input: InputStream, byteCount: Long): Buffer { - require(byteCount >= 0L) { "byteCount < 0: $byteCount" } +public fun Buffer.write(input: InputStream, byteCount: Long): Buffer { + require(byteCount >= 0L) { "byteCount: $byteCount" } readFrom(input, byteCount, false) return this } @@ -148,7 +148,7 @@ public fun Buffer.copyTo( * * @param sink the sink to write data to. */ -public fun Buffer.writeTo(sink: ByteBuffer): Int { +public fun Buffer.readAtMostTo(sink: ByteBuffer): Int { val s = head ?: return -1 val toCopy = minOf(sink.remaining(), s.limit - s.pos) @@ -168,7 +168,7 @@ public fun Buffer.writeTo(sink: ByteBuffer): Int { /** * Reads all data from [source] into this buffer. */ -public fun Buffer.readFrom(source: ByteBuffer): Buffer { +public fun Buffer.transferFrom(source: ByteBuffer): Buffer { val byteCount = source.remaining() var remaining = byteCount while (remaining > 0) { @@ -189,11 +189,11 @@ public fun Buffer.readFrom(source: ByteBuffer): Buffer { * Returns a new [ByteChannel] instance representing this buffer. */ public fun Buffer.channel(): ByteChannel = object : ByteChannel { - override fun read(sink: ByteBuffer): Int = writeTo(sink) + override fun read(sink: ByteBuffer): Int = readAtMostTo(sink) override fun write(source: ByteBuffer): Int { val sizeBefore = size - readFrom(source) + transferFrom(source) return (size - sizeBefore).toInt() } diff --git a/core/jvm/src/JvmCore.kt b/core/jvm/src/JvmCore.kt index f03154f3f..9d3724828 100644 --- a/core/jvm/src/JvmCore.kt +++ b/core/jvm/src/JvmCore.kt @@ -77,7 +77,7 @@ private open class InputStreamSource( private val input: InputStream, ) : RawSource { - override fun read(sink: Buffer, byteCount: Long): Long { + override fun readAtMostTo(sink: Buffer, byteCount: Long): Long { if (byteCount == 0L) return 0L require(byteCount >= 0L) { "byteCount < 0: $byteCount" } try { diff --git a/core/jvm/src/SinkExtJvm.kt b/core/jvm/src/SinkExtJvm.kt index fecc8e938..cd85844df 100644 --- a/core/jvm/src/SinkExtJvm.kt +++ b/core/jvm/src/SinkExtJvm.kt @@ -91,7 +91,7 @@ public fun Sink.outputStream(): OutputStream { @OptIn(InternalIoApi::class) public fun Sink.write(source: ByteBuffer): Int { val sizeBefore = buffer.size - buffer.readFrom(source) + buffer.transferFrom(source) val bytesRead = buffer.size - sizeBefore hintEmit() return bytesRead.toInt() diff --git a/core/jvm/src/SourceExtJvm.kt b/core/jvm/src/SourceExtJvm.kt index c525ff9c5..5876235c9 100644 --- a/core/jvm/src/SourceExtJvm.kt +++ b/core/jvm/src/SourceExtJvm.kt @@ -104,7 +104,7 @@ public fun Source.inputStream(): InputStream { if (isClosed()) throw IOException("closed") checkOffsetAndCount(data.size.toLong(), offset.toLong(), byteCount.toLong()) - return this@inputStream.read(data, offset, byteCount) + return this@inputStream.readAtMostTo(data, offset, byteCount) } override fun available(): Int { @@ -119,20 +119,20 @@ public fun Source.inputStream(): InputStream { } /** - * Reads data from this source into [sink]. + * Reads at most [ByteBuffer.remaining] bytes from this source into [sink] and returns the number of bytes read. * * @param sink the sink to write the data to. * * @throws IllegalStateException when the source is closed. */ @OptIn(InternalIoApi::class) -public fun Source.read(sink: ByteBuffer): Int { +public fun Source.readAtMostTo(sink: ByteBuffer): Int { if (buffer.size == 0L) { request(Segment.SIZE.toLong()) if (buffer.size == 0L) return -1 } - return buffer.writeTo(sink) + return buffer.readAtMostTo(sink) } /** @@ -151,6 +151,6 @@ public fun Source.channel(): ReadableByteChannel { override fun isOpen(): Boolean = !isClosed() - override fun read(sink: ByteBuffer): Int = this@channel.read(sink) + override fun read(sink: ByteBuffer): Int = this@channel.readAtMostTo(sink) } } diff --git a/core/jvm/test/AbstractSourceTestJVM.kt b/core/jvm/test/AbstractSourceTestJVM.kt index 8e600e8e4..9fea0b97f 100644 --- a/core/jvm/test/AbstractSourceTestJVM.kt +++ b/core/jvm/test/AbstractSourceTestJVM.kt @@ -194,7 +194,7 @@ abstract class AbstractSourceTestJVM(private val factory: SourceFactory) { sink.writeUtf8("abcdefg") sink.emit() val nioByteBuffer: ByteBuffer = ByteBuffer.allocate(1024) - val byteCount: Int = source.read(nioByteBuffer) + val byteCount: Int = source.readAtMostTo(nioByteBuffer) assertEquals(expected.length, byteCount) assertEquals(expected.length, nioByteBuffer.position()) assertEquals(nioByteBuffer.capacity(), nioByteBuffer.limit()) @@ -211,7 +211,7 @@ abstract class AbstractSourceTestJVM(private val factory: SourceFactory) { sink.writeUtf8("a".repeat(SEGMENT_SIZE * 4)) sink.emit() val nioByteBuffer: ByteBuffer = ByteBuffer.allocate(SEGMENT_SIZE * 3) - val byteCount: Int = source.read(nioByteBuffer) + val byteCount: Int = source.readAtMostTo(nioByteBuffer) assertEquals(expected.length, byteCount) assertEquals(expected.length, nioByteBuffer.position()) assertEquals(nioByteBuffer.capacity(), nioByteBuffer.limit()) @@ -223,7 +223,7 @@ abstract class AbstractSourceTestJVM(private val factory: SourceFactory) { @Test fun readNioBufferFromEmptySource() { - assertEquals(-1, source.read(ByteBuffer.allocate(10))) + assertEquals(-1, source.readAtMostTo(ByteBuffer.allocate(10))) } @Test diff --git a/core/jvm/test/BufferTest.kt b/core/jvm/test/BufferTest.kt index d4ce7254f..92188acd5 100644 --- a/core/jvm/test/BufferTest.kt +++ b/core/jvm/test/BufferTest.kt @@ -93,7 +93,7 @@ class BufferTest { fun readFromStream() { val input: InputStream = ByteArrayInputStream("hello, world!".toByteArray(UTF_8)) val buffer = Buffer() - buffer.readFrom(input) + buffer.transferFrom(input) val out = buffer.readUtf8() assertEquals("hello, world!", out) } @@ -102,7 +102,7 @@ class BufferTest { fun readFromSpanningSegments() { val input: InputStream = ByteArrayInputStream("hello, world!".toByteArray(UTF_8)) val buffer = Buffer().also { it.writeUtf8("a".repeat(SEGMENT_SIZE - 10)) } - buffer.readFrom(input) + buffer.transferFrom(input) val out = buffer.readUtf8() assertEquals("a".repeat(SEGMENT_SIZE - 10) + "hello, world!", out) } @@ -111,7 +111,7 @@ class BufferTest { fun readFromStreamWithCount() { val input: InputStream = ByteArrayInputStream("hello, world!".toByteArray(UTF_8)) val buffer = Buffer() - buffer.readFrom(input, 10) + buffer.write(input, 10) val out = buffer.readUtf8() assertEquals("hello, wor", out) } @@ -121,21 +121,21 @@ class BufferTest { val input = ByteArrayInputStream("hello, world!".toByteArray(UTF_8)) val buffer = Buffer() assertFailsWith { - buffer.readFrom(input, input.available() + 1L) + buffer.write(input, input.available() + 1L) } } @Test fun readFromStreamWithNegativeBytesCount() { assertFailsWith { - Buffer().readFrom(ByteArrayInputStream(ByteArray(1)), -1) + Buffer().write(ByteArrayInputStream(ByteArray(1)), -1) } } @Test fun readFromDoesNotLeaveEmptyTailSegment() { val buffer = Buffer() - buffer.readFrom(ByteArrayInputStream(ByteArray(SEGMENT_SIZE))) + buffer.transferFrom(ByteArrayInputStream(ByteArray(SEGMENT_SIZE))) assertNoEmptySegments(buffer) } diff --git a/core/jvm/test/JvmPlatformTest.kt b/core/jvm/test/JvmPlatformTest.kt index 730e27e8e..d059ada08 100644 --- a/core/jvm/test/JvmPlatformTest.kt +++ b/core/jvm/test/JvmPlatformTest.kt @@ -47,7 +47,7 @@ class JvmPlatformTest { val bais = ByteArrayInputStream(byteArrayOf(0x61)) val source = bais.source() val buffer = Buffer() - source.read(buffer, 1) + source.readAtMostTo(buffer, 1) assertEquals(buffer.readUtf8(), "a") } @@ -73,7 +73,7 @@ class JvmPlatformTest { file.writeText("a") val buffer = Buffer() file.source().use { source -> - source.read(buffer, 1L) + source.readAtMostTo(buffer, 1L) } assertEquals(buffer.readUtf8(), "a") } @@ -100,7 +100,7 @@ class JvmPlatformTest { file.writeText("a") val buffer = Buffer() file.toPath().source().use { source -> - source.read(buffer, 1L) + source.readAtMostTo(buffer, 1L) } assertEquals(buffer.readUtf8(), "a") } @@ -140,7 +140,7 @@ class JvmPlatformTest { } val source = socket.source() val buffer = Buffer() - source.read(buffer, 1L) + source.readAtMostTo(buffer, 1L) assertEquals(buffer.readUtf8(), "a") } } diff --git a/core/native/src/files/PathsNative.kt b/core/native/src/files/PathsNative.kt index e2c93b8f2..eb241cffd 100644 --- a/core/native/src/files/PathsNative.kt +++ b/core/native/src/files/PathsNative.kt @@ -35,7 +35,7 @@ internal class FileSource( ) : RawSource { private var closed = false - override fun read( + override fun readAtMostTo( sink: Buffer, byteCount: Long ): Long { From c3720f16bc269977600a7b0ecc07c225f516a3e3 Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Mon, 19 Jun 2023 11:39:59 +0200 Subject: [PATCH 56/83] Use start + end indices instead of offset + byteCount --- core/common/src/-Util.kt | 16 +++++++- core/common/src/Buffer.kt | 53 ++++++++++++++------------ core/common/src/PeekSource.kt | 2 +- core/common/src/RealSink.kt | 8 ++-- core/common/src/RealSource.kt | 11 +++--- core/common/src/Sink.kt | 10 ++--- core/common/src/Source.kt | 18 +++++---- core/common/src/SourceExt.kt | 29 +++++++------- core/common/src/Utf8.kt | 37 ++++++++---------- core/common/test/AbstractSinkTest.kt | 14 +++---- core/common/test/AbstractSourceTest.kt | 18 ++++++--- core/common/test/CommonBufferTest.kt | 38 +++++++++++------- core/common/test/Utf8Test.kt | 4 +- core/jvm/src/BufferExtJvm.kt | 21 +++++----- core/jvm/src/SinkExtJvm.kt | 21 +++++----- core/jvm/src/SourceExtJvm.kt | 4 +- core/jvm/test/AbstractSinkTestJVM.kt | 2 +- core/jvm/test/BufferTest.kt | 16 ++++---- 18 files changed, 175 insertions(+), 147 deletions(-) diff --git a/core/common/src/-Util.kt b/core/common/src/-Util.kt index 55b3dca6a..66f0bf24b 100644 --- a/core/common/src/-Util.kt +++ b/core/common/src/-Util.kt @@ -27,8 +27,20 @@ internal val HEX_DIGIT_CHARS = charArrayOf('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f') internal fun checkOffsetAndCount(size: Long, offset: Long, byteCount: Long) { - if (offset or byteCount < 0 || offset > size || size - offset < byteCount) { - throw IllegalArgumentException("size=$size offset=$offset byteCount=$byteCount") + if (offset < 0 || offset > size || size - offset < byteCount || byteCount < 0) { + throw IllegalArgumentException("offset: $offset, byteCount: $byteCount, size: $size") + } +} + +internal inline fun checkBounds(size: Int, startIndex: Int, endIndex: Int) = + checkBounds(size.toLong(), startIndex.toLong(), endIndex.toLong()) + +internal fun checkBounds(size: Long, startIndex: Long, endIndex: Long) { + if (startIndex < 0 || endIndex > size) { + throw IndexOutOfBoundsException("startIndex: $startIndex, endIndex: $endIndex, size: $size") + } + if (startIndex > endIndex) { + throw IllegalArgumentException("startIndex: $startIndex > endIndex: $endIndex") } } diff --git a/core/common/src/Buffer.kt b/core/common/src/Buffer.kt index 76065a8df..9ddbeb5a3 100644 --- a/core/common/src/Buffer.kt +++ b/core/common/src/Buffer.kt @@ -198,24 +198,26 @@ public class Buffer : Source, Sink { override fun flush(): Unit = Unit /** - * Copy [byteCount] bytes from this buffer, starting at [offset], to [out] buffer. + * Copy bytes from this buffer's subrange starting at [startIndex] and ending at [endIndex], to [out] buffer. * * @param out the destination buffer to copy data into. - * @param offset the offset to the first byte of data in this buffer to start copying from. - * @param byteCount the number of bytes to copy. + * @param startIndex the index (inclusive) of the first byte of data in this buffer to copy, + * 0 by default. + * @param endIndex the index (exclusive) of the last byte of data in this buffer to copy, `buffer.size` by default. * - * @throws IllegalArgumentException when [offset] and [byteCount] correspond to a range out of this buffer bounds. + * @throws IndexOutOfBoundsException when [startIndex] or [endIndex] is out of range of buffer indices. + * @throws IllegalArgumentException when `startIndex > endIndex`. */ public fun copyTo( out: Buffer, - offset: Long = 0L, - byteCount: Long = size - offset + startIndex: Long = 0L, + endIndex: Long = size ) { - checkOffsetAndCount(size, offset, byteCount) - if (byteCount == 0L) return + checkBounds(size, startIndex, endIndex) + if (startIndex == endIndex) return - var currentOffset = offset - var remainingByteCount = byteCount + var currentOffset = startIndex + var remainingByteCount = endIndex - startIndex out.size += remainingByteCount @@ -268,10 +270,12 @@ public class Buffer : Source, Sink { * Use of this method may expose significant performance penalties and it's not recommended to use it * for sequential access to a range of bytes within the buffer. * - * @throws IllegalArgumentException when [position] is out of this buffer's bounds. + * @throws IndexOutOfBoundsException when [position] is out of this buffer's bounds. */ public operator fun get(position: Long): Byte { - checkOffsetAndCount(size, position, 1L) + if (position < 0 || position >= size) { + throw IndexOutOfBoundsException("position: $position, size: $size") + } seek(position) { s, offset -> return s!!.data[(s.pos + position - offset).toInt()] } @@ -285,7 +289,7 @@ public class Buffer : Source, Sink { public fun clear(): Unit = skip(size) /** - * Discards [byteCount]` bytes from the head of this buffer. + * Discards [byteCount] bytes from the head of this buffer. * * @throws IllegalArgumentException when [byteCount] is negative. */ @@ -306,13 +310,13 @@ public class Buffer : Source, Sink { } } - override fun readAtMostTo(sink: ByteArray, offset: Int, byteCount: Int): Int { - checkOffsetAndCount(sink.size.toLong(), offset.toLong(), byteCount.toLong()) + override fun readAtMostTo(sink: ByteArray, startIndex: Int, endIndex: Int): Int { + checkBounds(sink.size, startIndex, endIndex) val s = head ?: return -1 - val toCopy = minOf(byteCount, s.limit - s.pos) + val toCopy = minOf(endIndex - startIndex, s.limit - s.pos) s.data.copyInto( - destination = sink, destinationOffset = offset, startIndex = s.pos, endIndex = s.pos + toCopy + destination = sink, destinationOffset = startIndex, startIndex = s.pos, endIndex = s.pos + toCopy ) s.pos += toCopy @@ -375,14 +379,13 @@ public class Buffer : Source, Sink { return tail } - override fun write(source: ByteArray, offset: Int, byteCount: Int): Unit { - var currentOffset = offset - checkOffsetAndCount(source.size.toLong(), currentOffset.toLong(), byteCount.toLong()) - val limit = currentOffset + byteCount - while (currentOffset < limit) { + override fun write(source: ByteArray, startIndex: Int, endIndex: Int) { + checkBounds(source.size, startIndex, endIndex) + var currentOffset = startIndex + while (currentOffset < endIndex) { val tail = writableSegment(1) - val toCopy = minOf(limit - currentOffset, Segment.SIZE - tail.limit) + val toCopy = minOf(endIndex - currentOffset, Segment.SIZE - tail.limit) source.copyInto( destination = tail.data, destinationOffset = tail.limit, @@ -393,7 +396,7 @@ public class Buffer : Source, Sink { currentOffset += toCopy tail.limit += toCopy } - size += byteCount.toLong() + size += endIndex - startIndex } override fun write(source: RawSource, byteCount: Long) { @@ -458,7 +461,6 @@ public class Buffer : Source, Sink { // yielding sink [51%, 91%, 30%] and source [62%, 82%]. require(source !== this) { "source == this" } - require(byteCount >= 0) { "byteCount ($byteCount) should not be negative." } checkOffsetAndCount(source.size, 0, byteCount) var remainingByteCount = byteCount @@ -625,6 +627,7 @@ public class Buffer : Source, Sink { } private fun ByteArray.hex(count: Int = this.size): String { + require(count >= 0) { "count: $count" } val builder = StringBuilder(count * 2) forEach { builder.append(HEX_DIGIT_CHARS[it.shr(4) and 0x0f]) diff --git a/core/common/src/PeekSource.kt b/core/common/src/PeekSource.kt index f94a34d81..fecb4bd0b 100644 --- a/core/common/src/PeekSource.kt +++ b/core/common/src/PeekSource.kt @@ -64,7 +64,7 @@ internal class PeekSource( } val toCopy = minOf(byteCount, buffer.size - pos) - buffer.copyTo(sink, pos, toCopy) + buffer.copyTo(sink, pos, pos + toCopy) pos += toCopy return toCopy } diff --git a/core/common/src/RealSink.kt b/core/common/src/RealSink.kt index 3433593c6..5785ee9a5 100644 --- a/core/common/src/RealSink.kt +++ b/core/common/src/RealSink.kt @@ -42,10 +42,10 @@ internal class RealSink( hintEmit() } - override fun write(source: ByteArray, offset: Int, byteCount: Int) { - checkOffsetAndCount(source.size.toLong(), offset.toLong(), byteCount.toLong()) + override fun write(source: ByteArray, startIndex: Int, endIndex: Int) { + checkBounds(source.size, startIndex, endIndex) check(!closed) { "closed" } - bufferField.write(source, offset, byteCount) + bufferField.write(source, startIndex, endIndex) hintEmit() } @@ -61,7 +61,7 @@ internal class RealSink( } override fun write(source: RawSource, byteCount: Long) { - require(byteCount >= 0) { "byteCount ($byteCount) should not be negative." } + require(byteCount >= 0) { "byteCount: $byteCount" } var remainingByteCount = byteCount while (remainingByteCount > 0L) { val read = source.readAtMostTo(bufferField, remainingByteCount) diff --git a/core/common/src/RealSource.kt b/core/common/src/RealSource.kt index 8bb7bbd39..41418fd28 100644 --- a/core/common/src/RealSource.kt +++ b/core/common/src/RealSource.kt @@ -57,7 +57,7 @@ internal class RealSource( } override fun request(byteCount: Long): Boolean { - require(byteCount >= 0L) { "byteCount < 0: $byteCount" } + require(byteCount >= 0L) { "byteCount: $byteCount" } check(!closed) { "closed" } while (bufferField.size < byteCount) { if (source.readAtMostTo(bufferField, Segment.SIZE.toLong()) == -1L) return false @@ -70,17 +70,15 @@ internal class RealSource( return bufferField.readByte() } - override fun readAtMostTo(sink: ByteArray, offset: Int, byteCount: Int): Int { - checkOffsetAndCount(sink.size.toLong(), offset.toLong(), byteCount.toLong()) + override fun readAtMostTo(sink: ByteArray, startIndex: Int, endIndex: Int): Int { + checkBounds(sink.size, startIndex, endIndex) if (bufferField.size == 0L) { val read = source.readAtMostTo(bufferField, Segment.SIZE.toLong()) if (read == -1L) return -1 } val toRead = minOf(endIndex - startIndex, bufferField.size).toInt() - //return bufferField.readAtMostTo(sink, startIndex, startIndex + toRead) - val toRead = minOf(byteCount, bufferField.size).toInt() - return bufferField.readAtMostTo(sink, offset, toRead) + return bufferField.readAtMostTo(sink, startIndex, startIndex + toRead) } override fun readTo(sink: RawSink, byteCount: Long) { @@ -126,6 +124,7 @@ internal class RealSource( } override fun skip(byteCount: Long) { + require(byteCount >= 0) { "byteCount: $byteCount" } var remainingByteCount = byteCount check(!closed) { "closed" } while (remainingByteCount > 0) { diff --git a/core/common/src/Sink.kt b/core/common/src/Sink.kt index 33e8eff4e..7b39dda90 100644 --- a/core/common/src/Sink.kt +++ b/core/common/src/Sink.kt @@ -58,14 +58,14 @@ public sealed interface Sink : RawSink { * Writes bytes from [source] array or its subrange to this sink. * * @param source the array from which bytes will be written into this sink. - * @param offset the beginning of data within the [source], 0 by default. - * @param byteCount the number of bytes to write, size of the [source] subarray starting at [offset] by default. + * @param startIndex the start index (inclusive) of the [source] subrange to be written, 0 by default. + * @param endIndex the endIndex (exclusive) of the [source] subrange to be written, size of the [source] by default. * - * @throws IllegalArgumentException when a range specified by [offset] and [byteCount] - * is out of range of [source] array indices. + * @throws IndexOutOfBoundsException when [startIndex] or [endIndex] is out of range of [source] array indices. + * @throws IllegalArgumentException when `startIndex > endIndex`. * @throws IllegalStateException when the sink is closed. */ - public fun write(source: ByteArray, offset: Int = 0, byteCount: Int = source.size - offset) + public fun write(source: ByteArray, startIndex: Int = 0, endIndex: Int = source.size) /** * Removes all bytes from [source] and write them to this sink. diff --git a/core/common/src/Source.kt b/core/common/src/Source.kt index 8b6289428..0752f095a 100644 --- a/core/common/src/Source.kt +++ b/core/common/src/Source.kt @@ -67,6 +67,7 @@ public sealed interface Source : RawSource { * * @throws EOFException when the source is exhausted before the required bytes count could be read. * @throws IllegalStateException when the source is closed. + * @throws IllegalArgumentException when [byteCount] is negative. */ public fun require(byteCount: Long) @@ -79,6 +80,7 @@ public sealed interface Source : RawSource { * * @param byteCount the number of bytes that the buffer should contain. * + * @throws IllegalArgumentException when [byteCount] is negative. * @throws IllegalStateException when the source is closed. */ public fun request(byteCount: Long): Boolean @@ -121,24 +123,24 @@ public sealed interface Source : RawSource { * @param byteCount the number of bytes to be skipped. * * @throws EOFException when the source is exhausted before the requested number of bytes can be skipped. + * @throws IllegalArgumentException when [byteCount] is negative. * @throws IllegalStateException when the source is closed. */ public fun skip(byteCount: Long) /** - * Removes up to [byteCount] bytes from this source, copies them into [sink] starting at [offset] and returns the - * number of bytes read, or -1 if this source is exhausted. + * Removes up to `endIndex - startIndex` bytes from this source, copies them into [sink] subrange starting at + * [startIndex] and ending at [endIndex], and returns the number of bytes read, or -1 if this source is exhausted. * * @param sink the array to which data will be written from this source. - * @param offset the offset to start writing data into [sink] at, 0 by default. - * @param byteCount the number of bytes that should be written into [sink], - * size of the [sink] subarray starting at [offset] by default. + * @param startIndex the startIndex (inclusive) of the [sink] subrange to read data into, 0 by default. + * @param endIndex the endIndex (exclusive) of the [sink] subrange to read data into, `sink.size` by default. * - * @throws IllegalArgumentException when a range specified by [offset] and [byteCount] - * is out of range of [sink] array indices. + * @throws IndexOutOfBoundsException when [startIndex] or [endIndex] is out of range of [sink] array indices. + * @throws IllegalArgumentException when `startIndex > endIndex`. * @throws IllegalStateException when the source is closed. */ - public fun readAtMostTo(sink: ByteArray, offset: Int = 0, byteCount: Int = sink.size - offset): Int + public fun readAtMostTo(sink: ByteArray, startIndex: Int = 0, endIndex: Int = sink.size): Int /** * Removes exactly [byteCount] bytes from this source and writes them to [sink]. diff --git a/core/common/src/SourceExt.kt b/core/common/src/SourceExt.kt index 1a7c71468..a3cfa86a8 100644 --- a/core/common/src/SourceExt.kt +++ b/core/common/src/SourceExt.kt @@ -148,32 +148,33 @@ public fun Source.readHexadecimalUnsignedLong(): Long { } /** - * Returns an index of [b] first occurrence in the range of [fromIndex] inclusive to [toIndex] - * exclusive, or `-1` when the range doesn't contain [b]. + * Returns an index of [b] first occurrence in the range of [startIndex] to [endIndex], + * or `-1` when the range doesn't contain [b]. * - * The scan terminates at either [toIndex] or source's exhaustion, whichever comes first. The + * The scan terminates at either [endIndex] or source's exhaustion, whichever comes first. The * maximum number of bytes scanned is `toIndex-fromIndex`. - * If [b] not found in buffered data, [toIndex] is yet to be reached and the underlying source is not yet exhausted + * If [b] not found in buffered data, [endIndex] is yet to be reached and the underlying source is not yet exhausted * then new data will be read from the underlying source into the buffer. * * @param b the value to find. - * @param fromIndex the start of the range to find [b], inclusive. - * @param toIndex the end of the range to find [b], exclusive. + * @param startIndex the start of the range (inclusive) to find [b], `0` by default. + * @param endIndex the end of the range (exclusive) to find [b], [Long.MAX_VALUE] by default. * * @throws IllegalStateException when the source is closed. + * @throws IllegalArgumentException when `startIndex > endIndex` or either of indices is negative. */ -public fun Source.indexOf(b: Byte, fromIndex: Long = 0L, toIndex: Long = Long.MAX_VALUE): Long { - require(fromIndex in 0..toIndex) - if (fromIndex == toIndex) return -1L +public fun Source.indexOf(b: Byte, startIndex: Long = 0L, endIndex: Long = Long.MAX_VALUE): Long { + require(startIndex in 0..endIndex) { "startIndex: $startIndex, endIndex: $endIndex" } + if (startIndex == endIndex) return -1L - var offset = fromIndex + var offset = startIndex val peekSource = peek() if (!peekSource.request(offset)) { return -1L } peekSource.skip(offset) - while (offset < toIndex && peekSource.request(1)) { + while (offset < endIndex && peekSource.request(1)) { if (peekSource.readByte() == b) return offset offset++ } @@ -194,12 +195,12 @@ public fun Source.readByteArray(): ByteArray { * * @param byteCount the number of bytes that should be read from the source. * - * @throws IllegalArgumentException when byteCount is negative. + * @throws IllegalArgumentException when [byteCount] is negative. * @throws EOFException when the underlying source is exhausted before [byteCount] bytes of data could be read. * @throws IllegalStateException when the source is closed. */ public fun Source.readByteArray(byteCount: Long): ByteArray { - check(byteCount >= 0) + require(byteCount >= 0) { "byteCount: $byteCount" } return readByteArrayImpl(byteCount) } @@ -307,7 +308,7 @@ public fun Source.readULongLe(): ULong = readLongLe().toULong() * Return `true` if the next byte to be consumed from this source is equal to [byte]. * Otherwise, return `false` as well as when the source is exhausted. * - * If there are no buffered data, this call will result in a fetch from the underlying source. + * If there is no buffered data, this call will result in a fetch from the underlying source. * * @throws IllegalStateException when the source is closed. */ diff --git a/core/common/src/Utf8.kt b/core/common/src/Utf8.kt index d87d55814..8db30e5bc 100644 --- a/core/common/src/Utf8.kt +++ b/core/common/src/Utf8.kt @@ -74,19 +74,17 @@ import kotlinx.io.internal.* /** * Returns the number of bytes used to encode the slice of `string` as UTF-8 when using [Sink.writeUtf8]. * - * @param beginIndex the index of the first character to encode, inclusive. - * @param endIndex the index of the character past the last character to encode, exclusive. + * @param startIndex the index (inclusive) of the first character to encode, `0` by default. + * @param endIndex the index (exclusive) of the character past the last character to encode, `string.length` by default. * - * @throws IllegalArgumentException when [beginIndex] or [endIndex] correspond to a range - * out of the current string bounds. + * @throws IndexOutOfBoundsException when [startIndex] or [endIndex] is out of range of string indices. + * @throws IllegalArgumentException when `startIndex > endIndex`. */ -public fun String.utf8Size(beginIndex: Int = 0, endIndex: Int = length): Long { - require(beginIndex >= 0) { "beginIndex < 0: $beginIndex" } - require(endIndex >= beginIndex) { "endIndex < beginIndex: $endIndex < $beginIndex" } - require(endIndex <= length) { "endIndex > string.length: $endIndex > $length" } +public fun String.utf8Size(startIndex: Int = 0, endIndex: Int = length): Long { + checkBounds(length, startIndex, endIndex) var result = 0L - var i = beginIndex + var i = startIndex while (i < endIndex) { val c = this[i].code @@ -131,19 +129,19 @@ public fun Sink.writeUtf8CodePoint(codePoint: Int): Unit = writeToInternalBuffer { it.commonWriteUtf8CodePoint(codePoint) } /** - * Encodes the characters at [beginIndex] up to [endIndex] from [string] in UTF-8 and writes it to this sink. + * Encodes the characters at [startIndex] up to [endIndex] from [string] in UTF-8 and writes it to this sink. * * @param string the string to be encoded. - * @param beginIndex the index of the first character to encode, 0 by default. - * @param endIndex the index of a character past to a last character to encode, `string.length` by default. + * @param startIndex the index (inclusive) of the first character to encode, 0 by default. + * @param endIndex the index (exclusive) of a character past to a last character to encode, `string.length` by default. * - * @throws IllegalArgumentException when [beginIndex] or [endIndex] correspond to a range - * out of the current string bounds. + * @throws IndexOutOfBoundsException when [startIndex] or [endIndex] is out of range of [string] indices. + * @throws IllegalArgumentException when `startIndex > endIndex`. * @throws IllegalStateException when the sink is closed. */ @OptIn(DelicateIoApi::class) -public fun Sink.writeUtf8(string: String, beginIndex: Int = 0, endIndex: Int = string.length): Unit = - writeToInternalBuffer { it.commonWriteUtf8(string, beginIndex, endIndex) } +public fun Sink.writeUtf8(string: String, startIndex: Int = 0, endIndex: Int = string.length): Unit = + writeToInternalBuffer { it.commonWriteUtf8(string, startIndex, endIndex) } /** * Removes all bytes from this source, decodes them as UTF-8, and returns the string. @@ -273,7 +271,7 @@ public fun Source.readUtf8Line(): String? { * @throws IllegalArgumentException when [limit] is negative. */ public fun Source.readUtf8LineStrict(limit: Long = Long.MAX_VALUE): String { - require(limit >= 0) { "limit is negative: $limit" } + require(limit >= 0) { "limit: $limit" } if (!request(1)) throw EOFException() val peekSource = peek() @@ -383,10 +381,7 @@ private fun Buffer.commonReadUtf8CodePoint(): Int { } private fun Buffer.commonWriteUtf8(string: String, beginIndex: Int, endIndex: Int) { - checkOffsetAndCount(string.length.toLong(), beginIndex.toLong(), (endIndex - beginIndex).toLong()) - //require(beginIndex >= 0) { "beginIndex < 0: $beginIndex" } - //require(endIndex >= beginIndex) { "endIndex < beginIndex: $endIndex < $beginIndex" } - //require(endIndex <= string.length) { "endIndex > string.length: $endIndex > ${string.length}" } + checkBounds(string.length, beginIndex, endIndex) // Transcode a UTF-16 Java String to UTF-8 bytes. var i = beginIndex diff --git a/core/common/test/AbstractSinkTest.kt b/core/common/test/AbstractSinkTest.kt index f3dbdcff5..52216af13 100644 --- a/core/common/test/AbstractSinkTest.kt +++ b/core/common/test/AbstractSinkTest.kt @@ -51,13 +51,13 @@ abstract class AbstractSinkTest internal constructor( assertEquals("[hex=000102]", data.toString()) data.clear() - assertFailsWith { - sink.write(source, -1, 1) + assertFailsWith { + sink.write(source, startIndex = -1, endIndex = 1) } assertEquals(0, data.size) - assertFailsWith { - sink.write(source, 1, source.size) + assertFailsWith { + sink.write(source, startIndex = 1, endIndex = source.size + 1) } assertEquals(0, data.size) } @@ -355,9 +355,9 @@ abstract class AbstractSinkTest internal constructor( } @Test fun writeUtf8WithInvalidIndexes() { - assertFailsWith { sink.writeUtf8("hello", -1) } - assertFailsWith { sink.writeUtf8("hello", 0, 6) } - assertFailsWith { sink.writeUtf8("hello", 6) } + assertFailsWith { sink.writeUtf8("hello", startIndex = -1) } + assertFailsWith { sink.writeUtf8("hello", startIndex = 0, endIndex = 6) } + assertFailsWith { sink.writeUtf8("hello", startIndex = 6) } } @Test fun writeUByte() { diff --git a/core/common/test/AbstractSourceTest.kt b/core/common/test/AbstractSourceTest.kt index c9c6b7d6b..ee48d419d 100644 --- a/core/common/test/AbstractSourceTest.kt +++ b/core/common/test/AbstractSourceTest.kt @@ -293,14 +293,14 @@ abstract class AbstractBufferedSourceTest internal constructor( // Either 0 or -1 is reasonable here. For consistency with Android's // ByteArrayInputStream we return 0. - assertEquals(-1, source.read(sink, 0)) + assertEquals(-1, source.readAtMostTo(sink, 0)) assertEquals(10, sink.size) assertTrue(source.exhausted()) } @Test fun readNegativeBytesFromSource() { assertFailsWith { - source.read(Buffer().also { it.writeByte(0) }, -1L) + source.readAtMostTo(Buffer().also { it.writeByte(0) }, -1L) } } @@ -426,7 +426,8 @@ abstract class AbstractBufferedSourceTest internal constructor( sink.emit() val sink = ByteArray(7) - val read = source.readAtMostTo(sink, 2, 3) + val bytesToRead = 3 + val read = source.readAtMostTo(sink, startIndex = 2, endIndex = 2 + bytesToRead) if (factory.isOneByteAtATime) { assertEquals(1, read.toLong()) val expected = byteArrayOf(0, 0, 'a'.code.toByte(), 0, 0, 0, 0) @@ -467,11 +468,11 @@ abstract class AbstractBufferedSourceTest internal constructor( source.readAtMostTo(sink, 4, 1) } - assertFailsWith { - source.readAtMostTo(sink, 1, 4) + assertFailsWith { + source.readAtMostTo(sink, 1, 5) } - assertFailsWith { + assertFailsWith { source.readAtMostTo(sink, -1, 2) } } @@ -500,6 +501,11 @@ abstract class AbstractBufferedSourceTest internal constructor( assertEquals("abc", source.readUtf8()) // The read shouldn't consume any data. } + @Test + fun readByteArrayWithNegativeSizeThrows() { + assertFailsWith { source.readByteArray(-20L) } + } + @Test fun readUtf8SpansSegments() { sink.writeUtf8("a".repeat(Segment.SIZE * 2)) sink.emit() diff --git a/core/common/test/CommonBufferTest.kt b/core/common/test/CommonBufferTest.kt index 92b73b972..8cfaa2c66 100644 --- a/core/common/test/CommonBufferTest.kt +++ b/core/common/test/CommonBufferTest.kt @@ -261,11 +261,19 @@ class CommonBufferTest { @Test fun getByteOfEmptyBuffer() { val buffer = Buffer() - assertFailsWith { + assertFailsWith { buffer[0] } } + @Test + fun getByteByInvalidIndex() { + val buffer = Buffer().also { it.write(ByteArray(10)) } + + assertFailsWith { buffer[-1] } + assertFailsWith { buffer[buffer.size] } + } + @Test fun writePrefixToEmptyBuffer() { val sink = Buffer() @@ -325,64 +333,66 @@ class CommonBufferTest { source.writeUtf8("party") val target = Buffer() - source.copyTo(target, 1, 3) + source.copyTo(target, startIndex = 1, endIndex = 4) assertEquals("art", target.readUtf8()) assertEquals("party", source.readUtf8()) } @Test fun copyToOnSegmentBoundary() { - val `as` = 'a'.repeat(Segment.SIZE) + val aStr = 'a'.repeat(Segment.SIZE) val bs = 'b'.repeat(Segment.SIZE) val cs = 'c'.repeat(Segment.SIZE) val ds = 'd'.repeat(Segment.SIZE) val source = Buffer() - source.writeUtf8(`as`) + source.writeUtf8(aStr) source.writeUtf8(bs) source.writeUtf8(cs) val target = Buffer() target.writeUtf8(ds) - source.copyTo(target, `as`.length.toLong(), (bs.length + cs.length).toLong()) + source.copyTo(target, startIndex = aStr.length.toLong(), + endIndex = aStr.length.toLong() + (bs.length + cs.length).toLong()) assertEquals(ds + bs + cs, target.readUtf8()) } @Test fun copyToOffSegmentBoundary() { - val `as` = 'a'.repeat(Segment.SIZE - 1) + val aStr = 'a'.repeat(Segment.SIZE - 1) val bs = 'b'.repeat(Segment.SIZE + 2) val cs = 'c'.repeat(Segment.SIZE - 4) val ds = 'd'.repeat(Segment.SIZE + 8) val source = Buffer() - source.writeUtf8(`as`) + source.writeUtf8(aStr) source.writeUtf8(bs) source.writeUtf8(cs) val target = Buffer() target.writeUtf8(ds) - source.copyTo(target, `as`.length.toLong(), (bs.length + cs.length).toLong()) + source.copyTo(target, startIndex = aStr.length.toLong(), + endIndex = aStr.length.toLong() + (bs.length + cs.length).toLong()) assertEquals(ds + bs + cs, target.readUtf8()) } @Test fun copyToSourceAndTargetCanBeTheSame() { - val `as` = 'a'.repeat(Segment.SIZE) + val aStr = 'a'.repeat(Segment.SIZE) val bs = 'b'.repeat(Segment.SIZE) val source = Buffer() - source.writeUtf8(`as`) + source.writeUtf8(aStr) source.writeUtf8(bs) - source.copyTo(source, 0, source.size) - assertEquals(`as` + bs + `as` + bs, source.readUtf8()) + source.copyTo(source, startIndex = 0, endIndex = source.size) + assertEquals(aStr + bs + aStr + bs, source.readUtf8()) } @Test fun copyToEmptySource() { val source = Buffer() val target = Buffer().also { it.writeUtf8("aaa") } - source.copyTo(target, 0L, 0L) + source.copyTo(target, startIndex = 0L, endIndex = 0L) assertEquals("", source.readUtf8()) assertEquals("aaa", target.readUtf8()) } @@ -390,7 +400,7 @@ class CommonBufferTest { @Test fun copyToEmptyTarget() { val source = Buffer().also { it.writeUtf8("aaa") } val target = Buffer() - source.copyTo(target, 0L, 3L) + source.copyTo(target, startIndex = 0L, endIndex = 3L) assertEquals("aaa", source.readUtf8()) assertEquals("aaa", target.readUtf8()) } diff --git a/core/common/test/Utf8Test.kt b/core/common/test/Utf8Test.kt index f92da04c8..8f0595b6c 100644 --- a/core/common/test/Utf8Test.kt +++ b/core/common/test/Utf8Test.kt @@ -189,7 +189,7 @@ class Utf8Test { } @Test fun sizeBoundsCheck() { - assertFailsWith { + assertFailsWith { "abc".utf8Size(-1, 2) } @@ -197,7 +197,7 @@ class Utf8Test { "abc".utf8Size(2, 1) } - assertFailsWith { + assertFailsWith { "abc".utf8Size(1, 4) } } diff --git a/core/jvm/src/BufferExtJvm.kt b/core/jvm/src/BufferExtJvm.kt index c90ab232e..454fae3f8 100644 --- a/core/jvm/src/BufferExtJvm.kt +++ b/core/jvm/src/BufferExtJvm.kt @@ -105,24 +105,25 @@ public fun Buffer.writeTo(out: OutputStream, byteCount: Long = size) { } /** - * Copy [byteCount] bytes from this buffer, starting at [offset], to [out]. + * Copy bytes from this buffer's subrange, starting at [startIndex] and ending at [endIndex], to [out]. * * @param out the destination to copy data into. - * @param offset the offset to start copying data from, `0` by default. - * @param byteCount the number of bytes to copy, all data starting from the [offset] by default. + * @param startIndex the index (inclusive) of the first byte to copy, `0` by default. + * @param endIndex the index (exclusive) of the last byte to copy, `buffer.size` by default. * - * @throws IllegalArgumentException when [byteCount] and [offset] represents a range out of the buffer bounds. + * @throws IndexOutOfBoundsException when [startIndex] or [endIndex] is out of range of buffer indices. + * @throws IllegalArgumentException when `startIndex > endIndex`. */ public fun Buffer.copyTo( out: OutputStream, - offset: Long = 0L, - byteCount: Long = size - offset + startIndex: Long = 0L, + endIndex: Long = size ) { - checkOffsetAndCount(size, offset, byteCount) - if (byteCount == 0L) return + checkBounds(size, startIndex, endIndex) + if (startIndex == endIndex) return - var currentOffset = offset - var remainingByteCount = byteCount + var currentOffset = startIndex + var remainingByteCount = endIndex - startIndex // Skip segments that we aren't copying from. var s = head diff --git a/core/jvm/src/SinkExtJvm.kt b/core/jvm/src/SinkExtJvm.kt index cd85844df..d4fffeba2 100644 --- a/core/jvm/src/SinkExtJvm.kt +++ b/core/jvm/src/SinkExtJvm.kt @@ -27,23 +27,22 @@ import java.nio.channels.WritableByteChannel import java.nio.charset.Charset /** - * Encodes substring of [string] starting at [beginIndex] and ending at [endIndex] using [charset] + * Encodes substring of [string] starting at [startIndex] and ending at [endIndex] using [charset] * and writes into this sink. * * @param string the string to encode into this sink. * @param charset the [Charset] to use for encoding. - * @param beginIndex the index of the first character to encode, inclusive, 0 by default. - * @param endIndex the index of the last character to encode, exclusive, `string.size` by default. + * @param startIndex the index of the first character to encode, inclusive, 0 by default. + * @param endIndex the index of the last character to encode, exclusive, `string.length` by default. * - * @throws IllegalArgumentException when [beginIndex] and [endIndex] correspond to a range out of [string] bounds. + * @throws IndexOutOfBoundsException when [startIndex] or [endIndex] is out of range of [string] indices. + * @throws IllegalArgumentException when `startIndex > endIndex`. * @throws IllegalStateException when the sink is closed. */ -public fun Sink.writeString(string: String, charset: Charset, beginIndex: Int = 0, endIndex: Int = string.length) { - require(beginIndex >= 0) { "beginIndex < 0: $beginIndex" } - require(endIndex >= beginIndex) { "endIndex < beginIndex: $endIndex < $beginIndex" } - require(endIndex <= string.length) { "endIndex > string.length: $endIndex > ${string.length}" } - if (charset == Charsets.UTF_8) return writeUtf8(string, beginIndex, endIndex) - val data = string.substring(beginIndex, endIndex).toByteArray(charset) +public fun Sink.writeString(string: String, charset: Charset, startIndex: Int = 0, endIndex: Int = string.length) { + checkBounds(string.length, startIndex, endIndex) + if (charset == Charsets.UTF_8) return writeUtf8(string, startIndex, endIndex) + val data = string.substring(startIndex, endIndex).toByteArray(charset) write(data, 0, data.size) } @@ -65,7 +64,7 @@ public fun Sink.outputStream(): OutputStream { override fun write(data: ByteArray, offset: Int, byteCount: Int) { if (isClosed()) throw IOException("closed") - writeToInternalBuffer { it.write(data, offset, byteCount) } + writeToInternalBuffer { it.write(data, offset, offset + byteCount) } } override fun flush() { diff --git a/core/jvm/src/SourceExtJvm.kt b/core/jvm/src/SourceExtJvm.kt index 5876235c9..4b6d6cbb7 100644 --- a/core/jvm/src/SourceExtJvm.kt +++ b/core/jvm/src/SourceExtJvm.kt @@ -73,7 +73,7 @@ public fun Source.readString(charset: Charset): String { * * @throws EOFException when the source exhausted before [byteCount] bytes could be read from it. * @throws IllegalStateException when the source is closed. - * @throws IllegalArgumentException if [byteCount] is negative. + * @throws IllegalArgumentException if [byteCount] is negative or its value is greater than [Int.MAX_VALUE]. */ @OptIn(InternalIoApi::class) public fun Source.readString(byteCount: Long, charset: Charset): String { @@ -104,7 +104,7 @@ public fun Source.inputStream(): InputStream { if (isClosed()) throw IOException("closed") checkOffsetAndCount(data.size.toLong(), offset.toLong(), byteCount.toLong()) - return this@inputStream.readAtMostTo(data, offset, byteCount) + return this@inputStream.readAtMostTo(data, offset, offset + byteCount) } override fun available(): Int { diff --git a/core/jvm/test/AbstractSinkTestJVM.kt b/core/jvm/test/AbstractSinkTestJVM.kt index 7e6009cad..571e3d101 100644 --- a/core/jvm/test/AbstractSinkTestJVM.kt +++ b/core/jvm/test/AbstractSinkTestJVM.kt @@ -51,7 +51,7 @@ abstract class AbstractSinkTestJVM internal constructor(factory: SinkFactory) { @Test fun outputStreamBounds() { val out: OutputStream = sink.outputStream() - assertFailsWith { + assertFailsWith { out.write(ByteArray(100), 50, 51) } } diff --git a/core/jvm/test/BufferTest.kt b/core/jvm/test/BufferTest.kt index 92188acd5..66c1c5d27 100644 --- a/core/jvm/test/BufferTest.kt +++ b/core/jvm/test/BufferTest.kt @@ -38,7 +38,7 @@ class BufferTest { source.writeUtf8("a".repeat(SEGMENT_SIZE * 2)) source.writeUtf8("b".repeat( SEGMENT_SIZE * 2)) val out = ByteArrayOutputStream() - source.copyTo(out, 10L, SEGMENT_SIZE * 3L) + source.copyTo(out, startIndex = 10L, endIndex = 10L + SEGMENT_SIZE * 3L) assertEquals("a".repeat( SEGMENT_SIZE * 2 - 10) + "b".repeat( SEGMENT_SIZE + 10), out.toString()) assertEquals("a".repeat(SEGMENT_SIZE * 2) + "b".repeat( SEGMENT_SIZE * 2), @@ -51,7 +51,7 @@ class BufferTest { source.writeUtf8("a".repeat(SEGMENT_SIZE * 2)) source.writeUtf8("b".repeat( SEGMENT_SIZE * 2)) val out = ByteArrayOutputStream() - source.copyTo(out, SEGMENT_SIZE * 2 + 1L, 3L) + source.copyTo(out, startIndex = SEGMENT_SIZE * 2 + 1L, endIndex = SEGMENT_SIZE * 2 + 4L) assertEquals("bbb", out.toString()) assertEquals("a".repeat(SEGMENT_SIZE * 2) + "b".repeat( SEGMENT_SIZE * 2), source.readUtf8(SEGMENT_SIZE * 4L)) @@ -176,32 +176,32 @@ class BufferTest { assertEquals("party", source.readUtf8()) } - @Test fun copyToOutputStreamWithOffset() { + @Test fun copyToOutputStreamWithStartIndex() { val source = Buffer() source.writeUtf8("party") val target = Buffer() - source.copyTo(target.outputStream(), offset = 2) + source.copyTo(target.outputStream(), startIndex = 2) assertEquals("rty", target.readUtf8()) assertEquals("party", source.readUtf8()) } - @Test fun copyToOutputStreamWithByteCount() { + @Test fun copyToOutputStreamWithEndIndex() { val source = Buffer() source.writeUtf8("party") val target = Buffer() - source.copyTo(target.outputStream(), byteCount = 3) + source.copyTo(target.outputStream(), endIndex = 3) assertEquals("par", target.readUtf8()) assertEquals("party", source.readUtf8()) } - @Test fun copyToOutputStreamWithOffsetAndByteCount() { + @Test fun copyToOutputStreamWithIndices() { val source = Buffer() source.writeUtf8("party") val target = Buffer() - source.copyTo(target.outputStream(), offset = 1, byteCount = 3) + source.copyTo(target.outputStream(), startIndex = 1, endIndex = 4) assertEquals("art", target.readUtf8()) assertEquals("party", source.readUtf8()) } From b29ea3cacaef23fa5f180f0580e0ce78288d749f Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Mon, 19 Jun 2023 11:45:19 +0200 Subject: [PATCH 57/83] Change readByteArray signature to avoid too large arrays allocation --- benchmarks/src/commonMain/kotlin/BufferOps.kt | 2 +- core/common/src/SourceExt.kt | 16 ++++++++-------- core/common/src/Utf8.kt | 2 +- core/common/test/AbstractSourceTest.kt | 2 +- core/jvm/src/SourceExtJvm.kt | 2 +- core/native/src/files/PathsNative.kt | 6 +++--- 6 files changed, 15 insertions(+), 15 deletions(-) diff --git a/benchmarks/src/commonMain/kotlin/BufferOps.kt b/benchmarks/src/commonMain/kotlin/BufferOps.kt index 30005b5eb..688819a2f 100644 --- a/benchmarks/src/commonMain/kotlin/BufferOps.kt +++ b/benchmarks/src/commonMain/kotlin/BufferOps.kt @@ -387,6 +387,6 @@ open class BufferReadNewByteArray: BufferRWBenchmarkBase() { @Benchmark fun benchmark(): ByteArray { buffer.write(inputArray) - return buffer.readByteArray(size.toLong()) + return buffer.readByteArray(size) } } diff --git a/core/common/src/SourceExt.kt b/core/common/src/SourceExt.kt index a3cfa86a8..3905e1301 100644 --- a/core/common/src/SourceExt.kt +++ b/core/common/src/SourceExt.kt @@ -187,7 +187,7 @@ public fun Source.indexOf(b: Byte, startIndex: Long = 0L, endIndex: Long = Long. * @throws IllegalStateException when the source is closed. */ public fun Source.readByteArray(): ByteArray { - return readByteArrayImpl( -1L) + return readByteArrayImpl( -1) } /** @@ -199,27 +199,27 @@ public fun Source.readByteArray(): ByteArray { * @throws EOFException when the underlying source is exhausted before [byteCount] bytes of data could be read. * @throws IllegalStateException when the source is closed. */ -public fun Source.readByteArray(byteCount: Long): ByteArray { +public fun Source.readByteArray(byteCount: Int): ByteArray { require(byteCount >= 0) { "byteCount: $byteCount" } return readByteArrayImpl(byteCount) } @OptIn(InternalIoApi::class) -private fun Source.readByteArrayImpl(size: Long): ByteArray { +private fun Source.readByteArrayImpl(size: Int): ByteArray { var arraySize = size - if (size == -1L) { + if (size == -1) { var fetchSize = Int.MAX_VALUE.toLong() while (buffer.size < Int.MAX_VALUE && request(fetchSize)) { fetchSize *= 2 } if (buffer.size >= Int.MAX_VALUE) { - throw IllegalStateException() + throw IllegalStateException("Can't create an array of size ${buffer.size}") } - arraySize = buffer.size + arraySize = buffer.size.toInt() } else { - require(size) + require(size.toLong()) } - val array = ByteArray(arraySize.toInt()) + val array = ByteArray(arraySize) buffer.readTo(array) return array } diff --git a/core/common/src/Utf8.kt b/core/common/src/Utf8.kt index 8db30e5bc..6cb180dee 100644 --- a/core/common/src/Utf8.kt +++ b/core/common/src/Utf8.kt @@ -513,7 +513,7 @@ private fun Buffer.commonReadUtf8(byteCount: Long): String { if (s.pos + byteCount > s.limit) { // If the string spans multiple segments, delegate to readBytes(). - return readByteArray(byteCount).commonToUtf8String() + return readByteArray(byteCount.toInt()).commonToUtf8String() } val result = s.data.commonToUtf8String(s.pos, s.pos + byteCount.toInt()) diff --git a/core/common/test/AbstractSourceTest.kt b/core/common/test/AbstractSourceTest.kt index ee48d419d..1d8161f54 100644 --- a/core/common/test/AbstractSourceTest.kt +++ b/core/common/test/AbstractSourceTest.kt @@ -503,7 +503,7 @@ abstract class AbstractBufferedSourceTest internal constructor( @Test fun readByteArrayWithNegativeSizeThrows() { - assertFailsWith { source.readByteArray(-20L) } + assertFailsWith { source.readByteArray(-20) } } @Test fun readUtf8SpansSegments() { diff --git a/core/jvm/src/SourceExtJvm.kt b/core/jvm/src/SourceExtJvm.kt index 4b6d6cbb7..8ce8926e6 100644 --- a/core/jvm/src/SourceExtJvm.kt +++ b/core/jvm/src/SourceExtJvm.kt @@ -34,7 +34,7 @@ private fun Buffer.readStringImpl(byteCount: Long, charset: Charset): String { val s = head!! if (s.pos + byteCount > s.limit) { // If the string spans multiple segments, delegate to readBytes(). - return String(readByteArray(byteCount), charset) + return String(readByteArray(byteCount.toInt()), charset) } val result = String(s.data, s.pos, byteCount.toInt(), charset) diff --git a/core/native/src/files/PathsNative.kt b/core/native/src/files/PathsNative.kt index eb241cffd..73cc2ed1f 100644 --- a/core/native/src/files/PathsNative.kt +++ b/core/native/src/files/PathsNative.kt @@ -5,8 +5,8 @@ package kotlinx.io.files -import kotlinx.io.* import kotlinx.cinterop.* +import kotlinx.io.* import platform.posix.* /* @@ -86,11 +86,11 @@ internal class FileSink( source: Buffer, byteCount: Long ) { - require(byteCount >= 0L) { "byteCount < 0: $byteCount" } + require(byteCount >= 0L) { "byteCount: $byteCount" } require(source.size >= byteCount) { "source.size=${source.size} < byteCount=$byteCount" } check(!closed) { "closed" } - val allContent = source.readByteArray(byteCount) + val allContent = source.readByteArray(byteCount.toInt()) // Copy bytes from that segment into the file. val bytesWritten = allContent.usePinned { pinned -> variantFwrite(pinned.addressOf(0), byteCount.toUInt(), file).toLong() From 76fc0ec5b6b810998c341f5cc0c9a5ad447b0770 Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Mon, 19 Jun 2023 12:23:41 +0200 Subject: [PATCH 58/83] Improve exception messages by adding more context to it --- core/common/src/Buffer.kt | 24 +++++++++++++++--------- core/common/src/PeekSource.kt | 2 +- core/common/src/RealSink.kt | 33 ++++++++++++++++++++++----------- core/common/src/RealSource.kt | 20 +++++++++++++------- core/common/src/SourceExt.kt | 4 ++-- core/common/src/Utf8.kt | 6 +++--- core/jvm/src/BufferExtJvm.kt | 2 +- core/jvm/src/SinkExtJvm.kt | 6 +++--- core/jvm/src/SourceExtJvm.kt | 10 ++++++---- 9 files changed, 66 insertions(+), 41 deletions(-) diff --git a/core/common/src/Buffer.kt b/core/common/src/Buffer.kt index 9ddbeb5a3..6cb910edd 100644 --- a/core/common/src/Buffer.kt +++ b/core/common/src/Buffer.kt @@ -58,13 +58,15 @@ public class Buffer : Source, Sink { override fun exhausted(): Boolean = size == 0L override fun require(byteCount: Long) { - if (size < byteCount) throw EOFException() + if (size < byteCount) { + throw EOFException("Buffer doesn't contain required number of bytes (size: $size, required: $byteCount)") + } } override fun request(byteCount: Long): Boolean = size >= byteCount override fun readByte(): Byte { - if (size == 0L) throw EOFException() + require(1) val segment = head!! var pos = segment.pos val limit = segment.limit @@ -81,7 +83,7 @@ public class Buffer : Source, Sink { } override fun readShort(): Short { - if (size < 2L) throw EOFException() + require(2) val segment = head!! var pos = segment.pos @@ -108,7 +110,7 @@ public class Buffer : Source, Sink { } override fun readInt(): Int { - if (size < 4L) throw EOFException() + require(4) val segment = head!! var pos = segment.pos @@ -144,7 +146,7 @@ public class Buffer : Source, Sink { } override fun readLong(): Long { - if (size < 8L) throw EOFException() + require(8) val segment = head!! var pos = segment.pos @@ -270,7 +272,7 @@ public class Buffer : Source, Sink { * Use of this method may expose significant performance penalties and it's not recommended to use it * for sequential access to a range of bytes within the buffer. * - * @throws IndexOutOfBoundsException when [position] is out of this buffer's bounds. + * @throws IndexOutOfBoundsException when [position] is negative or greater or equal to [Buffer.size]. */ public operator fun get(position: Long): Byte { if (position < 0 || position >= size) { @@ -294,9 +296,10 @@ public class Buffer : Source, Sink { * @throws IllegalArgumentException when [byteCount] is negative. */ override fun skip(byteCount: Long) { + require(byteCount >= 0) { "byteCount: $byteCount" } var remainingByteCount = byteCount while (remainingByteCount > 0) { - val head = head ?: throw EOFException() + val head = head ?: throw EOFException("Buffer exhausted before skipping $byteCount btyes.") val toSkip = minOf(remainingByteCount, head.limit - head.pos).toInt() size -= toSkip.toLong() @@ -342,7 +345,7 @@ public class Buffer : Source, Sink { require(byteCount >= 0) { "byteCount: $byteCount" } if (size < byteCount) { sink.write(this, size) // Exhaust ourselves. - throw EOFException() + throw EOFException("Buffer exhausted before writing $byteCount bytes. Only $size bytes were written.") } sink.write(this, byteCount) } @@ -404,7 +407,10 @@ public class Buffer : Source, Sink { var remainingByteCount = byteCount while (remainingByteCount > 0L) { val read = source.readAtMostTo(this, remainingByteCount) - if (read == -1L) throw EOFException() + if (read == -1L) { + throw EOFException("Source exhausted before reading $byteCount bytes. " + + "Only ${byteCount - remainingByteCount} were read.") + } remainingByteCount -= read } } diff --git a/core/common/src/PeekSource.kt b/core/common/src/PeekSource.kt index fecb4bd0b..d67f13078 100644 --- a/core/common/src/PeekSource.kt +++ b/core/common/src/PeekSource.kt @@ -42,8 +42,8 @@ internal class PeekSource( private var pos = 0L override fun readAtMostTo(sink: Buffer, byteCount: Long): Long { + check(!closed) { "Source is closed." } require(byteCount >= 0L) { "byteCount: $byteCount" } - check(!closed) { "closed" } // Source becomes invalid if there is an expected Segment and it and the expected position // do not match the current head and head position of the upstream buffer check( diff --git a/core/common/src/RealSink.kt b/core/common/src/RealSink.kt index 5785ee9a5..98cf5deaa 100644 --- a/core/common/src/RealSink.kt +++ b/core/common/src/RealSink.kt @@ -36,20 +36,21 @@ internal class RealSink( get() = bufferField override fun write(source: Buffer, byteCount: Long) { - require(byteCount >= 0) { "byteCount ($byteCount) should not be negative." } - check(!closed) { "closed" } + checkNotClosed() + require(byteCount >= 0) { "byteCount: $byteCount" } bufferField.write(source, byteCount) hintEmit() } override fun write(source: ByteArray, startIndex: Int, endIndex: Int) { + checkNotClosed() checkBounds(source.size, startIndex, endIndex) - check(!closed) { "closed" } bufferField.write(source, startIndex, endIndex) hintEmit() } override fun transferFrom(source: RawSource): Long { + checkNotClosed() var totalBytesRead = 0L while (true) { val readCount: Long = source.readAtMostTo(bufferField, Segment.SIZE.toLong()) @@ -61,55 +62,60 @@ internal class RealSink( } override fun write(source: RawSource, byteCount: Long) { + checkNotClosed() require(byteCount >= 0) { "byteCount: $byteCount" } var remainingByteCount = byteCount while (remainingByteCount > 0L) { val read = source.readAtMostTo(bufferField, remainingByteCount) - if (read == -1L) throw EOFException() + if (read == -1L) { + val bytesRead = byteCount - remainingByteCount + throw EOFException( + "Source exhausted before reading $byteCount bytes from it (number of bytes read: $bytesRead).") + } remainingByteCount -= read hintEmit() } } override fun writeByte(byte: Byte) { - check(!closed) { "closed" } + checkNotClosed() bufferField.writeByte(byte) hintEmit() } override fun writeShort(short: Short) { - check(!closed) { "closed" } + checkNotClosed() bufferField.writeShort(short) hintEmit() } override fun writeInt(int: Int) { - check(!closed) { "closed" } + checkNotClosed() bufferField.writeInt(int) hintEmit() } override fun writeLong(long: Long) { - check(!closed) { "closed" } + checkNotClosed() bufferField.writeLong(long) hintEmit() } @InternalIoApi override fun hintEmit() { - check(!closed) { "closed" } + checkNotClosed() val byteCount = bufferField.completeSegmentByteCount() if (byteCount > 0L) sink.write(bufferField, byteCount) } override fun emit() { - check(!closed) { "closed" } + checkNotClosed() val byteCount = bufferField.size if (byteCount > 0L) sink.write(bufferField, byteCount) } override fun flush() { - check(!closed) { "closed" } + checkNotClosed() if (bufferField.size > 0L) { sink.write(bufferField, bufferField.size) } @@ -142,4 +148,9 @@ internal class RealSink( } override fun toString() = "buffer($sink)" + + @Suppress("NOTHING_TO_INLINE") + private inline fun checkNotClosed() { + check(!closed) { "Sink is closed." } + } } diff --git a/core/common/src/RealSource.kt b/core/common/src/RealSource.kt index 41418fd28..ff7d72402 100644 --- a/core/common/src/RealSource.kt +++ b/core/common/src/RealSource.kt @@ -35,8 +35,8 @@ internal class RealSource( get() = bufferField override fun readAtMostTo(sink: Buffer, byteCount: Long): Long { - require(byteCount >= 0L) { "byteCount < 0: $byteCount" } - check(!closed) { "closed" } + checkNotClosed() + require(byteCount >= 0L) { "byteCount: $byteCount" } if (bufferField.size == 0L) { val read = source.readAtMostTo(bufferField, Segment.SIZE.toLong()) @@ -48,17 +48,17 @@ internal class RealSource( } override fun exhausted(): Boolean { - check(!closed) { "closed" } + checkNotClosed() return bufferField.exhausted() && source.readAtMostTo(bufferField, Segment.SIZE.toLong()) == -1L } override fun require(byteCount: Long) { - if (!request(byteCount)) throw EOFException() + if (!request(byteCount)) throw EOFException("Source doesn't contain required number of bytes ($byteCount).") } override fun request(byteCount: Long): Boolean { + checkNotClosed() require(byteCount >= 0L) { "byteCount: $byteCount" } - check(!closed) { "closed" } while (bufferField.size < byteCount) { if (source.readAtMostTo(bufferField, Segment.SIZE.toLong()) == -1L) return false } @@ -124,12 +124,13 @@ internal class RealSource( } override fun skip(byteCount: Long) { + checkNotClosed() require(byteCount >= 0) { "byteCount: $byteCount" } var remainingByteCount = byteCount - check(!closed) { "closed" } while (remainingByteCount > 0) { if (bufferField.size == 0L && source.readAtMostTo(bufferField, Segment.SIZE.toLong()) == -1L) { - throw EOFException() + throw EOFException("Source exhausted before skipping $byteCount bytes " + + "(only ${remainingByteCount - byteCount} bytes were skipped).") } val toSkip = minOf(remainingByteCount, bufferField.size) bufferField.skip(toSkip) @@ -147,4 +148,9 @@ internal class RealSource( } override fun toString(): String = "buffer($source)" + + @Suppress("NOTHING_TO_INLINE") + private inline fun checkNotClosed() { + check(!closed) { "Source is closed." } + } } diff --git a/core/common/src/SourceExt.kt b/core/common/src/SourceExt.kt index 3905e1301..47a72d09e 100644 --- a/core/common/src/SourceExt.kt +++ b/core/common/src/SourceExt.kt @@ -96,7 +96,7 @@ public fun Source.readDecimalLong(): Long { } if (seen < 1) { - if (!request(1)) throw EOFException() + require(1) val expected = if (negative) "Expected a digit" else "Expected a digit or '-'" throw NumberFormatException("$expected but was 0x${buffer[0].toHexString()}") } @@ -236,7 +236,7 @@ public fun Source.readTo(sink: ByteArray) { while (offset < sink.size) { val bytesRead = readAtMostTo(sink, offset) if (bytesRead == -1) { - throw EOFException() + throw EOFException("Source exhausted before reading ${sink.size} bytes. Only $bytesRead bytes were read.") } offset += bytesRead } diff --git a/core/common/src/Utf8.kt b/core/common/src/Utf8.kt index 6cb180dee..c207460e2 100644 --- a/core/common/src/Utf8.kt +++ b/core/common/src/Utf8.kt @@ -272,7 +272,7 @@ public fun Source.readUtf8Line(): String? { */ public fun Source.readUtf8LineStrict(limit: Long = Long.MAX_VALUE): String { require(limit >= 0) { "limit: $limit" } - if (!request(1)) throw EOFException() + require(1) val peekSource = peek() var offset = 0L @@ -306,7 +306,7 @@ public fun Source.readUtf8LineStrict(limit: Long = Long.MAX_VALUE): String { } private fun Buffer.commonReadUtf8CodePoint(): Int { - if (size == 0L) throw EOFException() + require(1) val b0 = this[0] var codePoint: Int @@ -506,7 +506,7 @@ private fun Buffer.commonWriteUtf8CodePoint(codePoint: Int) { private fun Buffer.commonReadUtf8(byteCount: Long): String { require(byteCount >= 0 && byteCount <= Int.MAX_VALUE) { "byteCount: $byteCount" } - if (size < byteCount) throw EOFException() + require(byteCount) if (byteCount == 0L) return "" val s = head!! diff --git a/core/jvm/src/BufferExtJvm.kt b/core/jvm/src/BufferExtJvm.kt index 454fae3f8..98100f5e5 100644 --- a/core/jvm/src/BufferExtJvm.kt +++ b/core/jvm/src/BufferExtJvm.kt @@ -66,7 +66,7 @@ private fun Buffer.readFrom(input: InputStream, byteCount: Long, forever: Boolea SegmentPool.recycle(tail) } if (forever) return - throw EOFException() + throw EOFException("Stream exhausted before $byteCount bytes were read.") } tail.limit += bytesRead size += bytesRead.toLong() diff --git a/core/jvm/src/SinkExtJvm.kt b/core/jvm/src/SinkExtJvm.kt index d4fffeba2..dc6362bbf 100644 --- a/core/jvm/src/SinkExtJvm.kt +++ b/core/jvm/src/SinkExtJvm.kt @@ -58,12 +58,12 @@ public fun Sink.outputStream(): OutputStream { return object : OutputStream() { override fun write(b: Int) { - if (isClosed()) throw IOException("closed") + if (isClosed()) throw IOException("Underlying sink is closed") writeToInternalBuffer { it.writeByte(b.toByte()) } } override fun write(data: ByteArray, offset: Int, byteCount: Int) { - if (isClosed()) throw IOException("closed") + if (isClosed()) throw IOException("Underlying sink is closed") writeToInternalBuffer { it.write(data, offset, offset + byteCount) } } @@ -113,7 +113,7 @@ public fun Sink.channel(): WritableByteChannel { override fun isOpen(): Boolean = !isClosed() override fun write(source: ByteBuffer): Int { - check(!isClosed()) { "closed" } + check(!isClosed()) { "Underlying sink is closed." } return this@channel.write(source) } } diff --git a/core/jvm/src/SourceExtJvm.kt b/core/jvm/src/SourceExtJvm.kt index 8ce8926e6..461de8dd5 100644 --- a/core/jvm/src/SourceExtJvm.kt +++ b/core/jvm/src/SourceExtJvm.kt @@ -28,7 +28,9 @@ import java.nio.charset.Charset private fun Buffer.readStringImpl(byteCount: Long, charset: Charset): String { require(byteCount >= 0 && byteCount <= Integer.MAX_VALUE) { "byteCount: $byteCount" } - if (size < byteCount) throw EOFException() + if (size < byteCount) { + throw EOFException("Buffer contains less bytes then required (byteCount: $byteCount, size: $size)") + } if (byteCount == 0L) return "" val s = head!! @@ -93,7 +95,7 @@ public fun Source.inputStream(): InputStream { return object : InputStream() { override fun read(): Int { - if (isClosed()) throw IOException("closed") + if (isClosed()) throw IOException("Underlying source is closed.") if (exhausted()) { return -1 } @@ -101,14 +103,14 @@ public fun Source.inputStream(): InputStream { } override fun read(data: ByteArray, offset: Int, byteCount: Int): Int { - if (isClosed()) throw IOException("closed") + if (isClosed()) throw IOException("Underlying source is closed.") checkOffsetAndCount(data.size.toLong(), offset.toLong(), byteCount.toLong()) return this@inputStream.readAtMostTo(data, offset, offset + byteCount) } override fun available(): Int { - if (isClosed()) throw IOException("closed") + if (isClosed()) throw IOException("Underlying source is closed.") return minOf(buffer.size, Integer.MAX_VALUE).toInt() } From 281f9c7ac3452c714e1664da0a58c04027117651 Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Mon, 19 Jun 2023 12:54:58 +0200 Subject: [PATCH 59/83] Fix readDecimalLong and readHexadecimalUnsignedLong behavior --- core/common/src/SourceExt.kt | 27 ++++++++++++++++---------- core/common/test/AbstractSourceTest.kt | 20 +++++++++++++++---- 2 files changed, 33 insertions(+), 14 deletions(-) diff --git a/core/common/src/SourceExt.kt b/core/common/src/SourceExt.kt index 47a72d09e..71d0be41b 100644 --- a/core/common/src/SourceExt.kt +++ b/core/common/src/SourceExt.kt @@ -53,12 +53,11 @@ internal const val OVERFLOW_DIGIT_START = Long.MIN_VALUE % 10L + 1 @OptIn(InternalIoApi::class) public fun Source.readDecimalLong(): Long { require(1) - var b = readByte() var negative = false var value = 0L var seen = 0 var overflowDigit = OVERFLOW_DIGIT_START - when (b) { + when (val b = buffer[0]) { '-'.code.toByte() -> { negative = true overflowDigit-- @@ -72,11 +71,13 @@ public fun Source.readDecimalLong(): Long { } } + val peekSource = peek() + peekSource.skip(1) + while (request(1)) { - b = buffer[0] + val b = peekSource.readByte() if (b in '0'.code..'9'.code) { val digit = '0'.code - b - readByte() // consume byte // Detect when the digit would cause an overflow. if (value < OVERFLOW_ZONE || value == OVERFLOW_ZONE && digit < overflowDigit) { @@ -101,6 +102,8 @@ public fun Source.readDecimalLong(): Long { throw NumberFormatException("$expected but was 0x${buffer[0].toHexString()}") } + skip(seen.toLong() + if (negative) 1 else 0) + return if (negative) value else -value } @@ -118,16 +121,19 @@ public fun Source.readDecimalLong(): Long { @OptIn(InternalIoApi::class) public fun Source.readHexadecimalUnsignedLong(): Long { require(1) - var b = readByte() - var result = when (b) { + var result = when (val b = buffer[0]) { in '0'.code..'9'.code -> b - '0'.code in 'a'.code..'f'.code -> b - 'a'.code + 10 in 'A'.code..'F'.code -> b - 'A'.code + 10 else -> throw NumberFormatException("Expected leading [0-9a-fA-F] character but was 0x${b.toHexString()}") }.toLong() - while (request(1)) { - b = buffer[0] + val peekSource = peek() + peekSource.skip(1) + var bytesRead = 1 + + while (peekSource.request(1)) { + val b = peekSource.readByte() val bDigit = when (b) { in '0'.code..'9'.code -> b - '0'.code in 'a'.code..'f'.code -> b - 'a'.code + 10 @@ -135,15 +141,16 @@ public fun Source.readHexadecimalUnsignedLong(): Long { else -> break } if (result and -0x1000000000000000L != 0L) { - with(Buffer()){ + with(Buffer()) { writeHexadecimalUnsignedLong(result) writeByte(b) throw NumberFormatException("Number too large: " + readUtf8()) } } - readByte() // consume byte result = result.shl(4) + bDigit + bytesRead++ } + skip(bytesRead.toLong()) return result } diff --git a/core/common/test/AbstractSourceTest.kt b/core/common/test/AbstractSourceTest.kt index 1d8161f54..9aa23b938 100644 --- a/core/common/test/AbstractSourceTest.kt +++ b/core/common/test/AbstractSourceTest.kt @@ -769,13 +769,15 @@ abstract class AbstractBufferedSourceTest internal constructor( } @Test fun longHexStringTooLongThrows() { - sink.writeUtf8("fffffffffffffffff") + val value = "fffffffffffffffff" + sink.writeUtf8(value) sink.emit() val e = assertFailsWith { source.readHexadecimalUnsignedLong() } assertEquals("Number too large: fffffffffffffffff", e.message) + assertEquals(value, source.readUtf8()) } @Test fun longHexStringTooShortThrows() { @@ -786,6 +788,7 @@ abstract class AbstractBufferedSourceTest internal constructor( source.readHexadecimalUnsignedLong() } assertEquals("Expected leading [0-9a-fA-F] character but was 0x20", e.message) + assertEquals(" ", source.readUtf8()) } @Test fun longHexEmptySourceThrows() { @@ -827,33 +830,39 @@ abstract class AbstractBufferedSourceTest internal constructor( } @Test fun longDecimalStringTooLongThrows() { - sink.writeUtf8("12345678901234567890") // Too many digits. + val value = "12345678901234567890" + sink.writeUtf8(value) // Too many digits. sink.emit() val e = assertFailsWith { source.readDecimalLong() } assertEquals("Number too large: 12345678901234567890", e.message) + assertEquals(value, source.readUtf8()) } @Test fun longDecimalStringTooHighThrows() { - sink.writeUtf8("9223372036854775808") // Right size but cannot fit. + val value = "9223372036854775808" + sink.writeUtf8(value) // Right size but cannot fit. sink.emit() val e = assertFailsWith { source.readDecimalLong() } assertEquals("Number too large: 9223372036854775808", e.message) + assertEquals(value, source.readUtf8()) } @Test fun longDecimalStringTooLowThrows() { - sink.writeUtf8("-9223372036854775809") // Right size but cannot fit. + val value = "-9223372036854775809" + sink.writeUtf8(value) // Right size but cannot fit. sink.emit() val e = assertFailsWith { source.readDecimalLong() } assertEquals("Number too large: -9223372036854775809", e.message) + assertEquals(value, source.readUtf8()) } @Test fun longDecimalStringTooShortThrows() { @@ -864,6 +873,7 @@ abstract class AbstractBufferedSourceTest internal constructor( source.readDecimalLong() } assertEquals("Expected a digit or '-' but was 0x20", e.message) + assertEquals(" ", source.readUtf8()) } @Test fun longDecimalEmptyThrows() { @@ -880,6 +890,7 @@ abstract class AbstractBufferedSourceTest internal constructor( assertFailsWith { source.readDecimalLong() } + assertEquals("-", source.readUtf8()) } @Test fun longDecimalDashFollowedByNonDigitThrows() { @@ -888,6 +899,7 @@ abstract class AbstractBufferedSourceTest internal constructor( assertFailsWith { source.readDecimalLong() } + assertEquals("- ", source.readUtf8()) } @Test fun codePoints() { From 2de32096aaba3f403a4dd0f9af9cb241d3b23160 Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Mon, 19 Jun 2023 13:00:08 +0200 Subject: [PATCH 60/83] Cleanup --- core/common/src/Buffer.kt | 2 +- core/common/src/SourceExt.kt | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/core/common/src/Buffer.kt b/core/common/src/Buffer.kt index 6cb910edd..363d3281c 100644 --- a/core/common/src/Buffer.kt +++ b/core/common/src/Buffer.kt @@ -299,7 +299,7 @@ public class Buffer : Source, Sink { require(byteCount >= 0) { "byteCount: $byteCount" } var remainingByteCount = byteCount while (remainingByteCount > 0) { - val head = head ?: throw EOFException("Buffer exhausted before skipping $byteCount btyes.") + val head = head ?: throw EOFException("Buffer exhausted before skipping $byteCount bytes.") val toSkip = minOf(remainingByteCount, head.limit - head.pos).toInt() size -= toSkip.toLong() diff --git a/core/common/src/SourceExt.kt b/core/common/src/SourceExt.kt index 71d0be41b..0dc931612 100644 --- a/core/common/src/SourceExt.kt +++ b/core/common/src/SourceExt.kt @@ -219,9 +219,7 @@ private fun Source.readByteArrayImpl(size: Int): ByteArray { while (buffer.size < Int.MAX_VALUE && request(fetchSize)) { fetchSize *= 2 } - if (buffer.size >= Int.MAX_VALUE) { - throw IllegalStateException("Can't create an array of size ${buffer.size}") - } + check (buffer.size >= Int.MAX_VALUE) { "Can't create an array of size ${buffer.size}" } arraySize = buffer.size.toInt() } else { require(size.toLong()) From 0e7646e68b05708be7f1967c19d44b94dc5e7040 Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Mon, 19 Jun 2023 13:41:54 +0200 Subject: [PATCH 61/83] Document naming convention and methods behavior --- core/common/src/RawSink.kt | 1 + core/common/src/RawSource.kt | 2 ++ core/common/src/Sink.kt | 12 ++++++++++++ core/common/src/Source.kt | 24 ++++++++++++++++++++++++ 4 files changed, 39 insertions(+) diff --git a/core/common/src/RawSink.kt b/core/common/src/RawSink.kt index 2fff51a28..0889f5e81 100644 --- a/core/common/src/RawSink.kt +++ b/core/common/src/RawSink.kt @@ -30,6 +30,7 @@ package kotlinx.io * Most application code shouldn't operate on a raw sink directly, but rather on a buffered [Sink] which * is both more efficient and more convenient. Use [buffer] to wrap any raw sink with a buffer. * + * Implementors should abstain from throwing exceptions other than those that are documented for RawSink methods. */ @OptIn(ExperimentalStdlibApi::class) public expect interface RawSink : AutoCloseableAlias { diff --git a/core/common/src/RawSource.kt b/core/common/src/RawSource.kt index 9ecf6d6fa..68453346e 100644 --- a/core/common/src/RawSource.kt +++ b/core/common/src/RawSource.kt @@ -29,6 +29,8 @@ package kotlinx.io * * Most applications shouldn't operate on a raw source directly, but rather on a buffered [Source] which * is both more efficient and more convenient. Use [buffer] to wrap any raw source with a buffer. + * + * Implementors should abstain from throwing exceptions other than those that are documented for RawSource methods. */ @OptIn(ExperimentalStdlibApi::class) public interface RawSource : AutoCloseableAlias { diff --git a/core/common/src/Sink.kt b/core/common/src/Sink.kt index 7b39dda90..c6c57d9e5 100644 --- a/core/common/src/Sink.kt +++ b/core/common/src/Sink.kt @@ -38,6 +38,18 @@ package kotlinx.io * The latter is aimed to reduce memory footprint by keeping the buffer as small as possible without excessive writes * to the upstream. * All write operations implicitly calls [hintEmit]. + * + * ### Write methods' behavior and naming conventions + * + * Methods writing a value of some type are usually name `write`, like [writeByte] or [writeInt], except methods + * writing data from a some collection of bytes, like `write(ByteArray, Int, Int)` or + * `write(source: RawSource, byteCount: Long)`. + * In the latter case, if a collection is consumable (i.e., once data was read from it will no longer be available for + * reading again), write method will consume as many bytes as it was requested to write. + * + * Methods fully consuming its argument are named `transferFrom`, like [transferFrom]. + * + * It is recommended to follow the same naming convention for Sink extensions. */ public sealed interface Sink : RawSink { /** diff --git a/core/common/src/Source.kt b/core/common/src/Source.kt index 0752f095a..07daa21cd 100644 --- a/core/common/src/Source.kt +++ b/core/common/src/Source.kt @@ -34,6 +34,30 @@ package kotlinx.io * using [require] or [request]. * [Sink] also allows skipping unneeded prefix of data using [skip] and * provides look ahead into incoming data, buffering as much as necessary, using [peek]. + * + * Source's read* methods have different guarantees of how much data will be consumed from the source + * and what to expect in case of error. + * + * ### Read methods' behavior and naming conventions + * + * Unless stated otherwise, all read methods consume the exact number of bytes + * requested (or the number of bytes required to represent a value of a requested type). + * If a source contains fewer bytes than requested, these methods will throw an exception. + * + * Methods reading up to requested number of bytes are named as `readAtMost` + * in contrast to methods reading exact number of bytes, which don't have `AtMost` suffix in their names. + * If a source contains fewer bytes than requested, these methods will not treat it as en error and will return + * gracefully. + * + * Methods returning a value as a result are named `read`, like [readInt] or [readByte]. + * These methods don't consume source's content in case of an error. + * + * Methods reading data into a consumer supplied as one of its arguments are named `read*To`, + * like [readTo] or [readAtMostTo]. These methods consume a source even when an error occurs. + * + * Methods moving all data from a source to some other sink are named `transferTo`, like [transferTo]. + * + * It is recommended to follow the same naming convention for Source extensions. */ public sealed interface Source : RawSource { /** From 5d3e19c9b020c0a086f365f6bf978ac8c9dbe19d Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Mon, 19 Jun 2023 14:13:53 +0200 Subject: [PATCH 62/83] Fixed typo --- core/common/src/SourceExt.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/common/src/SourceExt.kt b/core/common/src/SourceExt.kt index 0dc931612..1cce0c61c 100644 --- a/core/common/src/SourceExt.kt +++ b/core/common/src/SourceExt.kt @@ -219,7 +219,7 @@ private fun Source.readByteArrayImpl(size: Int): ByteArray { while (buffer.size < Int.MAX_VALUE && request(fetchSize)) { fetchSize *= 2 } - check (buffer.size >= Int.MAX_VALUE) { "Can't create an array of size ${buffer.size}" } + check (buffer.size < Int.MAX_VALUE) { "Can't create an array of size ${buffer.size}" } arraySize = buffer.size.toInt() } else { require(size.toLong()) From 115e8ea8c5b2379f8a35b95513a8a10ee9e631da Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Mon, 19 Jun 2023 14:13:59 +0200 Subject: [PATCH 63/83] Updated API dump --- core/api/kotlinx-io-core.api | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/core/api/kotlinx-io-core.api b/core/api/kotlinx-io-core.api index a204e6ebd..e2342c007 100644 --- a/core/api/kotlinx-io-core.api +++ b/core/api/kotlinx-io-core.api @@ -13,22 +13,22 @@ public final class kotlinx/io/Buffer : kotlinx/io/Sink, kotlinx/io/Source { public final fun getSize ()J public fun hintEmit ()V public fun peek ()Lkotlinx/io/Source; - public fun read (Lkotlinx/io/Buffer;J)J - public fun read ([BII)I - public fun readAll (Lkotlinx/io/RawSink;)J + public fun readAtMostTo (Lkotlinx/io/Buffer;J)J + public fun readAtMostTo ([BII)I public fun readByte ()B - public fun readFully (Lkotlinx/io/RawSink;J)V public fun readInt ()I public fun readLong ()J public fun readShort ()S + public fun readTo (Lkotlinx/io/RawSink;J)V public fun request (J)Z public fun require (J)V public fun skip (J)V public fun toString ()Ljava/lang/String; + public fun transferFrom (Lkotlinx/io/RawSource;)J + public fun transferTo (Lkotlinx/io/RawSink;)J public fun write (Lkotlinx/io/Buffer;J)V public fun write (Lkotlinx/io/RawSource;J)V public fun write ([BII)V - public fun writeAll (Lkotlinx/io/RawSource;)J public fun writeByte (B)V public fun writeInt (I)V public fun writeLong (J)V @@ -39,11 +39,11 @@ public final class kotlinx/io/BufferExtJvmKt { public static final fun channel (Lkotlinx/io/Buffer;)Ljava/nio/channels/ByteChannel; public static final fun copyTo (Lkotlinx/io/Buffer;Ljava/io/OutputStream;JJ)V public static synthetic fun copyTo$default (Lkotlinx/io/Buffer;Ljava/io/OutputStream;JJILjava/lang/Object;)V - public static final fun readFrom (Lkotlinx/io/Buffer;Ljava/io/InputStream;)Lkotlinx/io/Buffer; - public static final fun readFrom (Lkotlinx/io/Buffer;Ljava/io/InputStream;J)Lkotlinx/io/Buffer; - public static final fun readFrom (Lkotlinx/io/Buffer;Ljava/nio/ByteBuffer;)Lkotlinx/io/Buffer; + public static final fun readAtMostTo (Lkotlinx/io/Buffer;Ljava/nio/ByteBuffer;)I + public static final fun transferFrom (Lkotlinx/io/Buffer;Ljava/io/InputStream;)Lkotlinx/io/Buffer; + public static final fun transferFrom (Lkotlinx/io/Buffer;Ljava/nio/ByteBuffer;)Lkotlinx/io/Buffer; + public static final fun write (Lkotlinx/io/Buffer;Ljava/io/InputStream;J)Lkotlinx/io/Buffer; public static final fun writeTo (Lkotlinx/io/Buffer;Ljava/io/OutputStream;J)V - public static final fun writeTo (Lkotlinx/io/Buffer;Ljava/nio/ByteBuffer;)I public static synthetic fun writeTo$default (Lkotlinx/io/Buffer;Ljava/io/OutputStream;JILjava/lang/Object;)V } @@ -79,7 +79,7 @@ public abstract interface class kotlinx/io/RawSink : java/io/Flushable, java/lan public abstract interface class kotlinx/io/RawSource : java/lang/AutoCloseable { public abstract fun close ()V - public abstract fun read (Lkotlinx/io/Buffer;J)J + public abstract fun readAtMostTo (Lkotlinx/io/Buffer;J)J } public abstract interface class kotlinx/io/Sink : kotlinx/io/RawSink { @@ -87,9 +87,9 @@ public abstract interface class kotlinx/io/Sink : kotlinx/io/RawSink { public abstract fun flush ()V public abstract fun getBuffer ()Lkotlinx/io/Buffer; public abstract fun hintEmit ()V + public abstract fun transferFrom (Lkotlinx/io/RawSource;)J public abstract fun write (Lkotlinx/io/RawSource;J)V public abstract fun write ([BII)V - public abstract fun writeAll (Lkotlinx/io/RawSource;)J public abstract fun writeByte (B)V public abstract fun writeInt (I)V public abstract fun writeLong (J)V @@ -128,26 +128,26 @@ public abstract interface class kotlinx/io/Source : kotlinx/io/RawSource { public abstract fun exhausted ()Z public abstract fun getBuffer ()Lkotlinx/io/Buffer; public abstract fun peek ()Lkotlinx/io/Source; - public abstract fun read ([BII)I - public abstract fun readAll (Lkotlinx/io/RawSink;)J + public abstract fun readAtMostTo ([BII)I public abstract fun readByte ()B - public abstract fun readFully (Lkotlinx/io/RawSink;J)V public abstract fun readInt ()I public abstract fun readLong ()J public abstract fun readShort ()S + public abstract fun readTo (Lkotlinx/io/RawSink;J)V public abstract fun request (J)Z public abstract fun require (J)V public abstract fun skip (J)V + public abstract fun transferTo (Lkotlinx/io/RawSink;)J } public final class kotlinx/io/Source$DefaultImpls { - public static synthetic fun read$default (Lkotlinx/io/Source;[BIIILjava/lang/Object;)I + public static synthetic fun readAtMostTo$default (Lkotlinx/io/Source;[BIIILjava/lang/Object;)I } public final class kotlinx/io/SourceExtJvmKt { public static final fun channel (Lkotlinx/io/Source;)Ljava/nio/channels/ReadableByteChannel; public static final fun inputStream (Lkotlinx/io/Source;)Ljava/io/InputStream; - public static final fun read (Lkotlinx/io/Source;Ljava/nio/ByteBuffer;)I + public static final fun readAtMostTo (Lkotlinx/io/Source;Ljava/nio/ByteBuffer;)I public static final fun readString (Lkotlinx/io/Source;JLjava/nio/charset/Charset;)Ljava/lang/String; public static final fun readString (Lkotlinx/io/Source;Ljava/nio/charset/Charset;)Ljava/lang/String; } @@ -156,13 +156,13 @@ public final class kotlinx/io/SourceExtKt { public static final fun indexOf (Lkotlinx/io/Source;BJJ)J public static synthetic fun indexOf$default (Lkotlinx/io/Source;BJJILjava/lang/Object;)J public static final fun readByteArray (Lkotlinx/io/Source;)[B - public static final fun readByteArray (Lkotlinx/io/Source;J)[B + public static final fun readByteArray (Lkotlinx/io/Source;I)[B public static final fun readDecimalLong (Lkotlinx/io/Source;)J - public static final fun readFully (Lkotlinx/io/Source;[B)V public static final fun readHexadecimalUnsignedLong (Lkotlinx/io/Source;)J public static final fun readIntLe (Lkotlinx/io/Source;)I public static final fun readLongLe (Lkotlinx/io/Source;)J public static final fun readShortLe (Lkotlinx/io/Source;)S + public static final fun readTo (Lkotlinx/io/Source;[B)V public static final fun readUByte (Lkotlinx/io/Source;)B public static final fun readUInt (Lkotlinx/io/Source;)I public static final fun readUIntLe (Lkotlinx/io/Source;)I From 31821a7943183db8bf2b310eb8b77475115c9c8a Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Mon, 19 Jun 2023 14:47:27 +0200 Subject: [PATCH 64/83] Fix arguments handling and closed-state checks --- core/common/src/Buffer.kt | 6 ++- core/common/src/RealSource.kt | 5 +- core/common/test/AbstractSinkTest.kt | 2 +- core/common/test/AbstractSourceTest.kt | 62 ++++++++++++++++++------ core/common/test/CommonRealSinkTest.kt | 24 ++------- core/common/test/CommonRealSourceTest.kt | 22 ++++----- 6 files changed, 70 insertions(+), 51 deletions(-) diff --git a/core/common/src/Buffer.kt b/core/common/src/Buffer.kt index 363d3281c..5cc9a1578 100644 --- a/core/common/src/Buffer.kt +++ b/core/common/src/Buffer.kt @@ -58,12 +58,16 @@ public class Buffer : Source, Sink { override fun exhausted(): Boolean = size == 0L override fun require(byteCount: Long) { + require(byteCount >= 0) { "byteCount: $byteCount" } if (size < byteCount) { throw EOFException("Buffer doesn't contain required number of bytes (size: $size, required: $byteCount)") } } - override fun request(byteCount: Long): Boolean = size >= byteCount + override fun request(byteCount: Long): Boolean { + require(byteCount >= 0) { "byteCount: $byteCount" } + return size >= byteCount + } override fun readByte(): Byte { require(1) diff --git a/core/common/src/RealSource.kt b/core/common/src/RealSource.kt index ff7d72402..e0539ad76 100644 --- a/core/common/src/RealSource.kt +++ b/core/common/src/RealSource.kt @@ -138,7 +138,10 @@ internal class RealSource( } } - override fun peek(): Source = PeekSource(this).buffer() + override fun peek(): Source { + checkNotClosed() + return PeekSource(this).buffer() + } override fun close() { if (closed) return diff --git a/core/common/test/AbstractSinkTest.kt b/core/common/test/AbstractSinkTest.kt index 52216af13..cd5647db7 100644 --- a/core/common/test/AbstractSinkTest.kt +++ b/core/common/test/AbstractSinkTest.kt @@ -223,7 +223,7 @@ abstract class AbstractSinkTest internal constructor( assertEquals("abcd", data.readUtf8()) } - @Test fun writeBufferThrowsIEA() { + @Test fun writeBufferThrowsIAE() { val source: Buffer = Buffer() source.writeUtf8("abcd") diff --git a/core/common/test/AbstractSourceTest.kt b/core/common/test/AbstractSourceTest.kt index 9aa23b938..f42fed38f 100644 --- a/core/common/test/AbstractSourceTest.kt +++ b/core/common/test/AbstractSourceTest.kt @@ -92,6 +92,7 @@ abstract class AbstractBufferedSourceTest internal constructor( assertFailsWith { source.readShort() } + assertEquals(1, source.readByteArray().size) } @Test fun readShortLeTooShortThrows() { @@ -101,6 +102,7 @@ abstract class AbstractBufferedSourceTest internal constructor( assertFailsWith { source.readShortLe() } + assertEquals(1, source.readByteArray().size) } @Test fun readInt() { @@ -157,6 +159,7 @@ abstract class AbstractBufferedSourceTest internal constructor( assertFailsWith { source.readInt() } + assertEquals(3, source.readByteArray().size) } @Test fun readIntLeTooShortThrows() { @@ -166,6 +169,7 @@ abstract class AbstractBufferedSourceTest internal constructor( assertFailsWith { source.readIntLe() } + assertEquals(3, source.readByteArray().size) } @Test fun readLong() { @@ -249,6 +253,7 @@ abstract class AbstractBufferedSourceTest internal constructor( assertFailsWith { source.readLong() } + assertEquals(7, source.readByteArray().size) } @Test fun readLongLeTooShortThrows() { @@ -258,10 +263,11 @@ abstract class AbstractBufferedSourceTest internal constructor( assertFailsWith { source.readLongLe() } + assertEquals(7, source.readByteArray().size) } @OptIn(InternalIoApi::class) - @Test fun readAll() { + @Test fun transferTo() { source.buffer.writeUtf8("abc") sink.writeUtf8("def") sink.emit() @@ -272,7 +278,7 @@ abstract class AbstractBufferedSourceTest internal constructor( assertTrue(source.exhausted()) } - @Test fun readAllExhausted() { + @Test fun transferToExhausted() { val mockSink = MockSink() assertEquals(0, source.transferTo(mockSink)) assertTrue(source.exhausted()) @@ -300,7 +306,7 @@ abstract class AbstractBufferedSourceTest internal constructor( @Test fun readNegativeBytesFromSource() { assertFailsWith { - source.readAtMostTo(Buffer().also { it.writeByte(0) }, -1L) + source.readAtMostTo(Buffer(), -1L) } } @@ -311,11 +317,11 @@ abstract class AbstractBufferedSourceTest internal constructor( source.close() assertFailsWith { - source.readAtMostTo(Buffer().also { it.writeByte(0) }, 1L) + source.readAtMostTo(Buffer(), 1L) } } - @Test fun readFully() { + @Test fun readToSink() { sink.writeUtf8("a".repeat(10000)) sink.emit() val sink = Buffer() @@ -324,7 +330,7 @@ abstract class AbstractBufferedSourceTest internal constructor( assertEquals("a", source.readUtf8()) } - @Test fun readFullyTooShortThrows() { + @Test fun readToSinkTooShortThrows() { sink.writeUtf8("Hi") sink.emit() val sink = Buffer() @@ -334,22 +340,26 @@ abstract class AbstractBufferedSourceTest internal constructor( // Verify we read all that we could from the source. assertEquals("Hi", sink.readUtf8()) + assertTrue(source.exhausted()) } - @Test fun readFullyWithNegativeByteCount() { + @Test fun readToSinkWithNegativeByteCount() { val sink = Buffer() assertFailsWith { source.readTo(sink, -1) } } - @Test fun readFullyZeroBytes() { + @Test fun readToSinkZeroBytes() { + sink.writeUtf8("test") + sink.flush() val sink = Buffer() source.readTo(sink, 0) assertEquals(0, sink.size) + assertEquals("test", source.readUtf8()) } - @Test fun readFullyByteArray() { + @Test fun readToByteArray() { val data = Buffer() data.writeUtf8("Hello") data.writeUtf8("e".repeat(Segment.SIZE)) @@ -363,7 +373,7 @@ abstract class AbstractBufferedSourceTest internal constructor( assertArrayEquals(expected, sink) } - @Test fun readFullyByteArrayTooShortThrows() { + @Test fun readToByteArrayTooShortThrows() { sink.writeUtf8("Hello") sink.emit() @@ -386,7 +396,7 @@ abstract class AbstractBufferedSourceTest internal constructor( ) } - @Test fun readIntoByteArray() { + @Test fun readAtMostToByteArray() { sink.writeUtf8("abcd") sink.emit() @@ -403,7 +413,7 @@ abstract class AbstractBufferedSourceTest internal constructor( } } - @Test fun readIntoByteArrayNotEnough() { + @Test fun readAtMostToByteArrayNotEnough() { sink.writeUtf8("abcd") sink.emit() @@ -421,7 +431,7 @@ abstract class AbstractBufferedSourceTest internal constructor( } } - @Test fun readIntoByteArrayOffsetAndCount() { + @Test fun readAtMostToByteArrayOffsetAndCount() { sink.writeUtf8("abcd") sink.emit() @@ -440,7 +450,7 @@ abstract class AbstractBufferedSourceTest internal constructor( } } - @Test fun readIntoByteArrayOffset() { + @Test fun readAtMostToByteArrayFromOffset() { sink.writeUtf8("abcd") sink.emit() @@ -458,7 +468,7 @@ abstract class AbstractBufferedSourceTest internal constructor( } } - @Test fun readIntoByteArrayWithInvalidArguments() { + @Test fun readAtMostToByteArrayWithInvalidArguments() { sink.write(ByteArray(10)) sink.emit() @@ -562,6 +572,10 @@ abstract class AbstractBufferedSourceTest internal constructor( } } + @Test fun skipNegativeNumberOfBytes() { + assertFailsWith { source.skip(-1) } + } + @Test fun indexOf() { // The segment is empty. assertEquals(-1, source.indexOf('a'.code.toByte())) @@ -616,7 +630,7 @@ abstract class AbstractBufferedSourceTest internal constructor( assertEquals(15, source.indexOf('b'.code.toByte(), 15)) } - @Test fun indexOfByteWithBothOffsets() { + @Test fun indexOfByteWithIndices() { if (factory.isOneByteAtATime) { // When run on CI this causes out-of-memory errors. return @@ -702,6 +716,14 @@ abstract class AbstractBufferedSourceTest internal constructor( assertFalse(source.request((Segment.SIZE + 3).toLong())) } + @Test fun requestZeroBytes() { + assertTrue(source.request(0)) + } + + @Test fun requestNegativeNumberOfBytes() { + assertFailsWith { source.request(-1) } + } + @Test fun require() { with (sink) { writeUtf8("a") @@ -715,6 +737,14 @@ abstract class AbstractBufferedSourceTest internal constructor( } } + @Test fun requireZeroBytes() { + source.require(0L) // should not throw + } + + @Test fun requireNegativeNumberOfBytes() { + assertFailsWith { source.require(-1) } + } + @Test fun longHexString() { assertLongHexString("8000000000000000", Long.MIN_VALUE) assertLongHexString("fffffffffffffffe", -0x2L) diff --git a/core/common/test/CommonRealSinkTest.kt b/core/common/test/CommonRealSinkTest.kt index 8047e1ec3..a64dbc312 100644 --- a/core/common/test/CommonRealSinkTest.kt +++ b/core/common/test/CommonRealSinkTest.kt @@ -140,25 +140,11 @@ class CommonRealSinkTest { bufferedSink.close() // Test a sample set of methods. - assertFailsWith { - bufferedSink.writeByte('a'.code.toByte()) - } - - assertFailsWith { - bufferedSink.write(ByteArray(10)) - } - - assertFailsWith { - bufferedSink.hintEmit() - } - - assertFailsWith { - bufferedSink.emit() - } - - assertFailsWith { - bufferedSink.flush() - } + assertFailsWith { bufferedSink.writeByte('a'.code.toByte()) } + assertFailsWith { bufferedSink.write(ByteArray(10)) } + assertFailsWith { bufferedSink.hintEmit() } + assertFailsWith { bufferedSink.emit() } + assertFailsWith { bufferedSink.flush() } } @Test fun writeAll() { diff --git a/core/common/test/CommonRealSourceTest.kt b/core/common/test/CommonRealSourceTest.kt index 3c72148d0..826838b69 100644 --- a/core/common/test/CommonRealSourceTest.kt +++ b/core/common/test/CommonRealSourceTest.kt @@ -120,24 +120,20 @@ class CommonRealSourceTest { bufferedSource.close() // Test a sample set of methods. - assertFailsWith { - bufferedSource.indexOf(1.toByte()) - } - - assertFailsWith { - bufferedSource.skip(1) - } - - assertFailsWith { - bufferedSource.readByte() - } + assertFailsWith { bufferedSource.indexOf(1.toByte()) } + assertFailsWith { bufferedSource.skip(1) } + assertFailsWith { bufferedSource.readByte() } + assertFailsWith { bufferedSource.exhausted() } + assertFailsWith { bufferedSource.require(1) } + assertFailsWith { bufferedSource.readByteArray() } + assertFailsWith { bufferedSource.peek() } } /** - * We don't want readAll to buffer an unbounded amount of data. Instead it + * We don't want transferTo to buffer an unbounded amount of data. Instead it * should buffer a segment, write it, and repeat. */ - @Test fun readAllReadsOneSegmentAtATime() { + @Test fun transferToReadsOneSegmentAtATime() { val write1 = Buffer().also { it.writeUtf8("a".repeat(Segment.SIZE)) } val write2 = Buffer().also { it.writeUtf8("b".repeat(Segment.SIZE)) } val write3 = Buffer().also { it.writeUtf8("c".repeat(Segment.SIZE)) } From 0041f9f4c205ed34b0a279a9cdbb1bc0d9c3eeed Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Mon, 19 Jun 2023 16:06:58 +0200 Subject: [PATCH 65/83] Improve test coverage --- core/common/test/AbstractSinkTest.kt | 13 +++-- core/common/test/AbstractSourceTest.kt | 41 +++++++++++++++ core/common/test/CommonBufferTest.kt | 66 ++++++++++++++++++++++++ core/common/test/CommonRealSinkTest.kt | 15 ++++++ core/common/test/CommonRealSourceTest.kt | 41 +++++++++++++++ core/jvm/test/BufferTest.kt | 18 +++++++ core/jvm/test/JvmPlatformTest.kt | 39 ++++++++++++++ 7 files changed, 230 insertions(+), 3 deletions(-) diff --git a/core/common/test/AbstractSinkTest.kt b/core/common/test/AbstractSinkTest.kt index cd5647db7..e0f7cd4ab 100644 --- a/core/common/test/AbstractSinkTest.kt +++ b/core/common/test/AbstractSinkTest.kt @@ -224,7 +224,7 @@ abstract class AbstractSinkTest internal constructor( } @Test fun writeBufferThrowsIAE() { - val source: Buffer = Buffer() + val source = Buffer() source.writeUtf8("abcd") assertFailsWith { @@ -236,8 +236,15 @@ abstract class AbstractSinkTest internal constructor( } @Test fun writeSourceWithNegativeBytesCount() { - val source = Buffer() - source.writeByte(0) + val source: RawSource = Buffer().also { it.writeByte(0) } + + assertFailsWith { + sink.write(source, -1L) + } + } + + @Test fun writeBufferWithNegativeBytesCount() { + val source = Buffer().also { it.writeByte(0) } assertFailsWith { sink.write(source, -1L) diff --git a/core/common/test/AbstractSourceTest.kt b/core/common/test/AbstractSourceTest.kt index f42fed38f..5b866172f 100644 --- a/core/common/test/AbstractSourceTest.kt +++ b/core/common/test/AbstractSourceTest.kt @@ -23,6 +23,8 @@ package kotlinx.io import kotlin.test.* +private const val SEGMENT_SIZE = Segment.SIZE + class BufferSourceTest : AbstractBufferedSourceTest(SourceFactory.BUFFER) class RealBufferedSourceTest : AbstractBufferedSourceTest(SourceFactory.REAL_BUFFERED_SOURCE) class OneByteAtATimeBufferedSourceTest : AbstractBufferedSourceTest(SourceFactory.ONE_BYTE_AT_A_TIME_BUFFERED_SOURCE) @@ -321,6 +323,45 @@ abstract class AbstractBufferedSourceTest internal constructor( } } + @Test fun readAtMostToBufferFromSourceWithFilledBuffer() { + sink.writeByte(42) + sink.flush() + + source.request(1) + assertEquals(1, source.readAtMostTo(Buffer(), 128)) + } + + @Test fun readAtMostToNonEmptyBufferFromSourceWithFilledBuffer() { + if (factory.isOneByteAtATime) { + return + } + + val expectedReadSize = 123 + + sink.write(ByteArray(expectedReadSize)) + sink.flush() + + source.request(1) + val buffer = Buffer().also { it.write(ByteArray(SEGMENT_SIZE - expectedReadSize)) } + assertEquals(expectedReadSize.toLong(), source.readAtMostTo(buffer, SEGMENT_SIZE.toLong())) + + assertTrue(source.exhausted()) + sink.write(ByteArray(expectedReadSize)) + sink.flush() + + source.request(1) + buffer.clear() + assertEquals(42L, source.readAtMostTo(buffer, 42L)) + } + + @Test fun readAtMostToByteArrayFromSourceWithFilledBuffer() { + sink.writeByte(42) + sink.flush() + + source.request(1) + assertEquals(1, source.readAtMostTo(ByteArray(128))) + } + @Test fun readToSink() { sink.writeUtf8("a".repeat(10000)) sink.emit() diff --git a/core/common/test/CommonBufferTest.kt b/core/common/test/CommonBufferTest.kt index 8cfaa2c66..e475f0b87 100644 --- a/core/common/test/CommonBufferTest.kt +++ b/core/common/test/CommonBufferTest.kt @@ -62,6 +62,10 @@ class CommonBufferTest { assertEquals("[hex=0000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000]", Buffer().also { it.write(ByteArray(64)) }.toString()) + + assertEquals("[size=66 hex=000000000000000000000000000000000000000000000000000000000000" + + "00000000000000000000000000000000000000000000000000000000000000000000…]", + Buffer().also { it.write(ByteArray(66)) }.toString()) } @Test fun multipleSegmentBuffers() { @@ -204,6 +208,13 @@ class CommonBufferTest { assertEquals((Segment.SIZE * 2 - 20).toLong(), source.size) } + @Test fun writeSourceWithNegativeNumberOfBytes() { + val sink = Buffer() + val source: Source = Buffer() + + assertFailsWith { sink.write(source, -1L) } + } + @Test fun moveAllRequestedBytesWithRead() { val sink = Buffer() sink.writeUtf8('a'.repeat(10)) @@ -339,6 +350,39 @@ class CommonBufferTest { assertEquals("party", source.readUtf8()) } + @Test fun copyToAll() { + val source = Buffer() + source.writeUtf8("hello") + + val target = Buffer() + source.copyTo(target) + + assertEquals("hello", source.readUtf8()) + assertEquals("hello", target.readUtf8()) + } + + @Test fun copyToWithOnlyStartIndex() { + val source = Buffer() + source.writeUtf8("hello") + + val target = Buffer() + source.copyTo(target, startIndex = 1) + + assertEquals("hello", source.readUtf8()) + assertEquals("ello", target.readUtf8()) + } + + @Test fun copyToWithOnlyEndIndex() { + val source = Buffer() + source.writeUtf8("hello") + + val target = Buffer() + source.copyTo(target, endIndex = 1) + + assertEquals("hello", source.readUtf8()) + assertEquals("h", target.readUtf8()) + } + @Test fun copyToOnSegmentBoundary() { val aStr = 'a'.repeat(Segment.SIZE) val bs = 'b'.repeat(Segment.SIZE) @@ -471,4 +515,26 @@ class CommonBufferTest { assertEquals("a".repeat( SEGMENT_SIZE * 3) + "c".repeat(SEGMENT_SIZE * 3), clone.readUtf8((SEGMENT_SIZE * 6).toLong())) } + + @Test + fun readAndWriteToSelf() { + val buffer = Buffer().also { it.writeByte(1) } + val src: Source = buffer + val dst: Sink = buffer + + assertFailsWith { src.transferTo(dst) } + assertFailsWith { dst.transferFrom(src) } + assertFailsWith { src.readAtMostTo(buffer, 1) } + assertFailsWith { src.readTo(dst, 1) } + assertFailsWith { dst.write(buffer, 1) } + assertFailsWith { dst.write(src, 1) } + } + + @Test + fun transferCopy() { + val buffer = Buffer().also { it.writeByte(42) } + val copy = buffer.copy() + copy.transferTo(buffer) + assertArrayEquals(byteArrayOf(42, 42), buffer.readByteArray()) + } } diff --git a/core/common/test/CommonRealSinkTest.kt b/core/common/test/CommonRealSinkTest.kt index a64dbc312..0b3b5554a 100644 --- a/core/common/test/CommonRealSinkTest.kt +++ b/core/common/test/CommonRealSinkTest.kt @@ -187,4 +187,19 @@ class CommonRealSinkTest { "write($write3, ${write3.size})" ) } + + @Test fun closeMultipleTimes() { + var closeCalls = 0 + val rawSink: RawSink = object : RawSink { + override fun write(source: Buffer, byteCount: Long) = Unit + override fun flush() = Unit + override fun close() { closeCalls++ } + } + val sink = rawSink.buffer() + + sink.close() + assertFailsWith { sink.writeByte(0) } + sink.close() // should do nothing + assertEquals(1, closeCalls) + } } diff --git a/core/common/test/CommonRealSourceTest.kt b/core/common/test/CommonRealSourceTest.kt index 826838b69..eaa338d75 100644 --- a/core/common/test/CommonRealSourceTest.kt +++ b/core/common/test/CommonRealSourceTest.kt @@ -151,4 +151,45 @@ class CommonRealSourceTest { "write($write3, ${write3.size})" ) } + + @Test fun closeMultipleTimes() { + var closeCalls = 0 + val rawSource: RawSource = object : RawSource { + override fun readAtMostTo(sink: Buffer, byteCount: Long): Long = -1 + override fun close() { closeCalls++ } + } + val source = rawSource.buffer() + + source.close() + assertFailsWith { source.readByte() } + source.close() // should do nothing + assertEquals(1, closeCalls) + } + + @Test fun readAtMostFromEmptySource() { + val rawSource = object : RawSource { + override fun readAtMostTo(sink: Buffer, byteCount: Long): Long { return -1 } + override fun close() {} + } + + assertEquals(-1, rawSource.buffer().readAtMostTo(Buffer(), 1024)) + } + + @Test fun readAtMostFromFinite() { + val rawSource = object : RawSource { + var remainingBytes: Long = 10 + override fun readAtMostTo(sink: Buffer, byteCount: Long): Long { + if (remainingBytes == 0L) return -1 + val toWrite = minOf(remainingBytes, byteCount) + remainingBytes -= toWrite + sink.write(ByteArray(toWrite.toInt())) + return toWrite + } + override fun close() {} + } + + val source = rawSource.buffer() + assertEquals(10, source.readAtMostTo(Buffer(), 1024)) + assertEquals(-1, source.readAtMostTo(Buffer(), 1024)) + } } diff --git a/core/jvm/test/BufferTest.kt b/core/jvm/test/BufferTest.kt index 66c1c5d27..39f10b0e0 100644 --- a/core/jvm/test/BufferTest.kt +++ b/core/jvm/test/BufferTest.kt @@ -23,6 +23,7 @@ package kotlinx.io import java.io.ByteArrayInputStream import java.io.ByteArrayOutputStream import java.io.InputStream +import java.nio.ByteBuffer import java.util.* import kotlin.test.Test import kotlin.test.assertEquals @@ -206,6 +207,16 @@ class BufferTest { assertEquals("party", source.readUtf8()) } + @Test fun copyToOutputStreamWithEmptyRange() { + val source = Buffer() + source.writeUtf8("hello") + + val target = Buffer() + source.copyTo(target.outputStream(), startIndex = 1, endIndex = 1) + assertEquals("hello", source.readUtf8()) + assertEquals("", target.readUtf8()) + } + @Test fun writeToOutputStream() { val source = Buffer() source.writeUtf8("party") @@ -225,4 +236,11 @@ class BufferTest { assertEquals("par", target.readUtf8()) assertEquals("ty", source.readUtf8()) } + + @Test fun readEmptyBufferToByteBuffer() { + val bb = ByteBuffer.allocate(128) + val buffer = Buffer() + + assertEquals(-1, buffer.readAtMostTo(bb)) + } } diff --git a/core/jvm/test/JvmPlatformTest.kt b/core/jvm/test/JvmPlatformTest.kt index d059ada08..ddebfedcf 100644 --- a/core/jvm/test/JvmPlatformTest.kt +++ b/core/jvm/test/JvmPlatformTest.kt @@ -26,6 +26,7 @@ import java.io.ByteArrayInputStream import java.io.ByteArrayOutputStream import java.io.File import java.io.IOException +import java.lang.IllegalArgumentException import java.net.Socket import java.nio.file.Files import java.nio.file.LinkOption @@ -43,6 +44,30 @@ class JvmPlatformTest { assertArrayEquals(baos.toByteArray(), byteArrayOf(0x61)) } + @Test fun outputStreamSinkWriteZeroBytes() { + val baos = ByteArrayOutputStream() + val sink = baos.sink() + sink.write(Buffer().also { it.writeUtf8("a") }, 0L) + assertEquals(0, baos.size()) + } + + @Test fun outputStreamSinkWriteNegativeNumberOfBytes() { + val baos = ByteArrayOutputStream() + val sink = baos.sink() + assertFailsWith { + sink.write(Buffer().also { it.writeUtf8("a") }, -1) + } + } + + @Test fun outputStreamSinkWritePartOfTheBuffer() { + val baos = ByteArrayOutputStream() + val sink = baos.sink() + val buffer = Buffer().also { it.writeUtf8("hello") } + sink.write(buffer, 2) + assertArrayEquals(baos.toByteArray(), byteArrayOf('h'.code.toByte(), 'e'.code.toByte())) + assertEquals("llo", buffer.readUtf8()) + } + @Test fun inputStreamSource() { val bais = ByteArrayInputStream(byteArrayOf(0x61)) val source = bais.source() @@ -51,6 +76,20 @@ class JvmPlatformTest { assertEquals(buffer.readUtf8(), "a") } + @Test fun inputStreamSourceReadZeroBytes() { + val bais = ByteArrayInputStream(ByteArray(128)) + val source = bais.source() + val buffer = Buffer() + source.readAtMostTo(buffer, 0) + assertEquals(0, buffer.size) + } + + @Test fun inputStreamSourceReadNegativeNumberOfBytes() { + val bais = ByteArrayInputStream(ByteArray(128)) + val source = bais.source() + assertFailsWith { source.readAtMostTo(Buffer(), -1) } + } + @Test fun fileSink() { val file = File(tempDir, "test") file.sink().use { sink -> From 6a7a03fa71c9f443be6cc3db94b4b4038a02fbad Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Mon, 19 Jun 2023 16:07:09 +0200 Subject: [PATCH 66/83] Fixed Buffer::toString --- core/common/src/Buffer.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/common/src/Buffer.kt b/core/common/src/Buffer.kt index 5cc9a1578..982b74a08 100644 --- a/core/common/src/Buffer.kt +++ b/core/common/src/Buffer.kt @@ -639,9 +639,9 @@ public class Buffer : Source, Sink { private fun ByteArray.hex(count: Int = this.size): String { require(count >= 0) { "count: $count" } val builder = StringBuilder(count * 2) - forEach { - builder.append(HEX_DIGIT_CHARS[it.shr(4) and 0x0f]) - builder.append(HEX_DIGIT_CHARS[it and 0x0f]) + for (i in 0 until count) { + builder.append(HEX_DIGIT_CHARS[get(i).shr(4) and 0x0f]) + builder.append(HEX_DIGIT_CHARS[get(i) and 0x0f]) } return builder.toString() } From e6785c11fef04923c142cfbccc7999889799900f Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Mon, 19 Jun 2023 17:11:30 +0200 Subject: [PATCH 67/83] Rewrite hex- and decimal-long reading extensions w/o peek Slightly improved performance. Still, need to introduce an API replacing raw segments data access and rewrite these extensions using it to achieve the Okio performance level. --- core/common/src/SourceExt.kt | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/core/common/src/SourceExt.kt b/core/common/src/SourceExt.kt index 1cce0c61c..d0db19e46 100644 --- a/core/common/src/SourceExt.kt +++ b/core/common/src/SourceExt.kt @@ -52,12 +52,14 @@ internal const val OVERFLOW_DIGIT_START = Long.MIN_VALUE % 10L + 1 */ @OptIn(InternalIoApi::class) public fun Source.readDecimalLong(): Long { - require(1) + require(1L) + + var currIdx = 0L var negative = false var value = 0L var seen = 0 var overflowDigit = OVERFLOW_DIGIT_START - when (val b = buffer[0]) { + when (val b = buffer[currIdx++]) { '-'.code.toByte() -> { negative = true overflowDigit-- @@ -71,11 +73,8 @@ public fun Source.readDecimalLong(): Long { } } - val peekSource = peek() - peekSource.skip(1) - - while (request(1)) { - val b = peekSource.readByte() + while (request(currIdx + 1L)) { + val b = buffer[currIdx++] if (b in '0'.code..'9'.code) { val digit = '0'.code - b @@ -97,12 +96,12 @@ public fun Source.readDecimalLong(): Long { } if (seen < 1) { - require(1) + require(2) val expected = if (negative) "Expected a digit" else "Expected a digit or '-'" - throw NumberFormatException("$expected but was 0x${buffer[0].toHexString()}") + throw NumberFormatException("$expected but was 0x${buffer[1].toHexString()}") } - skip(seen.toLong() + if (negative) 1 else 0) + skip(currIdx.toLong() - 1) return if (negative) value else -value } @@ -128,12 +127,10 @@ public fun Source.readHexadecimalUnsignedLong(): Long { else -> throw NumberFormatException("Expected leading [0-9a-fA-F] character but was 0x${b.toHexString()}") }.toLong() - val peekSource = peek() - peekSource.skip(1) - var bytesRead = 1 + var bytesRead = 1L - while (peekSource.request(1)) { - val b = peekSource.readByte() + while (request(bytesRead + 1L)) { + val b = buffer[bytesRead] val bDigit = when (b) { in '0'.code..'9'.code -> b - '0'.code in 'a'.code..'f'.code -> b - 'a'.code + 10 From ada892756b495f5ada8348bf67b3be4f55978762 Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Tue, 20 Jun 2023 12:14:20 +0200 Subject: [PATCH 68/83] Support array's subrange in Source::readTo --- core/api/kotlinx-io-core.api | 3 ++- core/common/src/SourceExt.kt | 21 +++++++++++----- core/common/test/AbstractSourceTest.kt | 35 ++++++++++++++++++++++++++ 3 files changed, 52 insertions(+), 7 deletions(-) diff --git a/core/api/kotlinx-io-core.api b/core/api/kotlinx-io-core.api index e2342c007..a3037454f 100644 --- a/core/api/kotlinx-io-core.api +++ b/core/api/kotlinx-io-core.api @@ -162,7 +162,8 @@ public final class kotlinx/io/SourceExtKt { public static final fun readIntLe (Lkotlinx/io/Source;)I public static final fun readLongLe (Lkotlinx/io/Source;)J public static final fun readShortLe (Lkotlinx/io/Source;)S - public static final fun readTo (Lkotlinx/io/Source;[B)V + public static final fun readTo (Lkotlinx/io/Source;[BII)V + public static synthetic fun readTo$default (Lkotlinx/io/Source;[BIIILjava/lang/Object;)V public static final fun readUByte (Lkotlinx/io/Source;)B public static final fun readUInt (Lkotlinx/io/Source;)I public static final fun readUIntLe (Lkotlinx/io/Source;)I diff --git a/core/common/src/SourceExt.kt b/core/common/src/SourceExt.kt index d0db19e46..c59aa803c 100644 --- a/core/common/src/SourceExt.kt +++ b/core/common/src/SourceExt.kt @@ -228,17 +228,26 @@ private fun Source.readByteArrayImpl(size: Int): ByteArray { /** - * Removes exactly `sink.length` bytes from this source and copies them into [sink]. + * Removes exactly `endIndex - startIndex` bytes from this source and copies them into [sink] subrange starting at + * [startIndex] and ending at [endIndex]. + * + * @param sink the array to write data to + * @param startIndex the startIndex (inclusive) of the [sink] subrange to read data into, 0 by default. + * @param endIndex the endIndex (exclusive) of the [sink] subrange to read data into, `sink.size` by default. * * @throws EOFException when the requested number of bytes cannot be read. * @throws IllegalStateException when the source is closed. + * @throws IndexOutOfBoundsException when [startIndex] or [endIndex] is out of range of [sink] array indices. + * @throws IllegalArgumentException when `startIndex > endIndex`. */ -public fun Source.readTo(sink: ByteArray) { - var offset = 0 - while (offset < sink.size) { - val bytesRead = readAtMostTo(sink, offset) +public fun Source.readTo(sink: ByteArray, startIndex: Int = 0, endIndex: Int = sink.size) { + checkBounds(sink.size, startIndex, endIndex) + var offset = startIndex + while (offset < endIndex) { + val bytesRead = readAtMostTo(sink, offset, endIndex) if (bytesRead == -1) { - throw EOFException("Source exhausted before reading ${sink.size} bytes. Only $bytesRead bytes were read.") + throw EOFException("Source exhausted before reading ${endIndex - startIndex} bytes. " + + "Only $bytesRead bytes were read.") } offset += bytesRead } diff --git a/core/common/test/AbstractSourceTest.kt b/core/common/test/AbstractSourceTest.kt index 5b866172f..41ee3f63e 100644 --- a/core/common/test/AbstractSourceTest.kt +++ b/core/common/test/AbstractSourceTest.kt @@ -414,6 +414,41 @@ abstract class AbstractBufferedSourceTest internal constructor( assertArrayEquals(expected, sink) } + @Test fun readToByteArraySubrange() { + val buffer = Buffer() + val source: Source = buffer + + val sink = ByteArray(8) + + buffer.writeUtf8("hello") + source.readTo(sink, 0, 3) + assertContentEquals(byteArrayOf('h'.code.toByte(), 'e'.code.toByte(), 'l'.code.toByte(), 0, 0, 0, 0, 0), sink) + assertEquals("lo", source.readUtf8()) + + sink.fill(0) + buffer.writeUtf8("hello") + source.readTo(sink, 3) + assertContentEquals(byteArrayOf(0, 0, 0, 'h'.code.toByte(), 'e'.code.toByte(), 'l'.code.toByte(), 'l'.code.toByte(), + 'o'.code.toByte()), sink) + assertTrue(source.exhausted()) + + sink.fill(0) + buffer.writeUtf8("hello") + source.readTo(sink, 3, 4) + assertContentEquals(byteArrayOf(0, 0, 0, 'h'.code.toByte(), 0, 0, 0, 0), sink) + assertEquals("ello", source.readUtf8()) + } + + @Test fun readToByteArrayInvalidArguments() { + val source: Source = Buffer() + val sink = ByteArray(32) + + assertFailsWith { source.readTo(sink, 2, 0) } + assertFailsWith { source.readTo(sink, -1) } + assertFailsWith { source.readTo(sink, 33, endIndex = 34) } + assertFailsWith { source.readTo(sink, endIndex = 33) } + } + @Test fun readToByteArrayTooShortThrows() { sink.writeUtf8("Hello") sink.emit() From 873661eaa7b2a4b6a9bdf1fbedae3189416b0c48 Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Tue, 20 Jun 2023 12:15:51 +0200 Subject: [PATCH 69/83] Renamed Buffer::writeTo(OutputStream) to readTo --- core/api/kotlinx-io-core.api | 4 ++-- core/jvm/src/BufferExtJvm.kt | 10 +++++----- core/jvm/test/BufferTest.kt | 12 ++++++------ 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/core/api/kotlinx-io-core.api b/core/api/kotlinx-io-core.api index a3037454f..6f67eca57 100644 --- a/core/api/kotlinx-io-core.api +++ b/core/api/kotlinx-io-core.api @@ -40,11 +40,11 @@ public final class kotlinx/io/BufferExtJvmKt { public static final fun copyTo (Lkotlinx/io/Buffer;Ljava/io/OutputStream;JJ)V public static synthetic fun copyTo$default (Lkotlinx/io/Buffer;Ljava/io/OutputStream;JJILjava/lang/Object;)V public static final fun readAtMostTo (Lkotlinx/io/Buffer;Ljava/nio/ByteBuffer;)I + public static final fun readTo (Lkotlinx/io/Buffer;Ljava/io/OutputStream;J)V + public static synthetic fun readTo$default (Lkotlinx/io/Buffer;Ljava/io/OutputStream;JILjava/lang/Object;)V public static final fun transferFrom (Lkotlinx/io/Buffer;Ljava/io/InputStream;)Lkotlinx/io/Buffer; public static final fun transferFrom (Lkotlinx/io/Buffer;Ljava/nio/ByteBuffer;)Lkotlinx/io/Buffer; public static final fun write (Lkotlinx/io/Buffer;Ljava/io/InputStream;J)Lkotlinx/io/Buffer; - public static final fun writeTo (Lkotlinx/io/Buffer;Ljava/io/OutputStream;J)V - public static synthetic fun writeTo$default (Lkotlinx/io/Buffer;Ljava/io/OutputStream;JILjava/lang/Object;)V } public final class kotlinx/io/CoreKt { diff --git a/core/jvm/src/BufferExtJvm.kt b/core/jvm/src/BufferExtJvm.kt index 98100f5e5..b5dd03e26 100644 --- a/core/jvm/src/BufferExtJvm.kt +++ b/core/jvm/src/BufferExtJvm.kt @@ -33,7 +33,7 @@ import java.nio.channels.ByteChannel * @param input the stream to read data from. */ public fun Buffer.transferFrom(input: InputStream): Buffer { - readFrom(input, Long.MAX_VALUE, true) + write(input, Long.MAX_VALUE, true) return this } @@ -49,11 +49,11 @@ public fun Buffer.transferFrom(input: InputStream): Buffer { */ public fun Buffer.write(input: InputStream, byteCount: Long): Buffer { require(byteCount >= 0L) { "byteCount: $byteCount" } - readFrom(input, byteCount, false) + write(input, byteCount, false) return this } -private fun Buffer.readFrom(input: InputStream, byteCount: Long, forever: Boolean) { +private fun Buffer.write(input: InputStream, byteCount: Long, forever: Boolean) { var remainingByteCount = byteCount while (remainingByteCount > 0L || forever) { val tail = writableSegment(1) @@ -75,14 +75,14 @@ private fun Buffer.readFrom(input: InputStream, byteCount: Long, forever: Boolea } /** - * Writes [byteCount] bytes from this buffer to [out]. + * Consumes [byteCount] bytes from this buffer and writes it to [out]. * * @param out the [OutputStream] to write to. * @param byteCount the number of bytes to be written, [Buffer.size] by default. * * @throws IllegalArgumentException when [byteCount] is negative or exceeds the buffer size. */ -public fun Buffer.writeTo(out: OutputStream, byteCount: Long = size) { +public fun Buffer.readTo(out: OutputStream, byteCount: Long = size) { checkOffsetAndCount(size, 0, byteCount) var remainingByteCount = byteCount diff --git a/core/jvm/test/BufferTest.kt b/core/jvm/test/BufferTest.kt index 39f10b0e0..58d079e13 100644 --- a/core/jvm/test/BufferTest.kt +++ b/core/jvm/test/BufferTest.kt @@ -75,7 +75,7 @@ class BufferTest { buffer.writeUtf8("b".repeat(SEGMENT_SIZE * 2)) val out = ByteArrayOutputStream() buffer.skip(10) - buffer.writeTo(out, SEGMENT_SIZE * 3L) + buffer.readTo(out, SEGMENT_SIZE * 3L) assertEquals("a".repeat(SEGMENT_SIZE * 2 - 10) + "b".repeat(SEGMENT_SIZE + 10), out.toString()) assertEquals("b".repeat(SEGMENT_SIZE - 10), buffer.readUtf8(buffer.size)) } @@ -84,7 +84,7 @@ class BufferTest { fun writeToStream() { val buffer = Buffer().also { it.writeUtf8("hello, world!") } val out = ByteArrayOutputStream() - buffer.writeTo(out) + buffer.readTo(out) val outString = String(out.toByteArray(), UTF_8) assertEquals("hello, world!", outString) assertEquals(0, buffer.size) @@ -217,22 +217,22 @@ class BufferTest { assertEquals("", target.readUtf8()) } - @Test fun writeToOutputStream() { + @Test fun readToOutputStream() { val source = Buffer() source.writeUtf8("party") val target = Buffer() - source.writeTo(target.outputStream()) + source.readTo(target.outputStream()) assertEquals("party", target.readUtf8()) assertEquals("", source.readUtf8()) } - @Test fun writeToOutputStreamWithByteCount() { + @Test fun readToOutputStreamWithByteCount() { val source = Buffer() source.writeUtf8("party") val target = Buffer() - source.writeTo(target.outputStream(), byteCount = 3) + source.readTo(target.outputStream(), byteCount = 3) assertEquals("par", target.readUtf8()) assertEquals("ty", source.readUtf8()) } From 535a8f72a4d191bae7889cc8a8e003bebea2e262 Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Tue, 20 Jun 2023 12:24:03 +0200 Subject: [PATCH 70/83] Updated sample in module description --- core/Module.md | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/core/Module.md b/core/Module.md index d53b315e4..34f26be2c 100644 --- a/core/Module.md +++ b/core/Module.md @@ -28,18 +28,23 @@ data class Message(val timestamp: Long, val text: String) { fun Message.toBson(sink: Sink) { val buffer = Buffer() - buffer.writeByte(0x9) // UTC-timestamp field - .writeUtf8("timestamp").writeByte(0) // field name - .writeLongLe(timestamp) // field value - .writeByte(0x2) // string field - .writeUtf8("text").writeByte(0) // field name - .writeIntLe(text.utf8Size().toInt() + 1) // field value: length followed by the string - .writeUtf8(text).writeByte(0) - .writeByte(0) // end of BSON document + with (buffer) { + writeByte(0x9) // UTC-timestamp field + writeUtf8("timestamp") // field name + writeByte(0) + writeLongLe(timestamp) // field value + writeByte(0x2) // string field + writeUtf8("text") // field name + writeByte(0) + writeIntLe(text.utf8Size().toInt() + 1) // field value: length followed by the string + writeUtf8(text) + writeByte(0) + writeByte(0) // end of BSON document + } // Write document length and then its body sink.writeIntLe(buffer.size.toInt() + 4) - .writeAll(buffer) + buffer.transferTo(sink) sink.flush() } @@ -55,7 +60,7 @@ fun Message.Companion.fromBson(source: Source): Message { source.skip(1) // skip the terminator return fieldName } - + // for simplicity, let's assume that the order of fields matches serialization order var tag = source.readByte().toInt() // read the field type check(tag == 0x9 && readFieldName(source) == "timestamp") From dc7483a3e2fb3897aa0d1daefe5e8217148c997f Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Tue, 20 Jun 2023 12:40:07 +0200 Subject: [PATCH 71/83] Renamed unsigned int write methods to follow the common naming convention --- core/api/kotlinx-io-core.api | 14 +++++++------- core/common/src/SinkExt.kt | 14 +++++++------- core/common/test/AbstractSinkTest.kt | 14 +++++++------- 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/core/api/kotlinx-io-core.api b/core/api/kotlinx-io-core.api index 6f67eca57..e8eeaf7ed 100644 --- a/core/api/kotlinx-io-core.api +++ b/core/api/kotlinx-io-core.api @@ -109,19 +109,19 @@ public final class kotlinx/io/SinkExtJvmKt { } public final class kotlinx/io/SinkExtKt { - public static final fun writeByte-EK-6454 (Lkotlinx/io/Sink;B)V public static final fun writeDecimalLong (Lkotlinx/io/Sink;J)V public static final fun writeHexadecimalUnsignedLong (Lkotlinx/io/Sink;J)V - public static final fun writeInt-Qn1smSk (Lkotlinx/io/Sink;I)V public static final fun writeIntLe (Lkotlinx/io/Sink;I)V - public static final fun writeIntLe-Qn1smSk (Lkotlinx/io/Sink;I)V - public static final fun writeLong-2TYgG_w (Lkotlinx/io/Sink;J)V public static final fun writeLongLe (Lkotlinx/io/Sink;J)V - public static final fun writeLongLe-2TYgG_w (Lkotlinx/io/Sink;J)V - public static final fun writeShort-i8woANY (Lkotlinx/io/Sink;S)V public static final fun writeShortLe (Lkotlinx/io/Sink;S)V - public static final fun writeShortLe-i8woANY (Lkotlinx/io/Sink;S)V public static final fun writeToInternalBuffer (Lkotlinx/io/Sink;Lkotlin/jvm/functions/Function1;)V + public static final fun writeUByte-EK-6454 (Lkotlinx/io/Sink;B)V + public static final fun writeUInt-Qn1smSk (Lkotlinx/io/Sink;I)V + public static final fun writeUIntLe-Qn1smSk (Lkotlinx/io/Sink;I)V + public static final fun writeULong-2TYgG_w (Lkotlinx/io/Sink;J)V + public static final fun writeULongLe-2TYgG_w (Lkotlinx/io/Sink;J)V + public static final fun writeUShort-i8woANY (Lkotlinx/io/Sink;S)V + public static final fun writeUShortLe-i8woANY (Lkotlinx/io/Sink;S)V } public abstract interface class kotlinx/io/Source : kotlinx/io/RawSource { diff --git a/core/common/src/SinkExt.kt b/core/common/src/SinkExt.kt index efc85672d..0527b2e17 100644 --- a/core/common/src/SinkExt.kt +++ b/core/common/src/SinkExt.kt @@ -181,7 +181,7 @@ public fun Sink.writeHexadecimalUnsignedLong(long: Long) { * * @throws IllegalStateException when the sink is closed. */ -public fun Sink.writeByte(byte: UByte) { +public fun Sink.writeUByte(byte: UByte) { writeByte(byte.toByte()) } @@ -192,7 +192,7 @@ public fun Sink.writeByte(byte: UByte) { * * @throws IllegalStateException when the sink is closed. */ -public fun Sink.writeShort(short: UShort) { +public fun Sink.writeUShort(short: UShort) { writeShort(short.toShort()) } @@ -203,7 +203,7 @@ public fun Sink.writeShort(short: UShort) { * * @throws IllegalStateException when the sink is closed. */ -public fun Sink.writeInt(int: UInt) { +public fun Sink.writeUInt(int: UInt) { writeInt(int.toInt()) } @@ -214,7 +214,7 @@ public fun Sink.writeInt(int: UInt) { * * @throws IllegalStateException when the sink is closed. */ -public fun Sink.writeLong(long: ULong) { +public fun Sink.writeULong(long: ULong) { writeLong(long.toLong()) } @@ -225,7 +225,7 @@ public fun Sink.writeLong(long: ULong) { * * @throws IllegalStateException when the sink is closed. */ -public fun Sink.writeShortLe(short: UShort) { +public fun Sink.writeUShortLe(short: UShort) { writeShortLe(short.toShort()) } @@ -236,7 +236,7 @@ public fun Sink.writeShortLe(short: UShort) { * * @throws IllegalStateException when the sink is closed. */ -public fun Sink.writeIntLe(int: UInt) { +public fun Sink.writeUIntLe(int: UInt) { writeIntLe(int.toInt()) } @@ -247,7 +247,7 @@ public fun Sink.writeIntLe(int: UInt) { * * @throws IllegalStateException when the sink is closed. */ -public fun Sink.writeLongLe(long: ULong) { +public fun Sink.writeULongLe(long: ULong) { writeLongLe(long.toLong()) } diff --git a/core/common/test/AbstractSinkTest.kt b/core/common/test/AbstractSinkTest.kt index e0f7cd4ab..48685a31e 100644 --- a/core/common/test/AbstractSinkTest.kt +++ b/core/common/test/AbstractSinkTest.kt @@ -368,43 +368,43 @@ abstract class AbstractSinkTest internal constructor( } @Test fun writeUByte() { - sink.writeByte(0xffu) + sink.writeUByte(0xffu) sink.flush() assertEquals(-1, data.readByte()) } @Test fun writeUShort() { - sink.writeShort(0xffffu) + sink.writeUShort(0xffffu) sink.flush() assertEquals(-1, data.readShort()) } @Test fun writeUShortLe() { - sink.writeShortLe(0x1234u) + sink.writeUShortLe(0x1234u) sink.flush() assertEquals("[hex=3412]", data.toString()) } @Test fun writeUInt() { - sink.writeInt(0xffffffffu) + sink.writeUInt(0xffffffffu) sink.flush() assertEquals(-1, data.readInt()) } @Test fun writeUIntLe() { - sink.writeIntLe(0x12345678u) + sink.writeUIntLe(0x12345678u) sink.flush() assertEquals("[hex=78563412]", data.toString()) } @Test fun writeULong() { - sink.writeLong(0xffffffffffffffffu) + sink.writeULong(0xffffffffffffffffu) sink.flush() assertEquals(-1, data.readLong()) } @Test fun writeULongLe() { - sink.writeLongLe(0x1234567890abcdefu) + sink.writeULongLe(0x1234567890abcdefu) sink.flush() assertEquals("[hex=efcdab9078563412]", data.toString()) } From afcfd0819b6c15528fa82e6a2a85f1247de76873 Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Thu, 22 Jun 2023 12:18:46 +0200 Subject: [PATCH 72/83] Removed JVM-specific unnecessary extensions --- core/api/kotlinx-io-core.api | 7 ---- core/jvm/src/JvmCore.kt | 62 ++------------------------------ core/jvm/test/JvmPlatformTest.kt | 23 ++++++------ core/jvm/test/NioTest.kt | 10 ++++-- 4 files changed, 22 insertions(+), 80 deletions(-) diff --git a/core/api/kotlinx-io-core.api b/core/api/kotlinx-io-core.api index e8eeaf7ed..ce25f1ea8 100644 --- a/core/api/kotlinx-io-core.api +++ b/core/api/kotlinx-io-core.api @@ -60,15 +60,8 @@ public abstract interface annotation class kotlinx/io/InternalIoApi : java/lang/ } public final class kotlinx/io/JvmCoreKt { - public static final fun sink (Ljava/io/File;Z)Lkotlinx/io/RawSink; public static final fun sink (Ljava/io/OutputStream;)Lkotlinx/io/RawSink; - public static final fun sink (Ljava/net/Socket;)Lkotlinx/io/RawSink; - public static final fun sink (Ljava/nio/file/Path;[Ljava/nio/file/OpenOption;)Lkotlinx/io/RawSink; - public static synthetic fun sink$default (Ljava/io/File;ZILjava/lang/Object;)Lkotlinx/io/RawSink; - public static final fun source (Ljava/io/File;)Lkotlinx/io/RawSource; public static final fun source (Ljava/io/InputStream;)Lkotlinx/io/RawSource; - public static final fun source (Ljava/net/Socket;)Lkotlinx/io/RawSource; - public static final fun source (Ljava/nio/file/Path;[Ljava/nio/file/OpenOption;)Lkotlinx/io/RawSource; } public abstract interface class kotlinx/io/RawSink : java/io/Flushable, java/lang/AutoCloseable { diff --git a/core/jvm/src/JvmCore.kt b/core/jvm/src/JvmCore.kt index 9d3724828..e7cc070d2 100644 --- a/core/jvm/src/JvmCore.kt +++ b/core/jvm/src/JvmCore.kt @@ -21,12 +21,9 @@ package kotlinx.io -import java.io.* import java.io.IOException -import java.net.Socket -import java.nio.file.Files -import java.nio.file.OpenOption -import java.nio.file.Path as NioPath +import java.io.InputStream +import java.io.OutputStream /** * Returns [RawSink] that writes to an output stream. @@ -106,61 +103,6 @@ private open class InputStreamSource( override fun toString() = "source($input)" } -/** - * Returns [RawSink] that writes to a socket. Prefer this over [sink] - * because this method honors timeouts. When the socket - * write times out, the socket is asynchronously closed by a watchdog thread. - * - * Use [RawSink.buffer] to create a buffered sink from it. - */ -public fun Socket.sink(): RawSink = OutputStreamSink(getOutputStream()) - -/** - * Returns [RawSource] that reads from a socket. Prefer this over [source] - * because this method honors timeouts. When the socket - * read times out, the socket is asynchronously closed by a watchdog thread. - * - * Use [RawSource.buffer] to create a buffered source from it. - */ -public fun Socket.source(): RawSource = InputStreamSource(getInputStream()) - -/** - * Returns [RawSink] that writes to a file. - * - * Use [RawSink.buffer] to create a buffered sink from it. - * - * @param append the flag indicating whether the file should be overwritten or appended, `false` by default, - * meaning the file will be overwritten. - */ -public fun File.sink(append: Boolean = false): RawSink = FileOutputStream(this, append).sink() - -/** - * Returns [RawSource] that reads from a file. - * - * Use [RawSource.buffer] to create a buffered source from it. - */ -public fun File.source(): RawSource = InputStreamSource(inputStream()) - -/** - * Returns [RawSink] that reads from a path. - * - * Use [RawSink.buffer] to create a buffered sink from it. - * - * @param options set of [OpenOption] for opening a file. - */ -public fun NioPath.sink(vararg options: OpenOption): RawSink = - Files.newOutputStream(this, *options).sink() - -/** - * Returns [RawSource] that writes to a path. - * - * Use [RawSource.buffer] to create a buffered source from it. - * - * @param options set of [OpenOption] for opening a file. - */ -public fun NioPath.source(vararg options: OpenOption): RawSource = - Files.newInputStream(this, *options).source() - /** * Returns true if this error is due to a firmware bug fixed after Android 4.2.2. * https://code.google.com/p/android/issues/detail?id=54072 diff --git a/core/jvm/test/JvmPlatformTest.kt b/core/jvm/test/JvmPlatformTest.kt index ddebfedcf..7a73daf55 100644 --- a/core/jvm/test/JvmPlatformTest.kt +++ b/core/jvm/test/JvmPlatformTest.kt @@ -25,12 +25,15 @@ import org.junit.jupiter.api.io.TempDir import java.io.ByteArrayInputStream import java.io.ByteArrayOutputStream import java.io.File +import java.io.FileOutputStream import java.io.IOException import java.lang.IllegalArgumentException import java.net.Socket import java.nio.file.Files import java.nio.file.LinkOption import java.nio.file.StandardOpenOption +import kotlin.io.path.inputStream +import kotlin.io.path.outputStream import kotlin.test.* class JvmPlatformTest { @@ -92,7 +95,7 @@ class JvmPlatformTest { @Test fun fileSink() { val file = File(tempDir, "test") - file.sink().use { sink -> + file.outputStream().sink().use { sink -> sink.write(Buffer().also { it.writeUtf8("a") }, 1L) } assertEquals(file.readText(), "a") @@ -101,7 +104,7 @@ class JvmPlatformTest { @Test fun fileAppendingSink() { val file = File(tempDir, "test") file.writeText("a") - file.sink(append = true).use { sink -> + FileOutputStream(file, true).sink().use { sink -> sink.write(Buffer().also { it.writeUtf8("b") }, 1L) } assertEquals(file.readText(), "ab") @@ -111,7 +114,7 @@ class JvmPlatformTest { val file = File(tempDir, "test") file.writeText("a") val buffer = Buffer() - file.source().use { source -> + file.inputStream().source().use { source -> source.readAtMostTo(buffer, 1L) } assertEquals(buffer.readUtf8(), "a") @@ -119,7 +122,7 @@ class JvmPlatformTest { @Test fun pathSink() { val file = File(tempDir, "test") - file.toPath().sink().use { sink -> + file.toPath().outputStream().sink().use { sink -> sink.write(Buffer().also { it.writeUtf8("a") }, 1L) } assertEquals(file.readText(), "a") @@ -128,7 +131,7 @@ class JvmPlatformTest { @Test fun pathSinkWithOptions() { val file = File(tempDir, "test") file.writeText("a") - file.toPath().sink(StandardOpenOption.APPEND).use { sink -> + file.toPath().outputStream(StandardOpenOption.APPEND).sink().use { sink -> sink.write(Buffer().also { it.writeUtf8("b") }, 1L) } assertEquals(file.readText(), "ab") @@ -138,7 +141,7 @@ class JvmPlatformTest { val file = File(tempDir, "test") file.writeText("a") val buffer = Buffer() - file.toPath().source().use { source -> + file.toPath().inputStream().source().use { source -> source.readAtMostTo(buffer, 1L) } assertEquals(buffer.readUtf8(), "a") @@ -157,9 +160,9 @@ class JvmPlatformTest { } assertFailsWith { - link.toPath().source(LinkOption.NOFOLLOW_LINKS).use { it.buffer().readUtf8Line() } + link.toPath().inputStream(LinkOption.NOFOLLOW_LINKS).source().use { it.buffer().readUtf8Line() } } - assertNull(link.toPath().source().use { it.buffer().readUtf8Line() }) + assertNull(link.toPath().inputStream().source().use { it.buffer().readUtf8Line() }) } @Test fun socketSink() { @@ -167,7 +170,7 @@ class JvmPlatformTest { val socket = object : Socket() { override fun getOutputStream() = baos } - val sink = socket.sink() + val sink = socket.outputStream.sink() sink.write(Buffer().also { it.writeUtf8("a") }, 1L) assertArrayEquals(baos.toByteArray(), byteArrayOf(0x61)) } @@ -177,7 +180,7 @@ class JvmPlatformTest { val socket = object : Socket() { override fun getInputStream() = bais } - val source = socket.source() + val source = socket.inputStream.source() val buffer = Buffer() source.readAtMostTo(buffer, 1L) assertEquals(buffer.readUtf8(), "a") diff --git a/core/jvm/test/NioTest.kt b/core/jvm/test/NioTest.kt index 7b9225cbc..c09e51229 100644 --- a/core/jvm/test/NioTest.kt +++ b/core/jvm/test/NioTest.kt @@ -22,7 +22,6 @@ package kotlinx.io import org.junit.jupiter.api.io.TempDir -import kotlin.test.* import java.io.File import java.nio.ByteBuffer import java.nio.channels.FileChannel @@ -32,6 +31,11 @@ import java.nio.file.Path import java.nio.file.Paths import java.nio.file.StandardOpenOption import kotlin.io.path.createFile +import kotlin.io.path.inputStream +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertTrue import kotlin.text.Charsets.UTF_8 /** Test interop with java.nio. */ @@ -59,7 +63,7 @@ class NioTest { val file = Paths.get(temporaryFolder.toString(), "test").createFile() val fileChannel: FileChannel = FileChannel.open(file, StandardOpenOption.WRITE) testWritableByteChannel(false, fileChannel) - val emitted: Source = file.source().buffer() + val emitted: Source = file.inputStream().source().buffer() assertEquals("defghijklmnopqrstuvw", emitted.readUtf8()) emitted.close() } @@ -82,7 +86,7 @@ class NioTest { @Test fun readableChannelNioFile() { val file: File = Paths.get(temporaryFolder.toString(), "test").toFile() - val initialData: Sink = file.sink().buffer() + val initialData: Sink = file.outputStream().sink().buffer() initialData.writeUtf8("abcdefghijklmnopqrstuvwxyz") initialData.close() val fileChannel: FileChannel = FileChannel.open(file.toPath(), StandardOpenOption.READ) From 7c49413b87a7f84df5335dff65812af0f2b79339 Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Fri, 23 Jun 2023 09:54:32 +0200 Subject: [PATCH 73/83] Update dependencies --- build.gradle.kts | 7 ++++--- core/build.gradle.kts | 6 +++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index e58aff659..1d4e42472 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -3,11 +3,12 @@ * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. */ -import org.jetbrains.kotlin.gradle.tasks.* +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile +import org.jetbrains.kotlin.gradle.tasks.KotlinNativeCompile plugins { - id("org.jetbrains.kotlinx.binary-compatibility-validator") version "0.11.1" - id("org.jetbrains.dokka") version "1.8.10" + id("org.jetbrains.kotlinx.binary-compatibility-validator") version "0.13.2" + id("org.jetbrains.dokka") version "1.8.20" `maven-publish` signing } diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 620feeb55..a6a008bf6 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -4,12 +4,12 @@ */ import org.jetbrains.dokka.gradle.DokkaTaskPartial -import org.jetbrains.kotlin.gradle.plugin.* +import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet plugins { kotlin("multiplatform") - id("org.jetbrains.kotlinx.kover") version "0.7.0" - id("org.jetbrains.dokka") version "1.8.10" + id("org.jetbrains.kotlinx.kover") version "0.7.1" + id("org.jetbrains.dokka") version "1.8.20" } kotlin { From e5fec12086a61b0494d02e1ccc54d590c8465a32 Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Fri, 23 Jun 2023 10:15:19 +0200 Subject: [PATCH 74/83] Improved exception messages, fixed few typos, updated docs --- core/Module.md | 2 +- core/common/src/-Util.kt | 25 ++++++++----------------- core/common/src/Buffer.kt | 20 ++++++++++---------- core/common/src/PeekSource.kt | 2 +- core/common/src/SourceExt.kt | 9 ++++++--- core/common/src/Utf8.kt | 6 ++++-- core/jvm/src/BufferExtJvm.kt | 2 +- core/jvm/src/JvmCore.kt | 2 +- core/jvm/src/SourceExtJvm.kt | 4 +++- 9 files changed, 35 insertions(+), 37 deletions(-) diff --git a/core/Module.md b/core/Module.md index 34f26be2c..71e6dba86 100644 --- a/core/Module.md +++ b/core/Module.md @@ -2,7 +2,7 @@ The module provides core multiplatform IO primitives and integrates it with platform-specific APIs. -`kotlinx-io` aims to provide a concise but powerful API along with efficient implementation. +`kotlinx-io core` aims to provide a concise but powerful API along with efficient implementation. The main interfaces for the IO interaction are [kotlinx.io.Source] and [kotlinx.io.Sink] providing buffered read and write operations for integer types, byte arrays, and other sources and sinks. There are also extension functions diff --git a/core/common/src/-Util.kt b/core/common/src/-Util.kt index 66f0bf24b..77ed5e503 100644 --- a/core/common/src/-Util.kt +++ b/core/common/src/-Util.kt @@ -28,7 +28,8 @@ internal val HEX_DIGIT_CHARS = internal fun checkOffsetAndCount(size: Long, offset: Long, byteCount: Long) { if (offset < 0 || offset > size || size - offset < byteCount || byteCount < 0) { - throw IllegalArgumentException("offset: $offset, byteCount: $byteCount, size: $size") + throw IllegalArgumentException( + "offset ($offset) and byteCount ($byteCount) are not within the range [0..size($size))") } } @@ -37,14 +38,17 @@ internal inline fun checkBounds(size: Int, startIndex: Int, endIndex: Int) = internal fun checkBounds(size: Long, startIndex: Long, endIndex: Long) { if (startIndex < 0 || endIndex > size) { - throw IndexOutOfBoundsException("startIndex: $startIndex, endIndex: $endIndex, size: $size") + throw IndexOutOfBoundsException( + "startIndex ($startIndex) and endIndex ($endIndex) are not within the range [0..size($size))") } if (startIndex > endIndex) { - throw IllegalArgumentException("startIndex: $startIndex > endIndex: $endIndex") + throw IllegalArgumentException("startIndex ($startIndex) > endIndex ($endIndex)") } } -/* ktlint-disable no-multi-spaces indent */ +internal inline fun checkByteCount(byteCount: Long) { + require(byteCount >= 0) { "byteCount ($byteCount) < 0" } +} internal fun Short.reverseBytes(): Short { val i = toInt() and 0xffff @@ -105,19 +109,6 @@ internal inline fun minOf(a: Long, b: Int): Long = minOf(a, b.toLong()) // Syntactic sugar. internal inline fun minOf(a: Int, b: Long): Long = minOf(a.toLong(), b) -internal fun arrayRangeEquals( - a: ByteArray, - aOffset: Int, - b: ByteArray, - bOffset: Int, - byteCount: Int -): Boolean { - for (i in 0 until byteCount) { - if (a[i + aOffset] != b[i + bOffset]) return false - } - return true -} - internal fun Byte.toHexString(): String { val result = CharArray(2) result[0] = HEX_DIGIT_CHARS[this shr 4 and 0xf] diff --git a/core/common/src/Buffer.kt b/core/common/src/Buffer.kt index 982b74a08..1f6cbe752 100644 --- a/core/common/src/Buffer.kt +++ b/core/common/src/Buffer.kt @@ -65,7 +65,7 @@ public class Buffer : Source, Sink { } override fun request(byteCount: Long): Boolean { - require(byteCount >= 0) { "byteCount: $byteCount" } + require(byteCount >= 0) { "byteCount: $byteCount < 0" } return size >= byteCount } @@ -280,7 +280,7 @@ public class Buffer : Source, Sink { */ public operator fun get(position: Long): Byte { if (position < 0 || position >= size) { - throw IndexOutOfBoundsException("position: $position, size: $size") + throw IndexOutOfBoundsException("position ($position) is not within the range [0..size($size))") } seek(position) { s, offset -> return s!!.data[(s.pos + position - offset).toInt()] @@ -300,7 +300,7 @@ public class Buffer : Source, Sink { * @throws IllegalArgumentException when [byteCount] is negative. */ override fun skip(byteCount: Long) { - require(byteCount >= 0) { "byteCount: $byteCount" } + checkByteCount(byteCount) var remainingByteCount = byteCount while (remainingByteCount > 0) { val head = head ?: throw EOFException("Buffer exhausted before skipping $byteCount bytes.") @@ -338,7 +338,7 @@ public class Buffer : Source, Sink { } override fun readAtMostTo(sink: Buffer, byteCount: Long): Long { - require(byteCount >= 0L) { "byteCount: $byteCount" } + checkByteCount(byteCount) if (size == 0L) return -1L val bytesWritten = if (byteCount > size) size else byteCount sink.write(this, bytesWritten) @@ -346,7 +346,7 @@ public class Buffer : Source, Sink { } override fun readTo(sink: RawSink, byteCount: Long) { - require(byteCount >= 0) { "byteCount: $byteCount" } + checkByteCount(byteCount) if (size < byteCount) { sink.write(this, size) // Exhaust ourselves. throw EOFException("Buffer exhausted before writing $byteCount bytes. Only $size bytes were written.") @@ -407,7 +407,7 @@ public class Buffer : Source, Sink { } override fun write(source: RawSource, byteCount: Long) { - require(byteCount >= 0) { "byteCount: $byteCount" } + checkByteCount(byteCount) var remainingByteCount = byteCount while (remainingByteCount > 0L) { val read = source.readAtMostTo(this, remainingByteCount) @@ -636,10 +636,10 @@ public class Buffer : Source, Sink { } } -private fun ByteArray.hex(count: Int = this.size): String { - require(count >= 0) { "count: $count" } - val builder = StringBuilder(count * 2) - for (i in 0 until count) { +private fun ByteArray.hex(byteCount: Int = this.size): String { + checkByteCount(byteCount.toLong()) + val builder = StringBuilder(byteCount * 2) + for (i in 0 until byteCount) { builder.append(HEX_DIGIT_CHARS[get(i).shr(4) and 0x0f]) builder.append(HEX_DIGIT_CHARS[get(i) and 0x0f]) } diff --git a/core/common/src/PeekSource.kt b/core/common/src/PeekSource.kt index d67f13078..43a8bc8a8 100644 --- a/core/common/src/PeekSource.kt +++ b/core/common/src/PeekSource.kt @@ -43,7 +43,7 @@ internal class PeekSource( override fun readAtMostTo(sink: Buffer, byteCount: Long): Long { check(!closed) { "Source is closed." } - require(byteCount >= 0L) { "byteCount: $byteCount" } + checkByteCount(byteCount) // Source becomes invalid if there is an expected Segment and it and the expected position // do not match the current head and head position of the upstream buffer check( diff --git a/core/common/src/SourceExt.kt b/core/common/src/SourceExt.kt index c59aa803c..0ec7f83b8 100644 --- a/core/common/src/SourceExt.kt +++ b/core/common/src/SourceExt.kt @@ -147,7 +147,7 @@ public fun Source.readHexadecimalUnsignedLong(): Long { result = result.shl(4) + bDigit bytesRead++ } - skip(bytesRead.toLong()) + skip(bytesRead) return result } @@ -168,7 +168,10 @@ public fun Source.readHexadecimalUnsignedLong(): Long { * @throws IllegalArgumentException when `startIndex > endIndex` or either of indices is negative. */ public fun Source.indexOf(b: Byte, startIndex: Long = 0L, endIndex: Long = Long.MAX_VALUE): Long { - require(startIndex in 0..endIndex) { "startIndex: $startIndex, endIndex: $endIndex" } + require(startIndex in 0..endIndex) { + if (endIndex < 0) { "startIndex ($startIndex) and endIndex ($endIndex) should be non negative" } + else { "startIndex ($startIndex) is not within the range [0..endIndex($endIndex))" } + } if (startIndex == endIndex) return -1L var offset = startIndex @@ -204,7 +207,7 @@ public fun Source.readByteArray(): ByteArray { * @throws IllegalStateException when the source is closed. */ public fun Source.readByteArray(byteCount: Int): ByteArray { - require(byteCount >= 0) { "byteCount: $byteCount" } + checkByteCount(byteCount.toLong()) return readByteArrayImpl(byteCount) } diff --git a/core/common/src/Utf8.kt b/core/common/src/Utf8.kt index c207460e2..d1bb8147b 100644 --- a/core/common/src/Utf8.kt +++ b/core/common/src/Utf8.kt @@ -271,7 +271,7 @@ public fun Source.readUtf8Line(): String? { * @throws IllegalArgumentException when [limit] is negative. */ public fun Source.readUtf8LineStrict(limit: Long = Long.MAX_VALUE): String { - require(limit >= 0) { "limit: $limit" } + require(limit >= 0) { "limit ($limit) < 0" } require(1) val peekSource = peek() @@ -505,7 +505,9 @@ private fun Buffer.commonWriteUtf8CodePoint(codePoint: Int) { } private fun Buffer.commonReadUtf8(byteCount: Long): String { - require(byteCount >= 0 && byteCount <= Int.MAX_VALUE) { "byteCount: $byteCount" } + require(byteCount >= 0 && byteCount <= Int.MAX_VALUE) { + "byteCount ($byteCount) is not within the range [0..${Int.MAX_VALUE})" + } require(byteCount) if (byteCount == 0L) return "" diff --git a/core/jvm/src/BufferExtJvm.kt b/core/jvm/src/BufferExtJvm.kt index b5dd03e26..1905c49f9 100644 --- a/core/jvm/src/BufferExtJvm.kt +++ b/core/jvm/src/BufferExtJvm.kt @@ -48,7 +48,7 @@ public fun Buffer.transferFrom(input: InputStream): Buffer { * @throws IllegalArgumentException when [byteCount] is negative. */ public fun Buffer.write(input: InputStream, byteCount: Long): Buffer { - require(byteCount >= 0L) { "byteCount: $byteCount" } + checkByteCount(byteCount) write(input, byteCount, false) return this } diff --git a/core/jvm/src/JvmCore.kt b/core/jvm/src/JvmCore.kt index e7cc070d2..5c8a0ce2e 100644 --- a/core/jvm/src/JvmCore.kt +++ b/core/jvm/src/JvmCore.kt @@ -76,7 +76,7 @@ private open class InputStreamSource( override fun readAtMostTo(sink: Buffer, byteCount: Long): Long { if (byteCount == 0L) return 0L - require(byteCount >= 0L) { "byteCount < 0: $byteCount" } + checkByteCount(byteCount) try { val tail = sink.writableSegment(1) val maxToCopy = minOf(byteCount, Segment.SIZE - tail.limit).toInt() diff --git a/core/jvm/src/SourceExtJvm.kt b/core/jvm/src/SourceExtJvm.kt index 461de8dd5..9c6c39ec0 100644 --- a/core/jvm/src/SourceExtJvm.kt +++ b/core/jvm/src/SourceExtJvm.kt @@ -27,7 +27,9 @@ import java.nio.channels.ReadableByteChannel import java.nio.charset.Charset private fun Buffer.readStringImpl(byteCount: Long, charset: Charset): String { - require(byteCount >= 0 && byteCount <= Integer.MAX_VALUE) { "byteCount: $byteCount" } + require(byteCount >= 0 && byteCount <= Int.MAX_VALUE) { + "byteCount ($byteCount) is not within the range [0..${Int.MAX_VALUE})" + } if (size < byteCount) { throw EOFException("Buffer contains less bytes then required (byteCount: $byteCount, size: $size)") } From eb97548684768ccb62dcf1880fc8dde8b2dc7965 Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Fri, 23 Jun 2023 10:21:51 +0200 Subject: [PATCH 75/83] Add -Xjvm-default=all to compiler args Fixes #145 --- build.gradle.kts | 1 + core/api/kotlinx-io-core.api | 10 ++-------- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 1d4e42472..a759a940b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -65,6 +65,7 @@ subprojects { kotlinOptions { jvmTarget = JavaVersion.VERSION_1_8.toString() allWarningsAsErrors = true + freeCompilerArgs += "-Xjvm-default=all" } } tasks.withType().configureEach { diff --git a/core/api/kotlinx-io-core.api b/core/api/kotlinx-io-core.api index ce25f1ea8..f5087a614 100644 --- a/core/api/kotlinx-io-core.api +++ b/core/api/kotlinx-io-core.api @@ -83,16 +83,13 @@ public abstract interface class kotlinx/io/Sink : kotlinx/io/RawSink { public abstract fun transferFrom (Lkotlinx/io/RawSource;)J public abstract fun write (Lkotlinx/io/RawSource;J)V public abstract fun write ([BII)V + public static synthetic fun write$default (Lkotlinx/io/Sink;[BIIILjava/lang/Object;)V public abstract fun writeByte (B)V public abstract fun writeInt (I)V public abstract fun writeLong (J)V public abstract fun writeShort (S)V } -public final class kotlinx/io/Sink$DefaultImpls { - public static synthetic fun write$default (Lkotlinx/io/Sink;[BIIILjava/lang/Object;)V -} - public final class kotlinx/io/SinkExtJvmKt { public static final fun channel (Lkotlinx/io/Sink;)Ljava/nio/channels/WritableByteChannel; public static final fun outputStream (Lkotlinx/io/Sink;)Ljava/io/OutputStream; @@ -122,6 +119,7 @@ public abstract interface class kotlinx/io/Source : kotlinx/io/RawSource { public abstract fun getBuffer ()Lkotlinx/io/Buffer; public abstract fun peek ()Lkotlinx/io/Source; public abstract fun readAtMostTo ([BII)I + public static synthetic fun readAtMostTo$default (Lkotlinx/io/Source;[BIIILjava/lang/Object;)I public abstract fun readByte ()B public abstract fun readInt ()I public abstract fun readLong ()J @@ -133,10 +131,6 @@ public abstract interface class kotlinx/io/Source : kotlinx/io/RawSource { public abstract fun transferTo (Lkotlinx/io/RawSink;)J } -public final class kotlinx/io/Source$DefaultImpls { - public static synthetic fun readAtMostTo$default (Lkotlinx/io/Source;[BIIILjava/lang/Object;)I -} - public final class kotlinx/io/SourceExtJvmKt { public static final fun channel (Lkotlinx/io/Source;)Ljava/nio/channels/ReadableByteChannel; public static final fun inputStream (Lkotlinx/io/Source;)Ljava/io/InputStream; From 32f207fc93357c2baedf916728b6a6ba38497fbb Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Fri, 23 Jun 2023 10:43:46 +0200 Subject: [PATCH 76/83] Renamed methods returning wrappers for Sink/Source and vice versa --- .../src/jvmMain/kotlin/StreamBenchmarks.kt | 12 +++---- core/api/kotlinx-io-core.api | 18 +++++----- core/common/src/Buffer.kt | 2 +- core/common/src/Core.kt | 4 +-- core/common/src/RawSink.kt | 2 +- core/common/src/RawSource.kt | 2 +- core/common/src/RealSource.kt | 2 +- core/common/src/Sink.kt | 6 ++-- core/common/src/Source.kt | 2 +- core/common/test/CommonPlatformTest.kt | 4 +-- core/common/test/CommonRealSinkTest.kt | 32 ++++++++--------- core/common/test/CommonRealSourceTest.kt | 24 ++++++------- core/common/test/DelicateApiTest.kt | 2 +- core/common/test/SinkFactory.kt | 2 +- core/common/test/SourceFactory.kt | 8 ++--- core/jvm/src/BufferExtJvm.kt | 2 +- core/jvm/src/JvmCore.kt | 8 ++--- core/jvm/src/SinkExtJvm.kt | 14 ++++---- core/jvm/src/SourceExtJvm.kt | 14 ++++---- core/jvm/src/files/Paths.kt | 4 +-- core/jvm/test/AbstractSinkTestJVM.kt | 8 ++--- core/jvm/test/AbstractSourceTestJVM.kt | 18 +++++----- core/jvm/test/BufferTest.kt | 18 +++++----- core/jvm/test/JvmPlatformTest.kt | 34 +++++++++---------- core/jvm/test/NioTest.kt | 16 ++++----- core/native/src/files/PathsNative.kt | 4 +-- 26 files changed, 131 insertions(+), 131 deletions(-) diff --git a/benchmarks/src/jvmMain/kotlin/StreamBenchmarks.kt b/benchmarks/src/jvmMain/kotlin/StreamBenchmarks.kt index 248b2f8a9..9aae3e654 100644 --- a/benchmarks/src/jvmMain/kotlin/StreamBenchmarks.kt +++ b/benchmarks/src/jvmMain/kotlin/StreamBenchmarks.kt @@ -9,12 +9,12 @@ import kotlinx.benchmark.Benchmark import kotlinx.benchmark.Blackhole import kotlinx.benchmark.Param import kotlinx.benchmark.Setup -import kotlinx.io.inputStream -import kotlinx.io.outputStream +import kotlinx.io.asInputStream +import kotlinx.io.asOutputStream import kotlinx.io.readTo open class InputStreamByteRead: BufferRWBenchmarkBase() { - private val stream = buffer.inputStream() + private val stream = buffer.asInputStream() @Benchmark fun benchmark(): Int { @@ -24,7 +24,7 @@ open class InputStreamByteRead: BufferRWBenchmarkBase() { } open class OutputStreamByteWrite: BufferRWBenchmarkBase() { - private val stream = buffer.outputStream() + private val stream = buffer.asOutputStream() @Benchmark fun benchmark(): Byte { @@ -48,7 +48,7 @@ abstract class StreamByteArrayBenchmarkBase: BufferRWBenchmarkBase() { } open class InputStreamByteArrayRead: StreamByteArrayBenchmarkBase() { - private val stream = buffer.inputStream() + private val stream = buffer.asInputStream() @Benchmark fun benchmark(blackhole: Blackhole) { @@ -62,7 +62,7 @@ open class InputStreamByteArrayRead: StreamByteArrayBenchmarkBase() { } open class OutputStreamByteArrayWrite: StreamByteArrayBenchmarkBase() { - private val stream = buffer.outputStream() + private val stream = buffer.asOutputStream() @Benchmark fun benchmark(blackhole: Blackhole) { diff --git a/core/api/kotlinx-io-core.api b/core/api/kotlinx-io-core.api index f5087a614..63220e707 100644 --- a/core/api/kotlinx-io-core.api +++ b/core/api/kotlinx-io-core.api @@ -36,7 +36,7 @@ public final class kotlinx/io/Buffer : kotlinx/io/Sink, kotlinx/io/Source { } public final class kotlinx/io/BufferExtJvmKt { - public static final fun channel (Lkotlinx/io/Buffer;)Ljava/nio/channels/ByteChannel; + public static final fun asByteChannel (Lkotlinx/io/Buffer;)Ljava/nio/channels/ByteChannel; public static final fun copyTo (Lkotlinx/io/Buffer;Ljava/io/OutputStream;JJ)V public static synthetic fun copyTo$default (Lkotlinx/io/Buffer;Ljava/io/OutputStream;JJILjava/lang/Object;)V public static final fun readAtMostTo (Lkotlinx/io/Buffer;Ljava/nio/ByteBuffer;)I @@ -49,8 +49,8 @@ public final class kotlinx/io/BufferExtJvmKt { public final class kotlinx/io/CoreKt { public static final fun blackholeSink ()Lkotlinx/io/RawSink; - public static final fun buffer (Lkotlinx/io/RawSink;)Lkotlinx/io/Sink; - public static final fun buffer (Lkotlinx/io/RawSource;)Lkotlinx/io/Source; + public static final fun buffered (Lkotlinx/io/RawSink;)Lkotlinx/io/Sink; + public static final fun buffered (Lkotlinx/io/RawSource;)Lkotlinx/io/Source; } public abstract interface annotation class kotlinx/io/DelicateIoApi : java/lang/annotation/Annotation { @@ -60,8 +60,8 @@ public abstract interface annotation class kotlinx/io/InternalIoApi : java/lang/ } public final class kotlinx/io/JvmCoreKt { - public static final fun sink (Ljava/io/OutputStream;)Lkotlinx/io/RawSink; - public static final fun source (Ljava/io/InputStream;)Lkotlinx/io/RawSource; + public static final fun asSink (Ljava/io/OutputStream;)Lkotlinx/io/RawSink; + public static final fun asSource (Ljava/io/InputStream;)Lkotlinx/io/RawSource; } public abstract interface class kotlinx/io/RawSink : java/io/Flushable, java/lang/AutoCloseable { @@ -91,8 +91,8 @@ public abstract interface class kotlinx/io/Sink : kotlinx/io/RawSink { } public final class kotlinx/io/SinkExtJvmKt { - public static final fun channel (Lkotlinx/io/Sink;)Ljava/nio/channels/WritableByteChannel; - public static final fun outputStream (Lkotlinx/io/Sink;)Ljava/io/OutputStream; + public static final fun asByteChannel (Lkotlinx/io/Sink;)Ljava/nio/channels/WritableByteChannel; + public static final fun asOutputStream (Lkotlinx/io/Sink;)Ljava/io/OutputStream; public static final fun write (Lkotlinx/io/Sink;Ljava/nio/ByteBuffer;)I public static final fun writeString (Lkotlinx/io/Sink;Ljava/lang/String;Ljava/nio/charset/Charset;II)V public static synthetic fun writeString$default (Lkotlinx/io/Sink;Ljava/lang/String;Ljava/nio/charset/Charset;IIILjava/lang/Object;)V @@ -132,8 +132,8 @@ public abstract interface class kotlinx/io/Source : kotlinx/io/RawSource { } public final class kotlinx/io/SourceExtJvmKt { - public static final fun channel (Lkotlinx/io/Source;)Ljava/nio/channels/ReadableByteChannel; - public static final fun inputStream (Lkotlinx/io/Source;)Ljava/io/InputStream; + public static final fun asByteChannel (Lkotlinx/io/Source;)Ljava/nio/channels/ReadableByteChannel; + public static final fun asInputStream (Lkotlinx/io/Source;)Ljava/io/InputStream; public static final fun readAtMostTo (Lkotlinx/io/Source;Ljava/nio/ByteBuffer;)I public static final fun readString (Lkotlinx/io/Source;JLjava/nio/charset/Charset;)Ljava/lang/String; public static final fun readString (Lkotlinx/io/Source;Ljava/nio/charset/Charset;)Ljava/lang/String; diff --git a/core/common/src/Buffer.kt b/core/common/src/Buffer.kt index 1f6cbe752..3ffb257ea 100644 --- a/core/common/src/Buffer.kt +++ b/core/common/src/Buffer.kt @@ -362,7 +362,7 @@ public class Buffer : Source, Sink { return byteCount } - override fun peek(): Source = PeekSource(this).buffer() + override fun peek(): Source = PeekSource(this).buffered() /** * Returns a tail segment that we can write at least `minimumCapacity` diff --git a/core/common/src/Core.kt b/core/common/src/Core.kt index 6b44e9046..754855eba 100644 --- a/core/common/src/Core.kt +++ b/core/common/src/Core.kt @@ -25,13 +25,13 @@ package kotlinx.io * reads into its in-memory buffer. Use this wherever you read a source to get ergonomic and * efficient access to data. */ -public fun RawSource.buffer(): Source = RealSource(this) +public fun RawSource.buffered(): Source = RealSource(this) /** * Returns a new sink that buffers writes to the sink. The returned sink will batch writes to the sink. * Use this wherever you write to a sink to get ergonomic and efficient access to data. */ -public fun RawSink.buffer(): Sink = RealSink(this) +public fun RawSink.buffered(): Sink = RealSink(this) /** * Returns a sink that writes nowhere. diff --git a/core/common/src/RawSink.kt b/core/common/src/RawSink.kt index 0889f5e81..06b798a3c 100644 --- a/core/common/src/RawSink.kt +++ b/core/common/src/RawSink.kt @@ -28,7 +28,7 @@ package kotlinx.io * or add protocol framing. * * Most application code shouldn't operate on a raw sink directly, but rather on a buffered [Sink] which - * is both more efficient and more convenient. Use [buffer] to wrap any raw sink with a buffer. + * is both more efficient and more convenient. Use [buffered] to wrap any raw sink with a buffer. * * Implementors should abstain from throwing exceptions other than those that are documented for RawSink methods. */ diff --git a/core/common/src/RawSource.kt b/core/common/src/RawSource.kt index 68453346e..976f9e597 100644 --- a/core/common/src/RawSource.kt +++ b/core/common/src/RawSource.kt @@ -28,7 +28,7 @@ package kotlinx.io * or remove protocol framing. * * Most applications shouldn't operate on a raw source directly, but rather on a buffered [Source] which - * is both more efficient and more convenient. Use [buffer] to wrap any raw source with a buffer. + * is both more efficient and more convenient. Use [buffered] to wrap any raw source with a buffer. * * Implementors should abstain from throwing exceptions other than those that are documented for RawSource methods. */ diff --git a/core/common/src/RealSource.kt b/core/common/src/RealSource.kt index e0539ad76..87e9b3b5c 100644 --- a/core/common/src/RealSource.kt +++ b/core/common/src/RealSource.kt @@ -140,7 +140,7 @@ internal class RealSource( override fun peek(): Source { checkNotClosed() - return PeekSource(this).buffer() + return PeekSource(this).buffered() } override fun close() { diff --git a/core/common/src/Sink.kt b/core/common/src/Sink.kt index c6c57d9e5..37766b786 100644 --- a/core/common/src/Sink.kt +++ b/core/common/src/Sink.kt @@ -25,7 +25,7 @@ package kotlinx.io * sending it directly to an upstream. * * [Sink] is the main `kotlinx-io` interface to write data in client's code, - * any [RawSink] could be turned into [Sink] using [RawSink.buffer]. + * any [RawSink] could be turned into [Sink] using [RawSink.buffered]. * * Depending on the kind of upstream and the number of bytes written, buffering may improve the performance * by hiding the latency of small writes. @@ -166,10 +166,10 @@ public sealed interface Sink : RawSink { * there are no guarantees how many bytes will be emitted. * * Typically, application code will not need to call this: it is only necessary when - * application code writes directly to this [buffer]. + * application code writes directly to this [buffered]. * Use this to limit the memory held in the buffer. * - * Consider using [Sink.writeToInternalBuffer] for writes into [buffer] followed by [hintEmit] call. + * Consider using [Sink.writeToInternalBuffer] for writes into [buffered] followed by [hintEmit] call. * * @throws IllegalStateException when the sink is closed. */ diff --git a/core/common/src/Source.kt b/core/common/src/Source.kt index 07daa21cd..d33b7b799 100644 --- a/core/common/src/Source.kt +++ b/core/common/src/Source.kt @@ -25,7 +25,7 @@ package kotlinx.io * without requesting it from a downstream on every call. * * [Source] is the main `kotlinx-io` interface to read data in client's code, - * any [RawSource] could be converted into [Source] using [RawSource.buffer]. + * any [RawSource] could be converted into [Source] using [RawSource.buffered]. * * Depending on the kind of downstream and the number of bytes read, buffering may improve the performance by hiding * the latency of small reads. diff --git a/core/common/test/CommonPlatformTest.kt b/core/common/test/CommonPlatformTest.kt index 63d7c1b8a..27f959999 100644 --- a/core/common/test/CommonPlatformTest.kt +++ b/core/common/test/CommonPlatformTest.kt @@ -27,14 +27,14 @@ import kotlin.test.assertEquals class CommonPlatformTest { @Test fun sourceBuffer() { val source = Buffer().also { it.writeUtf8("a") } - val buffered = (source as RawSource).buffer() + val buffered = (source as RawSource).buffered() assertEquals(buffered.readUtf8(), "a") assertEquals(source.size, 0L) } @Test fun sinkBuffer() { val sink = Buffer() - val buffered = (sink as RawSink).buffer() + val buffered = (sink as RawSink).buffered() buffered.writeUtf8("a") assertEquals(sink.size, 0L) buffered.flush() diff --git a/core/common/test/CommonRealSinkTest.kt b/core/common/test/CommonRealSinkTest.kt index 0b3b5554a..3ad958560 100644 --- a/core/common/test/CommonRealSinkTest.kt +++ b/core/common/test/CommonRealSinkTest.kt @@ -33,7 +33,7 @@ import kotlin.test.assertFailsWith class CommonRealSinkTest { @Test fun bufferedSinkEmitsTailWhenItIsComplete() { val sink = Buffer() - val bufferedSink = (sink as RawSink).buffer() + val bufferedSink = (sink as RawSink).buffered() bufferedSink.writeUtf8("a".repeat(Segment.SIZE - 1)) assertEquals(0, sink.size) bufferedSink.writeByte(0) @@ -43,7 +43,7 @@ class CommonRealSinkTest { @Test fun bufferedSinkEmitMultipleSegments() { val sink = Buffer() - val bufferedSink = (sink as RawSink).buffer() + val bufferedSink = (sink as RawSink).buffered() bufferedSink.writeUtf8("a".repeat(Segment.SIZE * 4 - 1)) assertEquals(Segment.SIZE.toLong() * 3L, sink.size) assertEquals(Segment.SIZE.toLong() - 1L, bufferedSink.buffer.size) @@ -51,7 +51,7 @@ class CommonRealSinkTest { @Test fun bufferedSinkFlush() { val sink = Buffer() - val bufferedSink = (sink as RawSink).buffer() + val bufferedSink = (sink as RawSink).buffered() bufferedSink.writeByte('a'.code.toByte()) assertEquals(0, sink.size) bufferedSink.flush() @@ -61,7 +61,7 @@ class CommonRealSinkTest { @Test fun bytesEmittedToSinkWithFlush() { val sink = Buffer() - val bufferedSink = (sink as RawSink).buffer() + val bufferedSink = (sink as RawSink).buffered() bufferedSink.writeUtf8("abc") bufferedSink.flush() assertEquals(3, sink.size) @@ -69,14 +69,14 @@ class CommonRealSinkTest { @Test fun bytesNotEmittedToSinkWithoutFlush() { val sink = Buffer() - val bufferedSink = (sink as RawSink).buffer() + val bufferedSink = (sink as RawSink).buffered() bufferedSink.writeUtf8("abc") assertEquals(0, sink.size) } @Test fun bytesEmittedToSinkWithEmit() { val sink = Buffer() - val bufferedSink = (sink as RawSink).buffer() + val bufferedSink = (sink as RawSink).buffered() bufferedSink.writeUtf8("abc") bufferedSink.emit() assertEquals(3, sink.size) @@ -84,14 +84,14 @@ class CommonRealSinkTest { @Test fun completeSegmentsEmitted() { val sink = Buffer() - val bufferedSink = (sink as RawSink).buffer() + val bufferedSink = (sink as RawSink).buffered() bufferedSink.writeUtf8("a".repeat(Segment.SIZE * 3)) assertEquals(Segment.SIZE.toLong() * 3L, sink.size) } @Test fun incompleteSegmentsNotEmitted() { val sink = Buffer() - val bufferedSink = (sink as RawSink).buffer() + val bufferedSink = (sink as RawSink).buffered() bufferedSink.writeUtf8("a".repeat(Segment.SIZE * 3 - 1)) assertEquals(Segment.SIZE.toLong() * 2L, sink.size) } @@ -99,7 +99,7 @@ class CommonRealSinkTest { @Test fun closeWithExceptionWhenWriting() { val mockSink = MockSink() mockSink.scheduleThrow(0, IOException()) - val bufferedSink = mockSink.buffer() + val bufferedSink = mockSink.buffered() bufferedSink.writeByte('a'.code.toByte()) assertFailsWith { bufferedSink.close() @@ -111,7 +111,7 @@ class CommonRealSinkTest { @Test fun closeWithExceptionWhenClosing() { val mockSink = MockSink() mockSink.scheduleThrow(1, IOException()) - val bufferedSink = mockSink.buffer() + val bufferedSink = mockSink.buffered() bufferedSink.writeByte('a'.code.toByte()) assertFailsWith { bufferedSink.close() @@ -124,7 +124,7 @@ class CommonRealSinkTest { val mockSink = MockSink() mockSink.scheduleThrow(0, IOException("first")) mockSink.scheduleThrow(1, IOException("second")) - val bufferedSink = mockSink.buffer() + val bufferedSink = mockSink.buffered() bufferedSink.writeByte('a'.code.toByte()) assertFailsWith("first.*") { bufferedSink.close() @@ -135,7 +135,7 @@ class CommonRealSinkTest { @Test fun operationsAfterClose() { val mockSink = MockSink() - val bufferedSink = mockSink.buffer() + val bufferedSink = mockSink.buffered() bufferedSink.writeByte('a'.code.toByte()) bufferedSink.close() @@ -149,7 +149,7 @@ class CommonRealSinkTest { @Test fun writeAll() { val mockSink = MockSink() - val bufferedSink = mockSink.buffer() + val bufferedSink = mockSink.buffered() bufferedSink.buffer.writeUtf8("abc") assertEquals(3, bufferedSink.transferFrom(Buffer().also { it.writeUtf8("def") })) @@ -161,7 +161,7 @@ class CommonRealSinkTest { @Test fun writeAllExhausted() { val mockSink = MockSink() - val bufferedSink = mockSink.buffer() + val bufferedSink = mockSink.buffered() assertEquals(0, bufferedSink.transferFrom(Buffer())) assertEquals(0, bufferedSink.buffer.size) @@ -178,7 +178,7 @@ class CommonRealSinkTest { "${"a".repeat(Segment.SIZE)}${"b".repeat(Segment.SIZE)}${"c".repeat(Segment.SIZE)}") val mockSink = MockSink() - val bufferedSink = mockSink.buffer() + val bufferedSink = mockSink.buffered() assertEquals(Segment.SIZE.toLong() * 3L, bufferedSink.transferFrom(source)) mockSink.assertLog( @@ -195,7 +195,7 @@ class CommonRealSinkTest { override fun flush() = Unit override fun close() { closeCalls++ } } - val sink = rawSink.buffer() + val sink = rawSink.buffered() sink.close() assertFailsWith { sink.writeByte(0) } diff --git a/core/common/test/CommonRealSourceTest.kt b/core/common/test/CommonRealSourceTest.kt index eaa338d75..14c1a44f7 100644 --- a/core/common/test/CommonRealSourceTest.kt +++ b/core/common/test/CommonRealSourceTest.kt @@ -39,7 +39,7 @@ class CommonRealSourceTest { return buffer.readAtMostTo(sink, minOf(1, byteCount)) } } - ).buffer() + ).buffered() assertEquals(6, buffer.size) assertEquals(-1, bufferedSource.indexOf('e'.code.toByte(), 0, 4)) @@ -50,7 +50,7 @@ class CommonRealSourceTest { val source = Buffer() source.writeUtf8("bb") - val bufferedSource = (source as RawSource).buffer() + val bufferedSource = (source as RawSource).buffered() bufferedSource.buffer.writeUtf8("aa") bufferedSource.require(2) @@ -62,7 +62,7 @@ class CommonRealSourceTest { val source = Buffer() source.writeUtf8("b") - val bufferedSource = (source as RawSource).buffer() + val bufferedSource = (source as RawSource).buffered() bufferedSource.buffer.writeUtf8("a") bufferedSource.require(2) @@ -73,7 +73,7 @@ class CommonRealSourceTest { val source = Buffer() source.writeUtf8("a") - val bufferedSource = (source as RawSource).buffer() + val bufferedSource = (source as RawSource).buffered() assertFailsWith { bufferedSource.require(2) @@ -85,7 +85,7 @@ class CommonRealSourceTest { source.writeUtf8("a".repeat(Segment.SIZE)) source.writeUtf8("b".repeat(Segment.SIZE)) - val bufferedSource = (source as RawSource).buffer() + val bufferedSource = (source as RawSource).buffered() bufferedSource.require(2) assertEquals(Segment.SIZE.toLong(), source.size) @@ -96,7 +96,7 @@ class CommonRealSourceTest { val source = Buffer() source.writeUtf8("a".repeat(Segment.SIZE)) source.writeUtf8("b".repeat(Segment.SIZE)) - val bufferedSource = (source as RawSource).buffer() + val bufferedSource = (source as RawSource).buffered() bufferedSource.skip(2) assertEquals(Segment.SIZE.toLong(), source.size) assertEquals(Segment.SIZE.toLong() - 2L, bufferedSource.buffer.size) @@ -106,7 +106,7 @@ class CommonRealSourceTest { val source = Buffer() source.writeUtf8("bb") - val bufferedSource = (source as RawSource).buffer() + val bufferedSource = (source as RawSource).buffered() bufferedSource.buffer.writeUtf8("aa") bufferedSource.skip(2) @@ -116,7 +116,7 @@ class CommonRealSourceTest { @Test fun operationsAfterClose() { val source = Buffer() - val bufferedSource = (source as RawSource).buffer() + val bufferedSource = (source as RawSource).buffered() bufferedSource.close() // Test a sample set of methods. @@ -143,7 +143,7 @@ class CommonRealSourceTest { "${"a".repeat(Segment.SIZE)}${"b".repeat(Segment.SIZE)}${"c".repeat(Segment.SIZE)}") val mockSink = MockSink() - val bufferedSource = (source as RawSource).buffer() + val bufferedSource = (source as RawSource).buffered() assertEquals(Segment.SIZE.toLong() * 3L, bufferedSource.transferTo(mockSink)) mockSink.assertLog( "write($write1, ${write1.size})", @@ -158,7 +158,7 @@ class CommonRealSourceTest { override fun readAtMostTo(sink: Buffer, byteCount: Long): Long = -1 override fun close() { closeCalls++ } } - val source = rawSource.buffer() + val source = rawSource.buffered() source.close() assertFailsWith { source.readByte() } @@ -172,7 +172,7 @@ class CommonRealSourceTest { override fun close() {} } - assertEquals(-1, rawSource.buffer().readAtMostTo(Buffer(), 1024)) + assertEquals(-1, rawSource.buffered().readAtMostTo(Buffer(), 1024)) } @Test fun readAtMostFromFinite() { @@ -188,7 +188,7 @@ class CommonRealSourceTest { override fun close() {} } - val source = rawSource.buffer() + val source = rawSource.buffered() assertEquals(10, source.readAtMostTo(Buffer(), 1024)) assertEquals(-1, source.readAtMostTo(Buffer(), 1024)) } diff --git a/core/common/test/DelicateApiTest.kt b/core/common/test/DelicateApiTest.kt index 104732e95..40ddf783f 100644 --- a/core/common/test/DelicateApiTest.kt +++ b/core/common/test/DelicateApiTest.kt @@ -15,7 +15,7 @@ class DelicateApiTest { fun testWriteIntoBuffer() { val sink = Buffer() val rawSink = sink as RawSink - val bufferedSink = rawSink.buffer() + val bufferedSink = rawSink.buffered() bufferedSink.writeToInternalBuffer { it.writeByte(42) diff --git a/core/common/test/SinkFactory.kt b/core/common/test/SinkFactory.kt index 78764e5e4..5bb8b6b55 100644 --- a/core/common/test/SinkFactory.kt +++ b/core/common/test/SinkFactory.kt @@ -34,7 +34,7 @@ internal interface SinkFactory { val REAL_BUFFERED_SINK: SinkFactory = object : SinkFactory { override fun create(data: Buffer): Sink { - return (data as RawSink).buffer() + return (data as RawSink).buffered() } } } diff --git a/core/common/test/SourceFactory.kt b/core/common/test/SourceFactory.kt index 2b798bd59..799a7c4ee 100644 --- a/core/common/test/SourceFactory.kt +++ b/core/common/test/SourceFactory.kt @@ -56,7 +56,7 @@ interface SourceFactory { val buffer = Buffer() return Pipe( buffer, - (buffer as RawSource).buffer() + (buffer as RawSource).buffered() ) } } @@ -84,7 +84,7 @@ interface SourceFactory { if (result > 0L) sink.write(box.copy(), result) return result } - }.buffer() + }.buffered() ) } } @@ -108,7 +108,7 @@ interface SourceFactory { buffer.write(box.copy(), 1) } } - }.buffer(), + }.buffered(), buffer ) } @@ -138,7 +138,7 @@ interface SourceFactory { val buffer = Buffer() return Pipe( buffer, - (buffer as RawSource).buffer().peek() + (buffer as RawSource).buffered().peek() ) } } diff --git a/core/jvm/src/BufferExtJvm.kt b/core/jvm/src/BufferExtJvm.kt index 1905c49f9..60ae1c62f 100644 --- a/core/jvm/src/BufferExtJvm.kt +++ b/core/jvm/src/BufferExtJvm.kt @@ -189,7 +189,7 @@ public fun Buffer.transferFrom(source: ByteBuffer): Buffer { /** * Returns a new [ByteChannel] instance representing this buffer. */ -public fun Buffer.channel(): ByteChannel = object : ByteChannel { +public fun Buffer.asByteChannel(): ByteChannel = object : ByteChannel { override fun read(sink: ByteBuffer): Int = readAtMostTo(sink) override fun write(source: ByteBuffer): Int { diff --git a/core/jvm/src/JvmCore.kt b/core/jvm/src/JvmCore.kt index 5c8a0ce2e..d8276f12c 100644 --- a/core/jvm/src/JvmCore.kt +++ b/core/jvm/src/JvmCore.kt @@ -28,9 +28,9 @@ import java.io.OutputStream /** * Returns [RawSink] that writes to an output stream. * - * Use [RawSink.buffer] to create a buffered sink from it. + * Use [RawSink.buffered] to create a buffered sink from it. */ -public fun OutputStream.sink(): RawSink = OutputStreamSink(this) +public fun OutputStream.asSink(): RawSink = OutputStreamSink(this) private open class OutputStreamSink( private val out: OutputStream, @@ -66,9 +66,9 @@ private open class OutputStreamSink( /** * Returns [RawSource] that reads from an input stream. * - * Use [RawSource.buffer] to create a buffered source from it. + * Use [RawSource.buffered] to create a buffered source from it. */ -public fun InputStream.source(): RawSource = InputStreamSource(this) +public fun InputStream.asSource(): RawSource = InputStreamSource(this) private open class InputStreamSource( private val input: InputStream, diff --git a/core/jvm/src/SinkExtJvm.kt b/core/jvm/src/SinkExtJvm.kt index dc6362bbf..e61a68069 100644 --- a/core/jvm/src/SinkExtJvm.kt +++ b/core/jvm/src/SinkExtJvm.kt @@ -50,7 +50,7 @@ public fun Sink.writeString(string: String, charset: Charset, startIndex: Int = * Returns an output stream that writes to this sink. Closing the stream will also close this sink. */ @OptIn(DelicateIoApi::class) -public fun Sink.outputStream(): OutputStream { +public fun Sink.asOutputStream(): OutputStream { val isClosed: () -> Boolean = when (this) { is RealSink -> this::closed is Buffer -> { { false } } @@ -70,13 +70,13 @@ public fun Sink.outputStream(): OutputStream { override fun flush() { // For backwards compatibility, a flush() on a closed stream is a no-op. if (!isClosed()) { - this@outputStream.flush() + this@asOutputStream.flush() } } - override fun close() = this@outputStream.close() + override fun close() = this@asOutputStream.close() - override fun toString() = "${this@outputStream}.outputStream()" + override fun toString() = "${this@asOutputStream}.outputStream()" } } @@ -99,7 +99,7 @@ public fun Sink.write(source: ByteBuffer): Int { /** * Returns [WritableByteChannel] backed by this sink. Closing the channel will also close the sink. */ -public fun Sink.channel(): WritableByteChannel { +public fun Sink.asByteChannel(): WritableByteChannel { val isClosed: () -> Boolean = when (this) { is RealSink -> this::closed is Buffer -> { { false } } @@ -107,14 +107,14 @@ public fun Sink.channel(): WritableByteChannel { return object : WritableByteChannel { override fun close() { - this@channel.close() + this@asByteChannel.close() } override fun isOpen(): Boolean = !isClosed() override fun write(source: ByteBuffer): Int { check(!isClosed()) { "Underlying sink is closed." } - return this@channel.write(source) + return this@asByteChannel.write(source) } } } diff --git a/core/jvm/src/SourceExtJvm.kt b/core/jvm/src/SourceExtJvm.kt index 9c6c39ec0..32d42d8be 100644 --- a/core/jvm/src/SourceExtJvm.kt +++ b/core/jvm/src/SourceExtJvm.kt @@ -89,7 +89,7 @@ public fun Source.readString(byteCount: Long, charset: Charset): String { * Returns an input stream that reads from this source. Closing the stream will also close this source. */ @OptIn(InternalIoApi::class) -public fun Source.inputStream(): InputStream { +public fun Source.asInputStream(): InputStream { val isClosed: () -> Boolean = when (this) { is RealSource -> this::closed is Buffer -> { { false } } @@ -108,7 +108,7 @@ public fun Source.inputStream(): InputStream { if (isClosed()) throw IOException("Underlying source is closed.") checkOffsetAndCount(data.size.toLong(), offset.toLong(), byteCount.toLong()) - return this@inputStream.readAtMostTo(data, offset, offset + byteCount) + return this@asInputStream.readAtMostTo(data, offset, offset + byteCount) } override fun available(): Int { @@ -116,9 +116,9 @@ public fun Source.inputStream(): InputStream { return minOf(buffer.size, Integer.MAX_VALUE).toInt() } - override fun close() = this@inputStream.close() + override fun close() = this@asInputStream.close() - override fun toString() = "${this@inputStream}.inputStream()" + override fun toString() = "${this@asInputStream}.inputStream()" } } @@ -142,7 +142,7 @@ public fun Source.readAtMostTo(sink: ByteBuffer): Int { /** * Returns [ReadableByteChannel] backed by this source. Closing the source will close the source. */ -public fun Source.channel(): ReadableByteChannel { +public fun Source.asByteChannel(): ReadableByteChannel { val isClosed: () -> Boolean = when (this) { is RealSource -> this::closed is Buffer -> { { false } } @@ -150,11 +150,11 @@ public fun Source.channel(): ReadableByteChannel { return object : ReadableByteChannel { override fun close() { - this@channel.close() + this@asByteChannel.close() } override fun isOpen(): Boolean = !isClosed() - override fun read(sink: ByteBuffer): Int = this@channel.readAtMostTo(sink) + override fun read(sink: ByteBuffer): Int = this@asByteChannel.readAtMostTo(sink) } } diff --git a/core/jvm/src/files/Paths.kt b/core/jvm/src/files/Paths.kt index 148bb3cee..148ec4318 100644 --- a/core/jvm/src/files/Paths.kt +++ b/core/jvm/src/files/Paths.kt @@ -15,6 +15,6 @@ public actual class Path internal constructor(internal val nioPath: NioPath) public actual fun Path(path: String): Path = Path(Paths.get(path)) -public actual fun Path.source(): Source = FileInputStream(nioPath.toFile()).source().buffer() +public actual fun Path.source(): Source = FileInputStream(nioPath.toFile()).asSource().buffered() -public actual fun Path.sink(): Sink = FileOutputStream(nioPath.toFile()).sink().buffer() +public actual fun Path.sink(): Sink = FileOutputStream(nioPath.toFile()).asSink().buffered() diff --git a/core/jvm/test/AbstractSinkTestJVM.kt b/core/jvm/test/AbstractSinkTestJVM.kt index 571e3d101..d383c141d 100644 --- a/core/jvm/test/AbstractSinkTestJVM.kt +++ b/core/jvm/test/AbstractSinkTestJVM.kt @@ -40,7 +40,7 @@ abstract class AbstractSinkTestJVM internal constructor(factory: SinkFactory) { @Test fun outputStream() { - val out: OutputStream = sink.outputStream() + val out: OutputStream = sink.asOutputStream() out.write('a'.code) out.write("b".repeat(9998).toByteArray(UTF_8)) out.write('c'.code) @@ -50,7 +50,7 @@ abstract class AbstractSinkTestJVM internal constructor(factory: SinkFactory) { @Test fun outputStreamBounds() { - val out: OutputStream = sink.outputStream() + val out: OutputStream = sink.asOutputStream() assertFailsWith { out.write(ByteArray(100), 50, 51) } @@ -61,7 +61,7 @@ abstract class AbstractSinkTestJVM internal constructor(factory: SinkFactory) { if (sink is Buffer) { return } - val out = sink.outputStream() + val out = sink.asOutputStream() sink.close() assertFailsWith { out.write(0) } assertFailsWith { out.write(ByteArray(1)) } @@ -74,7 +74,7 @@ abstract class AbstractSinkTestJVM internal constructor(factory: SinkFactory) { return } - val out = sink.outputStream() + val out = sink.asOutputStream() out.close() assertFailsWith { sink.writeByte(0) } } diff --git a/core/jvm/test/AbstractSourceTestJVM.kt b/core/jvm/test/AbstractSourceTestJVM.kt index 9fea0b97f..521a4b023 100644 --- a/core/jvm/test/AbstractSourceTestJVM.kt +++ b/core/jvm/test/AbstractSourceTestJVM.kt @@ -51,7 +51,7 @@ abstract class AbstractSourceTestJVM(private val factory: SourceFactory) { fun inputStream() { sink.writeUtf8("abc") sink.emit() - val input: InputStream = source.inputStream() + val input: InputStream = source.asInputStream() val bytes = byteArrayOf('z'.code.toByte(), 'z'.code.toByte(), 'z'.code.toByte()) var read: Int = input.read(bytes) if (factory.isOneByteAtATime) { @@ -74,7 +74,7 @@ abstract class AbstractSourceTestJVM(private val factory: SourceFactory) { fun inputStreamOffsetCount() { sink.writeUtf8("abcde") sink.emit() - val input: InputStream = source.inputStream() + val input: InputStream = source.asInputStream() val bytes = byteArrayOf('z'.code.toByte(), 'z'.code.toByte(), 'z'.code.toByte(), 'z'.code.toByte(), 'z'.code.toByte()) val read: Int = input.read(bytes, 1, 3) @@ -91,7 +91,7 @@ abstract class AbstractSourceTestJVM(private val factory: SourceFactory) { fun inputStreamSkip() { sink.writeUtf8("abcde") sink.emit() - val input: InputStream = source.inputStream() + val input: InputStream = source.asInputStream() assertEquals(4, input.skip(4)) assertEquals('e'.code, input.read()) sink.writeUtf8("abcde") @@ -104,7 +104,7 @@ abstract class AbstractSourceTestJVM(private val factory: SourceFactory) { fun inputStreamCharByChar() { sink.writeUtf8("abc") sink.emit() - val input: InputStream = source.inputStream() + val input: InputStream = source.asInputStream() assertEquals('a'.code, input.read()) assertEquals('b'.code, input.read()) assertEquals('c'.code, input.read()) @@ -115,7 +115,7 @@ abstract class AbstractSourceTestJVM(private val factory: SourceFactory) { fun inputStreamBounds() { sink.writeUtf8("a".repeat(100)) sink.emit() - val input: InputStream = source.inputStream() + val input: InputStream = source.asInputStream() assertFailsWith { input.read(ByteArray(100), 50, 51) } @@ -130,7 +130,7 @@ abstract class AbstractSourceTestJVM(private val factory: SourceFactory) { sink.writeByte(0) sink.emit() - val input = source.inputStream() + val input = source.asInputStream() source.close() assertFailsWith { input.read() } assertFailsWith { input.read(ByteArray(1)) } @@ -146,7 +146,7 @@ abstract class AbstractSourceTestJVM(private val factory: SourceFactory) { sink.writeByte(0) sink.emit() - val input = source.inputStream() + val input = source.asInputStream() input.close() assertFailsWith { source.readByte() } @@ -154,7 +154,7 @@ abstract class AbstractSourceTestJVM(private val factory: SourceFactory) { @Test fun inputStreamAvailable() { - val input = source.inputStream() + val input = source.asInputStream() assertEquals(0, input.available()) sink.writeInt(42) @@ -182,7 +182,7 @@ abstract class AbstractSourceTestJVM(private val factory: SourceFactory) { return } - val input = source.inputStream() + val input = source.asInputStream() source.close() assertFailsWith { input.available() } diff --git a/core/jvm/test/BufferTest.kt b/core/jvm/test/BufferTest.kt index 58d079e13..a8a5a3746 100644 --- a/core/jvm/test/BufferTest.kt +++ b/core/jvm/test/BufferTest.kt @@ -144,7 +144,7 @@ class BufferTest { fun bufferInputStreamByteByByte() { val source = Buffer() source.writeUtf8("abc") - val input: InputStream = source.inputStream() + val input: InputStream = source.asInputStream() assertEquals(3, input.available()) assertEquals('a'.code, input.read()) assertEquals('b'.code, input.read()) @@ -159,7 +159,7 @@ class BufferTest { source.writeUtf8("abc") val byteArray = ByteArray(4) Arrays.fill(byteArray, (-5).toByte()) - val input: InputStream = source.inputStream() + val input: InputStream = source.asInputStream() assertEquals(3, input.read(byteArray)) assertEquals("[97, 98, 99, -5]", byteArray.contentToString()) Arrays.fill(byteArray, (-7).toByte()) @@ -172,7 +172,7 @@ class BufferTest { source.writeUtf8("party") val target = Buffer() - source.copyTo(target.outputStream()) + source.copyTo(target.asOutputStream()) assertEquals("party", target.readUtf8()) assertEquals("party", source.readUtf8()) } @@ -182,7 +182,7 @@ class BufferTest { source.writeUtf8("party") val target = Buffer() - source.copyTo(target.outputStream(), startIndex = 2) + source.copyTo(target.asOutputStream(), startIndex = 2) assertEquals("rty", target.readUtf8()) assertEquals("party", source.readUtf8()) } @@ -192,7 +192,7 @@ class BufferTest { source.writeUtf8("party") val target = Buffer() - source.copyTo(target.outputStream(), endIndex = 3) + source.copyTo(target.asOutputStream(), endIndex = 3) assertEquals("par", target.readUtf8()) assertEquals("party", source.readUtf8()) } @@ -202,7 +202,7 @@ class BufferTest { source.writeUtf8("party") val target = Buffer() - source.copyTo(target.outputStream(), startIndex = 1, endIndex = 4) + source.copyTo(target.asOutputStream(), startIndex = 1, endIndex = 4) assertEquals("art", target.readUtf8()) assertEquals("party", source.readUtf8()) } @@ -212,7 +212,7 @@ class BufferTest { source.writeUtf8("hello") val target = Buffer() - source.copyTo(target.outputStream(), startIndex = 1, endIndex = 1) + source.copyTo(target.asOutputStream(), startIndex = 1, endIndex = 1) assertEquals("hello", source.readUtf8()) assertEquals("", target.readUtf8()) } @@ -222,7 +222,7 @@ class BufferTest { source.writeUtf8("party") val target = Buffer() - source.readTo(target.outputStream()) + source.readTo(target.asOutputStream()) assertEquals("party", target.readUtf8()) assertEquals("", source.readUtf8()) } @@ -232,7 +232,7 @@ class BufferTest { source.writeUtf8("party") val target = Buffer() - source.readTo(target.outputStream(), byteCount = 3) + source.readTo(target.asOutputStream(), byteCount = 3) assertEquals("par", target.readUtf8()) assertEquals("ty", source.readUtf8()) } diff --git a/core/jvm/test/JvmPlatformTest.kt b/core/jvm/test/JvmPlatformTest.kt index 7a73daf55..556f63485 100644 --- a/core/jvm/test/JvmPlatformTest.kt +++ b/core/jvm/test/JvmPlatformTest.kt @@ -42,21 +42,21 @@ class JvmPlatformTest { @Test fun outputStreamSink() { val baos = ByteArrayOutputStream() - val sink = baos.sink() + val sink = baos.asSink() sink.write(Buffer().also { it.writeUtf8("a") }, 1L) assertArrayEquals(baos.toByteArray(), byteArrayOf(0x61)) } @Test fun outputStreamSinkWriteZeroBytes() { val baos = ByteArrayOutputStream() - val sink = baos.sink() + val sink = baos.asSink() sink.write(Buffer().also { it.writeUtf8("a") }, 0L) assertEquals(0, baos.size()) } @Test fun outputStreamSinkWriteNegativeNumberOfBytes() { val baos = ByteArrayOutputStream() - val sink = baos.sink() + val sink = baos.asSink() assertFailsWith { sink.write(Buffer().also { it.writeUtf8("a") }, -1) } @@ -64,7 +64,7 @@ class JvmPlatformTest { @Test fun outputStreamSinkWritePartOfTheBuffer() { val baos = ByteArrayOutputStream() - val sink = baos.sink() + val sink = baos.asSink() val buffer = Buffer().also { it.writeUtf8("hello") } sink.write(buffer, 2) assertArrayEquals(baos.toByteArray(), byteArrayOf('h'.code.toByte(), 'e'.code.toByte())) @@ -73,7 +73,7 @@ class JvmPlatformTest { @Test fun inputStreamSource() { val bais = ByteArrayInputStream(byteArrayOf(0x61)) - val source = bais.source() + val source = bais.asSource() val buffer = Buffer() source.readAtMostTo(buffer, 1) assertEquals(buffer.readUtf8(), "a") @@ -81,7 +81,7 @@ class JvmPlatformTest { @Test fun inputStreamSourceReadZeroBytes() { val bais = ByteArrayInputStream(ByteArray(128)) - val source = bais.source() + val source = bais.asSource() val buffer = Buffer() source.readAtMostTo(buffer, 0) assertEquals(0, buffer.size) @@ -89,13 +89,13 @@ class JvmPlatformTest { @Test fun inputStreamSourceReadNegativeNumberOfBytes() { val bais = ByteArrayInputStream(ByteArray(128)) - val source = bais.source() + val source = bais.asSource() assertFailsWith { source.readAtMostTo(Buffer(), -1) } } @Test fun fileSink() { val file = File(tempDir, "test") - file.outputStream().sink().use { sink -> + file.outputStream().asSink().use { sink -> sink.write(Buffer().also { it.writeUtf8("a") }, 1L) } assertEquals(file.readText(), "a") @@ -104,7 +104,7 @@ class JvmPlatformTest { @Test fun fileAppendingSink() { val file = File(tempDir, "test") file.writeText("a") - FileOutputStream(file, true).sink().use { sink -> + FileOutputStream(file, true).asSink().use { sink -> sink.write(Buffer().also { it.writeUtf8("b") }, 1L) } assertEquals(file.readText(), "ab") @@ -114,7 +114,7 @@ class JvmPlatformTest { val file = File(tempDir, "test") file.writeText("a") val buffer = Buffer() - file.inputStream().source().use { source -> + file.inputStream().asSource().use { source -> source.readAtMostTo(buffer, 1L) } assertEquals(buffer.readUtf8(), "a") @@ -122,7 +122,7 @@ class JvmPlatformTest { @Test fun pathSink() { val file = File(tempDir, "test") - file.toPath().outputStream().sink().use { sink -> + file.toPath().outputStream().asSink().use { sink -> sink.write(Buffer().also { it.writeUtf8("a") }, 1L) } assertEquals(file.readText(), "a") @@ -131,7 +131,7 @@ class JvmPlatformTest { @Test fun pathSinkWithOptions() { val file = File(tempDir, "test") file.writeText("a") - file.toPath().outputStream(StandardOpenOption.APPEND).sink().use { sink -> + file.toPath().outputStream(StandardOpenOption.APPEND).asSink().use { sink -> sink.write(Buffer().also { it.writeUtf8("b") }, 1L) } assertEquals(file.readText(), "ab") @@ -141,7 +141,7 @@ class JvmPlatformTest { val file = File(tempDir, "test") file.writeText("a") val buffer = Buffer() - file.toPath().inputStream().source().use { source -> + file.toPath().inputStream().asSource().use { source -> source.readAtMostTo(buffer, 1L) } assertEquals(buffer.readUtf8(), "a") @@ -160,9 +160,9 @@ class JvmPlatformTest { } assertFailsWith { - link.toPath().inputStream(LinkOption.NOFOLLOW_LINKS).source().use { it.buffer().readUtf8Line() } + link.toPath().inputStream(LinkOption.NOFOLLOW_LINKS).asSource().use { it.buffered().readUtf8Line() } } - assertNull(link.toPath().inputStream().source().use { it.buffer().readUtf8Line() }) + assertNull(link.toPath().inputStream().asSource().use { it.buffered().readUtf8Line() }) } @Test fun socketSink() { @@ -170,7 +170,7 @@ class JvmPlatformTest { val socket = object : Socket() { override fun getOutputStream() = baos } - val sink = socket.outputStream.sink() + val sink = socket.outputStream.asSink() sink.write(Buffer().also { it.writeUtf8("a") }, 1L) assertArrayEquals(baos.toByteArray(), byteArrayOf(0x61)) } @@ -180,7 +180,7 @@ class JvmPlatformTest { val socket = object : Socket() { override fun getInputStream() = bais } - val source = socket.inputStream.source() + val source = socket.inputStream.asSource() val buffer = Buffer() source.readAtMostTo(buffer, 1L) assertEquals(buffer.readUtf8(), "a") diff --git a/core/jvm/test/NioTest.kt b/core/jvm/test/NioTest.kt index c09e51229..bafc63ecc 100644 --- a/core/jvm/test/NioTest.kt +++ b/core/jvm/test/NioTest.kt @@ -44,7 +44,7 @@ class NioTest { lateinit var temporaryFolder: Path @Test fun sourceIsOpen() { - val source = RealSource(Buffer()).channel() + val source = RealSource(Buffer()).asByteChannel() assertTrue(source.isOpen()) source.close() assertFalse(source.isOpen()) @@ -52,7 +52,7 @@ class NioTest { @Test fun sinkIsOpen() { - val sink = RealSink(Buffer()).channel() + val sink = RealSink(Buffer()).asByteChannel() assertTrue(sink.isOpen()) sink.close() assertFalse(sink.isOpen()) @@ -63,7 +63,7 @@ class NioTest { val file = Paths.get(temporaryFolder.toString(), "test").createFile() val fileChannel: FileChannel = FileChannel.open(file, StandardOpenOption.WRITE) testWritableByteChannel(false, fileChannel) - val emitted: Source = file.inputStream().source().buffer() + val emitted: Source = file.inputStream().asSource().buffered() assertEquals("defghijklmnopqrstuvw", emitted.readUtf8()) emitted.close() } @@ -71,7 +71,7 @@ class NioTest { @Test fun writableChannelBuffer() { val buffer = Buffer() - testWritableByteChannel(true, buffer.channel()) + testWritableByteChannel(true, buffer.asByteChannel()) assertEquals("defghijklmnopqrstuvw", buffer.readUtf8()) } @@ -79,14 +79,14 @@ class NioTest { fun writableChannelBufferedSink() { val buffer = Buffer() val bufferedSink: Sink = buffer - testWritableByteChannel(true, bufferedSink.channel()) + testWritableByteChannel(true, bufferedSink.asByteChannel()) assertEquals("defghijklmnopqrstuvw", buffer.readUtf8()) } @Test fun readableChannelNioFile() { val file: File = Paths.get(temporaryFolder.toString(), "test").toFile() - val initialData: Sink = file.outputStream().sink().buffer() + val initialData: Sink = file.outputStream().asSink().buffered() initialData.writeUtf8("abcdefghijklmnopqrstuvwxyz") initialData.close() val fileChannel: FileChannel = FileChannel.open(file.toPath(), StandardOpenOption.READ) @@ -97,7 +97,7 @@ class NioTest { fun readableChannelBuffer() { val buffer = Buffer() buffer.writeUtf8("abcdefghijklmnopqrstuvwxyz") - testReadableByteChannel(true, buffer.channel()) + testReadableByteChannel(true, buffer.asByteChannel()) } @Test @@ -105,7 +105,7 @@ class NioTest { val buffer = Buffer() val bufferedSource: Source = buffer buffer.writeUtf8("abcdefghijklmnopqrstuvwxyz") - testReadableByteChannel(true, bufferedSource.channel()) + testReadableByteChannel(true, bufferedSource.asByteChannel()) } /** diff --git a/core/native/src/files/PathsNative.kt b/core/native/src/files/PathsNative.kt index 73cc2ed1f..c247463c9 100644 --- a/core/native/src/files/PathsNative.kt +++ b/core/native/src/files/PathsNative.kt @@ -21,13 +21,13 @@ public actual fun Path(path: String): Path = Path(path, null) public actual fun Path.source(): Source { val openFile: CPointer = fopen(path, "r") ?: throw IOException("Failed to open $path with ${strerror(errno)?.toKString()}") - return FileSource(openFile).buffer() + return FileSource(openFile).buffered() } public actual fun Path.sink(): Sink { val openFile: CPointer = fopen(path, "w") ?: throw IOException("Failed to open $path with ${strerror(errno)?.toKString()}") - return FileSink(openFile).buffer() + return FileSink(openFile).buffered() } internal class FileSource( From 2b7eb60e47670bfb8433257349caa5b85bb84881 Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Fri, 23 Jun 2023 11:26:38 +0200 Subject: [PATCH 77/83] Change Buffer::toString format Always print data as hex, don't try decoding it as text. --- core/common/src/Buffer.kt | 54 +++++++++++++------------- core/common/test/AbstractSinkTest.kt | 40 +++++++++---------- core/common/test/CommonBufferTest.kt | 16 ++++---- core/common/test/CommonRealSinkTest.kt | 6 +-- 4 files changed, 57 insertions(+), 59 deletions(-) diff --git a/core/common/src/Buffer.kt b/core/common/src/Buffer.kt index 3ffb257ea..f5a25588f 100644 --- a/core/common/src/Buffer.kt +++ b/core/common/src/Buffer.kt @@ -598,41 +598,39 @@ public class Buffer : Source, Sink { /** * Returns a human-readable string that describes the contents of this buffer. For buffers containing - * few bytes, this is a string like `[text=Hello]` or `[hex=0000ffff]`. However, if the buffer is too large, - * a string will contain its size and only a prefix of data, like `[size=1024 hex=01234…]`. Thus, the string could not - * be used to compare buffers or verify buffer's content. + * few bytes, this is a string like `Buffer(size=4 hex=0000ffff)`. However, if the buffer is too large, + * a string will contain its size and only a prefix of data, like `Buffer(size=1024 hex=01234…)`. + * Thus, the string could not be used to compare buffers or verify buffer's content. */ override fun toString(): String { - // TODO: optimize implementation - if (size == 0L) return "[size=0]" + if (size == 0L) return "Buffer(size=0)" - val peekSrc = peek() - val data = if (peekSrc.request(128)) { - peekSrc.readByteArray(128) - } else { - peekSrc.readByteArray() - } - val i = codePointIndexToCharIndex(data, 64) - if (i == -1) { - return if (data.size <= 64) { - "[hex=${data.hex()}]" - } else { - "[size=${size} hex=${data.hex(64)}…]" + val maxPrintableBytes = 64 + val len = minOf(maxPrintableBytes, size).toInt() + + val builder = StringBuilder(len * 2 + if (size > maxPrintableBytes) 1 else 0) + + var curr = head!! + var bytesWritten = 0 + var pos = curr.pos + while (bytesWritten < len) { + if (pos == curr.limit) { + curr = curr.next!! + pos = curr.pos } - } - val text = data.decodeToString() - val escapedText = text - .substring(0, i) - .replace("\\", "\\\\") - .replace("\n", "\\n") - .replace("\r", "\\r") + val b = curr.data[pos++].toInt() + bytesWritten++ - return if (i < text.length) { - "[size=${data.size} text=$escapedText…]" - } else { - "[text=$escapedText]" + builder.append(HEX_DIGIT_CHARS[(b shr 4) and 0xf]) + .append(HEX_DIGIT_CHARS[b and 0xf]) } + + if (size > maxPrintableBytes) { + builder.append('…') + } + + return "Buffer(size=$size hex=$builder)" } } diff --git a/core/common/test/AbstractSinkTest.kt b/core/common/test/AbstractSinkTest.kt index 48685a31e..af74f0c45 100644 --- a/core/common/test/AbstractSinkTest.kt +++ b/core/common/test/AbstractSinkTest.kt @@ -38,17 +38,17 @@ abstract class AbstractSinkTest internal constructor( val source = byteArrayOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9) sink.write(source) sink.flush() - assertEquals("[hex=00010203040506070809]", data.toString()) + assertEquals("Buffer(size=10 hex=00010203040506070809)", data.toString()) data.clear() sink.write(source, 3) sink.flush() - assertEquals("[hex=03040506070809]", data.toString()) + assertEquals("Buffer(size=7 hex=03040506070809)", data.toString()) data.clear() sink.write(source, 0, 3) sink.flush() - assertEquals("[hex=000102]", data.toString()) + assertEquals("Buffer(size=3 hex=000102)", data.toString()) data.clear() assertFailsWith { @@ -71,14 +71,14 @@ abstract class AbstractSinkTest internal constructor( @Test fun writeByte() { sink.writeByte(0xba.toByte()) sink.flush() - assertEquals("[hex=ba]", data.toString()) + assertEquals("Buffer(size=1 hex=ba)", data.toString()) } @Test fun writeBytes() { sink.writeByte(0xab.toByte()) sink.writeByte(0xcd.toByte()) sink.flush() - assertEquals("[hex=abcd]", data.toString()) + assertEquals("Buffer(size=2 hex=abcd)", data.toString()) } @Test fun writeLastByteInSegment() { @@ -88,40 +88,40 @@ abstract class AbstractSinkTest internal constructor( sink.flush() assertEquals(listOf(Segment.SIZE, 1), segmentSizes(data)) assertEquals("a".repeat(Segment.SIZE - 1), data.readUtf8(Segment.SIZE - 1L)) - assertEquals("[text= !]", data.toString()) + assertEquals("Buffer(size=2 hex=2021)", data.toString()) } @Test fun writeShort() { sink.writeShort(0xab01.toShort()) sink.flush() - assertEquals("[hex=ab01]", data.toString()) + assertEquals("Buffer(size=2 hex=ab01)", data.toString()) } @Test fun writeShorts() { sink.writeShort(0xabcd.toShort()) sink.writeShort(0x4321) sink.flush() - assertEquals("[hex=abcd4321]", data.toString()) + assertEquals("Buffer(size=4 hex=abcd4321)", data.toString()) } @Test fun writeShortLe() { sink.writeShortLe(0xcdab.toShort()) sink.writeShortLe(0x2143) sink.flush() - assertEquals("[hex=abcd4321]", data.toString()) + assertEquals("Buffer(size=4 hex=abcd4321)", data.toString()) } @Test fun writeInt() { sink.writeInt(0x197760) sink.flush() - assertEquals("[hex=00197760]", data.toString()) + assertEquals("Buffer(size=4 hex=00197760)", data.toString()) } @Test fun writeInts() { sink.writeInt(-0x543210ff) sink.writeInt(-0x789abcdf) sink.flush() - assertEquals("[hex=abcdef0187654321]", data.toString()) + assertEquals("Buffer(size=8 hex=abcdef0187654321)", data.toString()) } @Test fun writeLastIntegerInSegment() { @@ -131,7 +131,7 @@ abstract class AbstractSinkTest internal constructor( sink.flush() assertEquals(listOf(Segment.SIZE, 4), segmentSizes(data)) assertEquals("a".repeat(Segment.SIZE - 4), data.readUtf8(Segment.SIZE - 4L)) - assertEquals("[hex=abcdef0187654321]", data.toString()) + assertEquals("Buffer(size=8 hex=abcdef0187654321)", data.toString()) } @Test fun writeIntegerDoesNotQuiteFitInSegment() { @@ -141,34 +141,34 @@ abstract class AbstractSinkTest internal constructor( sink.flush() assertEquals(listOf(Segment.SIZE - 3, 8), segmentSizes(data)) assertEquals("a".repeat(Segment.SIZE - 3), data.readUtf8(Segment.SIZE - 3L)) - assertEquals("[hex=abcdef0187654321]", data.toString()) + assertEquals("Buffer(size=8 hex=abcdef0187654321)", data.toString()) } @Test fun writeIntLe() { sink.writeIntLe(-0x543210ff) sink.writeIntLe(-0x789abcdf) sink.flush() - assertEquals("[hex=01efcdab21436587]", data.toString()) + assertEquals("Buffer(size=8 hex=01efcdab21436587)", data.toString()) } @Test fun writeLong() { sink.writeLong(0x123456789abcdef0L) sink.flush() - assertEquals("[hex=123456789abcdef0]", data.toString()) + assertEquals("Buffer(size=8 hex=123456789abcdef0)", data.toString()) } @Test fun writeLongs() { sink.writeLong(-0x543210fe789abcdfL) sink.writeLong(-0x350145414f4ea400L) sink.flush() - assertEquals("[hex=abcdef0187654321cafebabeb0b15c00]", data.toString()) + assertEquals("Buffer(size=16 hex=abcdef0187654321cafebabeb0b15c00)", data.toString()) } @Test fun writeLongLe() { sink.writeLongLe(-0x543210fe789abcdfL) sink.writeLongLe(-0x350145414f4ea400L) sink.flush() - assertEquals("[hex=2143658701efcdab005cb1b0bebafeca]", data.toString()) + assertEquals("Buffer(size=16 hex=2143658701efcdab005cb1b0bebafeca)", data.toString()) } @Test fun writeAll() { @@ -382,7 +382,7 @@ abstract class AbstractSinkTest internal constructor( @Test fun writeUShortLe() { sink.writeUShortLe(0x1234u) sink.flush() - assertEquals("[hex=3412]", data.toString()) + assertEquals("Buffer(size=2 hex=3412)", data.toString()) } @Test fun writeUInt() { @@ -394,7 +394,7 @@ abstract class AbstractSinkTest internal constructor( @Test fun writeUIntLe() { sink.writeUIntLe(0x12345678u) sink.flush() - assertEquals("[hex=78563412]", data.toString()) + assertEquals("Buffer(size=4 hex=78563412)", data.toString()) } @Test fun writeULong() { @@ -406,6 +406,6 @@ abstract class AbstractSinkTest internal constructor( @Test fun writeULongLe() { sink.writeULongLe(0x1234567890abcdefu) sink.flush() - assertEquals("[hex=efcdab9078563412]", data.toString()) + assertEquals("Buffer(size=8 hex=efcdab9078563412)", data.toString()) } } diff --git a/core/common/test/CommonBufferTest.kt b/core/common/test/CommonBufferTest.kt index e475f0b87..54c56206f 100644 --- a/core/common/test/CommonBufferTest.kt +++ b/core/common/test/CommonBufferTest.kt @@ -48,23 +48,23 @@ class CommonBufferTest { } @Test fun bufferToString() { - assertEquals("[size=0]", Buffer().toString()) + assertEquals("Buffer(size=0)", Buffer().toString()) - assertEquals("[text=a\\r\\nb\\nc\\rd\\\\e]", + assertEquals("Buffer(size=10 hex=610d0a620a630d645c65)", Buffer().also { it.writeUtf8("a\r\nb\nc\rd\\e") }.toString()) - assertEquals("[text=Tyrannosaur]", + assertEquals("Buffer(size=11 hex=547972616e6e6f73617572)", Buffer().also { it.writeUtf8("Tyrannosaur") }.toString()) - assertEquals("[text=təˈranəˌsôr]", + assertEquals("Buffer(size=16 hex=74c999cb8872616ec999cb8c73c3b472)", Buffer().also { it.write("74c999cb8872616ec999cb8c73c3b472".decodeHex()) }.toString()) - assertEquals("[hex=0000000000000000000000000000000000000000000000000000000000000000000000000000" + - "0000000000000000000000000000000000000000000000000000]", + assertEquals("Buffer(size=64 hex=00000000000000000000000000000000000000000000000000000000000000000000000" + + "000000000000000000000000000000000000000000000000000000000)", Buffer().also { it.write(ByteArray(64)) }.toString()) - assertEquals("[size=66 hex=000000000000000000000000000000000000000000000000000000000000" + - "00000000000000000000000000000000000000000000000000000000000000000000…]", + assertEquals("Buffer(size=66 hex=000000000000000000000000000000000000000000000000000000000000" + + "00000000000000000000000000000000000000000000000000000000000000000000…)", Buffer().also { it.write(ByteArray(66)) }.toString()) } diff --git a/core/common/test/CommonRealSinkTest.kt b/core/common/test/CommonRealSinkTest.kt index 3ad958560..71f8ba320 100644 --- a/core/common/test/CommonRealSinkTest.kt +++ b/core/common/test/CommonRealSinkTest.kt @@ -105,7 +105,7 @@ class CommonRealSinkTest { bufferedSink.close() } - mockSink.assertLog("write([text=a], 1)", "close()") + mockSink.assertLog("write(Buffer(size=1 hex=61), 1)", "close()") } @Test fun closeWithExceptionWhenClosing() { @@ -117,7 +117,7 @@ class CommonRealSinkTest { bufferedSink.close() } - mockSink.assertLog("write([text=a], 1)", "close()") + mockSink.assertLog("write(Buffer(size=1 hex=61), 1)", "close()") } @Test fun closeWithExceptionWhenWritingAndClosing() { @@ -130,7 +130,7 @@ class CommonRealSinkTest { bufferedSink.close() } - mockSink.assertLog("write([text=a], 1)", "close()") + mockSink.assertLog("write(Buffer(size=1 hex=61), 1)", "close()") } @Test fun operationsAfterClose() { From 5171a2853be924e328c7c923494719e7a69a2dff Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Fri, 23 Jun 2023 11:38:18 +0200 Subject: [PATCH 78/83] Clarify copyTo behavior in the documentation --- core/common/src/Buffer.kt | 1 + core/jvm/src/BufferExtJvm.kt | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/core/common/src/Buffer.kt b/core/common/src/Buffer.kt index f5a25588f..3c54d8746 100644 --- a/core/common/src/Buffer.kt +++ b/core/common/src/Buffer.kt @@ -205,6 +205,7 @@ public class Buffer : Source, Sink { /** * Copy bytes from this buffer's subrange starting at [startIndex] and ending at [endIndex], to [out] buffer. + * This method does not consume data from the buffer. * * @param out the destination buffer to copy data into. * @param startIndex the index (inclusive) of the first byte of data in this buffer to copy, diff --git a/core/jvm/src/BufferExtJvm.kt b/core/jvm/src/BufferExtJvm.kt index 60ae1c62f..546990b56 100644 --- a/core/jvm/src/BufferExtJvm.kt +++ b/core/jvm/src/BufferExtJvm.kt @@ -105,7 +105,8 @@ public fun Buffer.readTo(out: OutputStream, byteCount: Long = size) { } /** - * Copy bytes from this buffer's subrange, starting at [startIndex] and ending at [endIndex], to [out]. + * Copy bytes from this buffer's subrange, starting at [startIndex] and ending at [endIndex], to [out]. This method + * does not consume data from the buffer. * * @param out the destination to copy data into. * @param startIndex the index (inclusive) of the first byte to copy, `0` by default. From a96726fb5a4af233516c3fda559f5afff271ac6f Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Mon, 26 Jun 2023 11:57:48 +0200 Subject: [PATCH 79/83] Make parameter names in public API more verbose --- core/common/src/SourceExt.kt | 16 ++++++++-------- core/jvm/src/SinkExtJvm.kt | 4 ++-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/core/common/src/SourceExt.kt b/core/common/src/SourceExt.kt index 0ec7f83b8..a1967672c 100644 --- a/core/common/src/SourceExt.kt +++ b/core/common/src/SourceExt.kt @@ -152,22 +152,22 @@ public fun Source.readHexadecimalUnsignedLong(): Long { } /** - * Returns an index of [b] first occurrence in the range of [startIndex] to [endIndex], - * or `-1` when the range doesn't contain [b]. + * Returns an index of [byte] first occurrence in the range of [startIndex] to [endIndex], + * or `-1` when the range doesn't contain [byte]. * * The scan terminates at either [endIndex] or source's exhaustion, whichever comes first. The * maximum number of bytes scanned is `toIndex-fromIndex`. - * If [b] not found in buffered data, [endIndex] is yet to be reached and the underlying source is not yet exhausted + * If [byte] not found in buffered data, [endIndex] is yet to be reached and the underlying source is not yet exhausted * then new data will be read from the underlying source into the buffer. * - * @param b the value to find. - * @param startIndex the start of the range (inclusive) to find [b], `0` by default. - * @param endIndex the end of the range (exclusive) to find [b], [Long.MAX_VALUE] by default. + * @param byte the value to find. + * @param startIndex the start of the range (inclusive) to find [byte], `0` by default. + * @param endIndex the end of the range (exclusive) to find [byte], [Long.MAX_VALUE] by default. * * @throws IllegalStateException when the source is closed. * @throws IllegalArgumentException when `startIndex > endIndex` or either of indices is negative. */ -public fun Source.indexOf(b: Byte, startIndex: Long = 0L, endIndex: Long = Long.MAX_VALUE): Long { +public fun Source.indexOf(byte: Byte, startIndex: Long = 0L, endIndex: Long = Long.MAX_VALUE): Long { require(startIndex in 0..endIndex) { if (endIndex < 0) { "startIndex ($startIndex) and endIndex ($endIndex) should be non negative" } else { "startIndex ($startIndex) is not within the range [0..endIndex($endIndex))" } @@ -182,7 +182,7 @@ public fun Source.indexOf(b: Byte, startIndex: Long = 0L, endIndex: Long = Long. } peekSource.skip(offset) while (offset < endIndex && peekSource.request(1)) { - if (peekSource.readByte() == b) return offset + if (peekSource.readByte() == byte) return offset offset++ } return -1L diff --git a/core/jvm/src/SinkExtJvm.kt b/core/jvm/src/SinkExtJvm.kt index e61a68069..c6237da40 100644 --- a/core/jvm/src/SinkExtJvm.kt +++ b/core/jvm/src/SinkExtJvm.kt @@ -57,9 +57,9 @@ public fun Sink.asOutputStream(): OutputStream { } return object : OutputStream() { - override fun write(b: Int) { + override fun write(byte: Int) { if (isClosed()) throw IOException("Underlying sink is closed") - writeToInternalBuffer { it.writeByte(b.toByte()) } + writeToInternalBuffer { it.writeByte(byte.toByte()) } } override fun write(data: ByteArray, offset: Int, byteCount: Int) { From c5f8d0e256e5212e3a32890f4bf3eb899d7cad92 Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Mon, 26 Jun 2023 12:00:38 +0200 Subject: [PATCH 80/83] Rename BlackholeSink to DiscardingSink --- core/api/kotlinx-io-core.api | 2 +- core/common/src/Core.kt | 6 +++--- core/common/test/AbstractSourceTest.kt | 6 +++--- core/common/test/CommonPlatformTest.kt | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/core/api/kotlinx-io-core.api b/core/api/kotlinx-io-core.api index 63220e707..e1eeae95f 100644 --- a/core/api/kotlinx-io-core.api +++ b/core/api/kotlinx-io-core.api @@ -48,9 +48,9 @@ public final class kotlinx/io/BufferExtJvmKt { } public final class kotlinx/io/CoreKt { - public static final fun blackholeSink ()Lkotlinx/io/RawSink; public static final fun buffered (Lkotlinx/io/RawSink;)Lkotlinx/io/Sink; public static final fun buffered (Lkotlinx/io/RawSource;)Lkotlinx/io/Source; + public static final fun discardingSink ()Lkotlinx/io/RawSink; } public abstract interface annotation class kotlinx/io/DelicateIoApi : java/lang/annotation/Annotation { diff --git a/core/common/src/Core.kt b/core/common/src/Core.kt index 754855eba..be72be491 100644 --- a/core/common/src/Core.kt +++ b/core/common/src/Core.kt @@ -34,11 +34,11 @@ public fun RawSource.buffered(): Source = RealSource(this) public fun RawSink.buffered(): Sink = RealSink(this) /** - * Returns a sink that writes nowhere. + * Returns a sink that discards all data written to it. */ -public fun blackholeSink(): RawSink = BlackholeSink() +public fun discardingSink(): RawSink = DiscardingSink() -private class BlackholeSink : RawSink { +private class DiscardingSink : RawSink { override fun write(source: Buffer, byteCount: Long) = source.skip(byteCount) override fun flush() {} override fun close() {} diff --git a/core/common/test/AbstractSourceTest.kt b/core/common/test/AbstractSourceTest.kt index 41ee3f63e..ca47e78d3 100644 --- a/core/common/test/AbstractSourceTest.kt +++ b/core/common/test/AbstractSourceTest.kt @@ -1151,7 +1151,7 @@ abstract class AbstractBufferedSourceTest internal constructor( // Peek a little data and skip the rest of the upstream source val peek = source.peek() assertEquals("ddd", peek.readUtf8(3)) - source.transferTo(blackholeSink()) + source.transferTo(discardingSink()) // Skip the rest of the buffered data peek.skip(peek.buffer.size) @@ -1227,7 +1227,7 @@ abstract class AbstractBufferedSourceTest internal constructor( sink.writeUtf8("Wot do u call it?\r\nWindows") sink.flush() assertEquals("Wot do u call it?", source.readUtf8Line()) - source.transferTo(blackholeSink()) + source.transferTo(discardingSink()) sink.writeUtf8("reo\rde\red\n") sink.flush() @@ -1254,7 +1254,7 @@ abstract class AbstractBufferedSourceTest internal constructor( sink.writeUtf8("Wot do u call it?\r\nWindows") sink.flush() assertEquals("Wot do u call it?", source.readUtf8LineStrict()) - source.transferTo(blackholeSink()) + source.transferTo(discardingSink()) sink.writeUtf8("reo\rde\red\n") sink.flush() diff --git a/core/common/test/CommonPlatformTest.kt b/core/common/test/CommonPlatformTest.kt index 27f959999..03651a43e 100644 --- a/core/common/test/CommonPlatformTest.kt +++ b/core/common/test/CommonPlatformTest.kt @@ -41,7 +41,7 @@ class CommonPlatformTest { assertEquals(sink.size, 1L) } - @Test fun blackhole() { - blackholeSink().write(Buffer().also { it.writeUtf8("a") }, 1L) + @Test fun discardingSinkTest() { + discardingSink().write(Buffer().also { it.writeUtf8("a") }, 1L) } } From a579e334d10c2183b8afd6c1abe086693517aca1 Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Mon, 26 Jun 2023 12:09:20 +0200 Subject: [PATCH 81/83] Explicitly specify what does the 'buffer bounds' mean --- core/common/src/Buffer.kt | 3 ++- core/jvm/src/BufferExtJvm.kt | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/core/common/src/Buffer.kt b/core/common/src/Buffer.kt index 3c54d8746..3bb6d0a09 100644 --- a/core/common/src/Buffer.kt +++ b/core/common/src/Buffer.kt @@ -212,7 +212,8 @@ public class Buffer : Source, Sink { * 0 by default. * @param endIndex the index (exclusive) of the last byte of data in this buffer to copy, `buffer.size` by default. * - * @throws IndexOutOfBoundsException when [startIndex] or [endIndex] is out of range of buffer indices. + * @throws IndexOutOfBoundsException when [startIndex] or [endIndex] is out of this buffer bounds + * (`[0..buffer.size)`). * @throws IllegalArgumentException when `startIndex > endIndex`. */ public fun copyTo( diff --git a/core/jvm/src/BufferExtJvm.kt b/core/jvm/src/BufferExtJvm.kt index 546990b56..8166e16e8 100644 --- a/core/jvm/src/BufferExtJvm.kt +++ b/core/jvm/src/BufferExtJvm.kt @@ -112,7 +112,7 @@ public fun Buffer.readTo(out: OutputStream, byteCount: Long = size) { * @param startIndex the index (inclusive) of the first byte to copy, `0` by default. * @param endIndex the index (exclusive) of the last byte to copy, `buffer.size` by default. * - * @throws IndexOutOfBoundsException when [startIndex] or [endIndex] is out of range of buffer indices. + * @throws IndexOutOfBoundsException when [startIndex] or [endIndex] is out of this buffer bounds (`[0..buffer.size)`). * @throws IllegalArgumentException when `startIndex > endIndex`. */ public fun Buffer.copyTo( From 3a506b8a43a273d56889296dc12077f8f66dc952 Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Mon, 26 Jun 2023 14:45:13 +0200 Subject: [PATCH 82/83] Removed unused code --- core/common/src/Buffer.kt | 33 --------------------------------- 1 file changed, 33 deletions(-) diff --git a/core/common/src/Buffer.kt b/core/common/src/Buffer.kt index 3bb6d0a09..abd67eef7 100644 --- a/core/common/src/Buffer.kt +++ b/core/common/src/Buffer.kt @@ -20,9 +20,6 @@ */ package kotlinx.io -import kotlinx.io.internal.REPLACEMENT_CODE_POINT -import kotlinx.io.internal.isIsoControl -import kotlinx.io.internal.processUtf8CodePoints import kotlin.jvm.JvmField /** @@ -636,36 +633,6 @@ public class Buffer : Source, Sink { } } -private fun ByteArray.hex(byteCount: Int = this.size): String { - checkByteCount(byteCount.toLong()) - val builder = StringBuilder(byteCount * 2) - for (i in 0 until byteCount) { - builder.append(HEX_DIGIT_CHARS[get(i).shr(4) and 0x0f]) - builder.append(HEX_DIGIT_CHARS[get(i) and 0x0f]) - } - return builder.toString() -} - - -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'.code && c != '\r'.code && isIsoControl(c)) || - c == REPLACEMENT_CODE_POINT - ) { - return -1 - } - - charCount += if (c < 0x10000) 1 else 2 - } - return charCount -} - /** * Invoke `lambda` with the segment and offset at `fromIndex`. Searches from the front or the back * depending on what's closer to `fromIndex`. From 47d2964fa3fa9fddff5757c4a90d0e5ad4dfdf00 Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Tue, 27 Jun 2023 11:18:41 +0200 Subject: [PATCH 83/83] Hide utf8Size and code-point related functions --- benchmarks/src/commonMain/kotlin/BufferOps.kt | 28 ++----------------- core/api/kotlinx-io-core.api | 5 ---- core/common/src/Utf8.kt | 8 +++--- 3 files changed, 7 insertions(+), 34 deletions(-) diff --git a/benchmarks/src/commonMain/kotlin/BufferOps.kt b/benchmarks/src/commonMain/kotlin/BufferOps.kt index 688819a2f..f2f2862c9 100644 --- a/benchmarks/src/commonMain/kotlin/BufferOps.kt +++ b/benchmarks/src/commonMain/kotlin/BufferOps.kt @@ -142,29 +142,6 @@ open class HexadecimalLongBenchmark: BufferRWBenchmarkBase() { } } -open class Utf8CodePointBenchmark: BufferRWBenchmarkBase() { - @Param("1", "2", "3") - var bytes: Int = 0 - - private var codePoint: Int = 0 - - @Setup - fun setupCodePoints() { - codePoint = when (bytes) { - 1 -> 'a'.code - 2 -> 'ɐ'.code - 3 -> 'ა'.code - else -> throw IllegalArgumentException() - } - } - - @Benchmark - fun benchmark(): Int { - buffer.writeUtf8CodePoint(codePoint) - return buffer.readUtf8CodePoint() - } -} - // This benchmark is based on Okio benchmark: // https://raw.githubusercontent.com/square/okio/master/okio/jvm/jmh/src/jmh/java/com/squareup/okio/benchmarks/BufferUtf8Benchmark.java open class Utf8StringBenchmark: BufferRWBenchmarkBase() { @@ -217,8 +194,9 @@ open class Utf8StringBenchmark: BufferRWBenchmarkBase() { override fun padding(): ByteArray { val baseString = constructString() - if (baseString.utf8Size() >= minGap) { - return baseString.encodeToByteArray() + val baseStringByteArray = baseString.encodeToByteArray() + if (baseStringByteArray.size >= minGap) { + return baseStringByteArray } val builder = StringBuilder((minGap * 1.5).toInt()) while (builder.length < minGap) { diff --git a/core/api/kotlinx-io-core.api b/core/api/kotlinx-io-core.api index e1eeae95f..75e8d9085 100644 --- a/core/api/kotlinx-io-core.api +++ b/core/api/kotlinx-io-core.api @@ -165,16 +165,11 @@ public final class kotlinx/io/Utf8Kt { public static final fun readUtf8 (Lkotlinx/io/Buffer;)Ljava/lang/String; public static final fun readUtf8 (Lkotlinx/io/Source;)Ljava/lang/String; public static final fun readUtf8 (Lkotlinx/io/Source;J)Ljava/lang/String; - public static final fun readUtf8CodePoint (Lkotlinx/io/Buffer;)I - public static final fun readUtf8CodePoint (Lkotlinx/io/Source;)I public static final fun readUtf8Line (Lkotlinx/io/Source;)Ljava/lang/String; public static final fun readUtf8LineStrict (Lkotlinx/io/Source;J)Ljava/lang/String; public static synthetic fun readUtf8LineStrict$default (Lkotlinx/io/Source;JILjava/lang/Object;)Ljava/lang/String; - public static final fun utf8Size (Ljava/lang/String;II)J - public static synthetic fun utf8Size$default (Ljava/lang/String;IIILjava/lang/Object;)J public static final fun writeUtf8 (Lkotlinx/io/Sink;Ljava/lang/String;II)V public static synthetic fun writeUtf8$default (Lkotlinx/io/Sink;Ljava/lang/String;IIILjava/lang/Object;)V - public static final fun writeUtf8CodePoint (Lkotlinx/io/Sink;I)V } public final class kotlinx/io/files/Path { diff --git a/core/common/src/Utf8.kt b/core/common/src/Utf8.kt index d1bb8147b..44f2f8ad1 100644 --- a/core/common/src/Utf8.kt +++ b/core/common/src/Utf8.kt @@ -80,7 +80,7 @@ import kotlinx.io.internal.* * @throws IndexOutOfBoundsException when [startIndex] or [endIndex] is out of range of string indices. * @throws IllegalArgumentException when `startIndex > endIndex`. */ -public fun String.utf8Size(startIndex: Int = 0, endIndex: Int = length): Long { +internal fun String.utf8Size(startIndex: Int = 0, endIndex: Int = length): Long { checkBounds(length, startIndex, endIndex) var result = 0L @@ -125,7 +125,7 @@ public fun String.utf8Size(startIndex: Int = 0, endIndex: Int = length): Long { * @throws IllegalStateException when the sink is closed. */ @OptIn(DelicateIoApi::class) -public fun Sink.writeUtf8CodePoint(codePoint: Int): Unit = +internal fun Sink.writeUtf8CodePoint(codePoint: Int): Unit = writeToInternalBuffer { it.commonWriteUtf8CodePoint(codePoint) } /** @@ -199,7 +199,7 @@ public fun Source.readUtf8(byteCount: Long): String { * @throws IllegalStateException when the source is closed. */ @OptIn(InternalIoApi::class) -public fun Source.readUtf8CodePoint(): Int { +internal fun Source.readUtf8CodePoint(): Int { require(1) val b0 = buffer[0].toInt() @@ -215,7 +215,7 @@ public fun Source.readUtf8CodePoint(): Int { /** * @see Source.readUtf8CodePoint */ -public fun Buffer.readUtf8CodePoint(): Int { +internal fun Buffer.readUtf8CodePoint(): Int { return this.commonReadUtf8CodePoint() }