1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.apache.commons.io.output;
18
19 import java.io.ByteArrayInputStream;
20 import java.io.IOException;
21 import java.io.InputStream;
22 import java.io.OutputStream;
23 import java.io.SequenceInputStream;
24 import java.io.UnsupportedEncodingException;
25 import java.nio.charset.Charset;
26 import java.util.ArrayList;
27 import java.util.Collections;
28 import java.util.List;
29
30 import org.apache.commons.io.input.ClosedInputStream;
31
32 /**
33 * This class implements an output stream in which the data is
34 * written into a byte array. The buffer automatically grows as data
35 * is written to it.
36 * <p>
37 * The data can be retrieved using <code>toByteArray()</code> and
38 * <code>toString()</code>.
39 * <p>
40 * Closing a <tt>ByteArrayOutputStream</tt> has no effect. The methods in
41 * this class can be called after the stream has been closed without
42 * generating an <tt>IOException</tt>.
43 * <p>
44 * This is an alternative implementation of the {@link java.io.ByteArrayOutputStream}
45 * class. The original implementation only allocates 32 bytes at the beginning.
46 * As this class is designed for heavy duty it starts at 1024 bytes. In contrast
47 * to the original it doesn't reallocate the whole memory block but allocates
48 * additional buffers. This way no buffers need to be garbage collected and
49 * the contents don't have to be copied to the new buffer. This class is
50 * designed to behave exactly like the original. The only exception is the
51 * deprecated toString(int) method that has been ignored.
52 *
53 * @version $Id: ByteArrayOutputStream.java 1471767 2013-04-24 23:24:19Z sebb $
54 */
55 public class ByteArrayOutputStream extends OutputStream {
56
57 /** A singleton empty byte array. */
58 private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
59
60 /** The list of buffers, which grows and never reduces. */
61 private final List<byte[]> buffers = new ArrayList<byte[]>();
62 /** The index of the current buffer. */
63 private int currentBufferIndex;
64 /** The total count of bytes in all the filled buffers. */
65 private int filledBufferSum;
66 /** The current buffer. */
67 private byte[] currentBuffer;
68 /** The total count of bytes written. */
69 private int count;
70 /** Flag to indicate if the buffers can be reused after reset */
71 private boolean reuseBuffers = true;
72
73 /**
74 * Creates a new byte array output stream. The buffer capacity is
75 * initially 1024 bytes, though its size increases if necessary.
76 */
77 public ByteArrayOutputStream() {
78 this(1024);
79 }
80
81 /**
82 * Creates a new byte array output stream, with a buffer capacity of
83 * the specified size, in bytes.
84 *
85 * @param size the initial size
86 * @throws IllegalArgumentException if size is negative
87 */
88 public ByteArrayOutputStream(final int size) {
89 if (size < 0) {
90 throw new IllegalArgumentException(
91 "Negative initial size: " + size);
92 }
93 synchronized (this) {
94 needNewBuffer(size);
95 }
96 }
97
98 /**
99 * Makes a new buffer available either by allocating
100 * a new one or re-cycling an existing one.
101 *
102 * @param newcount the size of the buffer if one is created
103 */
104 private void needNewBuffer(final int newcount) {
105 if (currentBufferIndex < buffers.size() - 1) {
106 //Recycling old buffer
107 filledBufferSum += currentBuffer.length;
108
109 currentBufferIndex++;
110 currentBuffer = buffers.get(currentBufferIndex);
111 } else {
112 //Creating new buffer
113 int newBufferSize;
114 if (currentBuffer == null) {
115 newBufferSize = newcount;
116 filledBufferSum = 0;
117 } else {
118 newBufferSize = Math.max(
119 currentBuffer.length << 1,
120 newcount - filledBufferSum);
121 filledBufferSum += currentBuffer.length;
122 }
123
124 currentBufferIndex++;
125 currentBuffer = new byte[newBufferSize];
126 buffers.add(currentBuffer);
127 }
128 }
129
130 /**
131 * Write the bytes to byte array.
132 * @param b the bytes to write
133 * @param off The start offset
134 * @param len The number of bytes to write
135 */
136 @Override
137 public void write(final byte[] b, final int off, final int len) {
138 if ((off < 0)
139 || (off > b.length)
140 || (len < 0)
141 || ((off + len) > b.length)
142 || ((off + len) < 0)) {
143 throw new IndexOutOfBoundsException();
144 } else if (len == 0) {
145 return;
146 }
147 synchronized (this) {
148 final int newcount = count + len;
149 int remaining = len;
150 int inBufferPos = count - filledBufferSum;
151 while (remaining > 0) {
152 final int part = Math.min(remaining, currentBuffer.length - inBufferPos);
153 System.arraycopy(b, off + len - remaining, currentBuffer, inBufferPos, part);
154 remaining -= part;
155 if (remaining > 0) {
156 needNewBuffer(newcount);
157 inBufferPos = 0;
158 }
159 }
160 count = newcount;
161 }
162 }
163
164 /**
165 * Write a byte to byte array.
166 * @param b the byte to write
167 */
168 @Override
169 public synchronized void write(final int b) {
170 int inBufferPos = count - filledBufferSum;
171 if (inBufferPos == currentBuffer.length) {
172 needNewBuffer(count + 1);
173 inBufferPos = 0;
174 }
175 currentBuffer[inBufferPos] = (byte) b;
176 count++;
177 }
178
179 /**
180 * Writes the entire contents of the specified input stream to this
181 * byte stream. Bytes from the input stream are read directly into the
182 * internal buffers of this streams.
183 *
184 * @param in the input stream to read from
185 * @return total number of bytes read from the input stream
186 * (and written to this stream)
187 * @throws IOException if an I/O error occurs while reading the input stream
188 * @since 1.4
189 */
190 public synchronized int write(final InputStream in) throws IOException {
191 int readCount = 0;
192 int inBufferPos = count - filledBufferSum;
193 int n = in.read(currentBuffer, inBufferPos, currentBuffer.length - inBufferPos);
194 while (n != -1) {
195 readCount += n;
196 inBufferPos += n;
197 count += n;
198 if (inBufferPos == currentBuffer.length) {
199 needNewBuffer(currentBuffer.length);
200 inBufferPos = 0;
201 }
202 n = in.read(currentBuffer, inBufferPos, currentBuffer.length - inBufferPos);
203 }
204 return readCount;
205 }
206
207 /**
208 * Return the current size of the byte array.
209 * @return the current size of the byte array
210 */
211 public synchronized int size() {
212 return count;
213 }
214
215 /**
216 * Closing a <tt>ByteArrayOutputStream</tt> has no effect. The methods in
217 * this class can be called after the stream has been closed without
218 * generating an <tt>IOException</tt>.
219 *
220 * @throws IOException never (this method should not declare this exception
221 * but it has to now due to backwards compatibility)
222 */
223 @Override
224 public void close() throws IOException {
225 //nop
226 }
227
228 /**
229 * @see java.io.ByteArrayOutputStream#reset()
230 */
231 public synchronized void reset() {
232 count = 0;
233 filledBufferSum = 0;
234 currentBufferIndex = 0;
235 if (reuseBuffers) {
236 currentBuffer = buffers.get(currentBufferIndex);
237 } else {
238 //Throw away old buffers
239 currentBuffer = null;
240 int size = buffers.get(0).length;
241 buffers.clear();
242 needNewBuffer(size);
243 reuseBuffers = true;
244 }
245 }
246
247 /**
248 * Writes the entire contents of this byte stream to the
249 * specified output stream.
250 *
251 * @param out the output stream to write to
252 * @throws IOException if an I/O error occurs, such as if the stream is closed
253 * @see java.io.ByteArrayOutputStream#writeTo(OutputStream)
254 */
255 public synchronized void writeTo(final OutputStream out) throws IOException {
256 int remaining = count;
257 for (final byte[] buf : buffers) {
258 final int c = Math.min(buf.length, remaining);
259 out.write(buf, 0, c);
260 remaining -= c;
261 if (remaining == 0) {
262 break;
263 }
264 }
265 }
266
267 /**
268 * Fetches entire contents of an <code>InputStream</code> and represent
269 * same data as result InputStream.
270 * <p>
271 * This method is useful where,
272 * <ul>
273 * <li>Source InputStream is slow.</li>
274 * <li>It has network resources associated, so we cannot keep it open for
275 * long time.</li>
276 * <li>It has network timeout associated.</li>
277 * </ul>
278 * It can be used in favor of {@link #toByteArray()}, since it
279 * avoids unnecessary allocation and copy of byte[].<br>
280 * This method buffers the input internally, so there is no need to use a
281 * <code>BufferedInputStream</code>.
282 *
283 * @param input Stream to be fully buffered.
284 * @return A fully buffered stream.
285 * @throws IOException if an I/O error occurs
286 * @since 2.0
287 */
288 public static InputStream toBufferedInputStream(final InputStream input)
289 throws IOException {
290 // It does not matter if a ByteArrayOutputStream is not closed as close() is a no-op
291 @SuppressWarnings("resource")
292 final ByteArrayOutputStream output = new ByteArrayOutputStream();
293 output.write(input);
294 return output.toInputStream();
295 }
296
297 /**
298 * Gets the current contents of this byte stream as a Input Stream. The
299 * returned stream is backed by buffers of <code>this</code> stream,
300 * avoiding memory allocation and copy, thus saving space and time.<br>
301 *
302 * @return the current contents of this output stream.
303 * @see java.io.ByteArrayOutputStream#toByteArray()
304 * @see #reset()
305 * @since 2.5
306 */
307 public synchronized InputStream toInputStream() {
308 int remaining = count;
309 if (remaining == 0) {
310 return new ClosedInputStream();
311 }
312 final List<ByteArrayInputStream> list = new ArrayList<ByteArrayInputStream>(buffers.size());
313 for (final byte[] buf : buffers) {
314 final int c = Math.min(buf.length, remaining);
315 list.add(new ByteArrayInputStream(buf, 0, c));
316 remaining -= c;
317 if (remaining == 0) {
318 break;
319 }
320 }
321 reuseBuffers = false;
322 return new SequenceInputStream(Collections.enumeration(list));
323 }
324
325 /**
326 * Gets the curent contents of this byte stream as a byte array.
327 * The result is independent of this stream.
328 *
329 * @return the current contents of this output stream, as a byte array
330 * @see java.io.ByteArrayOutputStream#toByteArray()
331 */
332 public synchronized byte[] toByteArray() {
333 int remaining = count;
334 if (remaining == 0) {
335 return EMPTY_BYTE_ARRAY;
336 }
337 final byte newbuf[] = new byte[remaining];
338 int pos = 0;
339 for (final byte[] buf : buffers) {
340 final int c = Math.min(buf.length, remaining);
341 System.arraycopy(buf, 0, newbuf, pos, c);
342 pos += c;
343 remaining -= c;
344 if (remaining == 0) {
345 break;
346 }
347 }
348 return newbuf;
349 }
350
351 /**
352 * Gets the curent contents of this byte stream as a string
353 * using the platform default charset.
354 * @return the contents of the byte array as a String
355 * @see java.io.ByteArrayOutputStream#toString()
356 * @deprecated 2.5 use {@link #toString(String)} instead
357 */
358 @Override
359 @Deprecated
360 public String toString() {
361 // make explicit the use of the default charset
362 return new String(toByteArray(), Charset.defaultCharset());
363 }
364
365 /**
366 * Gets the curent contents of this byte stream as a string
367 * using the specified encoding.
368 *
369 * @param enc the name of the character encoding
370 * @return the string converted from the byte array
371 * @throws UnsupportedEncodingException if the encoding is not supported
372 * @see java.io.ByteArrayOutputStream#toString(String)
373 */
374 public String toString(final String enc) throws UnsupportedEncodingException {
375 return new String(toByteArray(), enc);
376 }
377
378 /**
379 * Gets the curent contents of this byte stream as a string
380 * using the specified encoding.
381 *
382 * @param charset the character encoding
383 * @return the string converted from the byte array
384 * @see java.io.ByteArrayOutputStream#toString(String)
385 * @since 2.5
386 */
387 public String toString(final Charset charset) {
388 return new String(toByteArray(), charset);
389 }
390
391 }