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 buffers = new ArrayList();
     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 (byte[]) 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     public void write(byte[] b, int off, int len) {
    140         if ((off < 0)
    141                 || (off > b.length)
    142                 || (len < 0)
    143                 || ((off + len) > b.length)
    144                 || ((off + len) < 0)) {
    145             throw new IndexOutOfBoundsException();
    146         } else if (len == 0) {
    147             return;
    148         }
    149         synchronized (this) {
    150             int newcount = count + len;
    151             int remaining = len;
    152             int inBufferPos = count - filledBufferSum;
    153             while (remaining > 0) {
    154                 int part = Math.min(remaining, currentBuffer.length - inBufferPos);
    155                 System.arraycopy(b, off + len - remaining, currentBuffer, inBufferPos, part);
    156                 remaining -= part;
    157                 if (remaining > 0) {
    158                     needNewBuffer(newcount);
    159                     inBufferPos = 0;
    160                 }
    161             }
    162             count = newcount;
    163         }
    164     }
    165 
    166     /**
    167      * Write a byte to byte array.
    168      * @param b the byte to write
    169      */
    170     public synchronized void write(int b) {
    171         int inBufferPos = count - filledBufferSum;
    172         if (inBufferPos == currentBuffer.length) {
    173             needNewBuffer(count + 1);
    174             inBufferPos = 0;
    175         }
    176         currentBuffer[inBufferPos] = (byte) b;
    177         count++;
    178     }
    179 
    180     /**
    181      * Writes the entire contents of the specified input stream to this
    182      * byte stream. Bytes from the input stream are read directly into the
    183      * internal buffers of this streams.
    184      *
    185      * @param in the input stream to read from
    186      * @return total number of bytes read from the input stream
    187      *         (and written to this stream)
    188      * @throws IOException if an I/O error occurs while reading the input stream
    189      * @since Commons IO 1.4
    190      */
    191     public synchronized int write(InputStream in) throws IOException {
    192         int readCount = 0;
    193         int inBufferPos = count - filledBufferSum;
    194         int n = in.read(currentBuffer, inBufferPos, currentBuffer.length - inBufferPos);
    195         while (n != -1) {
    196             readCount += n;
    197             inBufferPos += n;
    198             count += n;
    199             if (inBufferPos == currentBuffer.length) {
    200                 needNewBuffer(currentBuffer.length);
    201                 inBufferPos = 0;
    202             }
    203             n = in.read(currentBuffer, inBufferPos, currentBuffer.length - inBufferPos);
    204         }
    205         return readCount;
    206     }
    207 
    208     /**
    209      * Return the current size of the byte array.
    210      * @return the current size of the byte array
    211      */
    212     public synchronized int size() {
    213         return count;
    214     }
    215 
    216     /**
    217      * Closing a <tt>ByteArrayOutputStream</tt> has no effect. The methods in
    218      * this class can be called after the stream has been closed without
    219      * generating an <tt>IOException</tt>.
    220      *
    221      * @throws IOException never (this method should not declare this exception
    222      * but it has to now due to backwards compatability)
    223      */
    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         currentBuffer = getBuffer(currentBufferIndex);
    236     }
    237 
    238     /**
    239      * Writes the entire contents of this byte stream to the
    240      * specified output stream.
    241      *
    242      * @param out  the output stream to write to
    243      * @throws IOException if an I/O error occurs, such as if the stream is closed
    244      * @see java.io.ByteArrayOutputStream#writeTo(OutputStream)
    245      */
    246     public synchronized void writeTo(OutputStream out) throws IOException {
    247         int remaining = count;
    248         for (int i = 0; i < buffers.size(); i++) {
    249             byte[] buf = getBuffer(i);
    250             int c = Math.min(buf.length, remaining);
    251             out.write(buf, 0, c);
    252             remaining -= c;
    253             if (remaining == 0) {
    254                 break;
    255             }
    256         }
    257     }
    258 
    259     /**
    260      * Gets the curent contents of this byte stream as a byte array.
    261      * The result is independent of this stream.
    262      *
    263      * @return the current contents of this output stream, as a byte array
    264      * @see java.io.ByteArrayOutputStream#toByteArray()
    265      */
    266     public synchronized byte[] toByteArray() {
    267         int remaining = count;
    268         if (remaining == 0) {
    269             return EMPTY_BYTE_ARRAY;
    270         }
    271         byte newbuf[] = new byte[remaining];
    272         int pos = 0;
    273         for (int i = 0; i < buffers.size(); i++) {
    274             byte[] buf = getBuffer(i);
    275             int c = Math.min(buf.length, remaining);
    276             System.arraycopy(buf, 0, newbuf, pos, c);
    277             pos += c;
    278             remaining -= c;
    279             if (remaining == 0) {
    280                 break;
    281             }
    282         }
    283         return newbuf;
    284     }
    285 
    286     /**
    287      * Gets the curent contents of this byte stream as a string.
    288      * @return the contents of the byte array as a String
    289      * @see java.io.ByteArrayOutputStream#toString()
    290      */
    291     public String toString() {
    292         return new String(toByteArray());
    293     }
    294 
    295     /**
    296      * Gets the curent contents of this byte stream as a string
    297      * using the specified encoding.
    298      *
    299      * @param enc  the name of the character encoding
    300      * @return the string converted from the byte array
    301      * @throws UnsupportedEncodingException if the encoding is not supported
    302      * @see java.io.ByteArrayOutputStream#toString(String)
    303      */
    304     public String toString(String enc) throws UnsupportedEncodingException {
    305         return new String(toByteArray(), enc);
    306     }
    307 
    308 }
    309