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 java.io.ByteArrayInputStream;
020import java.io.IOException;
021import java.io.InputStream;
022import java.io.OutputStream;
023import java.io.SequenceInputStream;
024import java.io.UnsupportedEncodingException;
025import java.util.ArrayList;
026import java.util.Collections;
027import java.util.List;
028
029import 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 {@link 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 * @version $Id: ByteArrayOutputStream.java 1304052 2012-03-22 20:55:29Z ggregory $
053 */
054public class ByteArrayOutputStream extends OutputStream {
055
056    /** A singleton empty byte array. */
057    private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
058
059    /** The list of buffers, which grows and never reduces. */
060    private final List<byte[]> buffers = new ArrayList<byte[]>();
061    /** The index of the current buffer. */
062    private int currentBufferIndex;
063    /** The total count of bytes in all the filled buffers. */
064    private int filledBufferSum;
065    /** The current buffer. */
066    private byte[] currentBuffer;
067    /** The total count of bytes written. */
068    private int count;
069
070    /**
071     * Creates a new byte array output stream. The buffer capacity is 
072     * initially 1024 bytes, though its size increases if necessary. 
073     */
074    public ByteArrayOutputStream() {
075        this(1024);
076    }
077
078    /**
079     * Creates a new byte array output stream, with a buffer capacity of 
080     * the specified size, in bytes. 
081     *
082     * @param size  the initial size
083     * @throws IllegalArgumentException if size is negative
084     */
085    public ByteArrayOutputStream(int size) {
086        if (size < 0) {
087            throw new IllegalArgumentException(
088                "Negative initial size: " + size);
089        }
090        synchronized (this) {
091            needNewBuffer(size);
092        }
093    }
094
095    /**
096     * Makes a new buffer available either by allocating
097     * a new one or re-cycling an existing one.
098     *
099     * @param newcount  the size of the buffer if one is created
100     */
101    private void needNewBuffer(int newcount) {
102        if (currentBufferIndex < buffers.size() - 1) {
103            //Recycling old buffer
104            filledBufferSum += currentBuffer.length;
105            
106            currentBufferIndex++;
107            currentBuffer = buffers.get(currentBufferIndex);
108        } else {
109            //Creating new buffer
110            int newBufferSize;
111            if (currentBuffer == null) {
112                newBufferSize = newcount;
113                filledBufferSum = 0;
114            } else {
115                newBufferSize = Math.max(
116                    currentBuffer.length << 1, 
117                    newcount - filledBufferSum);
118                filledBufferSum += currentBuffer.length;
119            }
120            
121            currentBufferIndex++;
122            currentBuffer = new byte[newBufferSize];
123            buffers.add(currentBuffer);
124        }
125    }
126
127    /**
128     * Write the bytes to byte array.
129     * @param b the bytes to write
130     * @param off The start offset
131     * @param len The number of bytes to write
132     */
133    @Override
134    public void write(byte[] b, int off, int len) {
135        if ((off < 0) 
136                || (off > b.length) 
137                || (len < 0) 
138                || ((off + len) > b.length) 
139                || ((off + len) < 0)) {
140            throw new IndexOutOfBoundsException();
141        } else if (len == 0) {
142            return;
143        }
144        synchronized (this) {
145            int newcount = count + len;
146            int remaining = len;
147            int inBufferPos = count - filledBufferSum;
148            while (remaining > 0) {
149                int part = Math.min(remaining, currentBuffer.length - inBufferPos);
150                System.arraycopy(b, off + len - remaining, currentBuffer, inBufferPos, part);
151                remaining -= part;
152                if (remaining > 0) {
153                    needNewBuffer(newcount);
154                    inBufferPos = 0;
155                }
156            }
157            count = newcount;
158        }
159    }
160
161    /**
162     * Write a byte to byte array.
163     * @param b the byte to write
164     */
165    @Override
166    public synchronized void write(int b) {
167        int inBufferPos = count - filledBufferSum;
168        if (inBufferPos == currentBuffer.length) {
169            needNewBuffer(count + 1);
170            inBufferPos = 0;
171        }
172        currentBuffer[inBufferPos] = (byte) b;
173        count++;
174    }
175
176    /**
177     * Writes the entire contents of the specified input stream to this
178     * byte stream. Bytes from the input stream are read directly into the
179     * internal buffers of this streams.
180     *
181     * @param in the input stream to read from
182     * @return total number of bytes read from the input stream
183     *         (and written to this stream)
184     * @throws IOException if an I/O error occurs while reading the input stream
185     * @since 1.4
186     */
187    public synchronized int write(InputStream in) throws IOException {
188        int readCount = 0;
189        int inBufferPos = count - filledBufferSum;
190        int n = in.read(currentBuffer, inBufferPos, currentBuffer.length - inBufferPos);
191        while (n != -1) {
192            readCount += n;
193            inBufferPos += n;
194            count += n;
195            if (inBufferPos == currentBuffer.length) {
196                needNewBuffer(currentBuffer.length);
197                inBufferPos = 0;
198            }
199            n = in.read(currentBuffer, inBufferPos, currentBuffer.length - inBufferPos);
200        }
201        return readCount;
202    }
203
204    /**
205     * Return the current size of the byte array.
206     * @return the current size of the byte array
207     */
208    public synchronized int size() {
209        return count;
210    }
211
212    /**
213     * Closing a <tt>ByteArrayOutputStream</tt> has no effect. The methods in
214     * this class can be called after the stream has been closed without
215     * generating an <tt>IOException</tt>.
216     *
217     * @throws IOException never (this method should not declare this exception
218     * but it has to now due to backwards compatability)
219     */
220    @Override
221    public void close() throws IOException {
222        //nop
223    }
224
225    /**
226     * @see java.io.ByteArrayOutputStream#reset()
227     */
228    public synchronized void reset() {
229        count = 0;
230        filledBufferSum = 0;
231        currentBufferIndex = 0;
232        currentBuffer = buffers.get(currentBufferIndex);
233    }
234
235    /**
236     * Writes the entire contents of this byte stream to the
237     * specified output stream.
238     *
239     * @param out  the output stream to write to
240     * @throws IOException if an I/O error occurs, such as if the stream is closed
241     * @see java.io.ByteArrayOutputStream#writeTo(OutputStream)
242     */
243    public synchronized void writeTo(OutputStream out) throws IOException {
244        int remaining = count;
245        for (byte[] buf : buffers) {
246            int c = Math.min(buf.length, remaining);
247            out.write(buf, 0, c);
248            remaining -= c;
249            if (remaining == 0) {
250                break;
251            }
252        }
253    }
254
255    /**
256     * Fetches entire contents of an <code>InputStream</code> and represent
257     * same data as result InputStream.
258     * <p>
259     * This method is useful where,
260     * <ul>
261     * <li>Source InputStream is slow.</li>
262     * <li>It has network resources associated, so we cannot keep it open for
263     * long time.</li>
264     * <li>It has network timeout associated.</li>
265     * </ul>
266     * It can be used in favor of {@link #toByteArray()}, since it
267     * avoids unnecessary allocation and copy of byte[].<br>
268     * This method buffers the input internally, so there is no need to use a
269     * <code>BufferedInputStream</code>.
270     * 
271     * @param input Stream to be fully buffered.
272     * @return A fully buffered stream.
273     * @throws IOException if an I/O error occurs
274     * @since 2.0
275     */
276    public static InputStream toBufferedInputStream(InputStream input)
277            throws IOException {
278        ByteArrayOutputStream output = new ByteArrayOutputStream();
279        output.write(input);
280        return output.toBufferedInputStream();
281    }
282
283    /**
284     * Gets the current contents of this byte stream as a Input Stream. The
285     * returned stream is backed by buffers of <code>this</code> stream,
286     * avoiding memory allocation and copy, thus saving space and time.<br>
287     * 
288     * @return the current contents of this output stream.
289     * @see java.io.ByteArrayOutputStream#toByteArray()
290     * @see #reset()
291     * @since 2.0
292     */
293    private InputStream toBufferedInputStream() {
294        int remaining = count;
295        if (remaining == 0) {
296            return new ClosedInputStream();
297        }
298        List<ByteArrayInputStream> list = new ArrayList<ByteArrayInputStream>(buffers.size());
299        for (byte[] buf : buffers) {
300            int c = Math.min(buf.length, remaining);
301            list.add(new ByteArrayInputStream(buf, 0, c));
302            remaining -= c;
303            if (remaining == 0) {
304                break;
305            }
306        }
307        return new SequenceInputStream(Collections.enumeration(list));
308    }
309
310    /**
311     * Gets the curent contents of this byte stream as a byte array.
312     * The result is independent of this stream.
313     *
314     * @return the current contents of this output stream, as a byte array
315     * @see java.io.ByteArrayOutputStream#toByteArray()
316     */
317    public synchronized byte[] toByteArray() {
318        int remaining = count;
319        if (remaining == 0) {
320            return EMPTY_BYTE_ARRAY; 
321        }
322        byte newbuf[] = new byte[remaining];
323        int pos = 0;
324        for (byte[] buf : buffers) {
325            int c = Math.min(buf.length, remaining);
326            System.arraycopy(buf, 0, newbuf, pos, c);
327            pos += c;
328            remaining -= c;
329            if (remaining == 0) {
330                break;
331            }
332        }
333        return newbuf;
334    }
335
336    /**
337     * Gets the curent contents of this byte stream as a string.
338     * @return the contents of the byte array as a String
339     * @see java.io.ByteArrayOutputStream#toString()
340     */
341    @Override
342    public String toString() {
343        return new String(toByteArray());
344    }
345
346    /**
347     * Gets the curent contents of this byte stream as a string
348     * using the specified encoding.
349     *
350     * @param enc  the name of the character encoding
351     * @return the string converted from the byte array
352     * @throws UnsupportedEncodingException if the encoding is not supported
353     * @see java.io.ByteArrayOutputStream#toString(String)
354     */
355    public String toString(String enc) throws UnsupportedEncodingException {
356        return new String(toByteArray(), enc);
357    }
358
359}