throw IO when image is used on close
[fanfix.git] / streams / BufferedOutputStream.java
1 package be.nikiroo.utils.streams;
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;
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;
37
38 private boolean closed;
39 private int openCounter;
40 private byte[] b1;
41
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];
53 this.b1 = new byte[1];
54 this.start = 0;
55 this.stop = 0;
56 }
57
58 @Override
59 public void write(int b) throws IOException {
60 b1[0] = (byte) b;
61 write(b1, 0, 1);
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
86 if (bypassFlush && sourceLength >= buffer.length) {
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);
94 bytesWritten += (sourceLength - sourceOffset);
95 return;
96 }
97
98 int done = 0;
99 while (done < sourceLength) {
100 if (available() <= 0) {
101 flush(false);
102 }
103
104 int now = Math.min(sourceLength - done, available());
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 &mdash; 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 /**
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.
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).
185 *
186 * @param includingSubStream
187 * also flush the under-laying stream
188 * @throws IOException
189 * in case of I/O error
190 */
191 public void flush(boolean includingSubStream) throws IOException {
192 if (stop > start) {
193 out.write(buffer, start, stop - start);
194 bytesWritten += (stop - start);
195 }
196 start = 0;
197 stop = 0;
198
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;
253 flush(includingSubStream);
254 if (includingSubStream && out != null) {
255 out.close();
256 }
257 }
258 }
259 }
260 }