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 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 1021884 2010-10-12 18:49:16Z 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 }