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 }