X-Git-Url: http://git.nikiroo.be/?p=fanfix.git;a=blobdiff_plain;f=src%2Fbe%2Fnikiroo%2Futils%2Fstreams%2FBufferedOutputStream.java;fp=src%2Fbe%2Fnikiroo%2Futils%2Fstreams%2FBufferedOutputStream.java;h=14425345c179196d8944d3c391b60393c28db9d5;hp=0000000000000000000000000000000000000000;hb=d46b7b96f94e88a776bcd2dfd756549ffb300cc9;hpb=c9994f27667bc421bcd448d39e55774fddf5c431 diff --git a/src/be/nikiroo/utils/streams/BufferedOutputStream.java b/src/be/nikiroo/utils/streams/BufferedOutputStream.java new file mode 100644 index 0000000..1442534 --- /dev/null +++ b/src/be/nikiroo/utils/streams/BufferedOutputStream.java @@ -0,0 +1,260 @@ +package be.nikiroo.utils.streams; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * A simple {@link OutputStream} that is buffered with a bytes array. + *

+ * It is mostly intended to be used as a base class to create new + * {@link OutputStream}s with special operation modes, and to give some default + * methods. + * + * @author niki + */ +public class BufferedOutputStream extends OutputStream { + /** The current position in the buffer. */ + protected int start; + /** The index of the last usable position of the buffer. */ + protected int stop; + /** The buffer itself. */ + protected byte[] buffer; + /** An End-Of-File (or buffer, here) marker. */ + protected boolean eof; + /** The actual under-laying stream. */ + protected OutputStream out; + /** The number of bytes written to the under-laying stream. */ + protected long bytesWritten; + /** + * Can bypass the flush process for big writes (will directly write to the + * under-laying buffer if the array to write is > the internal buffer + * size). + *

+ * By default, this is true. + */ + protected boolean bypassFlush = true; + + private boolean closed; + private int openCounter; + private byte[] b1; + + /** + * Create a new {@link BufferedInputStream} that wraps the given + * {@link InputStream}. + * + * @param out + * the {@link OutputStream} to wrap + */ + public BufferedOutputStream(OutputStream out) { + this.out = out; + + this.buffer = new byte[4096]; + this.b1 = new byte[1]; + this.start = 0; + this.stop = 0; + } + + @Override + public void write(int b) throws IOException { + b1[0] = (byte) b; + write(b1, 0, 1); + } + + @Override + public void write(byte[] b) throws IOException { + write(b, 0, b.length); + } + + @Override + public void write(byte[] source, int sourceOffset, int sourceLength) + throws IOException { + + checkClose(); + + if (source == null) { + throw new NullPointerException(); + } else if ((sourceOffset < 0) || (sourceOffset > source.length) + || (sourceLength < 0) + || ((sourceOffset + sourceLength) > source.length) + || ((sourceOffset + sourceLength) < 0)) { + throw new IndexOutOfBoundsException(); + } else if (sourceLength == 0) { + return; + } + + if (bypassFlush && sourceLength >= buffer.length) { + /* + * If the request length exceeds the size of the output buffer, + * flush the output buffer and then write the data directly. In this + * way buffered streams will cascade harmlessly. + */ + flush(false); + out.write(source, sourceOffset, sourceLength); + bytesWritten += (sourceLength - sourceOffset); + return; + } + + int done = 0; + while (done < sourceLength) { + if (available() <= 0) { + flush(false); + } + + int now = Math.min(sourceLength - done, available()); + System.arraycopy(source, sourceOffset + done, buffer, stop, now); + stop += now; + done += now; + } + } + + /** + * The available space in the buffer. + * + * @return the space in bytes + */ + private int available() { + if (closed) { + return 0; + } + + return Math.max(0, buffer.length - stop - 1); + } + + /** + * The number of bytes written to the under-laying {@link OutputStream}. + * + * @return the number of bytes + */ + public long getBytesWritten() { + return bytesWritten; + } + + /** + * Return this very same {@link BufferedInputStream}, but keep a counter of + * how many streams were open this way. When calling + * {@link BufferedInputStream#close()}, decrease this counter if it is not + * already zero instead of actually closing the stream. + *

+ * You are now responsible for it — you must close it. + *

+ * This method allows you to use a wrapping stream around this one and still + * close the wrapping stream. + * + * @return the same stream, but you are now responsible for closing it + * + * @throws IOException + * in case of I/O error or if the stream is closed + */ + public synchronized OutputStream open() throws IOException { + checkClose(); + openCounter++; + return this; + } + + /** + * Check that the stream was not closed, and throw an {@link IOException} if + * it was. + * + * @throws IOException + * if it was closed + */ + protected void checkClose() throws IOException { + if (closed) { + throw new IOException( + "This BufferedInputStream was closed, you cannot use it anymore."); + } + } + + @Override + public void flush() throws IOException { + flush(true); + } + + /** + * Flush the {@link BufferedOutputStream}, write the current buffered data + * to (and optionally also flush) the under-laying stream. + *

+ * If {@link BufferedOutputStream#bypassFlush} is false, all writes to the + * under-laying stream are done in this method. + *

+ * This can be used if you want to write some data in the under-laying + * stream yourself (in that case, flush this {@link BufferedOutputStream} + * with or without flushing the under-laying stream, then you can write to + * the under-laying stream). + * + * @param includingSubStream + * also flush the under-laying stream + * @throws IOException + * in case of I/O error + */ + public void flush(boolean includingSubStream) throws IOException { + if (stop > start) { + out.write(buffer, start, stop - start); + bytesWritten += (stop - start); + } + start = 0; + stop = 0; + + if (includingSubStream) { + out.flush(); + } + } + + /** + * Closes this stream and releases any system resources associated with the + * stream. + *

+ * Including the under-laying {@link InputStream}. + *

+ * Note: if you called the {@link BufferedInputStream#open()} method + * prior to this one, it will just decrease the internal count of how many + * open streams it held and do nothing else. The stream will actually be + * closed when you have called {@link BufferedInputStream#close()} once more + * than {@link BufferedInputStream#open()}. + * + * @exception IOException + * in case of I/O error + */ + @Override + public synchronized void close() throws IOException { + close(true); + } + + /** + * Closes this stream and releases any system resources associated with the + * stream. + *

+ * Including the under-laying {@link InputStream} if + * incudingSubStream is true. + *

+ * You can call this method multiple times, it will not cause an + * {@link IOException} for subsequent calls. + *

+ * Note: if you called the {@link BufferedInputStream#open()} method + * prior to this one, it will just decrease the internal count of how many + * open streams it held and do nothing else. The stream will actually be + * closed when you have called {@link BufferedInputStream#close()} once more + * than {@link BufferedInputStream#open()}. + * + * @param includingSubStream + * also close the under-laying stream + * + * @exception IOException + * in case of I/O error + */ + public synchronized void close(boolean includingSubStream) + throws IOException { + if (!closed) { + if (openCounter > 0) { + openCounter--; + } else { + closed = true; + flush(includingSubStream); + if (includingSubStream && out != null) { + out.close(); + } + } + } + } +}