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     * @author <a href="mailto:jeremias@apache.org">Jeremias Maerki</a>
053     * @author Holger Hoffstatte
054     * @version $Id: ByteArrayOutputStream.java 1153506 2011-08-03 14:32:38Z 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    }