Commit | Line | Data |
---|---|---|
8e76f6ab | 1 | package be.nikiroo.utils.streams; |
6ef54b36 NR |
2 | |
3 | import java.io.IOException; | |
4 | import java.io.InputStream; | |
efe7b598 NR |
5 | import java.util.ArrayList; |
6 | import java.util.List; | |
6ef54b36 | 7 | |
f8147a0e NR |
8 | import be.nikiroo.utils.StringUtils; |
9 | ||
876dbf8b | 10 | /** |
c8ce09c4 NR |
11 | * This {@link InputStream} will change some of its content by replacing it with |
12 | * something else. | |
876dbf8b NR |
13 | * |
14 | * @author niki | |
15 | */ | |
6ef54b36 | 16 | public class ReplaceInputStream extends BufferedInputStream { |
7194ac50 NR |
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 | ||
627f866e NR |
26 | private byte[][] froms; |
27 | private byte[][] tos; | |
efe7b598 | 28 | private int bufferSize; |
7194ac50 | 29 | private int maxFromSize; |
6ef54b36 | 30 | |
876dbf8b NR |
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 | */ | |
6ef54b36 | 42 | public ReplaceInputStream(InputStream in, String from, String to) { |
f8147a0e | 43 | this(in, StringUtils.getBytes(from), StringUtils.getBytes(to)); |
6ef54b36 NR |
44 | } |
45 | ||
876dbf8b NR |
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 | */ | |
6ef54b36 | 57 | public ReplaceInputStream(InputStream in, byte[] from, byte[] to) { |
627f866e NR |
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) { | |
f8147a0e | 76 | this(in, StreamUtils.getBytes(froms), StreamUtils.getBytes(tos)); |
627f866e NR |
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) { | |
6ef54b36 | 94 | super(in); |
627f866e NR |
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 | ||
7194ac50 NR |
104 | maxFromSize = 0; |
105 | for (int i = 0; i < froms.length; i++) { | |
106 | maxFromSize = Math.max(maxFromSize, froms[i].length); | |
107 | } | |
108 | ||
efe7b598 | 109 | int maxToSize = 0; |
627f866e NR |
110 | for (int i = 0; i < tos.length; i++) { |
111 | maxToSize = Math.max(maxToSize, tos[i].length); | |
112 | } | |
6ef54b36 | 113 | |
7194ac50 | 114 | // We need at least maxFromSize so we can iterate and replace |
efe7b598 NR |
115 | bufferSize = Math.max(4 * Math.max(maxToSize, maxFromSize), |
116 | MIN_BUFFER_SIZE); | |
6ef54b36 NR |
117 | } |
118 | ||
119 | @Override | |
efe7b598 NR |
120 | protected boolean preRead() throws IOException { |
121 | boolean rep = super.preRead(); | |
122 | start = stop; | |
123 | return rep; | |
124 | } | |
6ef54b36 | 125 | |
efe7b598 NR |
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; | |
6ef54b36 NR |
138 | } |
139 | ||
efe7b598 NR |
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); | |
627f866e | 156 | } |
efe7b598 NR |
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; | |
627f866e NR |
166 | } |
167 | } | |
efe7b598 NR |
168 | } |
169 | ||
170 | if (offset < read) { | |
171 | bbBuffers.add(newBuffer); | |
172 | bbOffsets.add(offset); | |
173 | bbLengths.add(read - offset); | |
174 | } | |
627f866e | 175 | |
efe7b598 NR |
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(")"); | |
6ef54b36 | 211 | } |
efe7b598 | 212 | rep.append("\n"); |
6ef54b36 NR |
213 | } |
214 | ||
efe7b598 | 215 | return "[" + rep + "]"; |
6ef54b36 | 216 | } |
6ef54b36 | 217 | } |