This is an automated email from the ASF dual-hosted git repository. ggregory pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/commons-io.git
commit 935fb384a783f45144e8bf2dbfd1a93916481350 Author: Gary Gregory <garydgreg...@gmail.com> AuthorDate: Tue Dec 10 15:30:00 2024 -0500 ReversedLinesFileReader implements IOIterable<String> --- src/changes/changes.xml | 1 + .../commons/io/input/ReversedLinesFileReader.java | 59 +++++++++++++++++++--- .../ReversedLinesFileReaderParamBlockSizeTest.java | 17 +++---- .../ReversedLinesFileReaderParamFileTest.java | 14 ++--- 4 files changed, 65 insertions(+), 26 deletions(-) diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 295203491..8283618e8 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -51,6 +51,7 @@ The <action> type attribute can be add,update,fix,remove. <!-- ADD --> <action dev="ggregory" type="add" issue="IO-860" due-to="Nico Strecker, Gary Gregory">Add ThrottledInputStream.Builder.setMaxBytes(long, ChronoUnit).</action> <action dev="ggregory" type="add" due-to="Gary Gregory">Add IOIterable.</action> + <action dev="ggregory" type="add" due-to="Gary Gregory">ReversedLinesFileReader implements IOIterable<String>.</action> <!-- UPDATE --> </release> <release version="2.18.0" date="2024-11-16" description="Version 2.18.0: Java 8 is required."> diff --git a/src/main/java/org/apache/commons/io/input/ReversedLinesFileReader.java b/src/main/java/org/apache/commons/io/input/ReversedLinesFileReader.java index 51ffb64ef..9649fac2c 100644 --- a/src/main/java/org/apache/commons/io/input/ReversedLinesFileReader.java +++ b/src/main/java/org/apache/commons/io/input/ReversedLinesFileReader.java @@ -31,23 +31,41 @@ import java.nio.file.StandardOpenOption; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.Iterator; import java.util.List; import org.apache.commons.io.Charsets; import org.apache.commons.io.FileSystem; import org.apache.commons.io.StandardLineSeparator; import org.apache.commons.io.build.AbstractStreamBuilder; +import org.apache.commons.io.function.IOIterable; +import org.apache.commons.io.function.IOIterator; /** * Reads lines in a file reversely (similar to a BufferedReader, but starting at the last line). Useful for e.g. searching in log files. * <p> * To build an instance, use {@link Builder}. * </p> + * <p> + * For example: + * </p> + * + * <pre> + * <code> + * try (ReversedLinesFileReader reader = ReversedLinesFileReader.builder() + * .setPath(path) + * .setBufferSize(4096) + * .setCharset(StandardCharsets.UTF_8) + * .get()) { + * reader.forEach(line -> System.out.println(line)); + * } + * </code> + * </pre> * * @see Builder * @since 2.2 */ -public class ReversedLinesFileReader implements Closeable { +public class ReversedLinesFileReader implements Closeable, IOIterable<String> { // @formatter:off /** @@ -57,11 +75,11 @@ public class ReversedLinesFileReader implements Closeable { * For example: * </p> * <pre>{@code - * ReversedLinesFileReader r = ReversedLinesFileReader.builder() + * ReversedLinesFileReader reader = ReversedLinesFileReader.builder() * .setPath(path) * .setBufferSize(4096) * .setCharset(StandardCharsets.UTF_8) - * .get();} + * .get());} * </pre> * * @see #get() @@ -470,7 +488,6 @@ public class ReversedLinesFileReader implements Closeable { * @throws IOException if an I/O error occurs. */ public String readLine() throws IOException { - String line = currentFilePart.readLine(); while (line == null) { currentFilePart = currentFilePart.rollOver(); @@ -480,13 +497,11 @@ public class ReversedLinesFileReader implements Closeable { } line = currentFilePart.readLine(); } - // aligned behavior with BufferedReader that doesn't return a last, empty line if (EMPTY_STRING.equals(line) && !trailingNewlineOfFileSkipped) { trailingNewlineOfFileSkipped = true; line = readLine(); } - return line; } @@ -538,4 +553,36 @@ public class ReversedLinesFileReader implements Closeable { return lines.isEmpty() ? EMPTY_STRING : String.join(System.lineSeparator(), lines) + System.lineSeparator(); } + @Override + public IOIterator<String> iterator() { + return new IOIterator<String>() { + + private String next; + + @Override + public boolean hasNext() throws IOException { + if (next == null) { + next = readLine(); + } + return next != null; + } + + @Override + public String next() throws IOException { + if (next == null) { + next = readLine(); + } + final String tmp = next; + next = null; + return tmp; + } + + @Override + public Iterator<String> unwrap() { + return null; + } + + }; + } + } diff --git a/src/test/java/org/apache/commons/io/input/ReversedLinesFileReaderParamBlockSizeTest.java b/src/test/java/org/apache/commons/io/input/ReversedLinesFileReaderParamBlockSizeTest.java index c9ede92af..6c8885eab 100644 --- a/src/test/java/org/apache/commons/io/input/ReversedLinesFileReaderParamBlockSizeTest.java +++ b/src/test/java/org/apache/commons/io/input/ReversedLinesFileReaderParamBlockSizeTest.java @@ -28,6 +28,8 @@ import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URISyntaxException; import java.nio.charset.StandardCharsets; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Supplier; import java.util.stream.IntStream; import org.apache.commons.io.TestResources; @@ -69,15 +71,15 @@ public class ReversedLinesFileReaderParamBlockSizeTest { private static final String TEST_LINE_X_WINDOWS_950_2 = "\u7E41\u9AD4\u4E2D\u6587"; static void assertEqualsAndNoLineBreaks(final String expected, final String actual) { - assertEqualsAndNoLineBreaks(null, expected, actual); + assertEqualsAndNoLineBreaks(expected, actual, null); } - static void assertEqualsAndNoLineBreaks(final String msg, final String expected, final String actual) { + static void assertEqualsAndNoLineBreaks(final String expected, final String actual, final Supplier<String> messageSupplier) { if (actual != null) { assertFalse(actual.contains(LF.getString()), "Line contains \\n: line=" + actual); assertFalse(actual.contains(CR.getString()), "Line contains \\r: line=" + actual); } - assertEquals(expected, actual, msg); + assertEquals(expected, actual, messageSupplier); } // 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 class ReversedLinesFileReaderParamBlockSizeTest { private ReversedLinesFileReader reversedLinesFileReader; private void assertFileWithShrinkingTestLines(final ReversedLinesFileReader reversedLinesFileReader) throws IOException { - String line = null; - int lineCount = 0; - while ((line = reversedLinesFileReader.readLine()) != null) { - lineCount++; - assertEqualsAndNoLineBreaks("Line " + lineCount + " is not matching", TEST_LINE.substring(0, lineCount), line); - } + final AtomicInteger count = new AtomicInteger(); + reversedLinesFileReader.forEach( + line -> assertEqualsAndNoLineBreaks(TEST_LINE.substring(0, count.incrementAndGet()), line, () -> "Line " + count + " is not matching")); } @AfterEach diff --git a/src/test/java/org/apache/commons/io/input/ReversedLinesFileReaderParamFileTest.java b/src/test/java/org/apache/commons/io/input/ReversedLinesFileReaderParamFileTest.java index 9c035fd19..c47888823 100644 --- a/src/test/java/org/apache/commons/io/input/ReversedLinesFileReaderParamFileTest.java +++ b/src/test/java/org/apache/commons/io/input/ReversedLinesFileReaderParamFileTest.java @@ -88,25 +88,17 @@ public class ReversedLinesFileReaderParamFileTest { private void testDataIntegrityWithBufferedReader(final Path filePath, final FileSystem fileSystem, final Charset charset, final ReversedLinesFileReader reversedLinesFileReader) throws IOException { final Stack<String> lineStack = new Stack<>(); - String line; - try (BufferedReader bufferedReader = Files.newBufferedReader(filePath, Charsets.toCharset(charset))) { // read all lines in normal order + String line; while ((line = bufferedReader.readLine()) != null) { lineStack.push(line); } } - // read in reverse order and compare with lines from stack - while ((line = reversedLinesFileReader.readLine()) != null) { - final String lineFromBufferedReader = lineStack.pop(); - assertEquals(lineFromBufferedReader, line); - } + reversedLinesFileReader.forEach(line -> assertEquals(lineStack.pop(), line)); assertEquals(0, lineStack.size(), "Stack should be empty"); - - if (fileSystem != null) { - fileSystem.close(); - } + IOUtils.close(fileSystem); } @ParameterizedTest(name = "{0}, encoding={1}, blockSize={2}, useNonDefaultFileSystem={3}, isResource={4}")