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.IOException;
020    import java.io.InputStream;
021    import java.io.OutputStream;
022    import java.io.UnsupportedEncodingException;
023    import java.util.ArrayList;
024    import java.util.List;
025    
026    /**
027     * This class implements an output stream in which the data is 
028     * written into a byte array. The buffer automatically grows as data 
029     * is written to it.
030     * <p> 
031     * The data can be retrieved using <code>toByteArray()</code> and
032     * <code>toString()</code>.
033     * <p>
034     * Closing a <tt>ByteArrayOutputStream</tt> has no effect. The methods in
035     * this class can be called after the stream has been closed without
036     * generating an <tt>IOException</tt>.
037     * <p>
038     * This is an alternative implementation of the java.io.ByteArrayOutputStream
039     * class. The original implementation only allocates 32 bytes at the beginning.
040     * As this class is designed for heavy duty it starts at 1024 bytes. In contrast
041     * to the original it doesn't reallocate the whole memory block but allocates
042     * additional buffers. This way no buffers need to be garbage collected and
043     * the contents don't have to be copied to the new buffer. This class is
044     * designed to behave exactly like the original. The only exception is the
045     * deprecated toString(int) method that has been ignored.
046     * 
047     * @author <a href="mailto:jeremias@apache.org">Jeremias Maerki</a>
048     * @author Holger Hoffstatte
049     * @version $Id: ByteArrayOutputStream.java 610010 2008-01-08 14:50:59Z niallp $
050     */
051    public class ByteArrayOutputStream extends OutputStream {
052    
053        /** A singleton empty byte array. */
054        private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
055    
056        /** The list of buffers, which grows and never reduces. */
057        private List buffers = new ArrayList();
058        /** The index of the current buffer. */
059        private int currentBufferIndex;
060        /** The total count of bytes in all the filled buffers. */
061        private int filledBufferSum;
062        /** The current buffer. */
063        private byte[] currentBuffer;
064        /** The total count of bytes written. */
065        private int count;
066    
067        /**
068         * Creates a new byte array output stream. The buffer capacity is 
069         * initially 1024 bytes, though its size increases if necessary. 
070         */
071        public ByteArrayOutputStream() {
072            this(1024);
073        }
074    
075        /**
076         * Creates a new byte array output stream, with a buffer capacity of 
077         * the specified size, in bytes. 
078         *
079         * @param size  the initial size
080         * @throws IllegalArgumentException if size is negative
081         */
082        public ByteArrayOutputStream(int size) {
083            if (size < 0) {
084                throw new IllegalArgumentException(
085                    "Negative initial size: " + size);
086            }
087            needNewBuffer(size);
088        }
089    
090        /**
091         * Return the appropriate <code>byte[]</code> buffer 
092         * specified by index.
093         *
094         * @param index  the index of the buffer required
095         * @return the buffer
096         */
097        private byte[] getBuffer(int index) {
098            return (byte[]) buffers.get(index);
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(int newcount) {
108            if (currentBufferIndex < buffers.size() - 1) {
109                //Recycling old buffer
110                filledBufferSum += currentBuffer.length;
111                
112                currentBufferIndex++;
113                currentBuffer = getBuffer(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        public void write(byte[] b, int off, 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                int newcount = count + len;
151                int remaining = len;
152                int inBufferPos = count - filledBufferSum;
153                while (remaining > 0) {
154                    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        public synchronized void write(int b) {
171            int inBufferPos = count - filledBufferSum;
172            if (inBufferPos == currentBuffer.length) {
173                needNewBuffer(count + 1);
174                inBufferPos = 0;
175            }
176            currentBuffer[inBufferPos] = (byte) b;
177            count++;
178        }
179    
180        /**
181         * Writes the entire contents of the specified input stream to this
182         * byte stream. Bytes from the input stream are read directly into the
183         * internal buffers of this streams.
184         *
185         * @param in the input stream to read from
186         * @return total number of bytes read from the input stream
187         *         (and written to this stream)
188         * @throws IOException if an I/O error occurs while reading the input stream
189         * @since Commons IO 1.4
190         */
191        public synchronized int write(InputStream in) throws IOException {
192            int readCount = 0;
193            int inBufferPos = count - filledBufferSum;
194            int n = in.read(currentBuffer, inBufferPos, currentBuffer.length - inBufferPos);
195            while (n != -1) {
196                readCount += n;
197                inBufferPos += n;
198                count += n;
199                if (inBufferPos == currentBuffer.length) {
200                    needNewBuffer(currentBuffer.length);
201                    inBufferPos = 0;
202                }
203                n = in.read(currentBuffer, inBufferPos, currentBuffer.length - inBufferPos);
204            }
205            return readCount;
206        }
207    
208        /**
209         * Return the current size of the byte array.
210         * @return the current size of the byte array
211         */
212        public synchronized int size() {
213            return count;
214        }
215    
216        /**
217         * Closing a <tt>ByteArrayOutputStream</tt> has no effect. The methods in
218         * this class can be called after the stream has been closed without
219         * generating an <tt>IOException</tt>.
220         *
221         * @throws IOException never (this method should not declare this exception
222         * but it has to now due to backwards compatability)
223         */
224        public void close() throws IOException {
225            //nop
226        }
227    
228        /**
229         * @see java.io.ByteArrayOutputStream#reset()
230         */
231        public synchronized void reset() {
232            count = 0;
233            filledBufferSum = 0;
234            currentBufferIndex = 0;
235            currentBuffer = getBuffer(currentBufferIndex);
236        }
237    
238        /**
239         * Writes the entire contents of this byte stream to the
240         * specified output stream.
241         *
242         * @param out  the output stream to write to
243         * @throws IOException if an I/O error occurs, such as if the stream is closed
244         * @see java.io.ByteArrayOutputStream#writeTo(OutputStream)
245         */
246        public synchronized void writeTo(OutputStream out) throws IOException {
247            int remaining = count;
248            for (int i = 0; i < buffers.size(); i++) {
249                byte[] buf = getBuffer(i);
250                int c = Math.min(buf.length, remaining);
251                out.write(buf, 0, c);
252                remaining -= c;
253                if (remaining == 0) {
254                    break;
255                }
256            }
257        }
258    
259        /**
260         * Gets the curent contents of this byte stream as a byte array.
261         * The result is independent of this stream.
262         *
263         * @return the current contents of this output stream, as a byte array
264         * @see java.io.ByteArrayOutputStream#toByteArray()
265         */
266        public synchronized byte[] toByteArray() {
267            int remaining = count;
268            if (remaining == 0) {
269                return EMPTY_BYTE_ARRAY; 
270            }
271            byte newbuf[] = new byte[remaining];
272            int pos = 0;
273            for (int i = 0; i < buffers.size(); i++) {
274                byte[] buf = getBuffer(i);
275                int c = Math.min(buf.length, remaining);
276                System.arraycopy(buf, 0, newbuf, pos, c);
277                pos += c;
278                remaining -= c;
279                if (remaining == 0) {
280                    break;
281                }
282            }
283            return newbuf;
284        }
285    
286        /**
287         * Gets the curent contents of this byte stream as a string.
288         * @return the contents of the byte array as a String
289         * @see java.io.ByteArrayOutputStream#toString()
290         */
291        public String toString() {
292            return new String(toByteArray());
293        }
294    
295        /**
296         * Gets the curent contents of this byte stream as a string
297         * using the specified encoding.
298         *
299         * @param enc  the name of the character encoding
300         * @return the string converted from the byte array
301         * @throws UnsupportedEncodingException if the encoding is not supported
302         * @see java.io.ByteArrayOutputStream#toString(String)
303         */
304        public String toString(String enc) throws UnsupportedEncodingException {
305            return new String(toByteArray(), enc);
306        }
307    
308    }