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