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