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 {@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     */
054    public 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    }