1 /*
2  * Copyright 2017 Google 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  *     https://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 trebuchet.io
18 
19 import kotlin.sequences.iterator
20 
findNewlineInWindownull21 private fun findNewlineInWindow(window: StreamingReader.Window, startIndex: Long): Long {
22     for (i in startIndex..window.globalEndIndex) {
23         if (window[i] == '\n'.toByte()) return i
24     }
25     return -1
26 }
27 
28 /**
29  * Iterates over all the lines in the stream, skipping any empty line. Lines do not contain
30  * the line-end marker(s). Handles both LF & CRLF endings.
31  */
<lambda>null32 fun StreamingReader.iterLines() = iterator {
33     val stream = this@iterLines
34 
35     var lineStartIndex = stream.startIndex
36     while (true) {
37         var index = lineStartIndex
38         var foundAt = -1L
39         while (true) {
40             if (index > stream.endIndex) {
41                 if (!stream.loadIndex(index)) break
42             }
43             val window = stream.windowFor(index)
44             foundAt = findNewlineInWindow(window, index)
45             if (foundAt != -1L) break
46             index = window.globalEndIndex + 1
47         }
48 
49         // Reached EOF with no data, return
50         if (lineStartIndex > stream.endIndex) break
51 
52         // Reached EOF, consume remaining as a line
53         if (foundAt == -1L) foundAt = stream.endIndex + 1
54 
55         val nextStart = foundAt + 1
56 
57         // Handle CLRF
58         if (foundAt > 0 && stream[foundAt - 1] == '\r'.toByte()) foundAt -= 1
59 
60         val lineEndIndexInclusive = foundAt - 1
61         if (lineStartIndex >= stream.startIndex && (lineEndIndexInclusive - lineStartIndex) >= 0) {
62             val window = stream.windowFor(lineStartIndex)
63             if (window === stream.windowFor(lineEndIndexInclusive)) {
64                 // slice endIndex is exclusive
65                 yield(window.slice.slice((lineStartIndex - window.globalStartIndex).toInt(),
66                         (lineEndIndexInclusive - window.globalStartIndex + 1).toInt()))
67             } else {
68                 val tmpBuffer = ByteArray((lineEndIndexInclusive - lineStartIndex + 1).toInt())
69                 stream.copyTo(tmpBuffer, lineStartIndex, lineEndIndexInclusive)
70                 yield(tmpBuffer.asSlice())
71             }
72         }
73         lineStartIndex = nextStart
74     }
75 }
76