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 | |
876dbf8b | 6 | /** |
c8ce09c4 NR |
7 | * This {@link InputStream} will change some of its content by replacing it with |
8 | * something else. | |
876dbf8b NR |
9 | * |
10 | * @author niki | |
11 | */ | |
6ef54b36 | 12 | public class ReplaceInputStream extends BufferedInputStream { |
7194ac50 NR |
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 | ||
627f866e NR |
22 | private byte[][] froms; |
23 | private byte[][] tos; | |
7194ac50 | 24 | private int maxFromSize; |
627f866e | 25 | private int maxToSize; |
6ef54b36 NR |
26 | |
27 | private byte[] source; | |
28 | private int spos; | |
29 | private int slen; | |
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) { |
c8ce09c4 | 43 | this(in, StreamUtils.bytes(from), StreamUtils.bytes(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) { | |
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) { | |
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 | ||
109 | 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 NR |
114 | // We need at least maxFromSize so we can iterate and replace |
115 | source = new byte[Math.max(2 * maxFromSize, MIN_BUFFER_SIZE)]; | |
6ef54b36 NR |
116 | spos = 0; |
117 | slen = 0; | |
118 | } | |
119 | ||
120 | @Override | |
028ff7c2 NR |
121 | protected int read(InputStream in, byte[] buffer, int off, int len) |
122 | throws IOException { | |
123 | if (len < maxToSize || source.length < maxToSize * 2) { | |
6ef54b36 | 124 | throw new IOException( |
627f866e | 125 | "An underlaying buffer is too small for these replace values"); |
6ef54b36 NR |
126 | } |
127 | ||
7194ac50 NR |
128 | // We need at least one byte of data to process |
129 | if (available() < Math.max(maxFromSize, 1) && !eof) { | |
6ef54b36 NR |
130 | spos = 0; |
131 | slen = in.read(source); | |
132 | } | |
133 | ||
7194ac50 | 134 | // Note: very simple, not efficient implementation; sorry. |
6ef54b36 | 135 | int count = 0; |
028ff7c2 | 136 | while (spos < slen && count < len - maxToSize) { |
627f866e NR |
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) { | |
028ff7c2 NR |
142 | System.arraycopy(tos[i], 0, buffer, off + spos, |
143 | tos[i].length); | |
627f866e NR |
144 | count += tos[i].length; |
145 | } | |
146 | ||
147 | spos += froms[i].length; | |
148 | replaced = true; | |
149 | break; | |
150 | } | |
151 | } | |
152 | ||
153 | if (!replaced) { | |
028ff7c2 | 154 | buffer[off + count++] = source[spos++]; |
6ef54b36 NR |
155 | } |
156 | } | |
157 | ||
158 | return count; | |
159 | } | |
6ef54b36 | 160 | } |