You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@pdfbox.apache.org by le...@apache.org on 2020/08/02 11:51:46 UTC

svn commit: r1880517 - in /pdfbox/trunk/pdfbox/src: main/java/org/apache/pdfbox/io/SequenceRandomAccessRead.java test/java/org/apache/pdfbox/io/SequenceRandomAccessReadTest.java

Author: lehmi
Date: Sun Aug  2 11:51:46 2020
New Revision: 1880517

URL: http://svn.apache.org/viewvc?rev=1880517&view=rev
Log:
PDFBOX-4836: introduce new class to wrap a sequence of RandomAccessRead into one

Added:
    pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/io/SequenceRandomAccessRead.java   (with props)
    pdfbox/trunk/pdfbox/src/test/java/org/apache/pdfbox/io/SequenceRandomAccessReadTest.java   (with props)

Added: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/io/SequenceRandomAccessRead.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/io/SequenceRandomAccessRead.java?rev=1880517&view=auto
==============================================================================
--- pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/io/SequenceRandomAccessRead.java (added)
+++ pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/io/SequenceRandomAccessRead.java Sun Aug  2 11:51:46 2020
@@ -0,0 +1,226 @@
+/*
+ * 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.pdfbox.io;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Wrapper class to combine several RandomAccessRead instances so that they can be accessed as one big RandomAccessRead.
+ */
+public class SequenceRandomAccessRead implements RandomAccessRead
+{
+    private final List<RandomAccessRead> randomAccessReadList;
+    private final long[] startPositions;
+    private final long[] endPositions;
+    private final int numberOfReader;
+    private int currentIndex = 0;
+    private long currentPosition = 0;
+    private long length = 0;
+    private boolean isClosed = false;
+    private RandomAccessRead currentRandomAccessRead = null;
+    
+    public SequenceRandomAccessRead(List<RandomAccessRead> randomAccessReadList)
+    {
+        if (randomAccessReadList == null)
+        {
+            throw new IllegalArgumentException("Missing input parameter");
+        }
+        if (randomAccessReadList.isEmpty())
+        {
+            throw new IllegalArgumentException("Empty list");
+        }
+        this.randomAccessReadList = new ArrayList<>(randomAccessReadList);
+        numberOfReader = randomAccessReadList.size();
+        currentRandomAccessRead = randomAccessReadList.get(currentIndex);
+        startPositions = new long[numberOfReader];
+        endPositions = new long[numberOfReader];
+        for(int i=0;i<numberOfReader;i++) 
+        {
+            try
+            {
+                startPositions[i] = length;
+                length += randomAccessReadList.get(i).length();
+                endPositions[i] = length - 1;
+            }
+            catch (IOException e)
+            {
+                throw new IllegalArgumentException("Problematic list", e);
+            }
+        }
+    }
+
+    @Override
+    public void close() throws IOException
+    {
+        for (RandomAccessRead randomAccessRead : randomAccessReadList)
+        {
+            randomAccessRead.close();
+        }
+        randomAccessReadList.clear();
+        currentRandomAccessRead = null;
+        isClosed = true;
+    }
+
+    private RandomAccessRead getCurrentReader() throws IOException
+    {
+        if (currentRandomAccessRead.isEOF() && currentIndex < numberOfReader - 1)
+        {
+            currentIndex++;
+            currentRandomAccessRead = randomAccessReadList.get(currentIndex);
+            currentRandomAccessRead.seek(0);
+        }
+        return currentRandomAccessRead;
+    }
+
+    @Override
+    public int read() throws IOException
+    {
+        checkClosed();
+        RandomAccessRead randomAccessRead = getCurrentReader();
+        int value = randomAccessRead.read();
+        if (value > -1)
+        {
+            currentPosition++;
+        }
+        return value;
+    }
+
+    @Override
+    public int read(byte[] b) throws IOException
+    {
+        return read(b, 0, b.length);
+    }
+
+    @Override
+    public int read(byte[] b, int offset, int length) throws IOException
+    {
+        checkClosed();
+        RandomAccessRead randomAccessRead = getCurrentReader();
+        int bytesRead = randomAccessRead.read(b, offset, length);
+        while (bytesRead < length && available() > 0)
+        {
+            randomAccessRead = getCurrentReader();
+            bytesRead += randomAccessRead.read(b, offset + bytesRead, length - bytesRead);
+        }
+        currentPosition += bytesRead;
+        return bytesRead;
+    }
+
+    @Override
+    public long getPosition() throws IOException
+    {
+        checkClosed();
+        return currentPosition;
+    }
+
+    @Override
+    public void seek(long position) throws IOException
+    {
+        checkClosed();
+        if (position < 0)
+        {
+            throw new IOException("Invalid position " + position);
+        }
+        // it is allowed to jump beyond the end of the file
+        // jump to the end of the reader
+        if (position >= length)
+        {
+            currentIndex = numberOfReader - 1;
+        }
+        else
+        {
+            for (int i = 0; i < numberOfReader; i++)
+            {
+                if (position >= startPositions[i] && position <= endPositions[i])
+                {
+                    currentIndex = i;
+                    break;
+                }
+            }
+        }
+        currentRandomAccessRead = randomAccessReadList.get(currentIndex);
+        currentRandomAccessRead.seek(position - startPositions[currentIndex]);
+        currentPosition = position;
+    }
+
+    @Override
+    public long length() throws IOException
+    {
+        checkClosed();
+        return length;
+    }
+
+    @Override
+    public void rewind(int bytes) throws IOException
+    {
+        seek(getPosition() - bytes);
+    }
+
+    @Override
+    public boolean isClosed()
+    {
+        return isClosed;
+    }
+
+    /**
+     * Ensure that the SequenceRandomAccessRead is not closed
+     * 
+     * @throws IOException
+     */
+    private void checkClosed() throws IOException
+    {
+        if (isClosed)
+        {
+            // consider that the rab is closed if there is no current buffer
+            throw new IOException("RandomAccessBuffer already closed");
+        }
+    }
+
+    @Override
+    public int peek() throws IOException
+    {
+        int result = read();
+        if (result != -1)
+        {
+            rewind(1);
+        }
+        return result;
+    }
+
+    @Override
+    public boolean isEOF() throws IOException
+    {
+        checkClosed();
+        return currentPosition >= length;
+    }
+
+    @Override
+    public int available() throws IOException
+    {
+        checkClosed();
+        return (int) Math.min(length - currentPosition, Integer.MAX_VALUE);
+    }
+
+    @Override
+    public RandomAccessReadView createView(long startPosition, long streamLength) throws IOException
+    {
+        throw new IOException(getClass().getName() + ".createView isn't supported.");
+    }
+
+}

Propchange: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/io/SequenceRandomAccessRead.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: pdfbox/trunk/pdfbox/src/test/java/org/apache/pdfbox/io/SequenceRandomAccessReadTest.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/pdfbox/src/test/java/org/apache/pdfbox/io/SequenceRandomAccessReadTest.java?rev=1880517&view=auto
==============================================================================
--- pdfbox/trunk/pdfbox/src/test/java/org/apache/pdfbox/io/SequenceRandomAccessReadTest.java (added)
+++ pdfbox/trunk/pdfbox/src/test/java/org/apache/pdfbox/io/SequenceRandomAccessReadTest.java Sun Aug  2 11:51:46 2020
@@ -0,0 +1,216 @@
+/*
+ * 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.pdfbox.io;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import org.junit.Test;
+
+/**
+ * Unittest for org.apache.pdfbox.io.SequenceRandomAccessRead
+ * 
+ */
+public class SequenceRandomAccessReadTest
+{
+
+    @Test
+    public void TestCreateAndRead() throws IOException
+    {
+        String input1 = "This is a test string number 1";
+        RandomAccessReadBuffer randomAccessReadBuffer1 = new RandomAccessReadBuffer(
+                input1.getBytes());
+        String input2 = "This is a test string number 2";
+        RandomAccessReadBuffer randomAccessReadBuffer2 = new RandomAccessReadBuffer(
+                input2.getBytes());
+        List<RandomAccessRead> inputList = Arrays.asList(randomAccessReadBuffer1,
+                randomAccessReadBuffer2);
+        SequenceRandomAccessRead sequenceRandomAccessRead = new SequenceRandomAccessRead(inputList);
+
+        int overallLength = input1.length() + input2.length();
+        assertEquals(overallLength, sequenceRandomAccessRead.length());
+        
+        byte[] bytesRead = new byte[overallLength];
+        
+        assertEquals(overallLength, sequenceRandomAccessRead.read(bytesRead));
+        assertEquals(input1 + input2, new String(bytesRead));
+
+        sequenceRandomAccessRead.close();
+
+        // test missing parameter
+        try (RandomAccessRead read = new SequenceRandomAccessRead(null))
+        {
+            fail("Constructor should have thrown an IllegalArgumentException");
+        }
+        catch (IllegalArgumentException e)
+        {
+        }
+
+        // test empty list
+        try (RandomAccessRead read = new SequenceRandomAccessRead(Collections.emptyList()))
+        {
+            fail("Constructor should have thrown an IllegalArgumentException");
+        }
+        catch (IllegalArgumentException e)
+        {
+        }
+        // test problematic list
+        try (RandomAccessRead read = new SequenceRandomAccessRead(inputList))
+        {
+            fail("Constructor should have thrown an IllegalArgumentException");
+        }
+        catch (IllegalArgumentException e)
+        {
+        }
+    }
+
+    @Test
+    public void TestSeekPeekAndRewind() throws IOException
+    {
+        String input1 = "01234567890123456789";
+        RandomAccessReadBuffer randomAccessReadBuffer1 = new RandomAccessReadBuffer(
+                input1.getBytes());
+        String input2 = "abcdefghijklmnopqrst";
+        RandomAccessReadBuffer randomAccessReadBuffer2 = new RandomAccessReadBuffer(
+                input2.getBytes());
+        List<RandomAccessRead> inputList = Arrays.asList(randomAccessReadBuffer1,
+                randomAccessReadBuffer2);
+        SequenceRandomAccessRead sequenceRandomAccessRead = new SequenceRandomAccessRead(inputList);
+
+        // test seek, rewind and peek in the first part of the sequence
+        sequenceRandomAccessRead.seek(4);
+        assertEquals(4, sequenceRandomAccessRead.getPosition());
+        assertEquals('4', sequenceRandomAccessRead.read());
+        assertEquals(5, sequenceRandomAccessRead.getPosition());
+        sequenceRandomAccessRead.rewind(1);
+        assertEquals(4, sequenceRandomAccessRead.getPosition());
+        assertEquals('4', sequenceRandomAccessRead.read());
+        assertEquals('5', sequenceRandomAccessRead.peek());
+        assertEquals(5, sequenceRandomAccessRead.getPosition());
+        assertEquals('5', sequenceRandomAccessRead.read());
+        assertEquals(6, sequenceRandomAccessRead.getPosition());
+
+        // test seek, rewind and peek in the second part of the sequence
+        sequenceRandomAccessRead.seek(24);
+        assertEquals(24, sequenceRandomAccessRead.getPosition());
+        assertEquals('e', sequenceRandomAccessRead.read());
+        sequenceRandomAccessRead.rewind(1);
+        assertEquals('e', sequenceRandomAccessRead.read());
+        assertEquals('f', sequenceRandomAccessRead.peek());
+        assertEquals('f', sequenceRandomAccessRead.read());
+
+        sequenceRandomAccessRead.close();
+    }
+
+    @Test
+    public void TestBorderCases() throws IOException
+    {
+        String input1 = "01234567890123456789";
+        RandomAccessReadBuffer randomAccessReadBuffer1 = new RandomAccessReadBuffer(
+                input1.getBytes());
+        String input2 = "abcdefghijklmnopqrst";
+        RandomAccessReadBuffer randomAccessReadBuffer2 = new RandomAccessReadBuffer(
+                input2.getBytes());
+        List<RandomAccessRead> inputList = Arrays.asList(randomAccessReadBuffer1,
+                randomAccessReadBuffer2);
+        SequenceRandomAccessRead sequenceRandomAccessRead = new SequenceRandomAccessRead(inputList);
+
+        // jump to the last byte of the first part of the sequence
+        sequenceRandomAccessRead.seek(19);
+        assertEquals('9', sequenceRandomAccessRead.read());
+        sequenceRandomAccessRead.rewind(1);
+        assertEquals('9', sequenceRandomAccessRead.read());
+        assertEquals('a', sequenceRandomAccessRead.peek());
+        assertEquals('a', sequenceRandomAccessRead.read());
+
+        // jump back to the first sequence
+        sequenceRandomAccessRead.seek(17);
+        byte[] bytesRead = new byte[6];
+        assertEquals(6, sequenceRandomAccessRead.read(bytesRead));
+        assertEquals("789abc", new String(bytesRead));
+        assertEquals(23, sequenceRandomAccessRead.getPosition());
+
+        // rewind back to the first sequence
+        sequenceRandomAccessRead.rewind(6);
+        assertEquals(17, sequenceRandomAccessRead.getPosition());
+        bytesRead = new byte[6];
+        assertEquals(6, sequenceRandomAccessRead.read(bytesRead));
+        assertEquals("789abc", new String(bytesRead));
+
+        // jump to the start of the sequence
+        sequenceRandomAccessRead.seek(0);
+        bytesRead = new byte[6];
+        assertEquals(6, sequenceRandomAccessRead.read(bytesRead));
+        assertEquals("012345", new String(bytesRead));
+
+        sequenceRandomAccessRead.close();
+    }
+
+    @Test
+    public void TestEOF() throws IOException
+    {
+        String input1 = "01234567890123456789";
+        RandomAccessReadBuffer randomAccessReadBuffer1 = new RandomAccessReadBuffer(
+                input1.getBytes());
+        String input2 = "abcdefghijklmnopqrst";
+        RandomAccessReadBuffer randomAccessReadBuffer2 = new RandomAccessReadBuffer(
+                input2.getBytes());
+        List<RandomAccessRead> inputList = Arrays.asList(randomAccessReadBuffer1,
+                randomAccessReadBuffer2);
+        SequenceRandomAccessRead sequenceRandomAccessRead = new SequenceRandomAccessRead(inputList);
+
+        int overallLength = input1.length() + input2.length();
+
+        sequenceRandomAccessRead.seek(overallLength - 1);
+        assertFalse(sequenceRandomAccessRead.isEOF());
+        assertEquals('t', sequenceRandomAccessRead.peek());
+        assertFalse(sequenceRandomAccessRead.isEOF());
+        assertEquals('t', sequenceRandomAccessRead.read());
+        assertTrue(sequenceRandomAccessRead.isEOF());
+        // rewind
+        sequenceRandomAccessRead.rewind(5);
+        assertFalse(sequenceRandomAccessRead.isEOF());
+        byte[] bytesRead = new byte[5];
+        assertEquals(5, sequenceRandomAccessRead.read(bytesRead));
+        assertEquals("pqrst", new String(bytesRead));
+        assertTrue(sequenceRandomAccessRead.isEOF());
+
+        // seek to a position beyond the end of the input
+        sequenceRandomAccessRead.seek(overallLength + 10);
+        assertTrue(sequenceRandomAccessRead.isEOF());
+        assertEquals(overallLength + 10, sequenceRandomAccessRead.getPosition());
+
+        sequenceRandomAccessRead.close();
+
+        try
+        {
+            sequenceRandomAccessRead.read();
+            fail("checkClosed should have thrown an IOException");
+        }
+        catch (IOException e)
+        {
+        }
+    }
+}

Propchange: pdfbox/trunk/pdfbox/src/test/java/org/apache/pdfbox/io/SequenceRandomAccessReadTest.java
------------------------------------------------------------------------------
    svn:eol-style = native