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 00f284bd Only read the relevant portion of a file
00f284bd is described below

commit 00f284bd774a59ac8db4c67d4066d302edc01b29
Author: Gary Gregory <garydgreg...@gmail.com>
AuthorDate: Wed May 31 17:01:48 2023 -0400

    Only read the relevant portion of a file
    
    - AbstractOrigin.FileOrigin.getByteArray(long, int)
    - AbstractOrigin.PathOrigin.getByteArray(long, int)
    - Above implemented using the new RandomAccessFiles
---
 src/changes/changes.xml                            |  9 +++
 src/main/java/org/apache/commons/io/IOUtils.java   | 82 +++++++++++++++-------
 .../org/apache/commons/io/RandomAccessFiles.java   | 45 ++++++++++++
 .../apache/commons/io/build/AbstractOrigin.java    | 24 ++++++-
 .../apache/commons/io/RandomAccessFilesTest.java   | 60 ++++++++++++++++
 5 files changed, 192 insertions(+), 28 deletions(-)

diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 4e6ad709..c733565c 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -61,6 +61,12 @@ The <action> type attribute can be add,update,fix,remove.
       <action issue="IO-796" dev="ggregory" type="fix" due-to="Giacomo 
Boccardo, Gary Gregory">
         FileAlreadyExistsException in PathUtils.createParentDirectories(Path, 
LinkOption, FileAttribute...).
       </action>
+      <action dev="ggregory" type="fix" due-to="Gary Gregory">
+        Only read the relevant portion of a file in 
AbstractOrigin.FileOrigin.getByteArray(long, int)
+      </action>
+      <action dev="ggregory" type="fix" due-to="Gary Gregory">
+        Only read the relevant portion of a file in 
AbstractOrigin.PathOrigin.getByteArray(long, int)
+      </action>
       <!-- ADD -->
       <action dev="ggregory" type="add" due-to="Gary Gregory">
         Add CharSequenceInputStream.Builder.
@@ -77,6 +83,9 @@ The <action> type attribute can be add,update,fix,remove.
       <action dev="ggregory" type="add" due-to="Gary Gregory">
         Add AbstractOrigin.getByteArray(long, int).
       </action>
+      <action dev="ggregory" type="add" due-to="Gary Gregory">
+        Add and use RandomAccessFiles.
+      </action>
       <!-- UPDATE -->
       <action dev="ggregory" type="update" due-to="Gary Gregory, Dependabot">
         Bump commons-parent from 57 to 58.
diff --git a/src/main/java/org/apache/commons/io/IOUtils.java 
b/src/main/java/org/apache/commons/io/IOUtils.java
index 3479b0b3..160bb176 100644
--- a/src/main/java/org/apache/commons/io/IOUtils.java
+++ b/src/main/java/org/apache/commons/io/IOUtils.java
@@ -57,6 +57,7 @@ import java.util.stream.Stream;
 
 import org.apache.commons.io.function.IOConsumer;
 import org.apache.commons.io.function.IOSupplier;
+import org.apache.commons.io.function.IOTriFunction;
 import org.apache.commons.io.input.QueueInputStream;
 import org.apache.commons.io.output.AppendableWriter;
 import org.apache.commons.io.output.ByteArrayOutputStream;
@@ -1914,7 +1915,7 @@ public class IOUtils {
      * as possible before giving up; this may not always be the case for
      * subclasses of {@link InputStream}.
      *
-     * @param input where to read input from
+     * @param input where to read input
      * @param buffer destination
      * @param offset initial offset into buffer
      * @param length length to read, must be &gt;= 0
@@ -1925,14 +1926,32 @@ public class IOUtils {
      */
     public static int read(final InputStream input, final byte[] buffer, final 
int offset, final int length)
             throws IOException {
+        return read(input::read, buffer, offset, length);
+    }
+
+    /**
+     * Reads bytes from an input. This implementation guarantees that it will 
read as many bytes as possible before giving up; this may not always be the case
+     * for subclasses of {@link InputStream}.
+     *
+     * @param input  How to read input
+     * @param buffer destination
+     * @param offset initial offset into buffer
+     * @param length length to read, must be &gt;= 0
+     * @return actual length read; may be less than requested if EOF was 
reached
+     * @throws IllegalArgumentException if length is negative
+     * @throws IOException              if a read error occurs
+     * @since 2.2
+     */
+    static int read(final IOTriFunction<byte[], Integer, Integer, Integer> 
input, final byte[] buffer, final int offset, final int length)
+            throws IOException {
         if (length < 0) {
             throw new IllegalArgumentException("Length must not be negative: " 
+ length);
         }
         int remaining = length;
         while (remaining > 0) {
             final int location = length - remaining;
-            final int count = input.read(buffer, offset + location, remaining);
-            if (EOF == count) { // EOF
+            final int count = input.apply(buffer, offset + location, 
remaining);
+            if (EOF == count) {
                 break;
             }
             remaining -= count;
@@ -2640,28 +2659,7 @@ public class IOUtils {
      * @since 2.1
      */
     public static byte[] toByteArray(final InputStream input, final int size) 
throws IOException {
-
-        if (size < 0) {
-            throw new IllegalArgumentException("Size must be equal or greater 
than zero: " + size);
-        }
-
-        if (size == 0) {
-            return EMPTY_BYTE_ARRAY;
-        }
-
-        final byte[] data = byteArray(size);
-        int offset = 0;
-        int read;
-
-        while (offset < size && (read = input.read(data, offset, size - 
offset)) != EOF) {
-            offset += read;
-        }
-
-        if (offset != size) {
-            throw new IOException("Unexpected read size, current: " + offset + 
", expected: " + size);
-        }
-
-        return data;
+        return toByteArray(input::read, size);
     }
 
     /**
@@ -2687,6 +2685,40 @@ public class IOUtils {
         return toByteArray(input, (int) size);
     }
 
+    /**
+     * Gets the contents of an input as a {@code byte[]}.
+     *
+     * @param input the input to read.
+     * @param size the size of the input to read, where 0 &lt; {@code size} 
&lt;= length of input.
+     * @return byte [] of length {@code size}.
+     * @throws IOException if an I/O error occurs or input length is smaller 
than parameter {@code size}.
+     * @throws IllegalArgumentException if {@code size} is less than zero.
+     */
+    static byte[] toByteArray(final IOTriFunction<byte[], Integer, Integer, 
Integer> input, final int size) throws IOException {
+
+        if (size < 0) {
+            throw new IllegalArgumentException("Size must be equal or greater 
than zero: " + size);
+        }
+
+        if (size == 0) {
+            return EMPTY_BYTE_ARRAY;
+        }
+
+        final byte[] data = byteArray(size);
+        int offset = 0;
+        int read;
+
+        while (offset < size && (read = input.apply(data, offset, size - 
offset)) != EOF) {
+            offset += read;
+        }
+
+        if (offset != size) {
+            throw new IOException("Unexpected read size, current: " + offset + 
", expected: " + size);
+        }
+
+        return data;
+    }
+
     /**
      * Gets the contents of a {@link Reader} as a {@code byte[]}
      * using the default character encoding of the platform.
diff --git a/src/main/java/org/apache/commons/io/RandomAccessFiles.java 
b/src/main/java/org/apache/commons/io/RandomAccessFiles.java
new file mode 100644
index 00000000..2fc071fc
--- /dev/null
+++ b/src/main/java/org/apache/commons/io/RandomAccessFiles.java
@@ -0,0 +1,45 @@
+/*
+ * 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;
+
+import java.io.IOException;
+import java.io.RandomAccessFile;
+
+/**
+ * Works on RandomAccessFile.
+ *
+ * @since 2.13.0
+ */
+public class RandomAccessFiles {
+
+    /**
+     * Reads a byte array starting at "position" for "length" bytes.
+     *
+     * @param input    The source RandomAccessFile.
+     * @param position The offset position, measured in bytes from the 
beginning of the file, at which to set the file pointer.
+     * @param length   How many bytes to read.
+     * @return a new byte array.
+     * @throws IOException If the first byte cannot be read for any reason 
other than end of file, or if the random access file has been closed, or if some
+     *                     other I/O error occurs.
+     */
+    public static byte[] read(final RandomAccessFile input, final long 
position, final int length) throws IOException {
+        input.seek(position);
+        return IOUtils.toByteArray(input::read, length);
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/io/build/AbstractOrigin.java 
b/src/main/java/org/apache/commons/io/build/AbstractOrigin.java
index 7e79c360..efb3d051 100644
--- a/src/main/java/org/apache/commons/io/build/AbstractOrigin.java
+++ b/src/main/java/org/apache/commons/io/build/AbstractOrigin.java
@@ -24,6 +24,7 @@ import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.io.OutputStream;
 import java.io.OutputStreamWriter;
+import java.io.RandomAccessFile;
 import java.io.Reader;
 import java.io.Writer;
 import java.net.URI;
@@ -36,6 +37,8 @@ import java.util.Arrays;
 import java.util.Objects;
 
 import org.apache.commons.io.IOUtils;
+import org.apache.commons.io.RandomAccessFileMode;
+import org.apache.commons.io.RandomAccessFiles;
 import org.apache.commons.io.input.ReaderInputStream;
 import org.apache.commons.io.output.WriterOutputStream;
 
@@ -143,6 +146,14 @@ public abstract class AbstractOrigin<T, B extends 
AbstractOrigin<T, B>> extends
             super(origin);
         }
 
+
+        @Override
+        public byte[] getByteArray(final long position, final int length) 
throws IOException {
+            try (RandomAccessFile raf = 
RandomAccessFileMode.READ_ONLY.create(origin)) {
+                return RandomAccessFiles.read(raf, position, length);
+            }
+        }
+
         @Override
         public File getFile() {
             // No conversion
@@ -237,6 +248,13 @@ public abstract class AbstractOrigin<T, B extends 
AbstractOrigin<T, B>> extends
             super(origin);
         }
 
+        @Override
+        public byte[] getByteArray(final long position, final int length) 
throws IOException {
+            try (RandomAccessFile raf = 
RandomAccessFileMode.READ_ONLY.create(origin)) {
+                return RandomAccessFiles.read(raf, position, length);
+            }
+        }
+
         @Override
         public File getFile() {
             return get().toFile();
@@ -385,16 +403,16 @@ public abstract class AbstractOrigin<T, B extends 
AbstractOrigin<T, B>> extends
     /**
      * Gets this origin as a byte array, if possible.
      *
-     * @param from the initial index of the range to be copied, inclusive.
+     * @param position the initial index of the range to be copied, inclusive.
      * @param length How many bytes to copy.
      * @return this origin as a byte array, if possible.
      * @throws IOException                   if an I/O error occurs.
      * @throws UnsupportedOperationException if the origin cannot be converted 
to a Path.
      * @since 2.13.0
      */
-    public byte[] getByteArray(final long from, final int length) throws 
IOException {
+    public byte[] getByteArray(final long position, final int length) throws 
IOException {
         final byte[] bytes = getByteArray();
-        final int start = (int) from;
+        final int start = (int) position;
         // We include a separate check for int overflow.
         if (start < 0 || length < 0 || start + length < 0 || start + length > 
bytes.length) {
             throw new IllegalArgumentException("Couldn't read array (start: " 
+ start + ", length: " + length
diff --git a/src/test/java/org/apache/commons/io/RandomAccessFilesTest.java 
b/src/test/java/org/apache/commons/io/RandomAccessFilesTest.java
new file mode 100644
index 00000000..dc5ee654
--- /dev/null
+++ b/src/test/java/org/apache/commons/io/RandomAccessFilesTest.java
@@ -0,0 +1,60 @@
+/*
+ * 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;
+
+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.IOException;
+import java.io.RandomAccessFile;
+
+import org.junit.jupiter.api.Test;
+
+public class RandomAccessFilesTest {
+
+    protected static final String FILE_RES_RO = 
"/org/apache/commons/io/test-file-20byteslength.bin";
+    protected static final String FILE_NAME_RO = "src/test/resources" + 
FILE_RES_RO;
+
+    @Test
+    public void testRead() throws IOException {
+        try (final RandomAccessFile raf = 
RandomAccessFileMode.READ_ONLY.create(FILE_NAME_RO)) {
+            final byte[] buffer = RandomAccessFiles.read(raf, 0, 0);
+            assertArrayEquals(new byte[] {}, buffer);
+        }
+        try (final RandomAccessFile raf = 
RandomAccessFileMode.READ_ONLY.create(FILE_NAME_RO)) {
+            final byte[] buffer = RandomAccessFiles.read(raf, 1, 0);
+            assertArrayEquals(new byte[] {}, buffer);
+        }
+        try (final RandomAccessFile raf = 
RandomAccessFileMode.READ_ONLY.create(FILE_NAME_RO)) {
+            final byte[] buffer = RandomAccessFiles.read(raf, 0, 1);
+            assertArrayEquals(new byte[] { '1' }, buffer);
+        }
+        try (final RandomAccessFile raf = 
RandomAccessFileMode.READ_ONLY.create(FILE_NAME_RO)) {
+            final byte[] buffer = RandomAccessFiles.read(raf, 1, 1);
+            assertArrayEquals(new byte[] { '2' }, buffer);
+        }
+        try (final RandomAccessFile raf = 
RandomAccessFileMode.READ_ONLY.create(FILE_NAME_RO)) {
+            final byte[] buffer = RandomAccessFiles.read(raf, 0, 20);
+            assertEquals(20, buffer.length);
+        }
+        try (final RandomAccessFile raf = 
RandomAccessFileMode.READ_ONLY.create(FILE_NAME_RO)) {
+            assertThrows(IOException.class, () -> RandomAccessFiles.read(raf, 
0, 21));
+        }
+    }
+}

Reply via email to