Skip to content

Commit 76dff29

Browse files
committed
fix: don't call peek line predicate with empty string if reached EOF
1 parent ced8dd3 commit 76dff29

File tree

4 files changed

+62
-18
lines changed

4 files changed

+62
-18
lines changed

lib/src/intTest/java/blackbox/reader/AbstractSkipLinesTest.java

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import java.io.UncheckedIOException;
99
import java.util.ArrayList;
1010
import java.util.List;
11+
import java.util.function.Predicate;
1112

1213
import org.junit.jupiter.api.Test;
1314
import org.junit.jupiter.params.ParameterizedTest;
@@ -114,6 +115,16 @@ void skipLinesWithCount(final String input) {
114115
.isStartingLineNumber(3).fields().containsExactly("C"));
115116
}
116117

118+
@Test
119+
void skipUntilLast() {
120+
final CsvReader<CsvRecord> csv = crb.ofCsvRecord("A\nB\nC");
121+
122+
csv.skipLines(3);
123+
124+
assertThat(csv.stream())
125+
.isEmpty();
126+
}
127+
117128
@ParameterizedTest
118129
@ValueSource(strings = {"A\r\nB\r\nC", "A\nB\nC", "A\rB\rC"})
119130
void tooMany(final String input) {
@@ -182,6 +193,17 @@ void noMatch() {
182193
.hasMessage("No matching line found. Skipped 2 line(s) before reaching end of data.");
183194
}
184195

196+
@Test
197+
void lastMatch() {
198+
final CsvReader<CsvRecord> csv = crb.ofCsvRecord("A\nB");
199+
final int linesSkipped = csv.skipLines("B"::equals, 10);
200+
assertThat(linesSkipped).isEqualTo(1);
201+
assertThat(csv.stream())
202+
.singleElement()
203+
.satisfies(rec -> CsvRecordAssert.assertThat(rec)
204+
.isStartingLineNumber(2).fields().containsExactly("B"));
205+
}
206+
185207
@ValueSource(strings = {
186208
"some arbitrary text\r\nbefore the actual data\r\n\r\nheader1,header2\r\nvalue1,value2\r\n",
187209
"some arbitrary text\rbefore the actual data\r\rheader1,header2\rvalue1,value2\r",
@@ -209,4 +231,19 @@ void predicateIoException() {
209231
.hasMessage("java.io.IOException: Cannot read");
210232
}
211233

234+
@Test
235+
void predicateNotCalledForEmptyLine() {
236+
final Predicate<String> p = line -> {
237+
if (!"A".equals(line)) {
238+
throw new IllegalArgumentException("Expected line 'A', but got '" + line + "'");
239+
}
240+
return false;
241+
};
242+
243+
final CsvReader<CsvRecord> csv = crb.ofCsvRecord("A\n");
244+
assertThatThrownBy(() -> csv.skipLines(p, 10))
245+
.isInstanceOf(CsvParseException.class)
246+
.hasMessage("No matching line found. Skipped %d line(s) before reaching end of data.".formatted(1));
247+
}
248+
212249
}

lib/src/main/java/de/siegmar/fastcsv/reader/CsvReader.java

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -128,17 +128,21 @@ public int skipLines(final Predicate<String> predicate, final int maxLines) {
128128
return 0;
129129
}
130130

131+
int i = 0;
131132
try {
132-
for (int i = 0; i < maxLines; i++) {
133+
for (; i < maxLines; i++) {
133134
final String line = csvParser.peekLine();
134-
if (predicate.test(line)) {
135-
return i;
136-
}
137135

138-
if (!csvParser.skipLine(line.length())) {
136+
if (line == null) {
139137
throw new CsvParseException(
140138
"No matching line found. Skipped %d line(s) before reaching end of data.".formatted(i));
141139
}
140+
141+
if (predicate.test(line)) {
142+
return i;
143+
}
144+
145+
csvParser.skipLine(line.length());
142146
}
143147
} catch (final IOException e) {
144148
throw new UncheckedIOException(e);

lib/src/main/java/de/siegmar/fastcsv/reader/RelaxedCsvParser.java

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -282,29 +282,29 @@ public String peekLine() throws IOException {
282282
return reader.peekLine();
283283
}
284284

285-
@SuppressWarnings({
286-
"checkstyle:MultipleVariableDeclarations",
287-
"PMD.OneDeclarationPerLine",
288-
"PMD.AssignmentInOperand"
289-
})
285+
@SuppressWarnings("checkstyle:MultipleVariableDeclarations")
290286
@Override
291287
public boolean skipLine(final int numCharsToSkip) throws IOException {
292288
reader.skip(numCharsToSkip);
293289

294-
int i, c;
295-
for (i = 0; (c = reader.read()) != EOF; i++) {
290+
int c = reader.read();
291+
if (c == EOF) {
292+
return false;
293+
}
294+
295+
do {
296296
if (c == CR) {
297297
reader.consumeIf(LF);
298298
startingLineNumber++;
299-
return true;
299+
break;
300300
}
301301
if (c == LF) {
302302
startingLineNumber++;
303-
return true;
303+
break;
304304
}
305-
}
305+
} while ((c = reader.read()) != EOF);
306306

307-
return numCharsToSkip + i > 0;
307+
return true;
308308
}
309309

310310
@Override
@@ -368,8 +368,7 @@ boolean consumeIf(final char[] chars) throws IOException {
368368
String peekLine() throws IOException {
369369
ensureBuffered(buffer.length);
370370
if (start >= len) {
371-
// Keep consistent with StrictCsvParser.peekLine()
372-
return "";
371+
return null;
373372
}
374373
int endIndex = start;
375374
while (endIndex < len && buffer[endIndex] != CR && buffer[endIndex] != LF) {

lib/src/main/java/de/siegmar/fastcsv/reader/StrictCsvParser.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,10 @@ public void close() throws IOException {
342342

343343
@Override
344344
public String peekLine() throws IOException {
345+
if (csvBuffer.pos == csvBuffer.len && !csvBuffer.fetchData()) {
346+
return null;
347+
}
348+
345349
final int savedPos = csvBuffer.pos;
346350

347351
for (; csvBuffer.pos < csvBuffer.len || csvBuffer.fetchData(); csvBuffer.pos++) {

0 commit comments

Comments
 (0)