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 }