Java tutorial
/* * Copyright (c) 2010-2011, Martijn Brinkers, Djigzo. * * This file is part of Djigzo email encryption. * * Djigzo is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License * version 3, 19 November 2007 as published by the Free Software * Foundation. * * Djigzo is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public * License along with Djigzo. If not, see <http://www.gnu.org/licenses/> * * Additional permission under GNU AGPL version 3 section 7 * * If you modify this Program, or any covered work, by linking or * combining it with aspectjrt.jar, aspectjweaver.jar, tyrex-1.0.3.jar, * freemarker.jar, dom4j.jar, mx4j-jmx.jar, mx4j-tools.jar, * spice-classman-1.0.jar, spice-loggerstore-0.5.jar, spice-salt-0.8.jar, * spice-xmlpolicy-1.0.jar, saaj-api-1.3.jar, saaj-impl-1.3.jar, * wsdl4j-1.6.1.jar (or modified versions of these libraries), * containing parts covered by the terms of Eclipse Public License, * tyrex license, freemarker license, dom4j license, mx4j license, * Spice Software License, Common Development and Distribution License * (CDDL), Common Public License (CPL) the licensors of this Program grant * you additional permission to convey the resulting work. */ package mitm.common.util; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.RandomAccessFile; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; /** * An InputStream that wraps another InputStream and which buffers all bytes read from the wrapped stream. The * buffered bytes are stored in memory if the number of bytes read does not exceed the threshold. If the threshold * is reached, the bytes will be stored in a temporary file. * * Note: the internal memory buffer grows exponentially. It's therefore advised to set the threshold to * an element of the exponential growth set: * * 8192, 16384, 32768, 65536, 131072, 262144, 524288, 1048576, .... (i.e. 4096 * 2^x, where 4096 is the * buffer size) * * * @author Martijn Brinkers * */ public class RewindableInputStream extends InputStream { protected final static int INITIAL_BUF_SIZE = 8192; /* * Threshold at which a buffer will be stored on disk */ private final int threshold; /* * The input to delegate to */ private final InputStream delegate; /* * Is used for storing the input when the threshold has not been reached. */ private DirectAccessByteArrayOutputStream byteBuffer = new DirectAccessByteArrayOutputStream(INITIAL_BUF_SIZE); /* * Is used for storing the input when the threshold is reached. */ private RandomAccessFile randomAccessFile; /* * Is used to buffer writing to the RandomAccessFile to speed-up the writing */ private BufferedOutputStream fileWriteBuffer; /* * Is used to buffer reading from the RandomAccessFile to speed-up reading */ private BufferedInputStream fileReadBuffer; /* * The temporary file used for storing the data in when threshold is reached. */ private File file; /* * The total number of bytes read */ private long count; /* * The current position */ private long position; /* * The marked position */ private long markPoint; /* * True if this stream is closed */ private boolean closed; /** * Creates an instance of RewindableInputStream. */ public RewindableInputStream(InputStream delegate, int threshold) { Check.notNull(delegate, "delegate"); if (threshold < 0) { threshold = 0; } this.threshold = threshold; this.delegate = delegate; } private void checkClosed() throws IOException { if (closed) { throw new IOException("The InputStream is closed."); } } private void storeInBuffer(byte b) throws IOException { if (byteBuffer != null) { byteBuffer.write(b); if (byteBuffer.size() > threshold) { /* * Threshold exceeded. Create a temp file and */ file = File.createTempFile(FileConstants.TEMP_FILE_PREFIX, null); randomAccessFile = new RandomAccessFile(file, "rw"); /* * wrap the randomAccessFile into a buffer to speedup the writing */ fileWriteBuffer = new BufferedOutputStream(new RandomAccessFileOutputStream(randomAccessFile)); /* * Copy the existing buffer and make buffer null. */ fileWriteBuffer.write(byteBuffer.getBuffer(), 0, byteBuffer.size()); byteBuffer = null; } } else { fileWriteBuffer.write(b); } } private int readFromBuffer() throws IOException { int b; if (byteBuffer != null) { b = byteBuffer.getBuffer()[(int) position] & 0xFF; } else { b = fileReadBuffer.read(); } if (b == -1) { throw new IOException("Unexpected EOF from buffer."); } position++; return b; } @Override public int read() throws IOException { checkClosed(); int b; if (position == count) { b = delegate.read(); if (b != -1) { storeInBuffer((byte) b); count++; position++; } } else { /* * Read from buffer */ b = readFromBuffer(); } return b; } @Override public void close() throws IOException { closed = true; IOUtils.closeQuietly(fileWriteBuffer); FileUtils.deleteQuietly(file); delegate.close(); } @Override public boolean markSupported() { return true; } @Override public void mark(int i) { markPoint = position; } @Override public void reset() throws IOException { checkClosed(); setPosition(markPoint); } @Override public int available() throws IOException { checkClosed(); return (int) (delegate.available() + (count - position)); } public void setPosition(long position) throws IOException { if (position > count) { throw new IllegalArgumentException("position cannot be larger than count."); } this.position = position; if (fileWriteBuffer != null) { /* * We must flush the buffer to be certain the buffer content is * written to the randomAccessFile. */ fileWriteBuffer.flush(); } if (randomAccessFile != null) { randomAccessFile.seek(this.position); fileReadBuffer = new BufferedInputStream(new RandomAccessFileInputStream(randomAccessFile)); } } public long getPosition() { return position; } public long getCount() { return count; } public boolean isClosed() { return closed; } protected File getFile() { return file; } protected DirectAccessByteArrayOutputStream getByteBuffer() { return byteBuffer; } }