Author: tilman
Date: Mon Oct 13 17:41:27 2025
New Revision: 1929123
Log:
PDFBOX-6083: optimize DataInputRandomAccessRead.readBytes() with a
RandomAccess.readFully() method
Modified:
pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/cff/DataInputRandomAccessRead.java
pdfbox/trunk/io/src/main/java/org/apache/pdfbox/io/RandomAccessRead.java
pdfbox/trunk/io/src/test/java/org/apache/pdfbox/io/RandomAccessReadBufferedFileTest.java
Modified:
pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/cff/DataInputRandomAccessRead.java
==============================================================================
---
pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/cff/DataInputRandomAccessRead.java
Mon Oct 13 17:35:47 2025 (r1929122)
+++
pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/cff/DataInputRandomAccessRead.java
Mon Oct 13 17:41:27 2025 (r1929123)
@@ -170,15 +170,8 @@ public class DataInputRandomAccessRead i
{
throw new IOException("length is negative");
}
- if (randomAccessRead.length() - randomAccessRead.getPosition() <
length)
- {
- throw new IOException("Premature end of buffer reached");
- }
byte[] bytes = new byte[length];
- for (int i = 0; i < length; i++)
- {
- bytes[i] = readByte();
- }
+ randomAccessRead.readFully(bytes);
return bytes;
}
Modified:
pdfbox/trunk/io/src/main/java/org/apache/pdfbox/io/RandomAccessRead.java
==============================================================================
--- pdfbox/trunk/io/src/main/java/org/apache/pdfbox/io/RandomAccessRead.java
Mon Oct 13 17:35:47 2025 (r1929122)
+++ pdfbox/trunk/io/src/main/java/org/apache/pdfbox/io/RandomAccessRead.java
Mon Oct 13 17:41:27 2025 (r1929123)
@@ -17,6 +17,7 @@
package org.apache.pdfbox.io;
import java.io.Closeable;
+import java.io.EOFException;
import java.io.IOException;
/**
@@ -159,4 +160,44 @@ public interface RandomAccessRead extend
*
*/
RandomAccessReadView createView(long startPosition, long streamLength)
throws IOException;
+
+ /**
+ * Same as {@link #read(byte[])} but will loop until exactly length bytes
are read or
+ * it will throw an exception.
+ *
+ * @param b The buffer to write the data to.
+ * @throws IOException
+ */
+ default void readFully(byte[] b) throws IOException
+ {
+ readFully(b, 0, b.length);
+ }
+
+ /**
+ * Same as {@link #read(byte[], int, int)} but will loop until exactly
length bytes are read or
+ * it will throw an exception.
+ *
+ * @param b The buffer to write the data to.
+ * @param offset Offset into the buffer to start writing.
+ * @param length The exact amount of data to attempt to read.
+ * @throws IOException
+ */
+ default void readFully(byte[] b, int offset, int length) throws IOException
+ {
+ if (length() - getPosition() < length)
+ {
+ throw new EOFException("Premature end of buffer reached");
+ }
+ int bytesReadTotal = 0;
+ do
+ {
+ int bytesReadNow = read(b, offset + bytesReadTotal, length -
bytesReadTotal);
+ if (bytesReadNow <= 0)
+ {
+ throw new EOFException("EOF, should have been detected
earlier");
+ }
+ bytesReadTotal += bytesReadNow;
+ }
+ while (bytesReadTotal < length);
+ }
}
Modified:
pdfbox/trunk/io/src/test/java/org/apache/pdfbox/io/RandomAccessReadBufferedFileTest.java
==============================================================================
---
pdfbox/trunk/io/src/test/java/org/apache/pdfbox/io/RandomAccessReadBufferedFileTest.java
Mon Oct 13 17:35:47 2025 (r1929122)
+++
pdfbox/trunk/io/src/test/java/org/apache/pdfbox/io/RandomAccessReadBufferedFileTest.java
Mon Oct 13 17:41:27 2025 (r1929123)
@@ -16,13 +16,17 @@
package org.apache.pdfbox.io;
+import java.io.EOFException;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.File;
+import java.io.FileInputStream;
import java.io.IOException;
+import java.io.InputStream;
import java.net.URISyntaxException;
+import java.nio.charset.StandardCharsets;
import java.nio.file.Paths;
import org.junit.jupiter.api.Assertions;
@@ -191,4 +195,105 @@ class RandomAccessReadBufferedFileTest
assertEquals(3, view.getPosition());
}
}
+
+ @Test
+ void testReadFully1() throws IOException, URISyntaxException
+ {
+ try (RandomAccessRead randomAccessSource = new
RandomAccessReadBufferedFile(
+ new
File(getClass().getResource("RandomAccessReadFile1.txt").toURI())))
+ {
+ byte[] b = new byte[10];
+ randomAccessSource.seek(1);
+ randomAccessSource.readFully(b);
+ String s = new String(b, StandardCharsets.US_ASCII);
+ assertEquals("1234567890", s);
+ }
+ }
+
+ @Test
+ void testReadFully2() throws IOException, URISyntaxException
+ {
+ try (RandomAccessRead randomAccessSource = new
RandomAccessReadBufferedFile(
+ new
File(getClass().getResource("RandomAccessReadFile1.txt").toURI())))
+ {
+ byte[] b = new byte[10];
+ randomAccessSource.readFully(b, 2, 8);
+ String s = new String(b, 2, 8, StandardCharsets.US_ASCII);
+ assertEquals("01234567", s);
+ assertEquals(0, b[0]);
+ assertEquals(0, b[1]);
+ }
+ }
+
+ @Test
+ void testReadFully3() throws IOException, URISyntaxException
+ {
+ try (RandomAccessRead randomAccessSource = new
RandomAccessReadBufferedFile(
+ new
File(getClass().getResource("RandomAccessReadFile1.txt").toURI())))
+ {
+ byte[] b = new byte[10];
+ randomAccessSource.seek(randomAccessSource.length() - b.length);
+ randomAccessSource.readFully(b);
+ String s = new String(b, StandardCharsets.US_ASCII);
+ assertEquals("0123456789", s);
+ }
+ }
+
+ @Test
+ void testReadFullyEOF() throws IOException, URISyntaxException
+ {
+ try (RandomAccessRead randomAccessSource = new
RandomAccessReadBufferedFile(
+ new
File(getClass().getResource("RandomAccessReadFile1.txt").toURI())))
+ {
+ byte[] b = new byte[10];
+ randomAccessSource.seek(randomAccessSource.length() - b.length +
1);
+ Assertions.assertThrows(EOFException.class, () ->
randomAccessSource.readFully(b));
+ }
+ }
+
+ @Test
+ void testReadFullyExact() throws IOException, URISyntaxException
+ {
+ try (RandomAccessRead randomAccessSource = new
RandomAccessReadBufferedFile(
+ new
File(getClass().getResource("RandomAccessReadFile1.txt").toURI())))
+ {
+ int length = (int) randomAccessSource.length();
+ byte[] b = new byte[length];
+ randomAccessSource.readFully(b);
+ byte[] allBytes =
getClass().getResourceAsStream("RandomAccessReadFile1.txt").readAllBytes();
+ Assertions.assertArrayEquals(allBytes, b);
+ }
+ }
+
+ @Test
+ void testReadFullyAcrossBuffers() throws IOException, URISyntaxException
+ {
+ int bufferLen;
+ File file = new
File("src/test/java/org/apache/pdfbox/io/NonSeekableRandomAccessReadInputStreamTest.java");
+ try (RandomAccessRead randomAccessSource = new
RandomAccessReadBufferedFile(file))
+ {
+ int length = (int) randomAccessSource.length();
+ byte[] b = new byte[length];
+ bufferLen = randomAccessSource.read(b);
+ // make sure that the double buffer size is smaller than the length
+ // if this test fails, we'll need a larger file
+ assertTrue(bufferLen * 2 < length);
+ }
+ byte[] expectedBytes;
+ try (InputStream is = new FileInputStream(file))
+ {
+ long skipped = is.skip(bufferLen / 2);
+ assertEquals(skipped, bufferLen / 2);
+ expectedBytes = new byte[bufferLen * 2];
+ int actualRead = is.read(expectedBytes, 1, expectedBytes.length -
1);
+ assertEquals(expectedBytes.length - 1, actualRead);
+ }
+ try (RandomAccessRead randomAccessSource = new
RandomAccessReadBufferedFile(file))
+ {
+ randomAccessSource.seek(bufferLen / 2);
+ byte[] b = new byte[bufferLen * 2];
+ randomAccessSource.readFully(b, 1, b.length - 1);
+ Assertions.assertArrayEquals(expectedBytes, b);
+ }
+ }
}