View Javadoc
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 1535612 2013-10-25 02:42:12Z brentworden $
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         return toBufferedInputStream(input, 1024);
291     }
292 
293     /**
294      * Fetches entire contents of an <code>InputStream</code> and represent
295      * same data as result InputStream.
296      * <p>
297      * This method is useful where,
298      * <ul>
299      * <li>Source InputStream is slow.</li>
300      * <li>It has network resources associated, so we cannot keep it open for
301      * long time.</li>
302      * <li>It has network timeout associated.</li>
303      * </ul>
304      * It can be used in favor of {@link #toByteArray()}, since it
305      * avoids unnecessary allocation and copy of byte[].<br>
306      * This method buffers the input internally, so there is no need to use a
307      * <code>BufferedInputStream</code>.
308      *
309      * @param input Stream to be fully buffered.
310      * @param size the initial buffer size
311      * @return A fully buffered stream.
312      * @throws IOException if an I/O error occurs
313      * @since 2.5
314      */
315     public static InputStream toBufferedInputStream(final InputStream input, int size)
316             throws IOException {
317         // It does not matter if a ByteArrayOutputStream is not closed as close() is a no-op
318         @SuppressWarnings("resource")
319         final ByteArrayOutputStream output = new ByteArrayOutputStream(size);
320         output.write(input);
321         return output.toInputStream();
322     }
323 
324     /**
325      * Gets the current contents of this byte stream as a Input Stream. The
326      * returned stream is backed by buffers of <code>this</code> stream,
327      * avoiding memory allocation and copy, thus saving space and time.<br>
328      *
329      * @return the current contents of this output stream.
330      * @see java.io.ByteArrayOutputStream#toByteArray()
331      * @see #reset()
332      * @since 2.5
333      */
334     public synchronized InputStream toInputStream() {
335         int remaining = count;
336         if (remaining == 0) {
337             return new ClosedInputStream();
338         }
339         final List<ByteArrayInputStream> list = new ArrayList<ByteArrayInputStream>(buffers.size());
340         for (final byte[] buf : buffers) {
341             final int c = Math.min(buf.length, remaining);
342             list.add(new ByteArrayInputStream(buf, 0, c));
343             remaining -= c;
344             if (remaining == 0) {
345                 break;
346             }
347         }
348         reuseBuffers = false;
349         return new SequenceInputStream(Collections.enumeration(list));
350     }
351 
352     /**
353      * Gets the curent contents of this byte stream as a byte array.
354      * The result is independent of this stream.
355      *
356      * @return the current contents of this output stream, as a byte array
357      * @see java.io.ByteArrayOutputStream#toByteArray()
358      */
359     public synchronized byte[] toByteArray() {
360         int remaining = count;
361         if (remaining == 0) {
362             return EMPTY_BYTE_ARRAY;
363         }
364         final byte newbuf[] = new byte[remaining];
365         int pos = 0;
366         for (final byte[] buf : buffers) {
367             final int c = Math.min(buf.length, remaining);
368             System.arraycopy(buf, 0, newbuf, pos, c);
369             pos += c;
370             remaining -= c;
371             if (remaining == 0) {
372                 break;
373             }
374         }
375         return newbuf;
376     }
377 
378     /**
379      * Gets the curent contents of this byte stream as a string
380      * using the platform default charset.
381      * @return the contents of the byte array as a String
382      * @see java.io.ByteArrayOutputStream#toString()
383      * @deprecated 2.5 use {@link #toString(String)} instead
384      */
385     @Override
386     @Deprecated
387     public String toString() {
388         // make explicit the use of the default charset
389         return new String(toByteArray(), Charset.defaultCharset());
390     }
391 
392     /**
393      * Gets the curent contents of this byte stream as a string
394      * using the specified encoding.
395      *
396      * @param enc  the name of the character encoding
397      * @return the string converted from the byte array
398      * @throws UnsupportedEncodingException if the encoding is not supported
399      * @see java.io.ByteArrayOutputStream#toString(String)
400      */
401     public String toString(final String enc) throws UnsupportedEncodingException {
402         return new String(toByteArray(), enc);
403     }
404 
405     /**
406      * Gets the curent contents of this byte stream as a string
407      * using the specified encoding.
408      *
409      * @param charset  the character encoding
410      * @return the string converted from the byte array
411      * @see java.io.ByteArrayOutputStream#toString(String)
412      * @since 2.5
413      */
414     public String toString(final Charset charset) {
415         return new String(toByteArray(), charset);
416     }
417 
418 }