Skip to content

Commit 935fb38

Browse files
committed
ReversedLinesFileReader implements IOIterable<String>
1 parent 0af0d17 commit 935fb38

4 files changed

Lines changed: 65 additions & 26 deletions

File tree

src/changes/changes.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ The <action> type attribute can be add,update,fix,remove.
5151
<!-- ADD -->
5252
<action dev="ggregory" type="add" issue="IO-860" due-to="Nico Strecker, Gary Gregory">Add ThrottledInputStream.Builder.setMaxBytes(long, ChronoUnit).</action>
5353
<action dev="ggregory" type="add" due-to="Gary Gregory">Add IOIterable.</action>
54+
<action dev="ggregory" type="add" due-to="Gary Gregory">ReversedLinesFileReader implements IOIterable&lt;String&gt;.</action>
5455
<!-- UPDATE -->
5556
</release>
5657
<release version="2.18.0" date="2024-11-16" description="Version 2.18.0: Java 8 is required.">

src/main/java/org/apache/commons/io/input/ReversedLinesFileReader.java

Lines changed: 53 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,23 +31,41 @@
3131
import java.util.ArrayList;
3232
import java.util.Arrays;
3333
import java.util.Collections;
34+
import java.util.Iterator;
3435
import java.util.List;
3536

3637
import org.apache.commons.io.Charsets;
3738
import org.apache.commons.io.FileSystem;
3839
import org.apache.commons.io.StandardLineSeparator;
3940
import org.apache.commons.io.build.AbstractStreamBuilder;
41+
import org.apache.commons.io.function.IOIterable;
42+
import org.apache.commons.io.function.IOIterator;
4043

4144
/**
4245
* Reads lines in a file reversely (similar to a BufferedReader, but starting at the last line). Useful for e.g. searching in log files.
4346
* <p>
4447
* To build an instance, use {@link Builder}.
4548
* </p>
49+
* <p>
50+
* For example:
51+
* </p>
52+
*
53+
* <pre>
54+
* <code>
55+
* try (ReversedLinesFileReader reader = ReversedLinesFileReader.builder()
56+
* .setPath(path)
57+
* .setBufferSize(4096)
58+
* .setCharset(StandardCharsets.UTF_8)
59+
* .get()) {
60+
* reader.forEach(line -> System.out.println(line));
61+
* }
62+
* </code>
63+
* </pre>
4664
*
4765
* @see Builder
4866
* @since 2.2
4967
*/
50-
public class ReversedLinesFileReader implements Closeable {
68+
public class ReversedLinesFileReader implements Closeable, IOIterable<String> {
5169

5270
// @formatter:off
5371
/**
@@ -57,11 +75,11 @@ public class ReversedLinesFileReader implements Closeable {
5775
* For example:
5876
* </p>
5977
* <pre>{@code
60-
* ReversedLinesFileReader r = ReversedLinesFileReader.builder()
78+
* ReversedLinesFileReader reader = ReversedLinesFileReader.builder()
6179
* .setPath(path)
6280
* .setBufferSize(4096)
6381
* .setCharset(StandardCharsets.UTF_8)
64-
* .get();}
82+
* .get());}
6583
* </pre>
6684
*
6785
* @see #get()
@@ -470,7 +488,6 @@ public void close() throws IOException {
470488
* @throws IOException if an I/O error occurs.
471489
*/
472490
public String readLine() throws IOException {
473-
474491
String line = currentFilePart.readLine();
475492
while (line == null) {
476493
currentFilePart = currentFilePart.rollOver();
@@ -480,13 +497,11 @@ public String readLine() throws IOException {
480497
}
481498
line = currentFilePart.readLine();
482499
}
483-
484500
// aligned behavior with BufferedReader that doesn't return a last, empty line
485501
if (EMPTY_STRING.equals(line) && !trailingNewlineOfFileSkipped) {
486502
trailingNewlineOfFileSkipped = true;
487503
line = readLine();
488504
}
489-
490505
return line;
491506
}
492507

@@ -538,4 +553,36 @@ public String toString(final int lineCount) throws IOException {
538553
return lines.isEmpty() ? EMPTY_STRING : String.join(System.lineSeparator(), lines) + System.lineSeparator();
539554
}
540555

556+
@Override
557+
public IOIterator<String> iterator() {
558+
return new IOIterator<String>() {
559+
560+
private String next;
561+
562+
@Override
563+
public boolean hasNext() throws IOException {
564+
if (next == null) {
565+
next = readLine();
566+
}
567+
return next != null;
568+
}
569+
570+
@Override
571+
public String next() throws IOException {
572+
if (next == null) {
573+
next = readLine();
574+
}
575+
final String tmp = next;
576+
next = null;
577+
return tmp;
578+
}
579+
580+
@Override
581+
public Iterator<String> unwrap() {
582+
return null;
583+
}
584+
585+
};
586+
}
587+
541588
}

src/test/java/org/apache/commons/io/input/ReversedLinesFileReaderParamBlockSizeTest.java

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
import java.io.UnsupportedEncodingException;
2929
import java.net.URISyntaxException;
3030
import java.nio.charset.StandardCharsets;
31+
import java.util.concurrent.atomic.AtomicInteger;
32+
import java.util.function.Supplier;
3133
import java.util.stream.IntStream;
3234

3335
import org.apache.commons.io.TestResources;
@@ -69,15 +71,15 @@ public class ReversedLinesFileReaderParamBlockSizeTest {
6971
private static final String TEST_LINE_X_WINDOWS_950_2 = "\u7E41\u9AD4\u4E2D\u6587";
7072

7173
static void assertEqualsAndNoLineBreaks(final String expected, final String actual) {
72-
assertEqualsAndNoLineBreaks(null, expected, actual);
74+
assertEqualsAndNoLineBreaks(expected, actual, null);
7375
}
7476

75-
static void assertEqualsAndNoLineBreaks(final String msg, final String expected, final String actual) {
77+
static void assertEqualsAndNoLineBreaks(final String expected, final String actual, final Supplier<String> messageSupplier) {
7678
if (actual != null) {
7779
assertFalse(actual.contains(LF.getString()), "Line contains \\n: line=" + actual);
7880
assertFalse(actual.contains(CR.getString()), "Line contains \\r: line=" + actual);
7981
}
80-
assertEquals(expected, actual, msg);
82+
assertEquals(expected, actual, messageSupplier);
8183
}
8284

8385
// small and uneven block sizes are not used in reality but are good to show that the algorithm is solid
@@ -88,12 +90,9 @@ public static IntStream blockSizes() {
8890
private ReversedLinesFileReader reversedLinesFileReader;
8991

9092
private void assertFileWithShrinkingTestLines(final ReversedLinesFileReader reversedLinesFileReader) throws IOException {
91-
String line = null;
92-
int lineCount = 0;
93-
while ((line = reversedLinesFileReader.readLine()) != null) {
94-
lineCount++;
95-
assertEqualsAndNoLineBreaks("Line " + lineCount + " is not matching", TEST_LINE.substring(0, lineCount), line);
96-
}
93+
final AtomicInteger count = new AtomicInteger();
94+
reversedLinesFileReader.forEach(
95+
line -> assertEqualsAndNoLineBreaks(TEST_LINE.substring(0, count.incrementAndGet()), line, () -> "Line " + count + " is not matching"));
9796
}
9897

9998
@AfterEach

src/test/java/org/apache/commons/io/input/ReversedLinesFileReaderParamFileTest.java

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -88,25 +88,17 @@ public static Stream<Arguments> testDataIntegrityWithBufferedReader() throws IOE
8888
private void testDataIntegrityWithBufferedReader(final Path filePath, final FileSystem fileSystem, final Charset charset,
8989
final ReversedLinesFileReader reversedLinesFileReader) throws IOException {
9090
final Stack<String> lineStack = new Stack<>();
91-
String line;
92-
9391
try (BufferedReader bufferedReader = Files.newBufferedReader(filePath, Charsets.toCharset(charset))) {
9492
// read all lines in normal order
93+
String line;
9594
while ((line = bufferedReader.readLine()) != null) {
9695
lineStack.push(line);
9796
}
9897
}
99-
10098
// read in reverse order and compare with lines from stack
101-
while ((line = reversedLinesFileReader.readLine()) != null) {
102-
final String lineFromBufferedReader = lineStack.pop();
103-
assertEquals(lineFromBufferedReader, line);
104-
}
99+
reversedLinesFileReader.forEach(line -> assertEquals(lineStack.pop(), line));
105100
assertEquals(0, lineStack.size(), "Stack should be empty");
106-
107-
if (fileSystem != null) {
108-
fileSystem.close();
109-
}
101+
IOUtils.close(fileSystem);
110102
}
111103

112104
@ParameterizedTest(name = "{0}, encoding={1}, blockSize={2}, useNonDefaultFileSystem={3}, isResource={4}")

0 commit comments

Comments
 (0)