new: ReplaceOutputStream
authorNiki Roo <niki@nikiroo.be>
Sat, 27 Apr 2019 16:55:28 +0000 (18:55 +0200)
committerNiki Roo <niki@nikiroo.be>
Sat, 27 Apr 2019 16:55:28 +0000 (18:55 +0200)
src/be/nikiroo/utils/streams/BufferedInputStream.java
src/be/nikiroo/utils/streams/BufferedOutputStream.java
src/be/nikiroo/utils/streams/ReplaceInputStream.java
src/be/nikiroo/utils/streams/ReplaceOutputStream.java [new file with mode: 0644]
src/be/nikiroo/utils/streams/StreamUtils.java [new file with mode: 0644]
src/be/nikiroo/utils/test_code/BufferedOutputStreamTest.java
src/be/nikiroo/utils/test_code/ReplaceOutputStreamTest.java [new file with mode: 0644]
src/be/nikiroo/utils/test_code/StringUtilsTest.java
src/be/nikiroo/utils/test_code/Test.java

index 7c20f634824841c8c7dd5d60fe94456dce1c78da..be4b24d2ec9b477b3556df34fd607923c8c30bf5 100644 (file)
@@ -167,7 +167,7 @@ public class BufferedInputStream extends InputStream {
 
                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) {
@@ -183,7 +183,7 @@ public class BufferedInputStream extends InputStream {
                                len2 += pos2;
                        }
 
-                       return startsWith(search, buffer2, pos2, len2);
+                       return StreamUtils.startsWith(search, buffer2, pos2, len2);
                }
 
                return false;
@@ -416,46 +416,4 @@ public class BufferedInputStream extends InputStream {
                                        "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;
-       }
 }
index 6f600eba7c06384db6f254ed8a89642257641156..8b74ae162d2b58bf90040650bf7d02312134fece 100644 (file)
@@ -22,13 +22,22 @@ public class BufferedOutputStream extends OutputStream {
        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 &gt; 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}.
@@ -165,19 +174,23 @@ public class BufferedOutputStream extends OutputStream {
        }
 
        /**
-        * 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();
                }
index 5eac0b448fef0262b9f2ded53a4c199a2c41d0b7..f5138eefeafc327dbd865e3b89713843c027aa0a 100644 (file)
@@ -2,11 +2,10 @@ package be.nikiroo.utils.streams;
 
 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
  */
@@ -30,7 +29,7 @@ public class ReplaceInputStream extends BufferedInputStream {
         *            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));
        }
 
        /**
@@ -69,7 +68,8 @@ public class ReplaceInputStream extends BufferedInputStream {
                // 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;
@@ -80,22 +80,4 @@ public class ReplaceInputStream extends BufferedInputStream {
 
                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;
-               }
-       }
 }
diff --git a/src/be/nikiroo/utils/streams/ReplaceOutputStream.java b/src/be/nikiroo/utils/streams/ReplaceOutputStream.java
new file mode 100644 (file)
index 0000000..e889b76
--- /dev/null
@@ -0,0 +1,72 @@
+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();
+               }
+       }
+}
diff --git a/src/be/nikiroo/utils/streams/StreamUtils.java b/src/be/nikiroo/utils/streams/StreamUtils.java
new file mode 100644 (file)
index 0000000..6b8251a
--- /dev/null
@@ -0,0 +1,70 @@
+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;
+               }
+       }
+}
index 8b5d5e9d28042a4ee2af4568623ef75e0c275ae6..cf6eb2ad43ee00c68582b6b978d75d561b1675e6 100644 (file)
@@ -53,7 +53,7 @@ class BufferedOutputStreamTest extends TestLauncher {
 
                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]);
diff --git a/src/be/nikiroo/utils/test_code/ReplaceOutputStreamTest.java b/src/be/nikiroo/utils/test_code/ReplaceOutputStreamTest.java
new file mode 100644 (file)
index 0000000..1db3397
--- /dev/null
@@ -0,0 +1,168 @@
+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]);
+               }
+       }
+}
index f9c52726cf75c7fb5158d369ed2005fec5995816..de283594d467f1b73e6a998642290b8e0b63ff5c 100644 (file)
@@ -238,7 +238,7 @@ class StringUtilsTest extends TestLauncher {
                                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));
                        }
                });
@@ -246,31 +246,31 @@ class StringUtilsTest extends TestLauncher {
                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));
                        }
                });
        }
index e00da1173129031bda4acefb261379a325c8bf57..f99448076e9fd86106e15bd1eee560407844394d 100644 (file)
@@ -37,6 +37,7 @@ public class Test extends TestLauncher {
                addSeries(new NextableInputStreamTest(args));
                addSeries(new ReplaceInputStreamTest(args));
                addSeries(new BufferedOutputStreamTest(args));
+               addSeries(new ReplaceOutputStreamTest(args));
 
                // TODO: test cache and downloader
                Cache cache = null;