Commit | Line | Data |
---|---|---|
8e76f6ab | 1 | package be.nikiroo.utils.streams; |
a26188d3 NR |
2 | |
3 | import java.io.IOException; | |
4 | import java.io.InputStream; | |
5 | import java.io.OutputStream; | |
6 | ||
7 | /** | |
8 | * A simple {@link OutputStream} that is buffered with a bytes array. | |
9 | * <p> | |
10 | * It is mostly intended to be used as a base class to create new | |
11 | * {@link OutputStream}s with special operation modes, and to give some default | |
12 | * methods. | |
13 | * | |
14 | * @author niki | |
15 | */ | |
16 | public class BufferedOutputStream extends OutputStream { | |
17 | /** The current position in the buffer. */ | |
18 | protected int start; | |
19 | /** The index of the last usable position of the buffer. */ | |
20 | protected int stop; | |
21 | /** The buffer itself. */ | |
22 | protected byte[] buffer; | |
23 | /** An End-Of-File (or buffer, here) marker. */ | |
24 | protected boolean eof; | |
c8ce09c4 NR |
25 | /** The actual under-laying stream. */ |
26 | protected OutputStream out; | |
27 | /** The number of bytes written to the under-laying stream. */ | |
28 | protected long bytesWritten; | |
29 | /** | |
30 | * Can bypass the flush process for big writes (will directly write to the | |
31 | * under-laying buffer if the array to write is > the internal buffer | |
32 | * size). | |
33 | * <p> | |
34 | * By default, this is true. | |
35 | */ | |
36 | protected boolean bypassFlush = true; | |
a26188d3 NR |
37 | |
38 | private boolean closed; | |
a26188d3 | 39 | private int openCounter; |
617ad86c | 40 | private byte[] b1; |
a26188d3 | 41 | |
a26188d3 NR |
42 | /** |
43 | * Create a new {@link BufferedInputStream} that wraps the given | |
44 | * {@link InputStream}. | |
45 | * | |
46 | * @param out | |
47 | * the {@link OutputStream} to wrap | |
48 | */ | |
49 | public BufferedOutputStream(OutputStream out) { | |
50 | this.out = out; | |
51 | ||
52 | this.buffer = new byte[4096]; | |
617ad86c | 53 | this.b1 = new byte[1]; |
a26188d3 NR |
54 | this.start = 0; |
55 | this.stop = 0; | |
56 | } | |
57 | ||
58 | @Override | |
59 | public void write(int b) throws IOException { | |
617ad86c NR |
60 | b1[0] = (byte) b; |
61 | write(b1, 0, 1); | |
a26188d3 NR |
62 | } |
63 | ||
64 | @Override | |
65 | public void write(byte[] b) throws IOException { | |
66 | write(b, 0, b.length); | |
67 | } | |
68 | ||
69 | @Override | |
70 | public void write(byte[] source, int sourceOffset, int sourceLength) | |
71 | throws IOException { | |
72 | ||
73 | checkClose(); | |
74 | ||
75 | if (source == null) { | |
76 | throw new NullPointerException(); | |
77 | } else if ((sourceOffset < 0) || (sourceOffset > source.length) | |
78 | || (sourceLength < 0) | |
79 | || ((sourceOffset + sourceLength) > source.length) | |
80 | || ((sourceOffset + sourceLength) < 0)) { | |
81 | throw new IndexOutOfBoundsException(); | |
82 | } else if (sourceLength == 0) { | |
83 | return; | |
84 | } | |
85 | ||
617ad86c | 86 | if (bypassFlush && sourceLength >= buffer.length) { |
a26188d3 NR |
87 | /* |
88 | * If the request length exceeds the size of the output buffer, | |
89 | * flush the output buffer and then write the data directly. In this | |
90 | * way buffered streams will cascade harmlessly. | |
91 | */ | |
92 | flush(false); | |
93 | out.write(source, sourceOffset, sourceLength); | |
617ad86c | 94 | bytesWritten += (sourceLength - sourceOffset); |
a26188d3 NR |
95 | return; |
96 | } | |
97 | ||
98 | int done = 0; | |
99 | while (done < sourceLength) { | |
100 | if (available() <= 0) { | |
101 | flush(false); | |
102 | } | |
103 | ||
617ad86c | 104 | int now = Math.min(sourceLength - done, available()); |
a26188d3 NR |
105 | System.arraycopy(source, sourceOffset + done, buffer, stop, now); |
106 | stop += now; | |
107 | done += now; | |
108 | } | |
109 | } | |
110 | ||
111 | /** | |
112 | * The available space in the buffer. | |
113 | * | |
114 | * @return the space in bytes | |
115 | */ | |
116 | private int available() { | |
117 | if (closed) { | |
118 | return 0; | |
119 | } | |
120 | ||
121 | return Math.max(0, buffer.length - stop - 1); | |
122 | } | |
123 | ||
124 | /** | |
125 | * The number of bytes written to the under-laying {@link OutputStream}. | |
126 | * | |
127 | * @return the number of bytes | |
128 | */ | |
129 | public long getBytesWritten() { | |
130 | return bytesWritten; | |
131 | } | |
132 | ||
133 | /** | |
134 | * Return this very same {@link BufferedInputStream}, but keep a counter of | |
135 | * how many streams were open this way. When calling | |
136 | * {@link BufferedInputStream#close()}, decrease this counter if it is not | |
137 | * already zero instead of actually closing the stream. | |
138 | * <p> | |
139 | * You are now responsible for it — you <b>must</b> close it. | |
140 | * <p> | |
141 | * This method allows you to use a wrapping stream around this one and still | |
142 | * close the wrapping stream. | |
143 | * | |
144 | * @return the same stream, but you are now responsible for closing it | |
145 | * | |
146 | * @throws IOException | |
147 | * in case of I/O error or if the stream is closed | |
148 | */ | |
149 | public synchronized OutputStream open() throws IOException { | |
150 | checkClose(); | |
151 | openCounter++; | |
152 | return this; | |
153 | } | |
154 | ||
155 | /** | |
156 | * Check that the stream was not closed, and throw an {@link IOException} if | |
157 | * it was. | |
158 | * | |
159 | * @throws IOException | |
160 | * if it was closed | |
161 | */ | |
162 | protected void checkClose() throws IOException { | |
163 | if (closed) { | |
164 | throw new IOException( | |
165 | "This BufferedInputStream was closed, you cannot use it anymore."); | |
166 | } | |
167 | } | |
168 | ||
169 | @Override | |
170 | public void flush() throws IOException { | |
171 | flush(true); | |
172 | } | |
173 | ||
174 | /** | |
c8ce09c4 NR |
175 | * Flush the {@link BufferedOutputStream}, write the current buffered data |
176 | * to (and optionally also flush) the under-laying stream. | |
177 | * <p> | |
178 | * If {@link BufferedOutputStream#bypassFlush} is false, all writes to the | |
179 | * under-laying stream are done in this method. | |
12784931 NR |
180 | * <p> |
181 | * This can be used if you want to write some data in the under-laying | |
182 | * stream yourself (in that case, flush this {@link BufferedOutputStream} | |
183 | * with or without flushing the under-laying stream, then you can write to | |
184 | * the under-laying stream). | |
a26188d3 NR |
185 | * |
186 | * @param includingSubStream | |
187 | * also flush the under-laying stream | |
188 | * @throws IOException | |
189 | * in case of I/O error | |
190 | */ | |
12784931 | 191 | public void flush(boolean includingSubStream) throws IOException { |
028ff7c2 NR |
192 | if (stop > start) { |
193 | out.write(buffer, start, stop - start); | |
194 | bytesWritten += (stop - start); | |
195 | } | |
a26188d3 NR |
196 | start = 0; |
197 | stop = 0; | |
c8ce09c4 | 198 | |
a26188d3 NR |
199 | if (includingSubStream) { |
200 | out.flush(); | |
201 | } | |
202 | } | |
203 | ||
204 | /** | |
205 | * Closes this stream and releases any system resources associated with the | |
206 | * stream. | |
207 | * <p> | |
208 | * Including the under-laying {@link InputStream}. | |
209 | * <p> | |
210 | * <b>Note:</b> if you called the {@link BufferedInputStream#open()} method | |
211 | * prior to this one, it will just decrease the internal count of how many | |
212 | * open streams it held and do nothing else. The stream will actually be | |
213 | * closed when you have called {@link BufferedInputStream#close()} once more | |
214 | * than {@link BufferedInputStream#open()}. | |
215 | * | |
216 | * @exception IOException | |
217 | * in case of I/O error | |
218 | */ | |
219 | @Override | |
220 | public synchronized void close() throws IOException { | |
221 | close(true); | |
222 | } | |
223 | ||
224 | /** | |
225 | * Closes this stream and releases any system resources associated with the | |
226 | * stream. | |
227 | * <p> | |
228 | * Including the under-laying {@link InputStream} if | |
229 | * <tt>incudingSubStream</tt> is true. | |
230 | * <p> | |
231 | * You can call this method multiple times, it will not cause an | |
232 | * {@link IOException} for subsequent calls. | |
233 | * <p> | |
234 | * <b>Note:</b> if you called the {@link BufferedInputStream#open()} method | |
235 | * prior to this one, it will just decrease the internal count of how many | |
236 | * open streams it held and do nothing else. The stream will actually be | |
237 | * closed when you have called {@link BufferedInputStream#close()} once more | |
238 | * than {@link BufferedInputStream#open()}. | |
239 | * | |
240 | * @param includingSubStream | |
241 | * also close the under-laying stream | |
242 | * | |
243 | * @exception IOException | |
244 | * in case of I/O error | |
245 | */ | |
246 | public synchronized void close(boolean includingSubStream) | |
247 | throws IOException { | |
248 | if (!closed) { | |
249 | if (openCounter > 0) { | |
250 | openCounter--; | |
251 | } else { | |
252 | closed = true; | |
617ad86c | 253 | flush(includingSubStream); |
a26188d3 NR |
254 | if (includingSubStream && out != null) { |
255 | out.close(); | |
256 | } | |
257 | } | |
258 | } | |
259 | } | |
260 | } |