Author: lehmi
Date: Sun Jun 21 14:14:15 2015
New Revision: 1686725

URL: http://svn.apache.org/r1686725
Log:
PDFBOX-2301: added new class SequenceRandomAccessRead including some test cases

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

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=1686725&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 Jun 21 14:14:15 2015
@@ -0,0 +1,286 @@
+/*
+ * 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;
+
+/**
+ * A <code>SequenceRandomAccessRead</code> represents the logical 
concatenation of a couple of RandomAccessRead
+ * instances.
+ *
+ */
+public class SequenceRandomAccessRead implements RandomAccessRead
+{
+    private boolean isClosed;
+
+    private final List<? extends RandomAccessRead> source;
+    private final List<Long> sourceLength;
+    private final long bufferLength;
+    private RandomAccessRead currentBuffer;
+    private long currentPosition;
+    private long currentBufferPosition;
+    private long currentBufferLength;
+    private int currentIndex;
+    private int maxIndex;
+
+    /**
+     * Create a read only wrapper for a RandomAccessRead instance.
+     *
+     * @param list a list containing all instances of RandomAccessRead to be 
read.
+     * 
+     * @throws IOException if something went wrong while accessing the given 
list of RandomAccessRead.
+     */
+    public SequenceRandomAccessRead(List<? extends RandomAccessRead> list) 
throws IOException
+    {
+        source = list;
+        maxIndex = list.size();
+        sourceLength = new ArrayList<Long>(maxIndex);
+        long sumLength = 0;
+        for (RandomAccessRead input : list)
+        {
+            long inputLength = input.length();
+            input.seek(0);
+            sourceLength.add(inputLength);
+            sumLength += inputLength;
+        }
+        bufferLength = sumLength;
+        currentBuffer = source.get(0);
+        currentBufferLength = sourceLength.get(0);
+        currentBufferPosition = 0;
+        currentPosition = 0;
+        currentIndex = 0;
+    }
+
+    /**
+     * Ensure that the RandomAccessFile is not closed.
+     * 
+     * @throws IOException if the buffer is already closed
+     */
+    private void checkClosed() throws IOException
+    {
+        if (isClosed)
+        {
+            throw new IOException("RandomAccessFile already closed");
+        }
+    }
+
+    /**
+     * Switch to the next buffer.
+     * 
+     * @return true if another buffer is available
+     * 
+     * @throws IOException if something went wrong when switching to the next 
buffer
+     */
+    private boolean nextBuffer() throws IOException
+    {
+        if (currentIndex < maxIndex)
+        {
+            currentIndex++;
+            switchBuffer(currentIndex, false);
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Switch to buffer with the given index.
+     * 
+     * @param index the index of the buffer to be switched to
+     * @param calculatePosition calculate the new position if set to true
+     * 
+     * @throws IOException if the given index exceeds the available number of 
buffers
+     */
+    private void switchBuffer(int index, boolean calculatePosition) throws 
IOException
+    {
+        currentBuffer = source.get(index);
+        currentBufferLength = sourceLength.get(index);
+        currentBufferPosition = 0;
+        if (calculatePosition)
+        {
+            currentPosition = 0;
+            for (int i = 0; i < index; i++)
+            {
+                currentPosition += sourceLength.get(i);
+            }
+        }
+    }
+
+    /** Returns offset in file at which next byte would be read. */
+    @Override
+    public long getPosition() throws IOException
+    {
+        checkClosed();
+        return currentPosition;
+    }
+
+    /**
+     * Seeks to new position. If new position is outside of current page the 
new page is either taken from cache or read
+     * from file and added to cache.
+     *
+     * @param newPosition the position to seek to.
+     * @throws java.io.IOException if something went wrong.
+     */
+    @Override
+    public void seek(final long newPosition) throws IOException
+    {
+        checkClosed();
+        // new position beyond EOF
+        if (newPosition >= bufferLength)
+        {
+            currentIndex = maxIndex - 1;
+            switchBuffer(currentIndex, false);
+            currentBufferPosition = sourceLength.get(currentIndex);
+            currentPosition = bufferLength;
+        }
+        else
+        {
+            int index = 0;
+            long position = sourceLength.get(index);
+            while (newPosition > position)
+            {
+                position += sourceLength.get(index++);
+            }
+            switchBuffer(index, true);
+            currentBufferPosition = newPosition - currentPosition;
+            currentPosition = newPosition;
+            currentBuffer.seek(currentBufferPosition);
+        }
+    }
+
+    @Override
+    public int read() throws IOException
+    {
+        checkClosed();
+        int returnValue = -1;
+        if (currentPosition < bufferLength)
+        {
+            if (currentBufferPosition < currentBufferLength)
+            {
+                returnValue = currentBuffer.read();
+                currentPosition++;
+                currentBufferPosition++;
+            }
+            else
+            {
+                if (nextBuffer())
+                {
+                    returnValue = currentBuffer.read();
+                    currentPosition++;
+                    currentBufferPosition++;
+                }
+            }
+        }
+        return returnValue;
+    }
+
+    @Override
+    public int read(byte[] b) throws IOException
+    {
+        return read(b, 0, b.length);
+    }
+
+    @Override
+    public int read(byte[] b, int off, int len) throws IOException
+    {
+        checkClosed();
+        int bytesReadTotal = readBytes(b, off, len);
+        int bytesRead = bytesReadTotal;
+        while (bytesReadTotal < len && bytesRead > 0)
+        {
+            bytesRead = read(b, bytesRead, len - bytesRead);
+            bytesReadTotal += bytesRead;
+        }
+        return bytesReadTotal;
+    }
+
+    private int readBytes(byte[] b, int off, int len) throws IOException
+    {
+        // end of current buffer reached?
+        if (currentBufferLength - currentBufferPosition == 0)
+        {
+            nextBuffer();
+        }
+        int bytesRead = currentBuffer.read(b, off, len);
+        currentBufferPosition += bytesRead;
+        currentPosition += bytesRead;
+        return bytesRead;
+    }
+
+    @Override
+    public int available() throws IOException
+    {
+        return (int) Math.min(bufferLength - getPosition(), Integer.MAX_VALUE);
+    }
+
+    @Override
+    public long length() throws IOException
+    {
+        return bufferLength;
+    }
+
+    @Override
+    public void close() throws IOException
+    {
+        // don't close the underlying random access
+        isClosed = true;
+        currentBuffer = null;
+        source.clear();
+    }
+
+    @Override
+    public boolean isClosed()
+    {
+        return isClosed || source == null;
+    }
+
+    @Override
+    public int peek() throws IOException
+    {
+        int result = read();
+        if (result != -1)
+        {
+            rewind(1);
+        }
+        return result;
+    }
+
+    @Override
+    public void rewind(int bytes) throws IOException
+    {
+        seek(getPosition() - bytes);
+    }
+
+    @Override
+    public byte[] readFully(int length) throws IOException
+    {
+        byte[] b = new byte[length];
+        int bytesRead = read(b);
+        while (bytesRead < length)
+        {
+            bytesRead += read(b, bytesRead, length - bytesRead);
+        }
+        return b;
+    }
+
+    @Override
+    public boolean isEOF() throws IOException
+    {
+        return peek() == -1;
+    }
+}

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

Modified: 
pdfbox/trunk/pdfbox/src/test/java/org/apache/pdfbox/io/TestRandomAccessBuffer.java
URL: 
http://svn.apache.org/viewvc/pdfbox/trunk/pdfbox/src/test/java/org/apache/pdfbox/io/TestRandomAccessBuffer.java?rev=1686725&r1=1686724&r2=1686725&view=diff
==============================================================================
--- 
pdfbox/trunk/pdfbox/src/test/java/org/apache/pdfbox/io/TestRandomAccessBuffer.java
 (original)
+++ 
pdfbox/trunk/pdfbox/src/test/java/org/apache/pdfbox/io/TestRandomAccessBuffer.java
 Sun Jun 21 14:14:15 2015
@@ -18,6 +18,7 @@
 package org.apache.pdfbox.io;
 
 import java.io.IOException;
+import java.util.Vector;
 
 import junit.framework.TestCase;
 
@@ -44,7 +45,7 @@ public class TestRandomAccessBuffer exte
         {
             byteArray[CHUNK_SIZE + i] = 1;
         }
-        buffer.write(byteArray, 0, byteArray.length);
+        buffer.write(byteArray);
         buffer.seek(CHUNK_SIZE - 2);
         // read the last bytes of the first chunk
         buffer.read(byteArray, 0, 2);
@@ -66,7 +67,7 @@ public class TestRandomAccessBuffer exte
         {
             byteArray[2*CHUNK_SIZE + i] = 2;
         }
-        buffer.write(byteArray, 0, byteArray.length);
+        buffer.write(byteArray);
         buffer.seek(700);
         byte[] bytesRead = new byte[1348];
         buffer.read(bytesRead, 0, bytesRead.length);
@@ -102,7 +103,7 @@ public class TestRandomAccessBuffer exte
 
     /**
      * Test the {@link RandomAccessBuffer#read(byte[], int, int)} 
-     * and {@link RandomAccessBuffer#write(byte[], int, int)} method.
+     * and {@link RandomAccessBuffer#write(byte[])} method.
      * 
      * @throws IOException is thrown if something went wrong.
      */
@@ -116,7 +117,7 @@ public class TestRandomAccessBuffer exte
         }
         // create an empty buffer and write the array to it
         RandomAccessBuffer buffer = new RandomAccessBuffer();
-        buffer.write(byteArray, 0, byteArray.length);
+        buffer.write(byteArray);
         // jump back to the beginning of the buffer
         buffer.seek(0);
         // read the buffer byte after byte and sum up all figures, 
@@ -143,7 +144,7 @@ public class TestRandomAccessBuffer exte
 
     /**
      * Test the {@link RandomAccessBuffer#read(byte[], int, int)} 
-     * and {@link RandomAccessBuffer#write(byte[], int, int)} method using
+     * and {@link RandomAccessBuffer#write(byte[])} method using
      * a couple of data to create more than one chunk.
      * 
      * @throws IOException is thrown if something went wrong.
@@ -162,7 +163,7 @@ public class TestRandomAccessBuffer exte
         }
         // write the array to a buffer 
         RandomAccessBuffer buffer = new RandomAccessBuffer();
-        buffer.write(byteArray, 0, byteArray.length);
+        buffer.write(byteArray);
         // jump to the beginning
         buffer.seek(0);
         // the first byte should be "0"
@@ -194,7 +195,7 @@ public class TestRandomAccessBuffer exte
         // read the last 5 bytes from the second and the first 5 bytes 
         // from the third chunk and sum them up. The result should be "15"
         byteArray = new byte[10];
-        buffer.read(byteArray,0, byteArray.length);
+        buffer.read(byteArray);
         result = 0;
         for ( int i=0;i < 10;i++ )
         {
@@ -218,7 +219,7 @@ public class TestRandomAccessBuffer exte
         {
             byteArray[i] = 1;
         }
-        buffer.write(byteArray, 0, byteArray.length);
+        buffer.write(byteArray);
         
         // jump to the end-5 of the first chunk
         buffer.seek(CHUNK_SIZE - 5);
@@ -278,15 +279,66 @@ public class TestRandomAccessBuffer exte
         buffer.seek(20);
         // try to read
         assertEquals(-1, buffer.read());
+        // check EOF
+        assertTrue(buffer.isEOF());
         buffer.close();
     }
     
+    public void testSequenceRandomAccessRead() throws IOException
+    {
+        RandomAccessBuffer buffer1 = new RandomAccessBuffer();
+        buffer1.write(new byte[] {1,1,1});
+        RandomAccessBuffer buffer2 = new RandomAccessBuffer();
+        buffer2.write(new byte[] {2,2,2,2});
+        RandomAccessBuffer buffer3 = new RandomAccessBuffer();
+        buffer3.write(new byte[] {3,3,3,3,3});
+        Vector<RandomAccessRead> buffers = new Vector<RandomAccessRead>();
+        buffers.add(buffer1);
+        buffers.add(buffer2);
+        buffers.add(buffer3);
+        // read the whole buffer
+        SequenceRandomAccessRead sequenceBuffer = new 
SequenceRandomAccessRead(buffers);
+        int byteRead = -1;
+        int sum = 0;
+        while((byteRead = sequenceBuffer.read()) > -1)
+        {
+            sum += byteRead;
+        }
+        assertEquals(26, sum);
+        sequenceBuffer.close();
+        buffers = new Vector<RandomAccessRead>();
+        buffers.add(buffer1);
+        buffers.add(buffer2);
+        buffers.add(buffer3);
+        // read parts of the buffer
+        sequenceBuffer = new SequenceRandomAccessRead(buffers);
+        sequenceBuffer.seek(2);
+        byte[] bytesRead = new byte[4];
+        assertEquals(4,sequenceBuffer.read(bytesRead));
+        sum = 0;
+        for (byte element : bytesRead)
+        {
+            sum += element;
+        }
+        assertEquals(7, sum);
+        // seek beyond EOF
+        sequenceBuffer.seek(sequenceBuffer.length()+1);
+        // check isEOF
+        assertTrue(sequenceBuffer.isEOF());
+        // check read
+        assertEquals(-1, sequenceBuffer.read());
+        sequenceBuffer.close();
+        buffer1.close();
+        buffer2.close();
+        buffer3.close();
+    }
+    
     public void testPDFBOX1490() throws Exception
     {
         // create a buffer filled with 1024 * "0" 
         byte[] byteArray = new byte[ CHUNK_SIZE-1];
         RandomAccessBuffer buffer = new RandomAccessBuffer();
-        buffer.write(byteArray,0, byteArray.length);
+        buffer.write(byteArray);
         // fill the first buffer until the end
         buffer.write(0);
         // seek the current == last position in the first buffer chunk


Reply via email to