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