Author: tilman
Date: Mon Oct 13 17:41:32 2025
New Revision: 1929124

Log:
PDFBOX-6083: optimize DataInputRandomAccessRead.readBytes() with a 
RandomAccess.readFully() method

Modified:
   
pdfbox/branches/3.0/fontbox/src/main/java/org/apache/fontbox/cff/DataInputRandomAccessRead.java
   
pdfbox/branches/3.0/io/src/main/java/org/apache/pdfbox/io/RandomAccessRead.java
   
pdfbox/branches/3.0/io/src/test/java/org/apache/pdfbox/io/RandomAccessReadBufferedFileTest.java

Modified: 
pdfbox/branches/3.0/fontbox/src/main/java/org/apache/fontbox/cff/DataInputRandomAccessRead.java
==============================================================================
--- 
pdfbox/branches/3.0/fontbox/src/main/java/org/apache/fontbox/cff/DataInputRandomAccessRead.java
     Mon Oct 13 17:41:27 2025        (r1929123)
+++ 
pdfbox/branches/3.0/fontbox/src/main/java/org/apache/fontbox/cff/DataInputRandomAccessRead.java
     Mon Oct 13 17:41:32 2025        (r1929124)
@@ -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/branches/3.0/io/src/main/java/org/apache/pdfbox/io/RandomAccessRead.java
==============================================================================
--- 
pdfbox/branches/3.0/io/src/main/java/org/apache/pdfbox/io/RandomAccessRead.java 
    Mon Oct 13 17:41:27 2025        (r1929123)
+++ 
pdfbox/branches/3.0/io/src/main/java/org/apache/pdfbox/io/RandomAccessRead.java 
    Mon Oct 13 17:41:32 2025        (r1929124)
@@ -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/branches/3.0/io/src/test/java/org/apache/pdfbox/io/RandomAccessReadBufferedFileTest.java
==============================================================================
--- 
pdfbox/branches/3.0/io/src/test/java/org/apache/pdfbox/io/RandomAccessReadBufferedFileTest.java
     Mon Oct 13 17:41:27 2025        (r1929123)
+++ 
pdfbox/branches/3.0/io/src/test/java/org/apache/pdfbox/io/RandomAccessReadBufferedFileTest.java
     Mon Oct 13 17:41:32 2025        (r1929124)
@@ -16,16 +16,20 @@
 
 package org.apache.pdfbox.io;
 
-import static org.junit.jupiter.api.Assertions.assertEquals;
+import java.io.EOFException;
 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;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 import org.junit.jupiter.api.Test;
 
 /**
@@ -191,4 +195,109 @@ 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;
+            try (InputStream is = 
getClass().getResourceAsStream("RandomAccessReadFile1.txt"))
+            {
+                allBytes = IOUtils.toByteArray(is);
+            }
+            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);
+        }
+    }
 }

Reply via email to