You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by gg...@apache.org on 2020/08/19 00:27:32 UTC

[commons-io] branch master updated: Add RandomAccessFileInputStream.

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


The following commit(s) were added to refs/heads/master by this push:
     new 117397e  Add RandomAccessFileInputStream.
117397e is described below

commit 117397e37a0adbe3147558ff2d0296ad3d0da29b
Author: Gary Gregory <ga...@gmail.com>
AuthorDate: Tue Aug 18 20:27:24 2020 -0400

    Add RandomAccessFileInputStream.
---
 src/changes/changes.xml                            |   3 +
 .../io/input/RandomAccessFileInputStream.java      | 151 +++++++++++++++++
 .../io/input/RandomAccessFileInputStreamTest.java  | 180 +++++++++++++++++++++
 3 files changed, 334 insertions(+)

diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index d975dea..5c9f569 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -112,6 +112,9 @@ The <action> type attribute can be add,update,fix,remove.
       <action dev="ggregory" type="fix" due-to="Gary Gregory">
         Fix SpotBugs issues in org.apache.commons.io.FileUtils.
       </action>
+      <action dev="ggregory" type="add" due-to="Gary Gregory">
+        Add RandomAccessFileInputStream.
+      </action>
       <!-- UPDATES -->
       <action dev="ggregory" type="update" due-to="Gary Gregory">
         Replace FindBugs with SpotBugs.
diff --git a/src/main/java/org/apache/commons/io/input/RandomAccessFileInputStream.java b/src/main/java/org/apache/commons/io/input/RandomAccessFileInputStream.java
new file mode 100644
index 0000000..9976c2d
--- /dev/null
+++ b/src/main/java/org/apache/commons/io/input/RandomAccessFileInputStream.java
@@ -0,0 +1,151 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.io.input;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.RandomAccessFile;
+import java.util.Objects;
+
+/**
+ * Streams data from a {@link RandomAccessFile} starting at its current position.
+ *
+ * @since 2.8.0
+ */
+public class RandomAccessFileInputStream extends InputStream {
+
+    private final boolean closeOnClose;
+    private final RandomAccessFile randomAccessFile;
+
+    /**
+     * Constructs a new instance configured to leave the underlying file open when this stream is closed.
+     *
+     * @param file The file to stream.
+     */
+    public RandomAccessFileInputStream(RandomAccessFile file) {
+        this(file, false);
+    }
+
+    /**
+     * Constructs a new instance.
+     *
+     * @param file The file to stream.
+     * @param closeOnClose Whether to close the underlying file when this stream is closed.
+     */
+    public RandomAccessFileInputStream(RandomAccessFile file, boolean closeOnClose) {
+        this.randomAccessFile = Objects.requireNonNull(file, "file");
+        this.closeOnClose = closeOnClose;
+    }
+
+    /**
+     * Returns an estimate of the number of bytes that can be read (or skipped over) from this input stream.
+     *
+     * If there are more than {@link Integer#MAX_VALUE} bytes available, return {@link Integer#MAX_VALUE}.
+     *
+     * @return An estimate of the number of bytes that can be read.
+     * @throws IOException If an I/O error occurs.
+     */
+    @Override
+    public int available() throws IOException {
+        final long avail = availableLong();
+        if (avail > Integer.MAX_VALUE) {
+            return Integer.MAX_VALUE;
+        }
+        return (int) avail;
+    }
+
+    /**
+     * Returns the number of bytes that can be read (or skipped over) from this input stream.
+     *
+     * @return The number of bytes that can be read.
+     * @throws IOException If an I/O error occurs.
+     */
+    public long availableLong() throws IOException {
+        return randomAccessFile.length() - randomAccessFile.getFilePointer();
+    }
+
+    @Override
+    public void close() throws IOException {
+        super.close();
+        if (closeOnClose) {
+            randomAccessFile.close();
+        }
+    }
+
+    /**
+     * Gets the underlying file.
+     *
+     * @return the underlying file.
+     */
+    public RandomAccessFile getRandomAccessFile() {
+        return randomAccessFile;
+    }
+
+    /**
+     * Returns whether to close the underlying file when this stream is closed.
+     *
+     * @return Whether to close the underlying file when this stream is closed.
+     */
+    public boolean isCloseOnClose() {
+        return closeOnClose;
+    }
+
+    @Override
+    public int read() throws IOException {
+        return randomAccessFile.read();
+    }
+
+    @Override
+    public int read(byte[] bytes) throws IOException {
+        return randomAccessFile.read(bytes);
+    }
+
+    @Override
+    public int read(byte[] bytes, int offset, int length) throws IOException {
+        return randomAccessFile.read(bytes, offset, length);
+    }
+
+    /**
+     * Delegates to the underlying file.
+     * 
+     * @param position See {@link RandomAccessFile#seek(long)}.
+     * @throws IOException See {@link RandomAccessFile#seek(long)}.
+     * @see RandomAccessFile#seek(long)
+     */
+    private void seek(long position) throws IOException {
+        randomAccessFile.seek(position);
+    }
+
+    @Override
+    public long skip(long skipCount) throws IOException {
+        if (skipCount <= 0) {
+            return 0;
+        }
+        final long filePointer = randomAccessFile.getFilePointer();
+        final long fileLength = randomAccessFile.length();
+        if (filePointer >= fileLength) {
+            return 0;
+        }
+        final long targetPos = filePointer + skipCount;
+        final long newPos = targetPos > fileLength ? fileLength - 1 : targetPos;
+        if (newPos > 0) {
+            seek(newPos);
+        }
+        return randomAccessFile.getFilePointer() - filePointer;
+    }
+}
diff --git a/src/test/java/org/apache/commons/io/input/RandomAccessFileInputStreamTest.java b/src/test/java/org/apache/commons/io/input/RandomAccessFileInputStreamTest.java
new file mode 100644
index 0000000..b4986e9
--- /dev/null
+++ b/src/test/java/org/apache/commons/io/input/RandomAccessFileInputStreamTest.java
@@ -0,0 +1,180 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.io.input;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.nio.charset.StandardCharsets;
+
+import org.junit.jupiter.api.Test;
+
+public class RandomAccessFileInputStreamTest {
+
+    private static final String DATA_FILE = "src/test/resources/test-file-iso8859-1.bin";
+    private static final int DATA_FILE_LEN = 1430;
+
+    private RandomAccessFile createRandomAccessFile() throws FileNotFoundException {
+        return new RandomAccessFile(DATA_FILE, "r");
+    }
+
+    @Test
+    public void testAvailable() throws IOException {
+        try (final RandomAccessFileInputStream inputStream = new RandomAccessFileInputStream(createRandomAccessFile(),
+            true)) {
+            assertEquals(DATA_FILE_LEN, inputStream.available());
+        }
+    }
+
+    @Test
+    public void testAvailableLong() throws IOException {
+        try (final RandomAccessFileInputStream inputStream = new RandomAccessFileInputStream(createRandomAccessFile(),
+            true)) {
+            assertEquals(DATA_FILE_LEN, inputStream.availableLong());
+        }
+    }
+
+    @Test
+    public void testCtorCloseOnCloseFalse() throws IOException {
+        try (RandomAccessFile file = createRandomAccessFile()) {
+            try (final RandomAccessFileInputStream inputStream = new RandomAccessFileInputStream(file, false)) {
+                assertEquals(false, inputStream.isCloseOnClose());
+            }
+            file.read();
+        }
+    }
+
+    @Test
+    public void testCtorCloseOnCloseTrue() throws IOException {
+        try (RandomAccessFile file = createRandomAccessFile()) {
+            try (final RandomAccessFileInputStream inputStream = new RandomAccessFileInputStream(file, true)) {
+                assertEquals(true, inputStream.isCloseOnClose());
+            }
+            assertThrows(IOException.class, () -> file.read());
+        }
+    }
+
+    @Test
+    public void testCtorNullFile() throws FileNotFoundException {
+        assertThrows(NullPointerException.class, () -> new RandomAccessFileInputStream(null));
+    }
+
+    @Test
+    public void testGetters() throws IOException {
+        try (RandomAccessFile file = createRandomAccessFile()) {
+            try (final RandomAccessFileInputStream inputStream = new RandomAccessFileInputStream(file, true)) {
+                assertEquals(file, inputStream.getRandomAccessFile());
+                assertEquals(true, inputStream.isCloseOnClose());
+            }
+        }
+    }
+
+    @Test
+    public void testRead() throws IOException {
+        try (final RandomAccessFileInputStream inputStream = new RandomAccessFileInputStream(createRandomAccessFile(),
+            true)) {
+            // A Test Line.
+            assertEquals('A', inputStream.read());
+            assertEquals(' ', inputStream.read());
+            assertEquals('T', inputStream.read());
+            assertEquals('e', inputStream.read());
+            assertEquals('s', inputStream.read());
+            assertEquals('t', inputStream.read());
+            assertEquals(' ', inputStream.read());
+            assertEquals('L', inputStream.read());
+            assertEquals('i', inputStream.read());
+            assertEquals('n', inputStream.read());
+            assertEquals('e', inputStream.read());
+            assertEquals('.', inputStream.read());
+            assertEquals(DATA_FILE_LEN - 12, inputStream.available());
+            assertEquals(DATA_FILE_LEN - 12, inputStream.availableLong());
+        }
+    }
+
+    @Test
+    public void testSkip() throws IOException {
+
+        try (final RandomAccessFile file = createRandomAccessFile();
+            final RandomAccessFileInputStream inputStream = new RandomAccessFileInputStream(file, false)) {
+            assertEquals(0, inputStream.skip(-1));
+            assertEquals(0, inputStream.skip(Integer.MIN_VALUE));
+            assertEquals(0, inputStream.skip(0));
+            // A Test Line.
+            assertEquals('A', inputStream.read());
+            assertEquals(1, inputStream.skip(1));
+            assertEquals('T', inputStream.read());
+            assertEquals(1, inputStream.skip(1));
+            assertEquals('s', inputStream.read());
+            assertEquals(1, inputStream.skip(1));
+            assertEquals(' ', inputStream.read());
+            assertEquals(1, inputStream.skip(1));
+            assertEquals('i', inputStream.read());
+            assertEquals(1, inputStream.skip(1));
+            assertEquals('e', inputStream.read());
+            assertEquals(1, inputStream.skip(1));
+            //
+            assertEquals(DATA_FILE_LEN - 12, inputStream.available());
+            assertEquals(DATA_FILE_LEN - 12, inputStream.availableLong());
+            assertEquals(10, inputStream.skip(10));
+            assertEquals(DATA_FILE_LEN - 22, inputStream.availableLong());
+            //
+            final long avail = inputStream.availableLong();
+            assertEquals(avail, inputStream.skip(inputStream.availableLong()));
+            // At EOF
+            assertEquals(DATA_FILE_LEN, file.length());
+            assertEquals(DATA_FILE_LEN, file.getFilePointer());
+            //
+            assertEquals(0, inputStream.skip(1));
+            assertEquals(0, inputStream.skip(1000000000000L));
+        }
+    }
+
+    @Test
+    public void testReadByteArray() throws IOException {
+        try (final RandomAccessFileInputStream inputStream = new RandomAccessFileInputStream(createRandomAccessFile(),
+            true)) {
+            // A Test Line.
+            final int dataLen = 12;
+            final byte[] buffer = new byte[dataLen];
+            assertEquals(dataLen, inputStream.read(buffer));
+            assertArrayEquals("A Test Line.".getBytes(StandardCharsets.ISO_8859_1), buffer);
+            //
+            assertEquals(DATA_FILE_LEN - dataLen, inputStream.available());
+            assertEquals(DATA_FILE_LEN - dataLen, inputStream.availableLong());
+        }
+    }
+
+    @Test
+    public void testReadByteArrayBounds() throws IOException {
+        try (final RandomAccessFileInputStream inputStream = new RandomAccessFileInputStream(createRandomAccessFile(),
+            true)) {
+            // A Test Line.
+            final int dataLen = 12;
+            final byte[] buffer = new byte[dataLen];
+            assertEquals(dataLen, inputStream.read(buffer, 0, dataLen));
+            assertArrayEquals("A Test Line.".getBytes(StandardCharsets.ISO_8859_1), buffer);
+            //
+            assertEquals(DATA_FILE_LEN - dataLen, inputStream.available());
+            assertEquals(DATA_FILE_LEN - dataLen, inputStream.availableLong());
+        }
+    }
+}