Coverage Report - org.codehaus.plexus.archiver.zip.ByteArrayOutputStream
 
Classes in this File Line Coverage Branch Coverage Complexity
ByteArrayOutputStream
42%
50/119
41%
19/46
2.647
 
 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  1
     private static final byte[] EMPTY_BYTE_ARRAY = new byte[ 0 ];
 59  
 
 60  
     /**
 61  
      * The list of buffers, which grows and never reduces.
 62  
      */
 63  343
     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  343
     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  0
         this( 1024 );
 97  0
     }
 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  343
     {
 109  343
         if ( size < 0 )
 110  
         {
 111  0
             throw new IllegalArgumentException( "Negative initial size: " + size );
 112  
         }
 113  343
         synchronized ( this )
 114  
         {
 115  343
             needNewBuffer( size );
 116  343
         }
 117  343
     }
 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  347
         if ( currentBufferIndex < buffers.size() - 1 )
 128  
         {
 129  
             //Recycling old buffer
 130  0
             filledBufferSum += currentBuffer.length;
 131  
 
 132  0
             currentBufferIndex++;
 133  0
             currentBuffer = buffers.get( currentBufferIndex );
 134  
         }
 135  
         else
 136  
         {
 137  
             //Creating new buffer
 138  
             int newBufferSize;
 139  347
             if ( currentBuffer == null )
 140  
             {
 141  343
                 newBufferSize = newcount;
 142  343
                 filledBufferSum = 0;
 143  
             }
 144  
             else
 145  
             {
 146  4
                 newBufferSize = Math.max(
 147  
                     currentBuffer.length << 1,
 148  
                     newcount - filledBufferSum );
 149  4
                 filledBufferSum += currentBuffer.length;
 150  
             }
 151  
 
 152  347
             currentBufferIndex++;
 153  347
             currentBuffer = new byte[ newBufferSize ];
 154  347
             buffers.add( currentBuffer );
 155  
         }
 156  347
     }
 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  47700
         if ( ( off < 0 )
 169  
                  || ( off > b.length )
 170  
                  || ( len < 0 )
 171  
                  || ( ( off + len ) > b.length )
 172  
                  || ( ( off + len ) < 0 ) )
 173  
         {
 174  0
             throw new IndexOutOfBoundsException();
 175  
         }
 176  47789
         else if ( len == 0 )
 177  
         {
 178  0
             return;
 179  
         }
 180  47813
         synchronized ( this )
 181  
         {
 182  47795
             final int newcount = count + len;
 183  47838
             int remaining = len;
 184  47848
             int inBufferPos = count - filledBufferSum;
 185  95635
             while ( remaining > 0 )
 186  
             {
 187  47840
                 final int part = Math.min( remaining, currentBuffer.length - inBufferPos );
 188  47846
                 System.arraycopy( b, off + len - remaining, currentBuffer, inBufferPos, part );
 189  47793
                 remaining -= part;
 190  47797
                 if ( remaining > 0 )
 191  
                 {
 192  4
                     needNewBuffer( newcount );
 193  4
                     inBufferPos = 0;
 194  
                 }
 195  47794
             }
 196  47805
             count = newcount;
 197  47799
         }
 198  47828
     }
 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  0
         int inBufferPos = count - filledBufferSum;
 209  0
         if ( inBufferPos == currentBuffer.length )
 210  
         {
 211  0
             needNewBuffer( count + 1 );
 212  0
             inBufferPos = 0;
 213  
         }
 214  0
         currentBuffer[inBufferPos] = (byte) b;
 215  0
         count++;
 216  0
     }
 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  0
         int readCount = 0;
 234  0
         int inBufferPos = count - filledBufferSum;
 235  0
         int n = in.read( currentBuffer, inBufferPos, currentBuffer.length - inBufferPos );
 236  0
         while ( n != -1 )
 237  
         {
 238  0
             readCount += n;
 239  0
             inBufferPos += n;
 240  0
             count += n;
 241  0
             if ( inBufferPos == currentBuffer.length )
 242  
             {
 243  0
                 needNewBuffer( currentBuffer.length );
 244  0
                 inBufferPos = 0;
 245  
             }
 246  0
             n = in.read( currentBuffer, inBufferPos, currentBuffer.length - inBufferPos );
 247  
         }
 248  0
         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  0
         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  686
     }
 274  
 
 275  
     /**
 276  
      * @see java.io.ByteArrayOutputStream#reset()
 277  
      */
 278  
     public synchronized void reset()
 279  
     {
 280  0
         count = 0;
 281  0
         filledBufferSum = 0;
 282  0
         currentBufferIndex = 0;
 283  0
         if ( reuseBuffers )
 284  
         {
 285  0
             currentBuffer = buffers.get( currentBufferIndex );
 286  
         }
 287  
         else
 288  
         {
 289  
             //Throw away old buffers
 290  0
             currentBuffer = null;
 291  0
             int size = buffers.get( 0 ).length;
 292  0
             buffers.clear();
 293  0
             needNewBuffer( size );
 294  0
             reuseBuffers = true;
 295  
         }
 296  0
     }
 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  0
         int remaining = count;
 310  0
         for ( final byte[] buf : buffers )
 311  
         {
 312  0
             final int c = Math.min( buf.length, remaining );
 313  0
             out.write( buf, 0, c );
 314  0
             remaining -= c;
 315  0
             if ( remaining == 0 )
 316  
             {
 317  0
                 break;
 318  
             }
 319  0
         }
 320  0
     }
 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  0
         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  0
         final ByteArrayOutputStream output = new ByteArrayOutputStream( size );
 381  0
         output.write( input );
 382  0
         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  343
         int remaining = count;
 399  343
         if ( remaining == 0 )
 400  
         {
 401  184
             return new ClosedInputStream();
 402  
         }
 403  159
         final List<ByteArrayInputStream> list = new ArrayList<ByteArrayInputStream>( buffers.size() );
 404  159
         for ( final byte[] buf : buffers )
 405  
         {
 406  163
             final int c = Math.min( buf.length, remaining );
 407  163
             list.add( new ByteArrayInputStream( buf, 0, c ) );
 408  163
             remaining -= c;
 409  163
             if ( remaining == 0 )
 410  
             {
 411  159
                 break;
 412  
             }
 413  4
         }
 414  159
         reuseBuffers = false;
 415  159
         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  0
         int remaining = count;
 429  0
         if ( remaining == 0 )
 430  
         {
 431  0
             return EMPTY_BYTE_ARRAY;
 432  
         }
 433  0
         final byte newbuf[] = new byte[ remaining ];
 434  0
         int pos = 0;
 435  0
         for ( final byte[] buf : buffers )
 436  
         {
 437  0
             final int c = Math.min( buf.length, remaining );
 438  0
             System.arraycopy( buf, 0, newbuf, pos, c );
 439  0
             pos += c;
 440  0
             remaining -= c;
 441  0
             if ( remaining == 0 )
 442  
             {
 443  0
                 break;
 444  
             }
 445  0
         }
 446  0
         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  0
         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  0
         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  0
         return new String( toByteArray(), charset );
 496  
     }
 497  
 
 498  
 }