diff options
author | Jesse Wilson <jwilson@squareup.com> | 2024-02-05 18:07:32 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-02-05 23:07:32 +0000 |
commit | 0d76d413d9bec5d7fff353354f3c107b9d5d71c8 (patch) | |
tree | bd23766af0e60a5fa8358d494bf69260ced52b89 | |
parent | 13e2f46f45e5c71ee639046cb1aa57fae692f59b (diff) | |
download | okio-0d76d413d9bec5d7fff353354f3c107b9d5d71c8.tar.gz |
Add TypedOptions (#1417)
* Add TypedOptions
* apiDump
* Expose the basic TypedOptions constructor
* Spotless
* Update okio/src/commonMain/kotlin/okio/TypedOptions.kt
Co-authored-by: Jake Wharton <jw@squareup.com>
---------
Co-authored-by: Jake Wharton <jw@squareup.com>
-rw-r--r-- | okio/api/okio.api | 14 | ||||
-rw-r--r-- | okio/src/commonMain/kotlin/okio/BufferedSource.kt | 35 | ||||
-rw-r--r-- | okio/src/commonMain/kotlin/okio/Options.kt | 6 | ||||
-rw-r--r-- | okio/src/commonMain/kotlin/okio/TypedOptions.kt | 51 | ||||
-rw-r--r-- | okio/src/commonMain/kotlin/okio/internal/BufferedSource.kt | 30 | ||||
-rw-r--r-- | okio/src/commonTest/kotlin/okio/TypedOptionsTest.kt | 90 | ||||
-rw-r--r-- | okio/src/jvmMain/kotlin/okio/Buffer.kt | 2 | ||||
-rw-r--r-- | okio/src/jvmMain/kotlin/okio/BufferedSource.kt | 3 | ||||
-rw-r--r-- | okio/src/jvmMain/kotlin/okio/RealBufferedSource.kt | 1 | ||||
-rw-r--r-- | okio/src/nonJvmMain/kotlin/okio/Buffer.kt | 2 | ||||
-rw-r--r-- | okio/src/nonJvmMain/kotlin/okio/BufferedSource.kt | 2 | ||||
-rw-r--r-- | okio/src/nonJvmMain/kotlin/okio/RealBufferedSource.kt | 1 |
12 files changed, 234 insertions, 3 deletions
diff --git a/okio/api/okio.api b/okio/api/okio.api index b85e1870..46e16435 100644 --- a/okio/api/okio.api +++ b/okio/api/okio.api @@ -141,6 +141,7 @@ public final class okio/Buffer : java/lang/Cloneable, java/nio/channels/ByteChan public fun request (J)Z public fun require (J)V public fun select (Lokio/Options;)I + public fun select (Lokio/TypedOptions;)Ljava/lang/Object; public final fun sha1 ()Lokio/ByteString; public final fun sha256 ()Lokio/ByteString; public final fun sha512 ()Lokio/ByteString; @@ -284,6 +285,7 @@ public abstract interface class okio/BufferedSource : java/nio/channels/Readable public abstract fun request (J)Z public abstract fun require (J)V public abstract fun select (Lokio/Options;)I + public abstract fun select (Lokio/TypedOptions;)Ljava/lang/Object; public abstract fun skip (J)V } @@ -790,6 +792,18 @@ public final class okio/Timeout$Companion { public final fun timeout-HG0u8IE (Lokio/Timeout;J)Lokio/Timeout; } +public final class okio/TypedOptions : kotlin/collections/AbstractList, java/util/RandomAccess { + public static final field Companion Lokio/TypedOptions$Companion; + public fun <init> (Ljava/util/List;Lokio/Options;)V + public fun get (I)Ljava/lang/Object; + public fun getSize ()I + public static final fun of (Ljava/lang/Iterable;Lkotlin/jvm/functions/Function1;)Lokio/TypedOptions; +} + +public final class okio/TypedOptions$Companion { + public final fun of (Ljava/lang/Iterable;Lkotlin/jvm/functions/Function1;)Lokio/TypedOptions; +} + public final class okio/Utf8 { public static final fun size (Ljava/lang/String;)J public static final fun size (Ljava/lang/String;I)J diff --git a/okio/src/commonMain/kotlin/okio/BufferedSource.kt b/okio/src/commonMain/kotlin/okio/BufferedSource.kt index 86b4803a..18d03347 100644 --- a/okio/src/commonMain/kotlin/okio/BufferedSource.kt +++ b/okio/src/commonMain/kotlin/okio/BufferedSource.kt @@ -242,8 +242,8 @@ expect sealed interface BufferedSource : Source { 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 + * Finds the first byte string in `options` that is a prefix of this buffer, consumes it from this + * source, 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 @@ -268,6 +268,37 @@ expect sealed interface BufferedSource : Source { */ fun select(options: Options): Int + /** + * Finds the first item in [options] whose encoding is a prefix of this buffer, consumes it from + * this buffer, and returns it. If no item in [options] is a prefix of this source, this function + * returns null 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. + * + * ``` + * TypedOptions<Direction> options = TypedOptions.of( + * Arrays.asList(Direction.values()), + * (direction) -> ByteString.encodeUtf8(direction.name().toLowerCase(Locale.ROOT)) + * ); + * + * Buffer buffer = new Buffer() + * .writeUtf8("north:100\n") + * .writeUtf8("east:50\n"); + * + * assertEquals(Direction.NORTH, buffer.select(options)); + * assertEquals(':', buffer.readByte()); + * assertEquals(100L, buffer.readDecimalLong()); + * assertEquals('\n', buffer.readByte()); + * + * assertEquals(Direction.EAST, buffer.select(options)); + * assertEquals(':', buffer.readByte()); + * assertEquals(50L, buffer.readDecimalLong()); + * assertEquals('\n', buffer.readByte()); + * ``` + */ + fun <T : Any> select(options: TypedOptions<T>): T? + /** Removes all bytes from this and returns them as a byte array. */ fun readByteArray(): ByteArray diff --git a/okio/src/commonMain/kotlin/okio/Options.kt b/okio/src/commonMain/kotlin/okio/Options.kt index 6c130218..b94054a2 100644 --- a/okio/src/commonMain/kotlin/okio/Options.kt +++ b/okio/src/commonMain/kotlin/okio/Options.kt @@ -17,7 +17,11 @@ package okio import kotlin.jvm.JvmStatic -/** An indexed set of values that may be read with [BufferedSource.select]. */ +/** + * An indexed set of values that may be read with [BufferedSource.select]. + * + * Also consider [TypedOptions] to select a typed value _T_. + */ class Options private constructor( internal val byteStrings: Array<out ByteString>, internal val trie: IntArray, diff --git a/okio/src/commonMain/kotlin/okio/TypedOptions.kt b/okio/src/commonMain/kotlin/okio/TypedOptions.kt new file mode 100644 index 00000000..98a26313 --- /dev/null +++ b/okio/src/commonMain/kotlin/okio/TypedOptions.kt @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2024 Square, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package okio + +import kotlin.jvm.JvmStatic + +/** + * A list of values that may be read with [BufferedSource.select]. + * + * Also consider [Options] to select an integer index. + */ +class TypedOptions<T : Any>( + list: List<T>, + internal val options: Options, +) : AbstractList<T>(), RandomAccess { + internal val list = list.toList() // Defensive copy. + + init { + require(this.list.size == options.size) + } + + override val size: Int + get() = list.size + + override fun get(index: Int) = list[index] + + companion object { + @JvmStatic + inline fun <T : Any> of( + values: Iterable<T>, + encode: (T) -> ByteString, + ): TypedOptions<T> { + val list = values.toList() + val options = Options.of(*Array(list.size) { encode(list[it]) }) + return TypedOptions(list, options) + } + } +} diff --git a/okio/src/commonMain/kotlin/okio/internal/BufferedSource.kt b/okio/src/commonMain/kotlin/okio/internal/BufferedSource.kt new file mode 100644 index 00000000..82ef82b7 --- /dev/null +++ b/okio/src/commonMain/kotlin/okio/internal/BufferedSource.kt @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2024 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. + */ + +@file:JvmName("-BufferedSource") // A leading '-' hides this class from Java. + +package okio.internal + +import kotlin.jvm.JvmName +import okio.BufferedSource +import okio.TypedOptions + +internal inline fun <T : Any> BufferedSource.commonSelect(options: TypedOptions<T>): T? { + return when (val index = select(options.options)) { + -1 -> null + else -> options[index] + } +} diff --git a/okio/src/commonTest/kotlin/okio/TypedOptionsTest.kt b/okio/src/commonTest/kotlin/okio/TypedOptionsTest.kt new file mode 100644 index 00000000..524bf4f8 --- /dev/null +++ b/okio/src/commonTest/kotlin/okio/TypedOptionsTest.kt @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2024 Square, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package okio + +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith +import okio.ByteString.Companion.encodeUtf8 + +class TypedOptionsTest { + @Test + fun happyPath() { + val colors = listOf("Red", "Green", "Blue") + val colorOptions = TypedOptions.of(colors) { it.lowercase().encodeUtf8() } + val buffer = Buffer().writeUtf8("bluegreenyellow") + assertEquals("Blue", buffer.select(colorOptions)) + assertEquals("greenyellow", buffer.snapshot().utf8()) + assertEquals("Green", buffer.select(colorOptions)) + assertEquals("yellow", buffer.snapshot().utf8()) + assertEquals(null, buffer.select(colorOptions)) + assertEquals("yellow", buffer.snapshot().utf8()) + } + + @Test + fun typedOptionsConstructor() { + val colors = listOf("Red", "Green", "Blue") + val colorOptions = TypedOptions( + colors, + Options.of("red".encodeUtf8(), "green".encodeUtf8(), "blue".encodeUtf8()), + ) + val buffer = Buffer().writeUtf8("bluegreenyellow") + assertEquals("Blue", buffer.select(colorOptions)) + assertEquals("greenyellow", buffer.snapshot().utf8()) + assertEquals("Green", buffer.select(colorOptions)) + assertEquals("yellow", buffer.snapshot().utf8()) + assertEquals(null, buffer.select(colorOptions)) + assertEquals("yellow", buffer.snapshot().utf8()) + } + + @Test + fun typedOptionsConstructorEnforcesSizeMatch() { + val colors = listOf("Red", "Green", "Blue") + assertFailsWith<IllegalArgumentException> { + TypedOptions( + colors, + Options.of("red".encodeUtf8(), "green".encodeUtf8()), + ) + } + } + + @Test + fun listFunctionsWork() { + val colors = listOf("Red", "Green", "Blue") + val colorOptions = TypedOptions.of(colors) { it.lowercase().encodeUtf8() } + assertEquals(3, colorOptions.size) + assertEquals("Red", colorOptions[0]) + assertEquals("Green", colorOptions[1]) + assertEquals("Blue", colorOptions[2]) + assertFailsWith<IndexOutOfBoundsException> { + colorOptions[3] + } + } + + /** + * Confirm we can mutate the collection used to create our [TypedOptions] without corrupting its + * behavior. + */ + @Test + fun safeToMutateSourceCollectionAfterConstruction() { + val colors = mutableListOf("Red", "Green") + val colorOptions = TypedOptions.of(colors) { it.lowercase().encodeUtf8() } + colors[0] = "Black" + + val buffer = Buffer().writeUtf8("red") + assertEquals("Red", buffer.select(colorOptions)) + } +} diff --git a/okio/src/jvmMain/kotlin/okio/Buffer.kt b/okio/src/jvmMain/kotlin/okio/Buffer.kt index 88dfaba7..a23241ab 100644 --- a/okio/src/jvmMain/kotlin/okio/Buffer.kt +++ b/okio/src/jvmMain/kotlin/okio/Buffer.kt @@ -288,6 +288,8 @@ actual class Buffer : BufferedSource, BufferedSink, Cloneable, ByteChannel { override fun select(options: Options): Int = commonSelect(options) + override fun <T : Any> select(options: TypedOptions<T>): T? = commonSelect(options) + @Throws(EOFException::class) override fun readFully(sink: Buffer, byteCount: Long): Unit = commonReadFully(sink, byteCount) diff --git a/okio/src/jvmMain/kotlin/okio/BufferedSource.kt b/okio/src/jvmMain/kotlin/okio/BufferedSource.kt index ca6b94bf..45e6688a 100644 --- a/okio/src/jvmMain/kotlin/okio/BufferedSource.kt +++ b/okio/src/jvmMain/kotlin/okio/BufferedSource.kt @@ -80,6 +80,9 @@ actual sealed interface BufferedSource : Source, ReadableByteChannel { actual fun select(options: Options): Int @Throws(IOException::class) + actual fun <T : Any> select(options: TypedOptions<T>): T? + + @Throws(IOException::class) actual fun readByteArray(): ByteArray @Throws(IOException::class) diff --git a/okio/src/jvmMain/kotlin/okio/RealBufferedSource.kt b/okio/src/jvmMain/kotlin/okio/RealBufferedSource.kt index 35f6c353..029b93d2 100644 --- a/okio/src/jvmMain/kotlin/okio/RealBufferedSource.kt +++ b/okio/src/jvmMain/kotlin/okio/RealBufferedSource.kt @@ -71,6 +71,7 @@ internal actual class RealBufferedSource actual constructor( override fun readByteString(): ByteString = commonReadByteString() override fun readByteString(byteCount: Long): ByteString = commonReadByteString(byteCount) override fun select(options: Options): Int = commonSelect(options) + override fun <T : Any> select(options: TypedOptions<T>): T? = 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/okio/src/nonJvmMain/kotlin/okio/Buffer.kt b/okio/src/nonJvmMain/kotlin/okio/Buffer.kt index 8dfb5622..223fd520 100644 --- a/okio/src/nonJvmMain/kotlin/okio/Buffer.kt +++ b/okio/src/nonJvmMain/kotlin/okio/Buffer.kt @@ -145,6 +145,8 @@ actual class Buffer : BufferedSource, BufferedSink { override fun select(options: Options): Int = commonSelect(options) + override fun <T : Any> select(options: TypedOptions<T>): T? = commonSelect(options) + override fun readByteArray(): ByteArray = commonReadByteArray() override fun readByteArray(byteCount: Long): ByteArray = commonReadByteArray(byteCount) diff --git a/okio/src/nonJvmMain/kotlin/okio/BufferedSource.kt b/okio/src/nonJvmMain/kotlin/okio/BufferedSource.kt index 369a3e63..44622233 100644 --- a/okio/src/nonJvmMain/kotlin/okio/BufferedSource.kt +++ b/okio/src/nonJvmMain/kotlin/okio/BufferedSource.kt @@ -50,6 +50,8 @@ actual sealed interface BufferedSource : Source { actual fun select(options: Options): Int + actual fun <T : Any> select(options: TypedOptions<T>): T? + actual fun readByteArray(): ByteArray actual fun readByteArray(byteCount: Long): ByteArray diff --git a/okio/src/nonJvmMain/kotlin/okio/RealBufferedSource.kt b/okio/src/nonJvmMain/kotlin/okio/RealBufferedSource.kt index 93ad10f0..59f532db 100644 --- a/okio/src/nonJvmMain/kotlin/okio/RealBufferedSource.kt +++ b/okio/src/nonJvmMain/kotlin/okio/RealBufferedSource.kt @@ -60,6 +60,7 @@ internal actual class RealBufferedSource actual constructor( override fun readByteString(): ByteString = commonReadByteString() override fun readByteString(byteCount: Long): ByteString = commonReadByteString(byteCount) override fun select(options: Options): Int = commonSelect(options) + override fun <T : Any> select(options: TypedOptions<T>): T? = 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) |