/*
* Carrot2 project.
*
* Copyright (C) 2002-2016, Dawid Weiss, Stanisław Osiński.
* All rights reserved.
*
* Refer to the full license file "carrot2.LICENSE"
* in the root folder of the repository checkout or at:
* http://www.carrot2.org/carrot2.LICENSE
*/
package org.carrot2.util.xsltfilter;
import java.io.*;
import javax.servlet.ServletOutputStream;
/**
* Implementation of a {@link ServletOutputStream} that buffers all the output in memory
* and delegates it to another stream if needed.
*
* @author The original code from <code>CompressionFilter</code> example by Amy Roh and
* Dmitri Valdin.
*/
final class DeferredOutputStream extends ServletOutputStream
{
/**
* If set, this is the stream we delegate to (without buffering).
*/
private OutputStream passthroughStream;
/**
* An exception saved when passing the buffered data to {@link #passthroughStream}.
*
* @see #passthrough(OutputStream)
*/
private IOException nextException;
/**
* If true, this stream is no longer usable (has been closed).
*/
protected boolean closed;
/**
* A buffer for incoming data until we know where to delegate it.
*/
protected ByteArrayOutputStream deferredOutput;
/**
* Construct a servlet output stream associated with the specified Response.
*/
public DeferredOutputStream() throws IOException
{
closed = false;
deferredOutput = new ByteArrayOutputStream();
}
/**
* Returns the bytes written to the deferred stream.
*/
public byte [] getBytes()
{
if (!closed)
{
throw new IllegalStateException(
"Stream must be closed to acquire buffered data.");
}
if (passthroughStream != null)
{
throw new IllegalStateException(
"All buffered data passed to the delegate stream already.");
}
return deferredOutput.toByteArray();
}
/**
* Close this output stream
*/
public void close() throws IOException
{
checkPendingExceptions();
if (this.passthroughStream != null)
{
passthroughStream.close();
}
closed = true;
}
/**
* Flush the stream.
*/
public void flush() throws IOException
{
checkPendingExceptions();
if (this.passthroughStream != null)
{
passthroughStream.flush();
}
}
/**
* Write the specified byte to the delegate stream or buffer it.
*/
public void write(final int b) throws IOException
{
checkPendingExceptions();
if (this.passthroughStream != null)
{
passthroughStream.write(b);
}
else
{
deferredOutput.write(b);
}
}
/**
* Write <code>b.length</code> bytes from the specified byte array to the delegate
* stream or buffer it.
*
* @param b The byte array to be written
*/
public void write(final byte [] b) throws IOException
{
checkPendingExceptions();
if (this.passthroughStream != null)
{
passthroughStream.write(b);
}
else
{
deferredOutput.write(b);
}
}
/**
* Write <code>len</code> bytes from the specified byte array, starting at the
* specified offset, to the delegate stream or buffer it.
*
* @param b The byte array containing the bytes to be written
* @param off Zero-relative starting offset of the bytes to be written
* @param len The number of bytes to be written
*/
public void write(final byte [] b, final int off, final int len) throws IOException
{
checkPendingExceptions();
if (this.passthroughStream != null)
{
passthroughStream.write(b, off, len);
}
else
{
deferredOutput.write(b, off, len);
}
}
/**
* Sets a delegate stream. Any data buffered so far is written to the delegate stream.
* Any future requests on this stream are directly delegated to <code>stream</code>,
* without buffering.
*/
protected final void passthrough(OutputStream stream)
{
final byte [] writtenSoFar = this.deferredOutput.toByteArray();
this.deferredOutput = null;
try
{
stream.write(writtenSoFar);
}
catch (IOException e)
{
this.nextException = e;
}
this.passthroughStream = stream;
}
/**
* Sets an {@link IOException} to be thrown on next call to any stream-access method.
*/
protected void setExceptionOnNext(IOException e)
{
this.nextException = e;
}
/**
* Checks if there are any pending exceptions. If so, throws it.
*
* @throws IOException
*/
private final void checkPendingExceptions() throws IOException
{
if (closed)
{
throw new IOException("Cannot write to a closed output stream");
}
if (nextException != null)
{
throw this.nextException;
}
}
}