aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJesse Wilson <jwilson@squareup.com>2024-02-05 18:07:32 -0500
committerGitHub <noreply@github.com>2024-02-05 23:07:32 +0000
commit0d76d413d9bec5d7fff353354f3c107b9d5d71c8 (patch)
treebd23766af0e60a5fa8358d494bf69260ced52b89
parent13e2f46f45e5c71ee639046cb1aa57fae692f59b (diff)
downloadokio-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.api14
-rw-r--r--okio/src/commonMain/kotlin/okio/BufferedSource.kt35
-rw-r--r--okio/src/commonMain/kotlin/okio/Options.kt6
-rw-r--r--okio/src/commonMain/kotlin/okio/TypedOptions.kt51
-rw-r--r--okio/src/commonMain/kotlin/okio/internal/BufferedSource.kt30
-rw-r--r--okio/src/commonTest/kotlin/okio/TypedOptionsTest.kt90
-rw-r--r--okio/src/jvmMain/kotlin/okio/Buffer.kt2
-rw-r--r--okio/src/jvmMain/kotlin/okio/BufferedSource.kt3
-rw-r--r--okio/src/jvmMain/kotlin/okio/RealBufferedSource.kt1
-rw-r--r--okio/src/nonJvmMain/kotlin/okio/Buffer.kt2
-rw-r--r--okio/src/nonJvmMain/kotlin/okio/BufferedSource.kt2
-rw-r--r--okio/src/nonJvmMain/kotlin/okio/RealBufferedSource.kt1
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)