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