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 >= 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)); + } + } +}