Merge branch 'subtree'
[fanfix.git] / streams / ReplaceOutputStream.java
1 package be.nikiroo.utils.streams;
2
3 import java.io.IOException;
4 import java.io.OutputStream;
5
6 import be.nikiroo.utils.StringUtils;
7
8 /**
9 * This {@link OutputStream} will change some of its content by replacing it
10 * with something else.
11 *
12 * @author niki
13 */
14 public class ReplaceOutputStream extends BufferedOutputStream {
15 private byte[][] froms;
16 private byte[][] tos;
17
18 /**
19 * Create a {@link ReplaceOutputStream} that will replace <tt>from</tt> with
20 * <tt>to</tt>.
21 *
22 * @param out
23 * the under-laying {@link OutputStream}
24 * @param from
25 * the {@link String} to replace
26 * @param to
27 * the {@link String} to replace with
28 */
29 public ReplaceOutputStream(OutputStream out, String from, String to) {
30 this(out, StringUtils.getBytes(from), StringUtils.getBytes(to));
31 }
32
33 /**
34 * Create a {@link ReplaceOutputStream} that will replace <tt>from</tt> with
35 * <tt>to</tt>.
36 *
37 * @param out
38 * the under-laying {@link OutputStream}
39 * @param from
40 * the value to replace
41 * @param to
42 * the value to replace with
43 */
44 public ReplaceOutputStream(OutputStream out, byte[] from, byte[] to) {
45 this(out, new byte[][] { from }, new byte[][] { to });
46 }
47
48 /**
49 * Create a {@link ReplaceOutputStream} that will replace all <tt>froms</tt>
50 * with <tt>tos</tt>.
51 * <p>
52 * Note that they will be replaced in order, and that for each <tt>from</tt>
53 * a <tt>to</tt> must correspond.
54 *
55 * @param out
56 * the under-laying {@link OutputStream}
57 * @param froms
58 * the values to replace
59 * @param tos
60 * the values to replace with
61 */
62 public ReplaceOutputStream(OutputStream out, String[] froms, String[] tos) {
63 this(out, StreamUtils.getBytes(froms), StreamUtils.getBytes(tos));
64 }
65
66 /**
67 * Create a {@link ReplaceOutputStream} that will replace all <tt>froms</tt>
68 * with <tt>tos</tt>.
69 * <p>
70 * Note that they will be replaced in order, and that for each <tt>from</tt>
71 * a <tt>to</tt> must correspond.
72 *
73 * @param out
74 * the under-laying {@link OutputStream}
75 * @param froms
76 * the values to replace
77 * @param tos
78 * the values to replace with
79 */
80 public ReplaceOutputStream(OutputStream out, byte[][] froms, byte[][] tos) {
81 super(out);
82 bypassFlush = false;
83
84 if (froms.length != tos.length) {
85 throw new IllegalArgumentException(
86 "For replacing, each FROM must have a corresponding TO");
87 }
88
89 this.froms = froms;
90 this.tos = tos;
91 }
92
93 /**
94 * Flush the {@link BufferedOutputStream}, write the current buffered data
95 * to (and optionally also flush) the under-laying stream.
96 * <p>
97 * If {@link BufferedOutputStream#bypassFlush} is false, all writes to the
98 * under-laying stream are done in this method.
99 * <p>
100 * This can be used if you want to write some data in the under-laying
101 * stream yourself (in that case, flush this {@link BufferedOutputStream}
102 * with or without flushing the under-laying stream, then you can write to
103 * the under-laying stream).
104 * <p>
105 * <b>But be careful!</b> If a replacement could be done with the end o the
106 * currently buffered data and the start of the data to come, we obviously
107 * will not be able to do it.
108 *
109 * @param includingSubStream
110 * also flush the under-laying stream
111 * @throws IOException
112 * in case of I/O error
113 */
114 @Override
115 public void flush(boolean includingSubStream) throws IOException {
116 // Note: very simple, not efficient implementation; sorry.
117 while (start < stop) {
118 boolean replaced = false;
119 for (int i = 0; i < froms.length; i++) {
120 if (froms[i] != null
121 && froms[i].length > 0
122 && StreamUtils
123 .startsWith(froms[i], buffer, start, stop)) {
124 if (tos[i] != null && tos[i].length > 0) {
125 out.write(tos[i]);
126 bytesWritten += tos[i].length;
127 }
128
129 start += froms[i].length;
130 replaced = true;
131 break;
132 }
133 }
134
135 if (!replaced) {
136 out.write(buffer[start++]);
137 bytesWritten++;
138 }
139 }
140
141 start = 0;
142 stop = 0;
143
144 if (includingSubStream) {
145 out.flush();
146 }
147 }
148 }