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