new: ReplaceOutputStream
[nikiroo-utils.git] / src / be / nikiroo / utils / streams / BufferedOutputStream.java
CommitLineData
8e76f6ab 1package be.nikiroo.utils.streams;
a26188d3
NR
2
3import java.io.IOException;
4import java.io.InputStream;
5import 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 */
16public 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 &gt; 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 &mdash; 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}