package com.bradmcevoy.io;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import org.apache.commons.io.output.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* An output stream which will buffer data, initially using memory up to
* maxMemorySize, and then overflowing to a temporary file.
*
* To use this class you will write to it, and then close it, and then
* call getInputStream to read the data.
*
* The temporary file, if it was created, will be deleted when the inputstream
* is closed.
*
* @author brad
*/
public class BufferingOutputStream extends OutputStream{
private static Logger log = LoggerFactory.getLogger(BufferingOutputStream.class);
private ByteArrayOutputStream tempMemoryBuffer = new ByteArrayOutputStream();
private int maxMemorySize;
private File tempFile;
private FileOutputStream fout;
private BufferedOutputStream bufOut;
private Runnable runnable;
private long size;
private boolean closed;
public BufferingOutputStream( int maxMemorySize ) {
this.maxMemorySize = maxMemorySize;
}
public InputStream getInputStream() {
if( !closed ) throw new IllegalStateException( "this output stream is not yet closed");
if( tempMemoryBuffer == null ) {
FileDeletingInputStream fin;
try {
fin = new FileDeletingInputStream( tempFile );
} catch( FileNotFoundException ex ) {
throw new RuntimeException( tempFile.getAbsolutePath(), ex );
}
BufferedInputStream bufIn = new BufferedInputStream( fin );
return bufIn;
} else {
return new ByteArrayInputStream( tempMemoryBuffer.toByteArray());
}
}
@Override
public void write( byte[] b ) throws IOException {
size += b.length;
if( tempMemoryBuffer != null ) {
tempMemoryBuffer.write( b );
} else {
bufOut.write( b );
}
checkSize();
}
@Override
public void write( int b ) throws IOException {
size++;
if( tempMemoryBuffer != null ) {
tempMemoryBuffer.write( b );
} else {
bufOut.write( b );
}
checkSize();
}
@Override
public void write( byte[] b, int off, int len ) throws IOException {
size+=len;
if( tempMemoryBuffer != null ) {
tempMemoryBuffer.write( b,off, len );
} else {
bufOut.write( b, off, len );
}
checkSize();
}
private void checkSize() throws IOException {
if( tempMemoryBuffer == null ) return ;
if( tempMemoryBuffer.size() < maxMemorySize ) return ;
tempFile = File.createTempFile( "" + System.currentTimeMillis(), ".buffer");
fout = new FileOutputStream( tempFile );
bufOut = new BufferedOutputStream( fout );
bufOut.write( tempMemoryBuffer.toByteArray());
tempMemoryBuffer = null;
}
@Override
public void flush() throws IOException {
if( tempMemoryBuffer != null ) {
tempMemoryBuffer.flush();
} else {
bufOut.flush();
fout.flush();
}
}
@Override
public void close() throws IOException {
if( tempMemoryBuffer != null ) {
tempMemoryBuffer.close();
} else {
bufOut.close();
fout.close();
}
closed = true;
if( runnable != null ) {
runnable.run();
}
}
public long getSize() {
return size;
}
File getTempFile() {
return tempFile;
}
ByteArrayOutputStream getTempMemoryBuffer() {
return tempMemoryBuffer;
}
public void setOnClose(Runnable r) {
this.runnable = r;
}
/**
* returns true if the data is completely held in memory
*
* @return
*/
public boolean isCompleteInMemory() {
return tempFile == null;
}
/**
* Gets the data currently held in memory
*
* @return
*/
public byte[] getInMemoryData() {
return this.tempMemoryBuffer.toByteArray();
}
@Override
protected void finalize() throws Throwable {
if( tempFile != null && tempFile.exists() ) {
log.error("temporary file was not deleted. Was close called on the inputstream? Will attempt to delete");
if( !tempFile.delete()) {
log.error("Still couldnt delete temporary file: " + tempFile.getAbsolutePath());
}
}
super.finalize();
}
}