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 <[email protected]>
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 >= 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 >= 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 < {@code size}
<= 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));
+ }
+ }
+}