Skip to content

Commit 0d76d41

Browse files
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 <[email protected]> --------- Co-authored-by: Jake Wharton <[email protected]>
1 parent 13e2f46 commit 0d76d41

File tree

12 files changed

+234
-3
lines changed

12 files changed

+234
-3
lines changed

okio/api/okio.api

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ public final class okio/Buffer : java/lang/Cloneable, java/nio/channels/ByteChan
141141
public fun request (J)Z
142142
public fun require (J)V
143143
public fun select (Lokio/Options;)I
144+
public fun select (Lokio/TypedOptions;)Ljava/lang/Object;
144145
public final fun sha1 ()Lokio/ByteString;
145146
public final fun sha256 ()Lokio/ByteString;
146147
public final fun sha512 ()Lokio/ByteString;
@@ -284,6 +285,7 @@ public abstract interface class okio/BufferedSource : java/nio/channels/Readable
284285
public abstract fun request (J)Z
285286
public abstract fun require (J)V
286287
public abstract fun select (Lokio/Options;)I
288+
public abstract fun select (Lokio/TypedOptions;)Ljava/lang/Object;
287289
public abstract fun skip (J)V
288290
}
289291

@@ -790,6 +792,18 @@ public final class okio/Timeout$Companion {
790792
public final fun timeout-HG0u8IE (Lokio/Timeout;J)Lokio/Timeout;
791793
}
792794

795+
public final class okio/TypedOptions : kotlin/collections/AbstractList, java/util/RandomAccess {
796+
public static final field Companion Lokio/TypedOptions$Companion;
797+
public fun <init> (Ljava/util/List;Lokio/Options;)V
798+
public fun get (I)Ljava/lang/Object;
799+
public fun getSize ()I
800+
public static final fun of (Ljava/lang/Iterable;Lkotlin/jvm/functions/Function1;)Lokio/TypedOptions;
801+
}
802+
803+
public final class okio/TypedOptions$Companion {
804+
public final fun of (Ljava/lang/Iterable;Lkotlin/jvm/functions/Function1;)Lokio/TypedOptions;
805+
}
806+
793807
public final class okio/Utf8 {
794808
public static final fun size (Ljava/lang/String;)J
795809
public static final fun size (Ljava/lang/String;I)J

okio/src/commonMain/kotlin/okio/BufferedSource.kt

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -242,8 +242,8 @@ expect sealed interface BufferedSource : Source {
242242
fun readByteString(byteCount: Long): ByteString
243243

244244
/**
245-
* Finds the first string in `options` that is a prefix of this buffer, consumes it from this
246-
* buffer, and returns its index. If no byte string in `options` is a prefix of this buffer this
245+
* Finds the first byte string in `options` that is a prefix of this buffer, consumes it from this
246+
* source, and returns its index. If no byte string in `options` is a prefix of this buffer this
247247
* returns -1 and no bytes are consumed.
248248
*
249249
* 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 {
268268
*/
269269
fun select(options: Options): Int
270270

271+
/**
272+
* Finds the first item in [options] whose encoding is a prefix of this buffer, consumes it from
273+
* this buffer, and returns it. If no item in [options] is a prefix of this source, this function
274+
* returns null and no bytes are consumed.
275+
*
276+
* This can be used as an alternative to [readByteString] or even [readUtf8] if the set of
277+
* expected values is known in advance.
278+
*
279+
* ```
280+
* TypedOptions<Direction> options = TypedOptions.of(
281+
* Arrays.asList(Direction.values()),
282+
* (direction) -> ByteString.encodeUtf8(direction.name().toLowerCase(Locale.ROOT))
283+
* );
284+
*
285+
* Buffer buffer = new Buffer()
286+
* .writeUtf8("north:100\n")
287+
* .writeUtf8("east:50\n");
288+
*
289+
* assertEquals(Direction.NORTH, buffer.select(options));
290+
* assertEquals(':', buffer.readByte());
291+
* assertEquals(100L, buffer.readDecimalLong());
292+
* assertEquals('\n', buffer.readByte());
293+
*
294+
* assertEquals(Direction.EAST, buffer.select(options));
295+
* assertEquals(':', buffer.readByte());
296+
* assertEquals(50L, buffer.readDecimalLong());
297+
* assertEquals('\n', buffer.readByte());
298+
* ```
299+
*/
300+
fun <T : Any> select(options: TypedOptions<T>): T?
301+
271302
/** Removes all bytes from this and returns them as a byte array. */
272303
fun readByteArray(): ByteArray
273304

okio/src/commonMain/kotlin/okio/Options.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,11 @@ package okio
1717

1818
import kotlin.jvm.JvmStatic
1919

20-
/** An indexed set of values that may be read with [BufferedSource.select]. */
20+
/**
21+
* An indexed set of values that may be read with [BufferedSource.select].
22+
*
23+
* Also consider [TypedOptions] to select a typed value _T_.
24+
*/
2125
class Options private constructor(
2226
internal val byteStrings: Array<out ByteString>,
2327
internal val trie: IntArray,
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
* Copyright (C) 2024 Square, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package okio
17+
18+
import kotlin.jvm.JvmStatic
19+
20+
/**
21+
* A list of values that may be read with [BufferedSource.select].
22+
*
23+
* Also consider [Options] to select an integer index.
24+
*/
25+
class TypedOptions<T : Any>(
26+
list: List<T>,
27+
internal val options: Options,
28+
) : AbstractList<T>(), RandomAccess {
29+
internal val list = list.toList() // Defensive copy.
30+
31+
init {
32+
require(this.list.size == options.size)
33+
}
34+
35+
override val size: Int
36+
get() = list.size
37+
38+
override fun get(index: Int) = list[index]
39+
40+
companion object {
41+
@JvmStatic
42+
inline fun <T : Any> of(
43+
values: Iterable<T>,
44+
encode: (T) -> ByteString,
45+
): TypedOptions<T> {
46+
val list = values.toList()
47+
val options = Options.of(*Array(list.size) { encode(list[it]) })
48+
return TypedOptions(list, options)
49+
}
50+
}
51+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* Copyright (C) 2024 Square, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
@file:JvmName("-BufferedSource") // A leading '-' hides this class from Java.
18+
19+
package okio.internal
20+
21+
import kotlin.jvm.JvmName
22+
import okio.BufferedSource
23+
import okio.TypedOptions
24+
25+
internal inline fun <T : Any> BufferedSource.commonSelect(options: TypedOptions<T>): T? {
26+
return when (val index = select(options.options)) {
27+
-1 -> null
28+
else -> options[index]
29+
}
30+
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/*
2+
* Copyright (C) 2024 Square, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package okio
17+
18+
import kotlin.test.Test
19+
import kotlin.test.assertEquals
20+
import kotlin.test.assertFailsWith
21+
import okio.ByteString.Companion.encodeUtf8
22+
23+
class TypedOptionsTest {
24+
@Test
25+
fun happyPath() {
26+
val colors = listOf("Red", "Green", "Blue")
27+
val colorOptions = TypedOptions.of(colors) { it.lowercase().encodeUtf8() }
28+
val buffer = Buffer().writeUtf8("bluegreenyellow")
29+
assertEquals("Blue", buffer.select(colorOptions))
30+
assertEquals("greenyellow", buffer.snapshot().utf8())
31+
assertEquals("Green", buffer.select(colorOptions))
32+
assertEquals("yellow", buffer.snapshot().utf8())
33+
assertEquals(null, buffer.select(colorOptions))
34+
assertEquals("yellow", buffer.snapshot().utf8())
35+
}
36+
37+
@Test
38+
fun typedOptionsConstructor() {
39+
val colors = listOf("Red", "Green", "Blue")
40+
val colorOptions = TypedOptions(
41+
colors,
42+
Options.of("red".encodeUtf8(), "green".encodeUtf8(), "blue".encodeUtf8()),
43+
)
44+
val buffer = Buffer().writeUtf8("bluegreenyellow")
45+
assertEquals("Blue", buffer.select(colorOptions))
46+
assertEquals("greenyellow", buffer.snapshot().utf8())
47+
assertEquals("Green", buffer.select(colorOptions))
48+
assertEquals("yellow", buffer.snapshot().utf8())
49+
assertEquals(null, buffer.select(colorOptions))
50+
assertEquals("yellow", buffer.snapshot().utf8())
51+
}
52+
53+
@Test
54+
fun typedOptionsConstructorEnforcesSizeMatch() {
55+
val colors = listOf("Red", "Green", "Blue")
56+
assertFailsWith<IllegalArgumentException> {
57+
TypedOptions(
58+
colors,
59+
Options.of("red".encodeUtf8(), "green".encodeUtf8()),
60+
)
61+
}
62+
}
63+
64+
@Test
65+
fun listFunctionsWork() {
66+
val colors = listOf("Red", "Green", "Blue")
67+
val colorOptions = TypedOptions.of(colors) { it.lowercase().encodeUtf8() }
68+
assertEquals(3, colorOptions.size)
69+
assertEquals("Red", colorOptions[0])
70+
assertEquals("Green", colorOptions[1])
71+
assertEquals("Blue", colorOptions[2])
72+
assertFailsWith<IndexOutOfBoundsException> {
73+
colorOptions[3]
74+
}
75+
}
76+
77+
/**
78+
* Confirm we can mutate the collection used to create our [TypedOptions] without corrupting its
79+
* behavior.
80+
*/
81+
@Test
82+
fun safeToMutateSourceCollectionAfterConstruction() {
83+
val colors = mutableListOf("Red", "Green")
84+
val colorOptions = TypedOptions.of(colors) { it.lowercase().encodeUtf8() }
85+
colors[0] = "Black"
86+
87+
val buffer = Buffer().writeUtf8("red")
88+
assertEquals("Red", buffer.select(colorOptions))
89+
}
90+
}

okio/src/jvmMain/kotlin/okio/Buffer.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,8 @@ actual class Buffer : BufferedSource, BufferedSink, Cloneable, ByteChannel {
288288

289289
override fun select(options: Options): Int = commonSelect(options)
290290

291+
override fun <T : Any> select(options: TypedOptions<T>): T? = commonSelect(options)
292+
291293
@Throws(EOFException::class)
292294
override fun readFully(sink: Buffer, byteCount: Long): Unit = commonReadFully(sink, byteCount)
293295

okio/src/jvmMain/kotlin/okio/BufferedSource.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,9 @@ actual sealed interface BufferedSource : Source, ReadableByteChannel {
7979
@Throws(IOException::class)
8080
actual fun select(options: Options): Int
8181

82+
@Throws(IOException::class)
83+
actual fun <T : Any> select(options: TypedOptions<T>): T?
84+
8285
@Throws(IOException::class)
8386
actual fun readByteArray(): ByteArray
8487

okio/src/jvmMain/kotlin/okio/RealBufferedSource.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ internal actual class RealBufferedSource actual constructor(
7171
override fun readByteString(): ByteString = commonReadByteString()
7272
override fun readByteString(byteCount: Long): ByteString = commonReadByteString(byteCount)
7373
override fun select(options: Options): Int = commonSelect(options)
74+
override fun <T : Any> select(options: TypedOptions<T>): T? = commonSelect(options)
7475
override fun readByteArray(): ByteArray = commonReadByteArray()
7576
override fun readByteArray(byteCount: Long): ByteArray = commonReadByteArray(byteCount)
7677
override fun read(sink: ByteArray): Int = read(sink, 0, sink.size)

okio/src/nonJvmMain/kotlin/okio/Buffer.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,8 @@ actual class Buffer : BufferedSource, BufferedSink {
145145

146146
override fun select(options: Options): Int = commonSelect(options)
147147

148+
override fun <T : Any> select(options: TypedOptions<T>): T? = commonSelect(options)
149+
148150
override fun readByteArray(): ByteArray = commonReadByteArray()
149151

150152
override fun readByteArray(byteCount: Long): ByteArray = commonReadByteArray(byteCount)

0 commit comments

Comments
 (0)