View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    * 
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   * 
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.io.output;
18   
19  import java.io.IOException;
20  import java.io.OutputStream;
21  import java.io.UnsupportedEncodingException;
22  import java.util.ArrayList;
23  import java.util.List;
24  
25  /**
26   * This class implements an output stream in which the data is 
27   * written into a byte array. The buffer automatically grows as data 
28   * is written to it.
29   * <p> 
30   * The data can be retrieved using <code>toByteArray()</code> and
31   * <code>toString()</code>.
32   * <p>
33   * Closing a <tt>ByteArrayOutputStream</tt> has no effect. The methods in
34   * this class can be called after the stream has been closed without
35   * generating an <tt>IOException</tt>.
36   * <p>
37   * This is an alternative implementation of the java.io.ByteArrayOutputStream
38   * class. The original implementation only allocates 32 bytes at the beginning.
39   * As this class is designed for heavy duty it starts at 1024 bytes. In contrast
40   * to the original it doesn't reallocate the whole memory block but allocates
41   * additional buffers. This way no buffers need to be garbage collected and
42   * the contents don't have to be copied to the new buffer. This class is
43   * designed to behave exactly like the original. The only exception is the
44   * deprecated toString(int) method that has been ignored.
45   * 
46   * @author <a href="mailto:jeremias@apache.org">Jeremias Maerki</a>
47   * @author Holger Hoffstatte
48   * @version $Id: ByteArrayOutputStream.java 491007 2006-12-29 13:50:34Z scolebourne $
49   */
50  public class ByteArrayOutputStream extends OutputStream {
51  
52      /** A singleton empty byte array. */
53      private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
54  
55      /** The list of buffers, which grows and never reduces. */
56      private List buffers = new ArrayList();
57      /** The index of the current buffer. */
58      private int currentBufferIndex;
59      /** The total count of bytes in all the filled buffers. */
60      private int filledBufferSum;
61      /** The current buffer. */
62      private byte[] currentBuffer;
63      /** The total count of bytes written. */
64      private int count;
65  
66      /**
67       * Creates a new byte array output stream. The buffer capacity is 
68       * initially 1024 bytes, though its size increases if necessary. 
69       */
70      public ByteArrayOutputStream() {
71          this(1024);
72      }
73  
74      /**
75       * Creates a new byte array output stream, with a buffer capacity of 
76       * the specified size, in bytes. 
77       *
78       * @param size  the initial size
79       * @throws IllegalArgumentException if size is negative
80       */
81      public ByteArrayOutputStream(int size) {
82          if (size < 0) {
83              throw new IllegalArgumentException(
84                  "Negative initial size: " + size);
85          }
86          needNewBuffer(size);
87      }
88  
89      /**
90       * Return the appropriate <code>byte[]</code> buffer 
91       * specified by index.
92       *
93       * @param index  the index of the buffer required
94       * @return the buffer
95       */
96      private byte[] getBuffer(int index) {
97          return (byte[]) buffers.get(index);
98      }
99  
100     /**
101      * Makes a new buffer available either by allocating
102      * a new one or re-cycling an existing one.
103      *
104      * @param newcount  the size of the buffer if one is created
105      */
106     private void needNewBuffer(int newcount) {
107         if (currentBufferIndex < buffers.size() - 1) {
108             //Recycling old buffer
109             filledBufferSum += currentBuffer.length;
110             
111             currentBufferIndex++;
112             currentBuffer = getBuffer(currentBufferIndex);
113         } else {
114             //Creating new buffer
115             int newBufferSize;
116             if (currentBuffer == null) {
117                 newBufferSize = newcount;
118                 filledBufferSum = 0;
119             } else {
120                 newBufferSize = Math.max(
121                     currentBuffer.length << 1, 
122                     newcount - filledBufferSum);
123                 filledBufferSum += currentBuffer.length;
124             }
125             
126             currentBufferIndex++;
127             currentBuffer = new byte[newBufferSize];
128             buffers.add(currentBuffer);
129         }
130     }
131 
132     /**
133      * @see java.io.OutputStream#write(byte[], int, int)
134      */
135     public void write(byte[] b, int off, int len) {
136         if ((off < 0) 
137                 || (off > b.length) 
138                 || (len < 0) 
139                 || ((off + len) > b.length) 
140                 || ((off + len) < 0)) {
141             throw new IndexOutOfBoundsException();
142         } else if (len == 0) {
143             return;
144         }
145         synchronized (this) {
146             int newcount = count + len;
147             int remaining = len;
148             int inBufferPos = count - filledBufferSum;
149             while (remaining > 0) {
150                 int part = Math.min(remaining, currentBuffer.length - inBufferPos);
151                 System.arraycopy(b, off + len - remaining, currentBuffer, inBufferPos, part);
152                 remaining -= part;
153                 if (remaining > 0) {
154                     needNewBuffer(newcount);
155                     inBufferPos = 0;
156                 }
157             }
158             count = newcount;
159         }
160     }
161 
162     /**
163      * @see java.io.OutputStream#write(int)
164      */
165     public synchronized void write(int b) {
166         int inBufferPos = count - filledBufferSum;
167         if (inBufferPos == currentBuffer.length) {
168             needNewBuffer(count + 1);
169             inBufferPos = 0;
170         }
171         currentBuffer[inBufferPos] = (byte) b;
172         count++;
173     }
174 
175     /**
176      * @see java.io.ByteArrayOutputStream#size()
177      */
178     public synchronized int size() {
179         return count;
180     }
181 
182     /**
183      * Closing a <tt>ByteArrayOutputStream</tt> has no effect. The methods in
184      * this class can be called after the stream has been closed without
185      * generating an <tt>IOException</tt>.
186      *
187      * @throws IOException never (this method should not declare this exception
188      * but it has to now due to backwards compatability)
189      */
190     public void close() throws IOException {
191         //nop
192     }
193 
194     /**
195      * @see java.io.ByteArrayOutputStream#reset()
196      */
197     public synchronized void reset() {
198         count = 0;
199         filledBufferSum = 0;
200         currentBufferIndex = 0;
201         currentBuffer = getBuffer(currentBufferIndex);
202     }
203 
204     /**
205      * Writes the entire contents of this byte stream to the
206      * specified output stream.
207      *
208      * @param out  the output stream to write to
209      * @throws IOException if an I/O error occurs, such as if the stream is closed
210      * @see java.io.ByteArrayOutputStream#writeTo(OutputStream)
211      */
212     public synchronized void writeTo(OutputStream out) throws IOException {
213         int remaining = count;
214         for (int i = 0; i < buffers.size(); i++) {
215             byte[] buf = getBuffer(i);
216             int c = Math.min(buf.length, remaining);
217             out.write(buf, 0, c);
218             remaining -= c;
219             if (remaining == 0) {
220                 break;
221             }
222         }
223     }
224 
225     /**
226      * Gets the curent contents of this byte stream as a byte array.
227      * The result is independent of this stream.
228      *
229      * @return the current contents of this output stream, as a byte array
230      * @see java.io.ByteArrayOutputStream#toByteArray()
231      */
232     public synchronized byte[] toByteArray() {
233         int remaining = count;
234         if (remaining == 0) {
235             return EMPTY_BYTE_ARRAY; 
236         }
237         byte newbuf[] = new byte[remaining];
238         int pos = 0;
239         for (int i = 0; i < buffers.size(); i++) {
240             byte[] buf = getBuffer(i);
241             int c = Math.min(buf.length, remaining);
242             System.arraycopy(buf, 0, newbuf, pos, c);
243             pos += c;
244             remaining -= c;
245             if (remaining == 0) {
246                 break;
247             }
248         }
249         return newbuf;
250     }
251 
252     /**
253      * Gets the curent contents of this byte stream as a string.
254      *
255      * @see java.io.ByteArrayOutputStream#toString()
256      */
257     public String toString() {
258         return new String(toByteArray());
259     }
260 
261     /**
262      * Gets the curent contents of this byte stream as a string
263      * using the specified encoding.
264      *
265      * @param enc  the name of the character encoding
266      * @return the string converted from the byte array
267      * @throws UnsupportedEncodingException if the encoding is not supported
268      * @see java.io.ByteArrayOutputStream#toString(String)
269      */
270     public String toString(String enc) throws UnsupportedEncodingException {
271         return new String(toByteArray(), enc);
272     }
273 
274 }