fix limit in replace for BufferedInputStream
[fanfix.git] / src / be / nikiroo / utils / streams / ReplaceInputStream.java
index f5138eefeafc327dbd865e3b89713843c027aa0a..ae576e25e7a4833e9ce8f4bea25616ff018eca52 100644 (file)
@@ -3,6 +3,8 @@ package be.nikiroo.utils.streams;
 import java.io.IOException;
 import java.io.InputStream;
 
+import be.nikiroo.utils.StringUtils;
+
 /**
  * This {@link InputStream} will change some of its content by replacing it with
  * something else.
@@ -10,8 +12,19 @@ import java.io.InputStream;
  * @author niki
  */
 public class ReplaceInputStream extends BufferedInputStream {
-       private byte[] from;
-       private byte[] to;
+       /**
+        * The minimum size of the internal buffer (could be more if at least one of
+        * the 'FROM' bytes arrays is > 2048 bytes — in that case the
+        * buffer will be twice the largest size of the 'FROM' bytes arrays).
+        * <p>
+        * This is a different buffer than the one from the inherited class.
+        */
+       static private final int MIN_BUFFER_SIZE = 4096;
+
+       private byte[][] froms;
+       private byte[][] tos;
+       private int maxFromSize;
+       private int maxToSize;
 
        private byte[] source;
        private int spos;
@@ -29,7 +42,7 @@ public class ReplaceInputStream extends BufferedInputStream {
         *            the {@link String} to replace with
         */
        public ReplaceInputStream(InputStream in, String from, String to) {
-               this(in, StreamUtils.bytes(from), StreamUtils.bytes(to));
+               this(in, StringUtils.getBytes(from), StringUtils.getBytes(to));
        }
 
        /**
@@ -44,37 +57,104 @@ public class ReplaceInputStream extends BufferedInputStream {
         *            the value to replace with
         */
        public ReplaceInputStream(InputStream in, byte[] from, byte[] to) {
+               this(in, new byte[][] { from }, new byte[][] { to });
+       }
+
+       /**
+        * Create a {@link ReplaceInputStream} that will replace all <tt>froms</tt>
+        * with <tt>tos</tt>.
+        * <p>
+        * Note that they will be replaced in order, and that for each <tt>from</tt>
+        * a <tt>to</tt> must correspond.
+        * 
+        * @param in
+        *            the under-laying {@link InputStream}
+        * @param froms
+        *            the values to replace
+        * @param tos
+        *            the values to replace with
+        */
+       public ReplaceInputStream(InputStream in, String[] froms, String[] tos) {
+               this(in, StreamUtils.getBytes(froms), StreamUtils.getBytes(tos));
+       }
+
+       /**
+        * Create a {@link ReplaceInputStream} that will replace all <tt>froms</tt>
+        * with <tt>tos</tt>.
+        * <p>
+        * Note that they will be replaced in order, and that for each <tt>from</tt>
+        * a <tt>to</tt> must correspond.
+        * 
+        * @param in
+        *            the under-laying {@link InputStream}
+        * @param froms
+        *            the values to replace
+        * @param tos
+        *            the values to replace with
+        */
+       public ReplaceInputStream(InputStream in, byte[][] froms, byte[][] tos) {
                super(in);
-               this.from = from;
-               this.to = to;
 
-               source = new byte[4096];
+               if (froms.length != tos.length) {
+                       throw new IllegalArgumentException(
+                                       "For replacing, each FROM must have a corresponding TO");
+               }
+
+               this.froms = froms;
+               this.tos = tos;
+
+               maxFromSize = 0;
+               for (int i = 0; i < froms.length; i++) {
+                       maxFromSize = Math.max(maxFromSize, froms[i].length);
+               }
+
+               maxToSize = 0;
+               for (int i = 0; i < tos.length; i++) {
+                       maxToSize = Math.max(maxToSize, tos[i].length);
+               }
+
+               // We need at least maxFromSize so we can iterate and replace
+               source = new byte[Math.max(2 * Math.max(maxToSize, maxFromSize),
+                               MIN_BUFFER_SIZE)];
                spos = 0;
                slen = 0;
        }
 
        @Override
-       protected int read(InputStream in, byte[] buffer) throws IOException {
-               if (buffer.length < to.length || source.length < to.length * 2) {
+       protected int read(InputStream in, byte[] buffer, int off, int len)
+                       throws IOException {
+               if (len < maxToSize || source.length < maxToSize * 2) {
                        throw new IOException(
-                                       "An underlaying buffer is too small for this replace value");
+                                       "An underlaying buffer is too small for these replace values");
                }
 
-               if (spos >= slen) {
+               // We need at least one byte of data to process
+               if (available() < Math.max(maxFromSize, 1) && !eof) {
                        spos = 0;
                        slen = in.read(source);
                }
 
-               // Note: very simple, not efficient implementation, sorry.
+               // Note: very simple, not efficient implementation; sorry.
                int count = 0;
-               while (spos < slen && count < buffer.length - to.length) {
-                       if (from.length > 0
-                                       && StreamUtils.startsWith(from, source, spos, slen)) {
-                               System.arraycopy(to, 0, buffer, spos, to.length);
-                               count += to.length;
-                               spos += from.length;
-                       } else {
-                               buffer[count++] = source[spos++];
+               while (spos < slen && count < len - maxToSize) {
+                       boolean replaced = false;
+                       for (int i = 0; i < froms.length; i++) {
+                               if (froms[i] != null && froms[i].length > 0
+                                               && StreamUtils.startsWith(froms[i], source, spos, slen)) {
+                                       if (tos[i] != null && tos[i].length > 0) {
+                                               System.arraycopy(tos[i], 0, buffer, off + count,
+                                                               tos[i].length);
+                                               count += tos[i].length;
+                                       }
+                                       
+                                       spos += froms[i].length;
+                                       replaced = true;
+                                       break;
+                               }
+                       }
+
+                       if (!replaced) {
+                               buffer[off + count++] = source[spos++];
                        }
                }