if (available() >= search.length) {
// Easy path
- return startsWith(search, buffer, start, stop);
+ return StreamUtils.startsWith(search, buffer, start, stop);
} else if (!eof) {
// Harder path
if (buffer2 == null && buffer.length == originalBuffer.length) {
len2 += pos2;
}
- return startsWith(search, buffer2, pos2, len2);
+ return StreamUtils.startsWith(search, buffer2, pos2, len2);
}
return false;
"This BufferedInputStream was closed, you cannot use it anymore.");
}
}
-
- /**
- * Check if the buffer starts with the given search term (given as an array,
- * a start position and a end position).
- * <p>
- * Note: the parameter <tt>len</tt> is the <b>index</b> of the last
- * position, <b>not</b> the length.
- * <p>
- * Note: the search term size <b>must</b> be smaller or equal the internal
- * buffer size.
- *
- * @param search
- * the term to search for
- * @param buffer
- * the buffer to look into
- * @param offset
- * the offset at which to start the search
- * @param len
- * the maximum index of the data to check (this is <b>not</b> a
- * length, but an index)
- *
- * @return TRUE if the search content is present at the given location and
- * does not exceed the <tt>len</tt> index
- */
- static protected boolean startsWith(byte[] search, byte[] buffer,
- int offset, int len) {
-
- // Check if there even is enough space for it
- if (search.length > (len - offset)) {
- return false;
- }
-
- boolean same = true;
- for (int i = 0; i < search.length; i++) {
- if (search[i] != buffer[offset + i]) {
- same = false;
- break;
- }
- }
-
- return same;
- }
}
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).
+ * <p>
+ * By default, this is true.
+ */
+ protected boolean bypassFlush = true;
private boolean closed;
- private OutputStream out;
private int openCounter;
- private long bytesWritten;
-
/**
* Create a new {@link BufferedInputStream} that wraps the given
* {@link InputStream}.
}
/**
- * Flush the {@link BufferedOutputStream}, and optionally the under-laying
- * stream, too.
+ * Flush the {@link BufferedOutputStream}, write the current buffered data
+ * to (and optionally also flush) the under-laying stream.
+ * <p>
+ * If {@link BufferedOutputStream#bypassFlush} is false, all writes to the
+ * under-laying stream are done in this method.
*
* @param includingSubStream
* also flush the under-laying stream
* @throws IOException
* in case of I/O error
*/
- private void flush(boolean includingSubStream) throws IOException {
+ protected void flush(boolean includingSubStream) throws IOException {
out.write(buffer, start, stop - start);
bytesWritten += (stop - start);
start = 0;
stop = 0;
+
if (includingSubStream) {
out.flush();
}
import java.io.IOException;
import java.io.InputStream;
-import java.io.UnsupportedEncodingException;
/**
- * This {@link InputStream} will replace some of its content by replacing it
- * with something else.
+ * This {@link InputStream} will change some of its content by replacing it with
+ * something else.
*
* @author niki
*/
* the {@link String} to replace with
*/
public ReplaceInputStream(InputStream in, String from, String to) {
- this(in, bytes(from), bytes(to));
+ this(in, StreamUtils.bytes(from), StreamUtils.bytes(to));
}
/**
// Note: very simple, not efficient implementation, sorry.
int count = 0;
while (spos < slen && count < buffer.length - to.length) {
- if (from.length > 0 && startsWith(from, source, spos, slen)) {
+ if (from.length > 0
+ && StreamUtils.startsWith(from, source, spos, slen)) {
System.arraycopy(to, 0, buffer, spos, to.length);
count += to.length;
spos += from.length;
return count;
}
-
- /**
- * Return the bytes array representation of the given {@link String} in
- * UTF-8.
- *
- * @param str
- * the string to transform into bytes
- * @return the content in bytes
- */
- static private byte[] bytes(String str) {
- try {
- return str.getBytes("UTF-8");
- } catch (UnsupportedEncodingException e) {
- // All conforming JVM must support UTF-8
- e.printStackTrace();
- return null;
- }
- }
}
--- /dev/null
+package be.nikiroo.utils.streams;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * This {@link OutputStream} will change some of its content by replacing it
+ * with something else.
+ *
+ * @author niki
+ */
+public class ReplaceOutputStream extends BufferedOutputStream {
+ private byte[] from;
+ private byte[] to;
+
+ /**
+ * Create a {@link ReplaceOutputStream} that will replace <tt>from</tt> with
+ * <tt>to</tt>.
+ *
+ * @param out
+ * the under-laying {@link OutputStream}
+ * @param from
+ * the {@link String} to replace
+ * @param to
+ * the {@link String} to replace with
+ */
+ public ReplaceOutputStream(OutputStream out, String from, String to) {
+ this(out, StreamUtils.bytes(from), StreamUtils.bytes(to));
+ }
+
+ /**
+ * Create a {@link ReplaceOutputStream} that will replace <tt>from</tt> with
+ * <tt>to</tt>.
+ *
+ * @param out
+ * the under-laying {@link OutputStream}
+ * @param from
+ * the value to replace
+ * @param to
+ * the value to replace with
+ */
+ public ReplaceOutputStream(OutputStream out, byte[] from, byte[] to) {
+ super(out);
+ bypassFlush = false;
+
+ this.from = from;
+ this.to = to;
+ }
+
+ @Override
+ protected void flush(boolean includingSubStream) throws IOException {
+ // Note: very simple, not efficient implementation, sorry.
+ while (start < stop) {
+ if (from.length > 0
+ && StreamUtils.startsWith(from, buffer, start, stop)) {
+ out.write(to);
+ bytesWritten += to.length;
+ start += from.length;
+ } else {
+ out.write(buffer[start++]);
+ bytesWritten++;
+ }
+ }
+
+ start = 0;
+ stop = 0;
+
+ if (includingSubStream) {
+ out.flush();
+ }
+ }
+}
--- /dev/null
+package be.nikiroo.utils.streams;
+
+import java.io.UnsupportedEncodingException;
+
+/**
+ * Some non-public utilities used in the stream classes.
+ *
+ * @author niki
+ */
+class StreamUtils {
+ /**
+ * Check if the buffer starts with the given search term (given as an array,
+ * a start position and an end position).
+ * <p>
+ * Note: the parameter <tt>stop</tt> is the <b>index</b> of the last
+ * position, <b>not</b> the length.
+ * <p>
+ * Note: the search term size <b>must</b> be smaller or equal the internal
+ * buffer size.
+ *
+ * @param search
+ * the term to search for
+ * @param buffer
+ * the buffer to look into
+ * @param start
+ * the offset at which to start the search
+ * @param stop
+ * the maximum index of the data to check (this is <b>not</b> a
+ * length, but an index)
+ *
+ * @return TRUE if the search content is present at the given location and
+ * does not exceed the <tt>len</tt> index
+ */
+ static public boolean startsWith(byte[] search, byte[] buffer, int start,
+ int stop) {
+
+ // Check if there even is enough space for it
+ if (search.length > (stop - start)) {
+ return false;
+ }
+
+ boolean same = true;
+ for (int i = 0; i < search.length; i++) {
+ if (search[i] != buffer[start + i]) {
+ same = false;
+ break;
+ }
+ }
+
+ return same;
+ }
+
+ /**
+ * Return the bytes array representation of the given {@link String} in
+ * UTF-8.
+ *
+ * @param str
+ * the string to transform into bytes
+ * @return the content in bytes
+ */
+ static public byte[] bytes(String str) {
+ try {
+ return str.getBytes("UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ // All conforming JVM must support UTF-8
+ e.printStackTrace();
+ return null;
+ }
+ }
+}
if (false) {
System.out.print("\nExpected data: [ ");
- for (int i = 0; i < actual.length; i++) {
+ for (int i = 0; i < expected.length; i++) {
if (i > 0)
System.out.print(", ");
System.out.print(expected[i]);
--- /dev/null
+package be.nikiroo.utils.test_code;
+
+import java.io.ByteArrayOutputStream;
+
+import be.nikiroo.utils.streams.ReplaceOutputStream;
+import be.nikiroo.utils.test.TestCase;
+import be.nikiroo.utils.test.TestLauncher;
+
+class ReplaceOutputStreamTest extends TestLauncher {
+ public ReplaceOutputStreamTest(String[] args) {
+ super("ReplaceOutputStream test", args);
+
+ addTest(new TestCase("Single write, empty bytes replaces") {
+ @Override
+ public void test() throws Exception {
+ ByteArrayOutputStream bout = new ByteArrayOutputStream();
+ ReplaceOutputStream out = new ReplaceOutputStream(bout,
+ new byte[0], new byte[0]);
+
+ byte[] data = new byte[] { 42, 12, 0, 127 };
+
+ out.write(data);
+ out.close();
+
+ checkArrays(this, "FIRST", bout, data);
+ }
+ });
+
+ addTest(new TestCase("Multiple writes, empty Strings replaces") {
+ @Override
+ public void test() throws Exception {
+ ByteArrayOutputStream bout = new ByteArrayOutputStream();
+ ReplaceOutputStream out = new ReplaceOutputStream(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);
+ }
+ });
+
+ addTest(new TestCase("Single write, bytes replaces") {
+ @Override
+ public void test() throws Exception {
+ ByteArrayOutputStream bout = new ByteArrayOutputStream();
+ ReplaceOutputStream out = new ReplaceOutputStream(bout,
+ new byte[] { 12 }, new byte[] { 55 });
+
+ byte[] data = new byte[] { 42, 12, 0, 127 };
+
+ out.write(data);
+ out.close();
+
+ checkArrays(this, "FIRST", bout, new byte[] { 42, 55, 0, 127 });
+ }
+ });
+
+ addTest(new TestCase("Multiple writes, Strings replaces") {
+ @Override
+ public void test() throws Exception {
+ ByteArrayOutputStream bout = new ByteArrayOutputStream();
+ ReplaceOutputStream out = new ReplaceOutputStream(bout, "(-)",
+ "(.)");
+
+ byte[] data1 = "un mot ".getBytes("UTF-8");
+ byte[] data2 = "(-) of twee ".getBytes("UTF-8");
+ byte[] data3 = "(-) makes the difference".getBytes("UTF-8");
+
+ out.write(data1);
+ out.write(data2);
+ out.write(data3);
+ out.close();
+
+ checkArrays(this, "FIRST", bout,
+ "un mot (.) of twee (.) makes the difference"
+ .getBytes("UTF-8"));
+ }
+ });
+
+ addTest(new TestCase("Single write, longer bytes replaces") {
+ @Override
+ public void test() throws Exception {
+ ByteArrayOutputStream bout = new ByteArrayOutputStream();
+ ReplaceOutputStream out = new ReplaceOutputStream(bout,
+ new byte[] { 12 }, new byte[] { 55, 55, 66 });
+
+ byte[] data = new byte[] { 42, 12, 0, 127 };
+
+ out.write(data);
+ out.close();
+
+ checkArrays(this, "FIRST", bout, new byte[] { 42, 55, 55, 66,
+ 0, 127 });
+ }
+ });
+
+ addTest(new TestCase("Single write, shorter bytes replaces") {
+ @Override
+ public void test() throws Exception {
+ ByteArrayOutputStream bout = new ByteArrayOutputStream();
+ ReplaceOutputStream out = new ReplaceOutputStream(bout,
+ new byte[] { 12, 0 }, new byte[] { 55 });
+
+ byte[] data = new byte[] { 42, 12, 0, 127 };
+
+ out.write(data);
+ out.close();
+
+ checkArrays(this, "FIRST", bout, new byte[] { 42, 55, 127 });
+ }
+ });
+
+ addTest(new TestCase("Single write, remove bytes replaces") {
+ @Override
+ public void test() throws Exception {
+ ByteArrayOutputStream bout = new ByteArrayOutputStream();
+ ReplaceOutputStream out = new ReplaceOutputStream(bout,
+ new byte[] { 12 }, new byte[] {});
+
+ byte[] data = new byte[] { 42, 12, 0, 127 };
+
+ out.write(data);
+ out.close();
+
+ checkArrays(this, "FIRST", bout, new byte[] { 42, 0, 127 });
+ }
+ });
+ }
+
+ 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 < expected.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]);
+ }
+ }
+}
assertEquals(21200l, StringUtils.toNumber("21200"));
assertEquals(0l, StringUtils.toNumber("0"));
assertEquals("263", StringUtils.formatNumber(263l));
- assertEquals("21k", StringUtils.formatNumber(21000l));
+ assertEquals("21 k", StringUtils.formatNumber(21000l));
assertEquals("0", StringUtils.formatNumber(0l));
}
});
addTest(new TestCase("format/toNumber not 000") {
@Override
public void test() throws Exception {
- assertEquals(263200l, StringUtils.toNumber("263.2k"));
- assertEquals(42000l, StringUtils.toNumber("42.0k"));
- assertEquals(12000000l, StringUtils.toNumber("12M"));
- assertEquals(2000000000l, StringUtils.toNumber("2G"));
- assertEquals("263k", StringUtils.formatNumber(263012l));
- assertEquals("42k", StringUtils.formatNumber(42012l));
- assertEquals("12M", StringUtils.formatNumber(12012121l));
- assertEquals("7G", StringUtils.formatNumber(7364635928l));
+ assertEquals(263200l, StringUtils.toNumber("263.2 k"));
+ assertEquals(42000l, StringUtils.toNumber("42.0 k"));
+ assertEquals(12000000l, StringUtils.toNumber("12 M"));
+ assertEquals(2000000000l, StringUtils.toNumber("2 G"));
+ assertEquals("263 k", StringUtils.formatNumber(263012l));
+ assertEquals("42 k", StringUtils.formatNumber(42012l));
+ assertEquals("12 M", StringUtils.formatNumber(12012121l));
+ assertEquals("7 G", StringUtils.formatNumber(7364635928l));
}
});
addTest(new TestCase("format/toNumber decimals") {
@Override
public void test() throws Exception {
- assertEquals(263200l, StringUtils.toNumber("263.2k"));
- assertEquals(1200l, StringUtils.toNumber("1.2k"));
- assertEquals(42700000l, StringUtils.toNumber("42.7M"));
- assertEquals(1220l, StringUtils.toNumber("1.22k"));
- assertEquals(1432l, StringUtils.toNumber("1.432k"));
- assertEquals(6938l, StringUtils.toNumber("6.938k"));
- assertEquals("1.3k", StringUtils.formatNumber(1300l, 1));
- assertEquals("263.2020k", StringUtils.formatNumber(263202l, 4));
- assertEquals("1.26k", StringUtils.formatNumber(1267l, 2));
- assertEquals("42.7M", StringUtils.formatNumber(42712121l, 1));
- assertEquals("5.09G", StringUtils.formatNumber(5094837485l, 2));
+ assertEquals(263200l, StringUtils.toNumber("263.2 k"));
+ assertEquals(1200l, StringUtils.toNumber("1.2 k"));
+ assertEquals(42700000l, StringUtils.toNumber("42.7 M"));
+ assertEquals(1220l, StringUtils.toNumber("1.22 k"));
+ assertEquals(1432l, StringUtils.toNumber("1.432 k"));
+ assertEquals(6938l, StringUtils.toNumber("6.938 k"));
+ assertEquals("1.3 k", StringUtils.formatNumber(1300l, 1));
+ assertEquals("263.2020 k", StringUtils.formatNumber(263202l, 4));
+ assertEquals("1.26 k", StringUtils.formatNumber(1267l, 2));
+ assertEquals("42.7 M", StringUtils.formatNumber(42712121l, 1));
+ assertEquals("5.09 G", StringUtils.formatNumber(5094837485l, 2));
}
});
}
addSeries(new NextableInputStreamTest(args));
addSeries(new ReplaceInputStreamTest(args));
addSeries(new BufferedOutputStreamTest(args));
+ addSeries(new ReplaceOutputStreamTest(args));
// TODO: test cache and downloader
Cache cache = null;