package eu.europeana.cloud.service.mcs.rest.storage.selector; import java.io.BufferedInputStream; import java.io.IOException; import java.io.InputStream; /** * InputStream with eager pre buffer initial number of bits. * * @author krystian. */ public class PreBufferedInputStream extends BufferedInputStream { private static final int DEFAULT_CHUNK_SIZE = 1024; private final int bufferSize; private byte[] buffer; private int bufferFill; private int position; public static PreBufferedInputStream wrap(final InputStream inputStream, final int preloadChunkSize){ return new PreBufferedInputStream(inputStream, preloadChunkSize); } /** * Creates {@link PreBufferedInputStream} and load data in to internal buffer. * @param stream input stream * @param bufferSize size of buffer */ public PreBufferedInputStream(InputStream stream, int bufferSize) { super(stream, DEFAULT_CHUNK_SIZE); this.bufferSize = bufferSize; this.buffer = new byte[bufferSize]; try { fillBuffer(); } catch (IOException e) { throw new ExceptionInInitializerError(e); } } /** * Get internal buffer size. * * @return buffer size */ public int getBufferSize() { return bufferSize; } private void fillBuffer() throws IOException { ensureIsNotClosed(); for (int i = 0; i < bufferSize; i += DEFAULT_CHUNK_SIZE) { int bufferLength = Math.min(bufferSize - bufferFill, DEFAULT_CHUNK_SIZE); byte[] bytes = new byte[bufferLength]; final int length = super.read(bytes, 0, bufferLength); if (length != -1) { System.arraycopy(bytes, 0, this.buffer, bufferFill, length); bufferFill += length; } } } /** * {@inheritDoc} */ @Override public int read() throws IOException { ensureIsNotClosed(); if (position < bufferFill) { return buffer[position++] & 0xff; } return super.read(); } /** * {@inheritDoc} */ @Override public int read(final byte[] b, final int offset, final int length) throws IOException { ensureIsNotClosed(); if (b == null) { throw new NullPointerException(); } else if (offset < 0 || length < 0 || length > b.length - offset) { throw new IndexOutOfBoundsException(); } else if (length == 0) { return 0; } int result ; result = readFromBufferAndStream(b, offset, length); return result; } private int readFromBufferAndStream(final byte[] b, int off, int len) throws IOException { int offset = off; int length = len; int available = bufferFill - position; if (available > 0) { //read pre-loaded bytes from internal buffer if (length < available) { available = length; } System.arraycopy(buffer, position, b, offset, available); position += available; offset += available; length -= available; } if (length > 0) { //read from stream length = super.read(b, offset, length); if (length == -1) { return available == 0 ? -1 : available; } else { return available + length; } } return available; } /** * {@inheritDoc} */ @Override public int available() throws IOException { ensureIsNotClosed(); int inBufferAvailable = bufferFill - position; int avail = super.available(); return inBufferAvailable > (Integer.MAX_VALUE - avail) ? Integer.MAX_VALUE : inBufferAvailable + avail; } /** * {@inheritDoc} */ @Override public boolean markSupported() { return true; } /** * {@inheritDoc} * * </br> * Marking is available only in range of internal buffer {@link PreBufferedInputStream#getBufferSize()}. */ @Override public synchronized void mark(int readLimit) { if (readLimit > bufferSize) throw new UnsupportedOperationException("Marking outside buffer is not supported!"); marklimit = readLimit; markpos = position; } /** * {@inheritDoc} */ @Override public synchronized void reset() throws IOException { ensureIsNotClosed(); if (markpos < 0) throw new IOException("Resetting to invalid mark"); position = markpos; } /** * {@inheritDoc} */ @Override public synchronized void close() throws IOException { if (in == null && buffer == null) return; buffer = null; in.close(); in = null; } /** * {@inheritDoc} */ @Override public long skip(final long length) throws IOException { ensureIsNotClosed(); long skip = length; if (skip <= 0) { return 0; } long actualSkip = (long) bufferFill - position; if (actualSkip > 0) { if (skip < actualSkip) { actualSkip = skip; } position += actualSkip; skip -= actualSkip; } if (skip > 0) { long currentSkip; do { currentSkip = super.skip(skip); actualSkip += currentSkip; skip -= currentSkip; } while (currentSkip > 0 && skip > 0); } return actualSkip; } private void ensureIsNotClosed() throws IOException { if (in == null) throw new IOException("Stream closed"); } }