xref: /aosp_15_r20/external/okio/okio/src/commonMain/kotlin/okio/PeekSource.kt (revision f9742813c14b702d71392179818a9e591da8620c)
1 /*
2  * Copyright (C) 2018 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 package okio
18 
19 /**
20  * A [Source] which peeks into an upstream [BufferedSource] and allows reading and expanding of the
21  * buffered data without consuming it. Does this by requesting additional data from the upstream
22  * source if needed and copying out of the internal buffer of the upstream source if possible.
23  *
24  * This source also maintains a snapshot of the starting location of the upstream buffer which it
25  * validates against on every read. If the upstream buffer is read from, this source will become
26  * invalid and throw [IllegalStateException] on any future reads.
27  */
28 internal class PeekSource(
29   private val upstream: BufferedSource,
30 ) : Source {
31   private val buffer = upstream.buffer
32   private var expectedSegment = buffer.head
33   private var expectedPos = buffer.head?.pos ?: -1
34 
35   private var closed = false
36   private var pos = 0L
37 
readnull38   override fun read(sink: Buffer, byteCount: Long): Long {
39     require(byteCount >= 0L) { "byteCount < 0: $byteCount" }
40     check(!closed) { "closed" }
41     // Source becomes invalid if there is an expected Segment and it and the expected position
42     // do not match the current head and head position of the upstream buffer
43     check(
44       expectedSegment == null ||
45         expectedSegment === buffer.head && expectedPos == buffer.head!!.pos,
46     ) {
47       "Peek source is invalid because upstream source was used"
48     }
49     if (byteCount == 0L) return 0L
50     if (!upstream.request(pos + 1)) return -1L
51 
52     if (expectedSegment == null && buffer.head != null) {
53       // Only once the buffer actually holds data should an expected Segment and position be
54       // recorded. This allows reads from the peek source to repeatedly return -1 and for data to be
55       // added later. Unit tests depend on this behavior.
56       expectedSegment = buffer.head
57       expectedPos = buffer.head!!.pos
58     }
59 
60     val toCopy = minOf(byteCount, buffer.size - pos)
61     buffer.copyTo(sink, pos, toCopy)
62     pos += toCopy
63     return toCopy
64   }
65 
timeoutnull66   override fun timeout(): Timeout {
67     return upstream.timeout()
68   }
69 
closenull70   override fun close() {
71     closed = true
72   }
73 }
74