package com.tom_roush.pdfbox.io; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.List; import static java.lang.System.arraycopy; /** * An implementation of the RandomAccess interface to store data in memory. * The data will be stored in chunks organized in an ArrayList. * */ public class RandomAccessBuffer implements RandomAccess, Cloneable { // default chunk size is 1kb private static final int DEFAULT_CHUNK_SIZE = 1024; // use the default chunk size private int chunkSize = DEFAULT_CHUNK_SIZE; // list containing all chunks private List<byte[]> bufferList = null; // current chunk private byte[] currentBuffer; // current pointer to the whole buffer private long pointer; // current pointer for the current chunk private int currentBufferPointer; // size of the whole buffer private long size; // current chunk list index private int bufferListIndex; // maximum chunk list index private int bufferListMaxIndex; /** * Default constructor. */ public RandomAccessBuffer() { // starting with one chunk bufferList = new ArrayList<byte[]>(); currentBuffer = new byte[chunkSize]; bufferList.add(currentBuffer); pointer = 0; currentBufferPointer = 0; size = 0; bufferListIndex = 0; bufferListMaxIndex = 0; } /** * Create a random access buffer using the given byte array. * * @param input the byte array to be read */ public RandomAccessBuffer(byte[] input) { // this is a special case. The given byte array is used as the one // and only chunk. bufferList = new ArrayList<byte[]>(1); chunkSize = input.length; currentBuffer = input; bufferList.add(currentBuffer); pointer = 0; currentBufferPointer = 0; size = chunkSize; bufferListIndex = 0; bufferListMaxIndex = 0; } /** * Create a random access buffer of the given input stream by copying the data. * * @param input the input stream to be read * @throws IOException if something went wrong while copying the data */ public RandomAccessBuffer(InputStream input) throws IOException { this(); byte[] byteBuffer = new byte[8192]; int bytesRead = 0; while ((bytesRead = input.read(byteBuffer)) > -1) { write(byteBuffer, 0, bytesRead); } seek(0); } @Override public RandomAccessBuffer clone() { RandomAccessBuffer copy = new RandomAccessBuffer(); copy.bufferList = new ArrayList<byte[]>(bufferList.size()); for (byte [] buffer : bufferList) { byte [] newBuffer = new byte [buffer.length]; arraycopy(buffer, 0, newBuffer, 0, buffer.length); copy.bufferList.add(newBuffer); } if (currentBuffer!=null) { copy.currentBuffer = copy.bufferList.get(copy.bufferList.size()-1); } else { copy.currentBuffer = null; } copy.pointer = pointer; copy.currentBufferPointer = currentBufferPointer; copy.size = size; copy.bufferListIndex = bufferListIndex; copy.bufferListMaxIndex = bufferListMaxIndex; return copy; } /** * {@inheritDoc} */ @Override public void close() throws IOException { currentBuffer = null; bufferList.clear(); pointer = 0; currentBufferPointer = 0; size = 0; bufferListIndex = 0; } /** * {@inheritDoc} */ @Override public void clear() { bufferList.clear(); currentBuffer = new byte[chunkSize]; bufferList.add(currentBuffer); pointer = 0; currentBufferPointer = 0; size = 0; bufferListIndex = 0; bufferListMaxIndex = 0; } /** * {@inheritDoc} */ @Override public void seek(long position) throws IOException { checkClosed(); if (position < 0) { throw new IOException("Invalid position " + position); } pointer = position; if (pointer < size) { // calculate the chunk list index bufferListIndex = (int) (pointer / chunkSize); currentBufferPointer = (int) (pointer % chunkSize); currentBuffer = bufferList.get(bufferListIndex); } else { // it is allowed to jump beyond the end of the file // jump to the end of the buffer bufferListIndex = bufferListMaxIndex; currentBuffer = bufferList.get(bufferListIndex); currentBufferPointer = (int) (size % chunkSize); } } /** * {@inheritDoc} */ @Override public long getPosition() throws IOException { checkClosed(); return pointer; } /** * {@inheritDoc} */ @Override public int read() throws IOException { checkClosed(); if (pointer >= this.size) { return -1; } if (currentBufferPointer >= chunkSize) { if (bufferListIndex >= bufferListMaxIndex) { return -1; } else { currentBuffer = bufferList.get(++bufferListIndex); currentBufferPointer = 0; } } pointer++; return currentBuffer[currentBufferPointer++] & 0xff; } /** * {@inheritDoc} */ @Override public int read(byte[] b, int offset, int length) throws IOException { checkClosed(); if (pointer >= size) { return 0; } int bytesRead = readRemainingBytes(b, offset, length); while (bytesRead < length && available() > 0) { bytesRead += readRemainingBytes(b, offset + bytesRead, length - bytesRead); if (currentBufferPointer == chunkSize) { nextBuffer(); } } return bytesRead; } private int readRemainingBytes(byte[] b, int offset, int length) throws IOException { if (pointer >= size) { return 0; } int maxLength = (int) Math.min(length, size - pointer); int remainingBytes = chunkSize - currentBufferPointer; // no more bytes left if (remainingBytes == 0) { return 0; } if (maxLength >= remainingBytes) { // copy the remaining bytes from the current buffer arraycopy(currentBuffer, currentBufferPointer, b, offset, remainingBytes); // end of file reached currentBufferPointer += remainingBytes; pointer += remainingBytes; return remainingBytes; } else { // copy the remaining bytes from the whole buffer arraycopy(currentBuffer, currentBufferPointer, b, offset, maxLength); // end of file reached currentBufferPointer += maxLength; pointer += maxLength; return maxLength; } } /** * {@inheritDoc} */ @Override public long length() throws IOException { checkClosed(); return size; } /** * {@inheritDoc} */ @Override public void write(int b) throws IOException { checkClosed(); // end of buffer reached? if (currentBufferPointer >= chunkSize) { if (pointer + chunkSize >= Integer.MAX_VALUE) { throw new IOException("RandomAccessBuffer overflow"); } expandBuffer(); } currentBuffer[currentBufferPointer++] = (byte) b; pointer++; if (pointer > this.size) { this.size = pointer; } // end of buffer reached now? if (currentBufferPointer >= chunkSize) { if (pointer + chunkSize >= Integer.MAX_VALUE) { throw new IOException("RandomAccessBuffer overflow"); } expandBuffer(); } } /** * {@inheritDoc} */ @Override public void write(byte[] b, int offset, int length) throws IOException { checkClosed(); long newSize = pointer + length; int remainingBytes = chunkSize - currentBufferPointer; if (length >= remainingBytes) { if (newSize > Integer.MAX_VALUE) { throw new IOException("RandomAccessBuffer overflow"); } // copy the first bytes to the current buffer System.arraycopy(b, offset, currentBuffer, currentBufferPointer, remainingBytes); int newOffset = offset + remainingBytes; long remainingBytes2Write = length - remainingBytes; // determine how many buffers are needed for the remaining bytes int numberOfNewArrays = (int) remainingBytes2Write / chunkSize; for (int i=0;i<numberOfNewArrays;i++) { expandBuffer(); System.arraycopy(b, newOffset, currentBuffer, currentBufferPointer, chunkSize); newOffset += chunkSize; } // are there still some bytes to be written? remainingBytes2Write -= numberOfNewArrays * (long) chunkSize; if (remainingBytes2Write >= 0) { expandBuffer(); if (remainingBytes2Write > 0) { System.arraycopy(b, newOffset, currentBuffer, currentBufferPointer, (int) remainingBytes2Write); } currentBufferPointer = (int) remainingBytes2Write; } } else { arraycopy(b, offset, currentBuffer, (int) currentBufferPointer, length); currentBufferPointer += length; } pointer += length; if (pointer > this.size) { this.size = pointer; } } /** * create a new buffer chunk and adjust all pointers and indices. */ private void expandBuffer() throws IOException { if (bufferListMaxIndex > bufferListIndex) { // there is already an existing chunk nextBuffer(); } else { // create a new chunk and add it to the buffer currentBuffer = new byte[chunkSize]; bufferList.add(currentBuffer); currentBufferPointer = 0; bufferListMaxIndex++; bufferListIndex++; } } /** * switch to the next buffer chunk and reset the buffer pointer. */ private void nextBuffer() throws IOException { if (bufferListIndex == bufferListMaxIndex) { throw new IOException("No more chunks available, end of buffer reached"); } currentBufferPointer = 0; currentBuffer = bufferList.get(++bufferListIndex); } /** * Ensure that the RandomAccessBuffer is not closed * @throws IOException */ private void checkClosed () throws IOException { if (currentBuffer==null) { // consider that the rab is closed if there is no current buffer throw new IOException("RandomAccessBuffer already closed"); } } /** * {@inheritDoc} */ @Override public void write(byte[] b) throws IOException { write(b, 0, b.length); } /** * {@inheritDoc} */ @Override public boolean isClosed() { return currentBuffer == null; } /** * {@inheritDoc} */ @Override public boolean isEOF() throws IOException { checkClosed(); return pointer >= size; } /** * {@inheritDoc} */ @Override public int available() throws IOException { return (int) Math.min(length() - getPosition(), Integer.MAX_VALUE); } /** * {@inheritDoc} */ @Override public int peek() throws IOException { int result = read(); if (result != -1) { rewind(1); } return result; } /** * {@inheritDoc} */ @Override public void rewind(int bytes) throws IOException { checkClosed(); seek(getPosition() - bytes); } /** * {@inheritDoc} */ @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; } /** * {@inheritDoc} */ @Override public int read(byte[] b) throws IOException { return read(b, 0, b.length); } }