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