Commit | Line | Data |
---|---|---|
8e76f6ab | 1 | package be.nikiroo.utils.streams; |
6ef54b36 NR |
2 | |
3 | import java.io.IOException; | |
4 | import java.io.InputStream; | |
6ef54b36 | 5 | |
f8147a0e NR |
6 | import 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 | 14 | public 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 | } |