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