Merge branch 'subtree'
[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 import java.util.ArrayList;
6 import java.util.List;
7
8 import be.nikiroo.utils.StringUtils;
9
10 /**
11 * This {@link InputStream} will change some of its content by replacing it with
12 * something else.
13 *
14 * @author niki
15 */
16 public class ReplaceInputStream extends BufferedInputStream {
17 /**
18 * The minimum size of the internal buffer (could be more if at least one of
19 * the 'FROM' bytes arrays is > 2048 bytes — in that case the
20 * buffer will be twice the largest size of the 'FROM' bytes arrays).
21 * <p>
22 * This is a different buffer than the one from the inherited class.
23 */
24 static private final int MIN_BUFFER_SIZE = 4096;
25
26 private byte[][] froms;
27 private byte[][] tos;
28 private int bufferSize;
29 private int maxFromSize;
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, StringUtils.getBytes(from), StringUtils.getBytes(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.getBytes(froms), StreamUtils.getBytes(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 int 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 bufferSize = Math.max(4 * Math.max(maxToSize, maxFromSize),
116 MIN_BUFFER_SIZE);
117 }
118
119 @Override
120 protected boolean preRead() throws IOException {
121 boolean rep = super.preRead();
122 start = stop;
123 return rep;
124 }
125
126 @Override
127 protected int read(InputStream in, byte[] buffer) throws IOException {
128 buffer = null; // do not use the buffer.
129
130 byte[] newBuffer = new byte[bufferSize];
131 int read = 0;
132 while (read < bufferSize / 2) {
133 int thisTime = in.read(newBuffer, read, bufferSize / 2 - read);
134 if (thisTime <= 0) {
135 break;
136 }
137 read += thisTime;
138 }
139
140 List<byte[]> bbBuffers = new ArrayList<byte[]>();
141 List<Integer> bbOffsets = new ArrayList<Integer>();
142 List<Integer> bbLengths = new ArrayList<Integer>();
143
144 int offset = 0;
145 for (int i = 0; i < read; i++) {
146 for (int fromIndex = 0; fromIndex < froms.length; fromIndex++) {
147 byte[] from = froms[fromIndex];
148 byte[] to = tos[fromIndex];
149
150 if (from.length > 0
151 && StreamUtils.startsWith(from, newBuffer, i, read)) {
152 if (i - offset > 0) {
153 bbBuffers.add(newBuffer);
154 bbOffsets.add(offset);
155 bbLengths.add(i - offset);
156 }
157
158 if (to.length > 0) {
159 bbBuffers.add(to);
160 bbOffsets.add(0);
161 bbLengths.add(to.length);
162 }
163
164 i += from.length;
165 offset = i;
166 }
167 }
168 }
169
170 if (offset < read) {
171 bbBuffers.add(newBuffer);
172 bbOffsets.add(offset);
173 bbLengths.add(read - offset);
174 }
175
176 for (int i = bbBuffers.size() - 1; i >= 0; i--) {
177 // DEBUG("pushback", bbBuffers.get(i), bbOffsets.get(i),
178 // bbLengths.get(i));
179 pushback(bbBuffers.get(i), bbOffsets.get(i), bbLengths.get(i));
180 }
181
182 return read;
183 }
184
185 // static public void DEBUG(String title, byte[] b, int off, int len) {
186 // String str = new String(b,off,len);
187 // if(str.length()>20) {
188 // str=str.substring(0,10)+" ...
189 // "+str.substring(str.length()-10,str.length());
190 // }
191 // }
192
193 @Override
194 public String toString() {
195 StringBuilder rep = new StringBuilder();
196 rep.append(getClass().getSimpleName()).append("\n");
197
198 for (int i = 0; i < froms.length; i++) {
199 byte[] from = froms[i];
200 byte[] to = tos[i];
201
202 rep.append("\t");
203 rep.append("bytes[").append(from.length).append("]");
204 if (from.length <= 20) {
205 rep.append(" (").append(new String(from)).append(")");
206 }
207 rep.append(" -> ");
208 rep.append("bytes[").append(to.length).append("]");
209 if (to.length <= 20) {
210 rep.append(" (").append(new String(to)).append(")");
211 }
212 rep.append("\n");
213 }
214
215 return "[" + rep + "]";
216 }
217 }