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 */
017package org.apache.commons.io.output;
018
019import static org.apache.commons.io.IOUtils.EOF;
020
021import java.io.ByteArrayInputStream;
022import java.io.IOException;
023import java.io.InputStream;
024import java.io.OutputStream;
025import java.io.SequenceInputStream;
026import java.io.UnsupportedEncodingException;
027import java.nio.charset.Charset;
028import java.util.ArrayList;
029import java.util.Collections;
030import java.util.List;
031
032import org.apache.commons.io.input.ClosedInputStream;
033
034/**
035 * This class implements an output stream in which the data is
036 * written into a byte array. The buffer automatically grows as data
037 * is written to it.
038 * <p>
039 * The data can be retrieved using <code>toByteArray()</code> and
040 * <code>toString()</code>.
041 * <p>
042 * Closing a {@code ByteArrayOutputStream} has no effect. The methods in
043 * this class can be called after the stream has been closed without
044 * generating an {@code IOException}.
045 * <p>
046 * This is an alternative implementation of the {@link java.io.ByteArrayOutputStream}
047 * class. The original implementation only allocates 32 bytes at the beginning.
048 * As this class is designed for heavy duty it starts at 1024 bytes. In contrast
049 * to the original it doesn't reallocate the whole memory block but allocates
050 * additional buffers. This way no buffers need to be garbage collected and
051 * the contents don't have to be copied to the new buffer. This class is
052 * designed to behave exactly like the original. The only exception is the
053 * deprecated toString(int) method that has been ignored.
054 *
055 * @version $Id: ByteArrayOutputStream.java 1612034 2014-07-20 06:35:19Z ggregory $
056 */
057public class ByteArrayOutputStream extends OutputStream {
058
059    /** A singleton empty byte array. */
060    private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
061
062    /** The list of buffers, which grows and never reduces. */
063    private final List<byte[]> buffers = new ArrayList<byte[]>();
064    /** The index of the current buffer. */
065    private int currentBufferIndex;
066    /** The total count of bytes in all the filled buffers. */
067    private int filledBufferSum;
068    /** The current buffer. */
069    private byte[] currentBuffer;
070    /** The total count of bytes written. */
071    private int count;
072    /** Flag to indicate if the buffers can be reused after reset */
073    private boolean reuseBuffers = true;
074
075    /**
076     * Creates a new byte array output stream. The buffer capacity is
077     * initially 1024 bytes, though its size increases if necessary.
078     */
079    public ByteArrayOutputStream() {
080        this(1024);
081    }
082
083    /**
084     * Creates a new byte array output stream, with a buffer capacity of
085     * the specified size, in bytes.
086     *
087     * @param size  the initial size
088     * @throws IllegalArgumentException if size is negative
089     */
090    public ByteArrayOutputStream(final int size) {
091        if (size < 0) {
092            throw new IllegalArgumentException(
093                "Negative initial size: " + size);
094        }
095        synchronized (this) {
096            needNewBuffer(size);
097        }
098    }
099
100    /**
101     * Makes a new buffer available either by allocating
102     * a new one or re-cycling an existing one.
103     *
104     * @param newcount  the size of the buffer if one is created
105     */
106    private void needNewBuffer(final int newcount) {
107        if (currentBufferIndex < buffers.size() - 1) {
108            //Recycling old buffer
109            filledBufferSum += currentBuffer.length;
110
111            currentBufferIndex++;
112            currentBuffer = buffers.get(currentBufferIndex);
113        } else {
114            //Creating new buffer
115            int newBufferSize;
116            if (currentBuffer == null) {
117                newBufferSize = newcount;
118                filledBufferSum = 0;
119            } else {
120                newBufferSize = Math.max(
121                    currentBuffer.length << 1,
122                    newcount - filledBufferSum);
123                filledBufferSum += currentBuffer.length;
124            }
125
126            currentBufferIndex++;
127            currentBuffer = new byte[newBufferSize];
128            buffers.add(currentBuffer);
129        }
130    }
131
132    /**
133     * Write the bytes to byte array.
134     * @param b the bytes to write
135     * @param off The start offset
136     * @param len The number of bytes to write
137     */
138    @Override
139    public void write(final byte[] b, final int off, final 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            final int newcount = count + len;
151            int remaining = len;
152            int inBufferPos = count - filledBufferSum;
153            while (remaining > 0) {
154                final 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    @Override
171    public synchronized void write(final int b) {
172        int inBufferPos = count - filledBufferSum;
173        if (inBufferPos == currentBuffer.length) {
174            needNewBuffer(count + 1);
175            inBufferPos = 0;
176        }
177        currentBuffer[inBufferPos] = (byte) b;
178        count++;
179    }
180
181    /**
182     * Writes the entire contents of the specified input stream to this
183     * byte stream. Bytes from the input stream are read directly into the
184     * internal buffers of this streams.
185     *
186     * @param in the input stream to read from
187     * @return total number of bytes read from the input stream
188     *         (and written to this stream)
189     * @throws IOException if an I/O error occurs while reading the input stream
190     * @since 1.4
191     */
192    public synchronized int write(final InputStream in) throws IOException {
193        int readCount = 0;
194        int inBufferPos = count - filledBufferSum;
195        int n = in.read(currentBuffer, inBufferPos, currentBuffer.length - inBufferPos);
196        while (n != EOF) {
197            readCount += n;
198            inBufferPos += n;
199            count += n;
200            if (inBufferPos == currentBuffer.length) {
201                needNewBuffer(currentBuffer.length);
202                inBufferPos = 0;
203            }
204            n = in.read(currentBuffer, inBufferPos, currentBuffer.length - inBufferPos);
205        }
206        return readCount;
207    }
208
209    /**
210     * Return the current size of the byte array.
211     * @return the current size of the byte array
212     */
213    public synchronized int size() {
214        return count;
215    }
216
217    /**
218     * Closing a {@code ByteArrayOutputStream} has no effect. The methods in
219     * this class can be called after the stream has been closed without
220     * generating an {@code IOException}.
221     *
222     * @throws IOException never (this method should not declare this exception
223     * but it has to now due to backwards compatibility)
224     */
225    @Override
226    public void close() throws IOException {
227        //nop
228    }
229
230    /**
231     * @see java.io.ByteArrayOutputStream#reset()
232     */
233    public synchronized void reset() {
234        count = 0;
235        filledBufferSum = 0;
236        currentBufferIndex = 0;
237        if (reuseBuffers) {
238            currentBuffer = buffers.get(currentBufferIndex);
239        } else {
240            //Throw away old buffers
241            currentBuffer = null;
242            int size = buffers.get(0).length;
243            buffers.clear();
244            needNewBuffer(size);
245            reuseBuffers = true;
246        }
247    }
248
249    /**
250     * Writes the entire contents of this byte stream to the
251     * specified output stream.
252     *
253     * @param out  the output stream to write to
254     * @throws IOException if an I/O error occurs, such as if the stream is closed
255     * @see java.io.ByteArrayOutputStream#writeTo(OutputStream)
256     */
257    public synchronized void writeTo(final OutputStream out) throws IOException {
258        int remaining = count;
259        for (final byte[] buf : buffers) {
260            final int c = Math.min(buf.length, remaining);
261            out.write(buf, 0, c);
262            remaining -= c;
263            if (remaining == 0) {
264                break;
265            }
266        }
267    }
268
269    /**
270     * Fetches entire contents of an <code>InputStream</code> and represent
271     * same data as result InputStream.
272     * <p>
273     * This method is useful where,
274     * <ul>
275     * <li>Source InputStream is slow.</li>
276     * <li>It has network resources associated, so we cannot keep it open for
277     * long time.</li>
278     * <li>It has network timeout associated.</li>
279     * </ul>
280     * It can be used in favor of {@link #toByteArray()}, since it
281     * avoids unnecessary allocation and copy of byte[].<br>
282     * This method buffers the input internally, so there is no need to use a
283     * <code>BufferedInputStream</code>.
284     *
285     * @param input Stream to be fully buffered.
286     * @return A fully buffered stream.
287     * @throws IOException if an I/O error occurs
288     * @since 2.0
289     */
290    public static InputStream toBufferedInputStream(final InputStream input)
291            throws IOException {
292        return toBufferedInputStream(input, 1024);
293    }
294
295    /**
296     * Fetches entire contents of an <code>InputStream</code> and represent
297     * same data as result InputStream.
298     * <p>
299     * This method is useful where,
300     * <ul>
301     * <li>Source InputStream is slow.</li>
302     * <li>It has network resources associated, so we cannot keep it open for
303     * long time.</li>
304     * <li>It has network timeout associated.</li>
305     * </ul>
306     * It can be used in favor of {@link #toByteArray()}, since it
307     * avoids unnecessary allocation and copy of byte[].<br>
308     * This method buffers the input internally, so there is no need to use a
309     * <code>BufferedInputStream</code>.
310     *
311     * @param input Stream to be fully buffered.
312     * @param size the initial buffer size
313     * @return A fully buffered stream.
314     * @throws IOException if an I/O error occurs
315     * @since 2.5
316     */
317    public static InputStream toBufferedInputStream(final InputStream input, int size)
318            throws IOException {
319        // It does not matter if a ByteArrayOutputStream is not closed as close() is a no-op
320        @SuppressWarnings("resource")
321        final ByteArrayOutputStream output = new ByteArrayOutputStream(size);
322        output.write(input);
323        return output.toInputStream();
324    }
325
326    /**
327     * Gets the current contents of this byte stream as a Input Stream. The
328     * returned stream is backed by buffers of <code>this</code> stream,
329     * avoiding memory allocation and copy, thus saving space and time.<br>
330     *
331     * @return the current contents of this output stream.
332     * @see java.io.ByteArrayOutputStream#toByteArray()
333     * @see #reset()
334     * @since 2.5
335     */
336    public synchronized InputStream toInputStream() {
337        int remaining = count;
338        if (remaining == 0) {
339            return new ClosedInputStream();
340        }
341        final List<ByteArrayInputStream> list = new ArrayList<ByteArrayInputStream>(buffers.size());
342        for (final byte[] buf : buffers) {
343            final int c = Math.min(buf.length, remaining);
344            list.add(new ByteArrayInputStream(buf, 0, c));
345            remaining -= c;
346            if (remaining == 0) {
347                break;
348            }
349        }
350        reuseBuffers = false;
351        return new SequenceInputStream(Collections.enumeration(list));
352    }
353
354    /**
355     * Gets the curent contents of this byte stream as a byte array.
356     * The result is independent of this stream.
357     *
358     * @return the current contents of this output stream, as a byte array
359     * @see java.io.ByteArrayOutputStream#toByteArray()
360     */
361    public synchronized byte[] toByteArray() {
362        int remaining = count;
363        if (remaining == 0) {
364            return EMPTY_BYTE_ARRAY;
365        }
366        final byte newbuf[] = new byte[remaining];
367        int pos = 0;
368        for (final byte[] buf : buffers) {
369            final int c = Math.min(buf.length, remaining);
370            System.arraycopy(buf, 0, newbuf, pos, c);
371            pos += c;
372            remaining -= c;
373            if (remaining == 0) {
374                break;
375            }
376        }
377        return newbuf;
378    }
379
380    /**
381     * Gets the curent contents of this byte stream as a string
382     * using the platform default charset.
383     * @return the contents of the byte array as a String
384     * @see java.io.ByteArrayOutputStream#toString()
385     * @deprecated 2.5 use {@link #toString(String)} instead
386     */
387    @Override
388    @Deprecated
389    public String toString() {
390        // make explicit the use of the default charset
391        return new String(toByteArray(), Charset.defaultCharset());
392    }
393
394    /**
395     * Gets the curent contents of this byte stream as a string
396     * using the specified encoding.
397     *
398     * @param enc  the name of the character encoding
399     * @return the string converted from the byte array
400     * @throws UnsupportedEncodingException if the encoding is not supported
401     * @see java.io.ByteArrayOutputStream#toString(String)
402     */
403    public String toString(final String enc) throws UnsupportedEncodingException {
404        return new String(toByteArray(), enc);
405    }
406
407    /**
408     * Gets the curent contents of this byte stream as a string
409     * using the specified encoding.
410     *
411     * @param charset  the character encoding
412     * @return the string converted from the byte array
413     * @see java.io.ByteArrayOutputStream#toString(String)
414     * @since 2.5
415     */
416    public String toString(final Charset charset) {
417        return new String(toByteArray(), charset);
418    }
419
420}