    001    /*
    002     * Licensed to the Apache Software Foundation (ASF) under one or more
    003     * contributor license agreements.  See the NOTICE file distributed with
    004     * this work for additional information regarding copyright ownership.
    005     * The ASF licenses this file to You under the Apache License, Version 2.0
    006     * (the "License"); you may not use this file except in compliance with
    007     * the License.  You may obtain a copy of the License at
    008     *
    009     *      http://www.apache.org/licenses/LICENSE-2.0
    010     *
    011     * Unless required by applicable law or agreed to in writing, software
    012     * distributed under the License is distributed on an "AS IS" BASIS,
    013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014     * See the License for the specific language governing permissions and
    015     * limitations under the License.
    016     */
    017    package org.apache.commons.io.output;
    018
    019    import java.io.ByteArrayInputStream;
    020    import java.io.IOException;
    021    import java.io.InputStream;
    022    import java.io.OutputStream;
    023    import java.io.SequenceInputStream;
    024    import java.io.UnsupportedEncodingException;
    025    import java.util.ArrayList;
    026    import java.util.Collections;
    027    import java.util.List;
    028
    029    import org.apache.commons.io.input.ClosedInputStream;
    030
    031    /**
    032     * This class implements an output stream in which the data is
    033     * written into a byte array. The buffer automatically grows as data
    034     * is written to it.
    035     * <p>
    036     * The data can be retrieved using <code>toByteArray()</code> and
    037     * <code>toString()</code>.
    038     * <p>
    039     * Closing a <tt>ByteArrayOutputStream</tt> has no effect. The methods in
    040     * this class can be called after the stream has been closed without
    041     * generating an <tt>IOException</tt>.
    042     * <p>
    043     * This is an alternative implementation of the java.io.ByteArrayOutputStream
    044     * class. The original implementation only allocates 32 bytes at the beginning.
    045     * As this class is designed for heavy duty it starts at 1024 bytes. In contrast
    046     * to the original it doesn't reallocate the whole memory block but allocates
    047     * additional buffers. This way no buffers need to be garbage collected and
    048     * the contents don't have to be copied to the new buffer. This class is
    049     * designed to behave exactly like the original. The only exception is the
    050     * deprecated toString(int) method that has been ignored.
    051     *
    052     * @author <a href="mailto:jeremias@apache.org">Jeremias Maerki</a>
    053     * @author Holger Hoffstatte
    054     * @version $Id: ByteArrayOutputStream.java 1021884 2010-10-12 18:49:16Z ggregory $
    055     */
    056    public class ByteArrayOutputStream extends OutputStream {
    057
    058        /** A singleton empty byte array. */
    059        private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
    060
    061        /** The list of buffers, which grows and never reduces. */
    062        private final List<byte[]> buffers = new ArrayList<byte[]>();
    063        /** The index of the current buffer. */
    064        private int currentBufferIndex;
    065        /** The total count of bytes in all the filled buffers. */
    066        private int filledBufferSum;
    067        /** The current buffer. */
    068        private byte[] currentBuffer;
    069        /** The total count of bytes written. */
    070        private int count;
    071
    072        /**
    073         * Creates a new byte array output stream. The buffer capacity is
    074         * initially 1024 bytes, though its size increases if necessary.
    075         */
    076        public ByteArrayOutputStream() {
    077            this(1024);
    078        }
    079
    080        /**
    081         * Creates a new byte array output stream, with a buffer capacity of
    082         * the specified size, in bytes.
    083         *
    084         * @param size  the initial size
    085         * @throws IllegalArgumentException if size is negative
    086         */
    087        public ByteArrayOutputStream(int size) {
    088            if (size < 0) {
    089                throw new IllegalArgumentException(
    090                    "Negative initial size: " + size);
    091            }
    092            synchronized (this) {
    093                needNewBuffer(size);
    094            }
    095        }
    096
    097        /**
    098         * Makes a new buffer available either by allocating
    099         * a new one or re-cycling an existing one.
    100         *
    101         * @param newcount  the size of the buffer if one is created
    102         */
    103        private void needNewBuffer(int newcount) {
    104            if (currentBufferIndex < buffers.size() - 1) {
    105                //Recycling old buffer
    106                filledBufferSum += currentBuffer.length;
    107
    108                currentBufferIndex++;
    109                currentBuffer = buffers.get(currentBufferIndex);
    110            } else {
    111                //Creating new buffer
    112                int newBufferSize;
    113                if (currentBuffer == null) {
    114                    newBufferSize = newcount;
    115                    filledBufferSum = 0;
    116                } else {
    117                    newBufferSize = Math.max(
    118                        currentBuffer.length << 1,
    119                        newcount - filledBufferSum);
    120                    filledBufferSum += currentBuffer.length;
    121                }
    122
    123                currentBufferIndex++;
    124                currentBuffer = new byte[newBufferSize];
    125                buffers.add(currentBuffer);
    126            }
    127        }
    128
    129        /**
    130         * Write the bytes to byte array.
    131         * @param b the bytes to write
    132         * @param off The start offset
    133         * @param len The number of bytes to write
    134         */
    135        @Override
    136        public void write(byte[] b, int off, int len) {
    137            if ((off < 0)
    138                    || (off > b.length)
    139                    || (len < 0)
    140                    || ((off + len) > b.length)
    141                    || ((off + len) < 0)) {
    142                throw new IndexOutOfBoundsException();
    143            } else if (len == 0) {
    144                return;
    145            }
    146            synchronized (this) {
    147                int newcount = count + len;
    148                int remaining = len;
    149                int inBufferPos = count - filledBufferSum;
    150                while (remaining > 0) {
    151                    int part = Math.min(remaining, currentBuffer.length - inBufferPos);
    152                    System.arraycopy(b, off + len - remaining, currentBuffer, inBufferPos, part);
    153                    remaining -= part;
    154                    if (remaining > 0) {
    155                        needNewBuffer(newcount);
    156                        inBufferPos = 0;
    157                    }
    158                }
    159                count = newcount;
    160            }
    161        }
    162
    163        /**
    164         * Write a byte to byte array.
    165         * @param b the byte to write
    166         */
    167        @Override
    168        public synchronized void write(int b) {
    169            int inBufferPos = count - filledBufferSum;
    170            if (inBufferPos == currentBuffer.length) {
    171                needNewBuffer(count + 1);
    172                inBufferPos = 0;
    173            }
    174            currentBuffer[inBufferPos] = (byte) b;
    175            count++;
    176        }
    177
    178        /**
    179         * Writes the entire contents of the specified input stream to this
    180         * byte stream. Bytes from the input stream are read directly into the
    181         * internal buffers of this streams.
    182         *
    183         * @param in the input stream to read from
    184         * @return total number of bytes read from the input stream
    185         *         (and written to this stream)
    186         * @throws IOException if an I/O error occurs while reading the input stream
    187         * @since Commons IO 1.4
    188         */
    189        public synchronized int write(InputStream in) throws IOException {
    190            int readCount = 0;
    191            int inBufferPos = count - filledBufferSum;
    192            int n = in.read(currentBuffer, inBufferPos, currentBuffer.length - inBufferPos);
    193            while (n != -1) {
    194                readCount += n;
    195                inBufferPos += n;
    196                count += n;
    197                if (inBufferPos == currentBuffer.length) {
    198                    needNewBuffer(currentBuffer.length);
    199                    inBufferPos = 0;
    200                }
    201                n = in.read(currentBuffer, inBufferPos, currentBuffer.length - inBufferPos);
    202            }
    203            return readCount;
    204        }
    205
    206        /**
    207         * Return the current size of the byte array.
    208         * @return the current size of the byte array
    209         */
    210        public synchronized int size() {
    211            return count;
    212        }
    213
    214        /**
    215         * Closing a <tt>ByteArrayOutputStream</tt> has no effect. The methods in
    216         * this class can be called after the stream has been closed without
    217         * generating an <tt>IOException</tt>.
    218         *
    219         * @throws IOException never (this method should not declare this exception
    220         * but it has to now due to backwards compatability)
    221         */
    222        @Override
    223        public void close() throws IOException {
    224            //nop
    225        }
    226
    227        /**
    228         * @see java.io.ByteArrayOutputStream#reset()
    229         */
    230        public synchronized void reset() {
    231            count = 0;
    232            filledBufferSum = 0;
    233            currentBufferIndex = 0;
    234            currentBuffer = buffers.get(currentBufferIndex);
    235        }
    236
    237        /**
    238         * Writes the entire contents of this byte stream to the
    239         * specified output stream.
    240         *
    241         * @param out  the output stream to write to
    242         * @throws IOException if an I/O error occurs, such as if the stream is closed
    243         * @see java.io.ByteArrayOutputStream#writeTo(OutputStream)
    244         */
    245        public synchronized void writeTo(OutputStream out) throws IOException {
    246            int remaining = count;
    247            for (byte[] buf : buffers) {
    248                int c = Math.min(buf.length, remaining);
    249                out.write(buf, 0, c);
    250                remaining -= c;
    251                if (remaining == 0) {
    252                    break;
    253                }
    254            }
    255        }
    256
    257        /**
    258         * Fetches entire contents of an <code>InputStream</code> and represent
    259         * same data as result InputStream.
    260         * <p>
    261         * This method is useful where,
    262         * <ul>
    263         * <li>Source InputStream is slow.</li>
    264         * <li>It has network resources associated, so we cannot keep it open for
    265         * long time.</li>
    266         * <li>It has network timeout associated.</li>
    267         * </ul>
    268         * It can be used in favor of {@link #toByteArray()}, since it
    269         * avoids unnecessary allocation and copy of byte[].<br>
    270         * This method buffers the input internally, so there is no need to use a
    271         * <code>BufferedInputStream</code>.
    272         *
    273         * @param input Stream to be fully buffered.
    274         * @return A fully buffered stream.
    275         * @throws IOException if an I/O error occurs
    276         * @since Commons IO 2.0
    277         */
    278        public static InputStream toBufferedInputStream(InputStream input)
    279                throws IOException {
    280            ByteArrayOutputStream output = new ByteArrayOutputStream();
    281            output.write(input);
    282            return output.toBufferedInputStream();
    283        }
    284
    285        /**
    286         * Gets the current contents of this byte stream as a Input Stream. The
    287         * returned stream is backed by buffers of <code>this</code> stream,
    288         * avoiding memory allocation and copy, thus saving space and time.<br>
    289         *
    290         * @return the current contents of this output stream.
    291         * @see java.io.ByteArrayOutputStream#toByteArray()
    292         * @see #reset()
    293         * @since Commons IO 2.0
    294         */
    295        private InputStream toBufferedInputStream() {
    296            int remaining = count;
    297            if (remaining == 0) {
    298                return new ClosedInputStream();
    299            }
    300            List<ByteArrayInputStream> list = new ArrayList<ByteArrayInputStream>(buffers.size());
    301            for (byte[] buf : buffers) {
    302                int c = Math.min(buf.length, remaining);
    303                list.add(new ByteArrayInputStream(buf, 0, c));
    304                remaining -= c;
    305                if (remaining == 0) {
    306                    break;
    307                }
    308            }
    309            return new SequenceInputStream(Collections.enumeration(list));
    310        }
    311
    312        /**
    313         * Gets the curent contents of this byte stream as a byte array.
    314         * The result is independent of this stream.
    315         *
    316         * @return the current contents of this output stream, as a byte array
    317         * @see java.io.ByteArrayOutputStream#toByteArray()
    318         */
    319        public synchronized byte[] toByteArray() {
    320            int remaining = count;
    321            if (remaining == 0) {
    322                return EMPTY_BYTE_ARRAY;
    323            }
    324            byte newbuf[] = new byte[remaining];
    325            int pos = 0;
    326            for (byte[] buf : buffers) {
    327                int c = Math.min(buf.length, remaining);
    328                System.arraycopy(buf, 0, newbuf, pos, c);
    329                pos += c;
    330                remaining -= c;
    331                if (remaining == 0) {
    332                    break;
    333                }
    334            }
    335            return newbuf;
    336        }
    337
    338        /**
    339         * Gets the curent contents of this byte stream as a string.
    340         * @return the contents of the byte array as a String
    341         * @see java.io.ByteArrayOutputStream#toString()
    342         */
    343        @Override
    344        public String toString() {
    345            return new String(toByteArray());
    346        }
    347
    348        /**
    349         * Gets the curent contents of this byte stream as a string
    350         * using the specified encoding.
    351         *
    352         * @param enc  the name of the character encoding
    353         * @return the string converted from the byte array
    354         * @throws UnsupportedEncodingException if the encoding is not supported
    355         * @see java.io.ByteArrayOutputStream#toString(String)
    356         */
    357        public String toString(String enc) throws UnsupportedEncodingException {
    358            return new String(toByteArray(), enc);
    359        }
    360
    361    }