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 */
056public class ByteArrayOutputStream extends OutputStream {
057
058    static final int DEFAULT_SIZE = 1024;
059
060    /** A singleton empty byte array. */
061    private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
062
063    /** The list of buffers, which grows and never reduces. */
064    private final List<byte[]> buffers = new ArrayList<>();
065    /** The index of the current buffer. */
066    private int currentBufferIndex;
067    /** The total count of bytes in all the filled buffers. */
068    private int filledBufferSum;
069    /** The current buffer. */
070    private byte[] currentBuffer;
071    /** The total count of bytes written. */
072    private int count;
073    /** Flag to indicate if the buffers can be reused after reset */
074    private boolean reuseBuffers = true;
075
076    /**
077     * Creates a new byte array output stream. The buffer capacity is
078     * initially 1024 bytes, though its size increases if necessary.
079     */
080    public ByteArrayOutputStream() {
081        this(DEFAULT_SIZE);
082    }
083
084    /**
085     * Creates a new byte array output stream, with a buffer capacity of
086     * the specified size, in bytes.
087     *
088     * @param size  the initial size
089     * @throws IllegalArgumentException if size is negative
090     */
091    public ByteArrayOutputStream(final int size) {
092        if (size < 0) {
093            throw new IllegalArgumentException(
094                "Negative initial size: " + size);
095        }
096        synchronized (this) {
097            needNewBuffer(size);
098        }
099    }
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(final int newcount) {
108        if (currentBufferIndex < buffers.size() - 1) {
109            //Recycling old buffer
110            filledBufferSum += currentBuffer.length;
111
112            currentBufferIndex++;
113            currentBuffer = buffers.get(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(final byte[] b, final int off, final 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            final int newcount = count + len;
152            int remaining = len;
153            int inBufferPos = count - filledBufferSum;
154            while (remaining > 0) {
155                final 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(final 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 1.4
192     */
193    public synchronized int write(final 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 != EOF) {
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 {@code ByteArrayOutputStream} has no effect. The methods in
220     * this class can be called after the stream has been closed without
221     * generating an {@code IOException}.
222     *
223     * @throws IOException never (this method should not declare this exception
224     * but it has to now due to backwards compatibility)
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        if (reuseBuffers) {
239            currentBuffer = buffers.get(currentBufferIndex);
240        } else {
241            //Throw away old buffers
242            currentBuffer = null;
243            final int size = buffers.get(0).length;
244            buffers.clear();
245            needNewBuffer(size);
246            reuseBuffers = true;
247        }
248    }
249
250    /**
251     * Writes the entire contents of this byte stream to the
252     * specified output stream.
253     *
254     * @param out  the output stream to write to
255     * @throws IOException if an I/O error occurs, such as if the stream is closed
256     * @see java.io.ByteArrayOutputStream#writeTo(OutputStream)
257     */
258    public synchronized void writeTo(final OutputStream out) throws IOException {
259        int remaining = count;
260        for (final byte[] buf : buffers) {
261            final int c = Math.min(buf.length, remaining);
262            out.write(buf, 0, c);
263            remaining -= c;
264            if (remaining == 0) {
265                break;
266            }
267        }
268    }
269
270    /**
271     * Fetches entire contents of an <code>InputStream</code> and represent
272     * same data as result InputStream.
273     * <p>
274     * This method is useful where,
275     * <ul>
276     * <li>Source InputStream is slow.</li>
277     * <li>It has network resources associated, so we cannot keep it open for
278     * long time.</li>
279     * <li>It has network timeout associated.</li>
280     * </ul>
281     * It can be used in favor of {@link #toByteArray()}, since it
282     * avoids unnecessary allocation and copy of byte[].<br>
283     * This method buffers the input internally, so there is no need to use a
284     * <code>BufferedInputStream</code>.
285     *
286     * @param input Stream to be fully buffered.
287     * @return A fully buffered stream.
288     * @throws IOException if an I/O error occurs
289     * @since 2.0
290     */
291    public static InputStream toBufferedInputStream(final InputStream input)
292            throws IOException {
293        return toBufferedInputStream(input, 1024);
294    }
295
296    /**
297     * Fetches entire contents of an <code>InputStream</code> and represent
298     * same data as result InputStream.
299     * <p>
300     * This method is useful where,
301     * <ul>
302     * <li>Source InputStream is slow.</li>
303     * <li>It has network resources associated, so we cannot keep it open for
304     * long time.</li>
305     * <li>It has network timeout associated.</li>
306     * </ul>
307     * It can be used in favor of {@link #toByteArray()}, since it
308     * avoids unnecessary allocation and copy of byte[].<br>
309     * This method buffers the input internally, so there is no need to use a
310     * <code>BufferedInputStream</code>.
311     *
312     * @param input Stream to be fully buffered.
313     * @param size the initial buffer size
314     * @return A fully buffered stream.
315     * @throws IOException if an I/O error occurs
316     * @since 2.5
317     */
318    public static InputStream toBufferedInputStream(final InputStream input, final int size)
319            throws IOException {
320        // It does not matter if a ByteArrayOutputStream is not closed as close() is a no-op
321        @SuppressWarnings("resource")
322        final ByteArrayOutputStream output = new ByteArrayOutputStream(size);
323        output.write(input);
324        return output.toInputStream();
325    }
326
327    /**
328     * Gets the current contents of this byte stream as a Input Stream. The
329     * returned stream is backed by buffers of <code>this</code> stream,
330     * avoiding memory allocation and copy, thus saving space and time.<br>
331     *
332     * @return the current contents of this output stream.
333     * @see java.io.ByteArrayOutputStream#toByteArray()
334     * @see #reset()
335     * @since 2.5
336     */
337    public synchronized InputStream toInputStream() {
338        int remaining = count;
339        if (remaining == 0) {
340            return new ClosedInputStream();
341        }
342        final List<ByteArrayInputStream> list = new ArrayList<>(buffers.size());
343        for (final byte[] buf : buffers) {
344            final int c = Math.min(buf.length, remaining);
345            list.add(new ByteArrayInputStream(buf, 0, c));
346            remaining -= c;
347            if (remaining == 0) {
348                break;
349            }
350        }
351        reuseBuffers = false;
352        return new SequenceInputStream(Collections.enumeration(list));
353    }
354
355    /**
356     * Gets the current contents of this byte stream as a byte array.
357     * The result is independent of this stream.
358     *
359     * @return the current contents of this output stream, as a byte array
360     * @see java.io.ByteArrayOutputStream#toByteArray()
361     */
362    public synchronized byte[] toByteArray() {
363        int remaining = count;
364        if (remaining == 0) {
365            return EMPTY_BYTE_ARRAY;
366        }
367        final byte newbuf[] = new byte[remaining];
368        int pos = 0;
369        for (final byte[] buf : buffers) {
370            final int c = Math.min(buf.length, remaining);
371            System.arraycopy(buf, 0, newbuf, pos, c);
372            pos += c;
373            remaining -= c;
374            if (remaining == 0) {
375                break;
376            }
377        }
378        return newbuf;
379    }
380
381    /**
382     * Gets the current contents of this byte stream as a string
383     * using the platform default charset.
384     * @return the contents of the byte array as a String
385     * @see java.io.ByteArrayOutputStream#toString()
386     * @deprecated 2.5 use {@link #toString(String)} instead
387     */
388    @Override
389    @Deprecated
390    public String toString() {
391        // make explicit the use of the default charset
392        return new String(toByteArray(), Charset.defaultCharset());
393    }
394
395    /**
396     * Gets the current contents of this byte stream as a string
397     * using the specified encoding.
398     *
399     * @param enc  the name of the character encoding
400     * @return the string converted from the byte array
401     * @throws UnsupportedEncodingException if the encoding is not supported
402     * @see java.io.ByteArrayOutputStream#toString(String)
403     */
404    public String toString(final String enc) throws UnsupportedEncodingException {
405        return new String(toByteArray(), enc);
406    }
407
408    /**
409     * Gets the current contents of this byte stream as a string
410     * using the specified encoding.
411     *
412     * @param charset  the character encoding
413     * @return the string converted from the byte array
414     * @see java.io.ByteArrayOutputStream#toString(String)
415     * @since 2.5
416     */
417    public String toString(final Charset charset) {
418        return new String(toByteArray(), charset);
419    }
420
421}