*/
public class BufferedInputStream extends InputStream {
/** The current position in the buffer. */
- protected int pos;
+ protected int start;
/** The index of the last usable position of the buffer. */
- protected int len;
+ protected int stop;
/** The buffer itself. */
protected byte[] buffer;
/** An End-Of-File (or buffer, here) marker. */
this.buffer = new byte[4096];
this.originalBuffer = this.buffer;
- this.pos = 0;
- this.len = 0;
+ this.start = 0;
+ this.stop = 0;
}
/**
this.buffer = in;
this.originalBuffer = this.buffer;
- this.pos = offset;
- this.len = length;
+ this.start = offset;
+ this.stop = length;
}
/**
if (available() >= search.length) {
// Easy path
- return startsWith(search, buffer, pos, len);
+ return startsWith(search, buffer, start, stop);
} else if (!eof) {
// Harder path
if (buffer2 == null && buffer.length == originalBuffer.length) {
* @return TRUE if it is
*/
public boolean eof() {
- return closed || (len < 0 && !hasMoreData());
+ return closed || (stop < 0 && !hasMoreData());
}
@Override
return -1;
}
- return buffer[pos++];
+ return buffer[start++];
}
@Override
while (hasMoreData() && done < blen) {
preRead();
if (hasMoreData()) {
- int now = Math.min(blen, len) - pos;
+ int now = Math.min(blen, stop) - start;
if (now > 0) {
- System.arraycopy(buffer, pos, b, boff + done, now);
- pos += now;
+ System.arraycopy(buffer, start, b, boff + done, now);
+ start += now;
done += now;
}
}
preRead();
long inBuffer = Math.min(n, available());
- pos += inBuffer;
+ start += inBuffer;
n -= inBuffer;
skipped += inBuffer;
}
return 0;
}
- return Math.max(0, len - pos);
+ return Math.max(0, stop - start);
}
/**
*/
protected boolean preRead() throws IOException {
boolean hasRead = false;
- if (!eof && in != null && pos >= len) {
- pos = 0;
+ if (!eof && in != null && start >= stop) {
+ start = 0;
if (buffer2 != null) {
buffer = buffer2;
- pos = pos2;
- len = len2;
+ start = pos2;
+ stop = len2;
buffer2 = null;
pos2 = 0;
} else {
buffer = originalBuffer;
- len = read(in, buffer);
- if (len > 0) {
- bytesRead += len;
+ stop = read(in, buffer);
+ if (stop > 0) {
+ bytesRead += stop;
}
}
hasRead = true;
}
- if (pos >= len) {
+ if (start >= stop) {
eof = true;
}
* @return TRUE if it is the case, FALSE if not
*/
protected boolean hasMoreData() {
- return !closed && !(eof && pos >= len);
+ return !closed && !(eof && start >= stop);
}
/**
--- /dev/null
+package be.nikiroo.utils;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * A simple {@link OutputStream} that is buffered with a bytes array.
+ * <p>
+ * 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;
+
+ private boolean closed;
+ private OutputStream out;
+ private int openCounter;
+
+ private long bytesWritten;
+
+ /**
+ * 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.start = 0;
+ this.stop = 0;
+ }
+
+ @Override
+ public void write(int b) throws IOException {
+ checkClose();
+
+ if (available() <= 0) {
+ flush(false);
+ }
+
+ buffer[start++] = (byte) b;
+ }
+
+ @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 (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);
+ return;
+ }
+
+ int done = 0;
+ while (done < sourceLength) {
+ if (available() <= 0) {
+ flush(false);
+ }
+
+ int now = Math.min(sourceLength, 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.
+ * <p>
+ * You are now responsible for it — you <b>must</b> close it.
+ * <p>
+ * 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}, and optionally the under-laying
+ * stream, too.
+ *
+ * @param includingSubStream
+ * also flush the under-laying stream
+ * @throws IOException
+ * in case of I/O error
+ */
+ private void flush(boolean includingSubStream) throws IOException {
+ 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.
+ * <p>
+ * Including the under-laying {@link InputStream}.
+ * <p>
+ * <b>Note:</b> 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.
+ * <p>
+ * Including the under-laying {@link InputStream} if
+ * <tt>incudingSubStream</tt> is true.
+ * <p>
+ * You can call this method multiple times, it will not cause an
+ * {@link IOException} for subsequent calls.
+ * <p>
+ * <b>Note:</b> 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(true);
+ if (includingSubStream && out != null) {
+ out.close();
+ }
+ }
+ }
+ }
+}
return bufferChanged;
}
- if (pos >= len) {
+ if (start >= stop) {
eof = true;
}
/**
* Check that the buffer didn't overshot to the next item, and fix
- * {@link NextableInputStream#len} if needed.
+ * {@link NextableInputStream#stop} if needed.
* <p>
- * If {@link NextableInputStream#len} is fixed,
+ * If {@link NextableInputStream#stop} is fixed,
* {@link NextableInputStream#eof} and {@link NextableInputStream#stopped}
* are set to TRUE.
*
* the {@link NextableInputStreamStep}
*/
private void checkBuffer(boolean newBuffer) {
- if (step != null && len > 0) {
+ if (step != null && stop > 0) {
if (newBuffer) {
step.clearBuffer();
}
- int stopAt = step.stop(buffer, pos, len);
+ int stopAt = step.stop(buffer, start, stop);
if (stopAt >= 0) {
- len = stopAt;
+ stop = stopAt;
eof = true;
stopped = true;
}
}
if (step != null && !hasMoreData() && stopped) {
- len = step.getResumeLen();
- pos += step.getResumeSkip();
+ stop = step.getResumeLen();
+ start += step.getResumeSkip();
eof = false;
if (all) {
import be.nikiroo.utils.test.TestCase;
import be.nikiroo.utils.test.TestLauncher;
-public class BufferedInputStreamTest extends TestLauncher {
+class BufferedInputStreamTest extends TestLauncher {
public BufferedInputStreamTest(String[] args) {
super("BufferedInputStream test", args);
+ " resulting array has not the correct number of items",
expected.length, actual.length);
for (int i = 0; i < actual.length; i++) {
- test.assertEquals("Item " + i + " (0-based) is not the same",
- expected[i], actual[i]);
+ test.assertEquals(prefix + ": item " + i
+ + " (0-based) is not the same", expected[i], actual[i]);
}
}
}
--- /dev/null
+package be.nikiroo.utils.test_code;
+
+import java.io.ByteArrayOutputStream;
+
+import be.nikiroo.utils.BufferedOutputStream;
+import be.nikiroo.utils.test.TestCase;
+import be.nikiroo.utils.test.TestLauncher;
+
+class BufferedOutputStreamTest extends TestLauncher {
+ public BufferedOutputStreamTest(String[] args) {
+ super("BufferedOutputStream test", args);
+
+ addTest(new TestCase("Single write") {
+ @Override
+ public void test() throws Exception {
+ ByteArrayOutputStream bout = new ByteArrayOutputStream();
+ BufferedOutputStream out = new BufferedOutputStream(bout);
+
+ byte[] data = new byte[] { 42, 12, 0, 127 };
+
+ out.write(data);
+ out.close();
+
+ checkArrays(this, "FIRST", bout, data);
+ }
+ });
+
+ addTest(new TestCase("Multiple writes") {
+ @Override
+ public void test() throws Exception {
+ ByteArrayOutputStream bout = new ByteArrayOutputStream();
+ BufferedOutputStream out = new BufferedOutputStream(bout);
+
+ byte[] data1 = new byte[] { 42, 12, 0, 127 };
+ byte[] data2 = new byte[] { 15, 55 };
+ byte[] data3 = new byte[] {};
+
+ byte[] dataAll = new byte[] { 42, 12, 0, 127, 15, 55 };
+
+ out.write(data1);
+ out.write(data2);
+ out.write(data3);
+ out.close();
+
+ checkArrays(this, "FIRST", bout, dataAll);
+ }
+ });
+ }
+
+ static void checkArrays(TestCase test, String prefix,
+ ByteArrayOutputStream bout, byte[] expected) throws Exception {
+ byte[] actual = bout.toByteArray();
+
+ if (false) {
+ System.out.print("\nExpected data: [ ");
+ for (int i = 0; i < actual.length; i++) {
+ if (i > 0)
+ System.out.print(", ");
+ System.out.print(expected[i]);
+ }
+ System.out.println(" ]");
+
+ System.out.print("Actual data : [ ");
+ for (int i = 0; i < actual.length; i++) {
+ if (i > 0)
+ System.out.print(", ");
+ System.out.print(actual[i]);
+ }
+ System.out.println(" ]");
+ }
+
+ test.assertEquals("The " + prefix
+ + " resulting array has not the correct number of items",
+ expected.length, actual.length);
+ for (int i = 0; i < actual.length; i++) {
+ test.assertEquals(prefix + ": item " + i
+ + " (0-based) is not the same", expected[i], actual[i]);
+ }
+ }
+}
addSeries(new BufferedInputStreamTest(args));
addSeries(new NextableInputStreamTest(args));
addSeries(new ReplaceInputStreamTest(args));
+ addSeries(new BufferedOutputStreamTest(args));
// TODO: test cache and downloader
Cache cache = null;