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