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; | |
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 — 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 | } |