Home | History | Annotate | Download | only in output
      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.IOException;
     20 import java.io.InputStream;
     21 import java.io.OutputStream;
     22 import java.io.UnsupportedEncodingException;
     23 import java.util.ArrayList;
     24 import java.util.List;
     25 
     26 /**
     27  * This class implements an output stream in which the data is
     28  * written into a byte array. The buffer automatically grows as data
     29  * is written to it.
     30  * <p>
     31  * The data can be retrieved using <code>toByteArray()</code> and
     32  * <code>toString()</code>.
     33  * <p>
     34  * Closing a <tt>ByteArrayOutputStream</tt> has no effect. The methods in
     35  * this class can be called after the stream has been closed without
     36  * generating an <tt>IOException</tt>.
     37  * <p>
     38  * This is an alternative implementation of the java.io.ByteArrayOutputStream
     39  * class. The original implementation only allocates 32 bytes at the beginning.
     40  * As this class is designed for heavy duty it starts at 1024 bytes. In contrast
     41  * to the original it doesn't reallocate the whole memory block but allocates
     42  * additional buffers. This way no buffers need to be garbage collected and
     43  * the contents don't have to be copied to the new buffer. This class is
     44  * designed to behave exactly like the original. The only exception is the
     45  * deprecated toString(int) method that has been ignored.
     46  *
     47  * @author <a href="mailto:jeremias (at) apache.org">Jeremias Maerki</a>
     48  * @author Holger Hoffstatte
     49  * @version $Id: ByteArrayOutputStream.java 610010 2008-01-08 14:50:59Z niallp $
     50  */
     51 public class ByteArrayOutputStream extends OutputStream {
     52 
     53     /** A singleton empty byte array. */
     54     private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
     55 
     56     /** The list of buffers, which grows and never reduces. */
     57     private List<byte[]> buffers = new ArrayList<byte[]>();
     58     /** The index of the current buffer. */
     59     private int currentBufferIndex;
     60     /** The total count of bytes in all the filled buffers. */
     61     private int filledBufferSum;
     62     /** The current buffer. */
     63     private byte[] currentBuffer;
     64     /** The total count of bytes written. */
     65     private int count;
     66 
     67     /**
     68      * Creates a new byte array output stream. The buffer capacity is
     69      * initially 1024 bytes, though its size increases if necessary.
     70      */
     71     public ByteArrayOutputStream() {
     72         this(1024);
     73     }
     74 
     75     /**
     76      * Creates a new byte array output stream, with a buffer capacity of
     77      * the specified size, in bytes.
     78      *
     79      * @param size  the initial size
     80      * @throws IllegalArgumentException if size is negative
     81      */
     82     public ByteArrayOutputStream(int size) {
     83         if (size < 0) {
     84             throw new IllegalArgumentException(
     85                 "Negative initial size: " + size);
     86         }
     87         needNewBuffer(size);
     88     }
     89 
     90     /**
     91      * Return the appropriate <code>byte[]</code> buffer
     92      * specified by index.
     93      *
     94      * @param index  the index of the buffer required
     95      * @return the buffer
     96      */
     97     private byte[] getBuffer(int index) {
     98         return buffers.get(index);
     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(int newcount) {
    108         if (currentBufferIndex < buffers.size() - 1) {
    109             //Recycling old buffer
    110             filledBufferSum += currentBuffer.length;
    111 
    112             currentBufferIndex++;
    113             currentBuffer = getBuffer(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(byte[] b, int off, 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             int newcount = count + len;
    152             int remaining = len;
    153             int inBufferPos = count - filledBufferSum;
    154             while (remaining > 0) {
    155                 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(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 Commons IO 1.4
    192      */
    193     public synchronized int write(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 != -1) {
    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 <tt>ByteArrayOutputStream</tt> has no effect. The methods in
    220      * this class can be called after the stream has been closed without
    221      * generating an <tt>IOException</tt>.
    222      *
    223      * @throws IOException never (this method should not declare this exception
    224      * but it has to now due to backwards compatability)
    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         currentBuffer = getBuffer(currentBufferIndex);
    239     }
    240 
    241     /**
    242      * Writes the entire contents of this byte stream to the
    243      * specified output stream.
    244      *
    245      * @param out  the output stream to write to
    246      * @throws IOException if an I/O error occurs, such as if the stream is closed
    247      * @see java.io.ByteArrayOutputStream#writeTo(OutputStream)
    248      */
    249     public synchronized void writeTo(OutputStream out) throws IOException {
    250         int remaining = count;
    251         for (int i = 0; i < buffers.size(); i++) {
    252             byte[] buf = getBuffer(i);
    253             int c = Math.min(buf.length, remaining);
    254             out.write(buf, 0, c);
    255             remaining -= c;
    256             if (remaining == 0) {
    257                 break;
    258             }
    259         }
    260     }
    261 
    262     /**
    263      * Gets the curent contents of this byte stream as a byte array.
    264      * The result is independent of this stream.
    265      *
    266      * @return the current contents of this output stream, as a byte array
    267      * @see java.io.ByteArrayOutputStream#toByteArray()
    268      */
    269     public synchronized byte[] toByteArray() {
    270         int remaining = count;
    271         if (remaining == 0) {
    272             return EMPTY_BYTE_ARRAY;
    273         }
    274         byte newbuf[] = new byte[remaining];
    275         int pos = 0;
    276         for (int i = 0; i < buffers.size(); i++) {
    277             byte[] buf = getBuffer(i);
    278             int c = Math.min(buf.length, remaining);
    279             System.arraycopy(buf, 0, newbuf, pos, c);
    280             pos += c;
    281             remaining -= c;
    282             if (remaining == 0) {
    283                 break;
    284             }
    285         }
    286         return newbuf;
    287     }
    288 
    289     /**
    290      * Gets the curent contents of this byte stream as a string.
    291      * @return the contents of the byte array as a String
    292      * @see java.io.ByteArrayOutputStream#toString()
    293      */
    294     @Override
    295     public String toString() {
    296         return new String(toByteArray());
    297     }
    298 
    299     /**
    300      * Gets the curent contents of this byte stream as a string
    301      * using the specified encoding.
    302      *
    303      * @param enc  the name of the character encoding
    304      * @return the string converted from the byte array
    305      * @throws UnsupportedEncodingException if the encoding is not supported
    306      * @see java.io.ByteArrayOutputStream#toString(String)
    307      */
    308     public String toString(String enc) throws UnsupportedEncodingException {
    309         return new String(toByteArray(), enc);
    310     }
    311 
    312 }
    313