code cleanup, fix for ReplaceInputStream
[nikiroo-utils.git] / src / be / nikiroo / utils / streams / ReplaceInputStream.java
1 package be.nikiroo.utils.streams;
2
3 import java.io.IOException;
4 import java.io.InputStream;
5
6 /**
7 * This {@link InputStream} will change some of its content by replacing it with
8 * something else.
9 *
10 * @author niki
11 */
12 public class ReplaceInputStream extends BufferedInputStream {
13 /**
14 * The minimum size of the internal buffer (could be more if at least one of
15 * the 'FROM' bytes arrays is > 2048 bytes — in that case the
16 * buffer will be twice the largest size of the 'FROM' bytes arrays).
17 * <p>
18 * This is a different buffer than the one from the inherited class.
19 */
20 static private final int MIN_BUFFER_SIZE = 4096;
21
22 private byte[][] froms;
23 private byte[][] tos;
24 private int maxFromSize;
25 private int maxToSize;
26
27 private byte[] source;
28 private int spos;
29 private int slen;
30
31 /**
32 * Create a {@link ReplaceInputStream} that will replace <tt>from</tt> with
33 * <tt>to</tt>.
34 *
35 * @param in
36 * the under-laying {@link InputStream}
37 * @param from
38 * the {@link String} to replace
39 * @param to
40 * the {@link String} to replace with
41 */
42 public ReplaceInputStream(InputStream in, String from, String to) {
43 this(in, StreamUtils.bytes(from), StreamUtils.bytes(to));
44 }
45
46 /**
47 * Create a {@link ReplaceInputStream} that will replace <tt>from</tt> with
48 * <tt>to</tt>.
49 *
50 * @param in
51 * the under-laying {@link InputStream}
52 * @param from
53 * the value to replace
54 * @param to
55 * the value to replace with
56 */
57 public ReplaceInputStream(InputStream in, byte[] from, byte[] to) {
58 this(in, new byte[][] { from }, new byte[][] { to });
59 }
60
61 /**
62 * Create a {@link ReplaceInputStream} that will replace all <tt>froms</tt>
63 * with <tt>tos</tt>.
64 * <p>
65 * Note that they will be replaced in order, and that for each <tt>from</tt>
66 * a <tt>to</tt> must correspond.
67 *
68 * @param in
69 * the under-laying {@link InputStream}
70 * @param froms
71 * the values to replace
72 * @param tos
73 * the values to replace with
74 */
75 public ReplaceInputStream(InputStream in, String[] froms, String[] tos) {
76 this(in, StreamUtils.bytes(froms), StreamUtils.bytes(tos));
77 }
78
79 /**
80 * Create a {@link ReplaceInputStream} that will replace all <tt>froms</tt>
81 * with <tt>tos</tt>.
82 * <p>
83 * Note that they will be replaced in order, and that for each <tt>from</tt>
84 * a <tt>to</tt> must correspond.
85 *
86 * @param in
87 * the under-laying {@link InputStream}
88 * @param froms
89 * the values to replace
90 * @param tos
91 * the values to replace with
92 */
93 public ReplaceInputStream(InputStream in, byte[][] froms, byte[][] tos) {
94 super(in);
95
96 if (froms.length != tos.length) {
97 throw new IllegalArgumentException(
98 "For replacing, each FROM must have a corresponding TO");
99 }
100
101 this.froms = froms;
102 this.tos = tos;
103
104 maxFromSize = 0;
105 for (int i = 0; i < froms.length; i++) {
106 maxFromSize = Math.max(maxFromSize, froms[i].length);
107 }
108
109 maxToSize = 0;
110 for (int i = 0; i < tos.length; i++) {
111 maxToSize = Math.max(maxToSize, tos[i].length);
112 }
113
114 // We need at least maxFromSize so we can iterate and replace
115 source = new byte[Math.max(2 * maxFromSize, MIN_BUFFER_SIZE)];
116 spos = 0;
117 slen = 0;
118 }
119
120 @Override
121 protected int read(InputStream in, byte[] buffer, int off, int len)
122 throws IOException {
123 if (len < maxToSize || source.length < maxToSize * 2) {
124 throw new IOException(
125 "An underlaying buffer is too small for these replace values");
126 }
127
128 // We need at least one byte of data to process
129 if (available() < Math.max(maxFromSize, 1) && !eof) {
130 spos = 0;
131 slen = in.read(source);
132 }
133
134 // Note: very simple, not efficient implementation; sorry.
135 int count = 0;
136 while (spos < slen && count < len - maxToSize) {
137 boolean replaced = false;
138 for (int i = 0; i < froms.length; i++) {
139 if (froms[i] != null && froms[i].length > 0
140 && StreamUtils.startsWith(froms[i], source, spos, slen)) {
141 if (tos[i] != null && tos[i].length > 0) {
142 System.arraycopy(tos[i], 0, buffer, off + spos,
143 tos[i].length);
144 count += tos[i].length;
145 }
146
147 spos += froms[i].length;
148 replaced = true;
149 break;
150 }
151 }
152
153 if (!replaced) {
154 buffer[off + count++] = source[spos++];
155 }
156 }
157
158 return count;
159 }
160 }