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 {@link 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 * @version $Id: ByteArrayOutputStream.java 1304052 2012-03-22 20:55:29Z ggregory $
053 */
054 public class ByteArrayOutputStream extends OutputStream {
055
056 /** A singleton empty byte array. */
057 private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
058
059 /** The list of buffers, which grows and never reduces. */
060 private final List<byte[]> buffers = new ArrayList<byte[]>();
061 /** The index of the current buffer. */
062 private int currentBufferIndex;
063 /** The total count of bytes in all the filled buffers. */
064 private int filledBufferSum;
065 /** The current buffer. */
066 private byte[] currentBuffer;
067 /** The total count of bytes written. */
068 private int count;
069
070 /**
071 * Creates a new byte array output stream. The buffer capacity is
072 * initially 1024 bytes, though its size increases if necessary.
073 */
074 public ByteArrayOutputStream() {
075 this(1024);
076 }
077
078 /**
079 * Creates a new byte array output stream, with a buffer capacity of
080 * the specified size, in bytes.
081 *
082 * @param size the initial size
083 * @throws IllegalArgumentException if size is negative
084 */
085 public ByteArrayOutputStream(int size) {
086 if (size < 0) {
087 throw new IllegalArgumentException(
088 "Negative initial size: " + size);
089 }
090 synchronized (this) {
091 needNewBuffer(size);
092 }
093 }
094
095 /**
096 * Makes a new buffer available either by allocating
097 * a new one or re-cycling an existing one.
098 *
099 * @param newcount the size of the buffer if one is created
100 */
101 private void needNewBuffer(int newcount) {
102 if (currentBufferIndex < buffers.size() - 1) {
103 //Recycling old buffer
104 filledBufferSum += currentBuffer.length;
105
106 currentBufferIndex++;
107 currentBuffer = buffers.get(currentBufferIndex);
108 } else {
109 //Creating new buffer
110 int newBufferSize;
111 if (currentBuffer == null) {
112 newBufferSize = newcount;
113 filledBufferSum = 0;
114 } else {
115 newBufferSize = Math.max(
116 currentBuffer.length << 1,
117 newcount - filledBufferSum);
118 filledBufferSum += currentBuffer.length;
119 }
120
121 currentBufferIndex++;
122 currentBuffer = new byte[newBufferSize];
123 buffers.add(currentBuffer);
124 }
125 }
126
127 /**
128 * Write the bytes to byte array.
129 * @param b the bytes to write
130 * @param off The start offset
131 * @param len The number of bytes to write
132 */
133 @Override
134 public void write(byte[] b, int off, int len) {
135 if ((off < 0)
136 || (off > b.length)
137 || (len < 0)
138 || ((off + len) > b.length)
139 || ((off + len) < 0)) {
140 throw new IndexOutOfBoundsException();
141 } else if (len == 0) {
142 return;
143 }
144 synchronized (this) {
145 int newcount = count + len;
146 int remaining = len;
147 int inBufferPos = count - filledBufferSum;
148 while (remaining > 0) {
149 int part = Math.min(remaining, currentBuffer.length - inBufferPos);
150 System.arraycopy(b, off + len - remaining, currentBuffer, inBufferPos, part);
151 remaining -= part;
152 if (remaining > 0) {
153 needNewBuffer(newcount);
154 inBufferPos = 0;
155 }
156 }
157 count = newcount;
158 }
159 }
160
161 /**
162 * Write a byte to byte array.
163 * @param b the byte to write
164 */
165 @Override
166 public synchronized void write(int b) {
167 int inBufferPos = count - filledBufferSum;
168 if (inBufferPos == currentBuffer.length) {
169 needNewBuffer(count + 1);
170 inBufferPos = 0;
171 }
172 currentBuffer[inBufferPos] = (byte) b;
173 count++;
174 }
175
176 /**
177 * Writes the entire contents of the specified input stream to this
178 * byte stream. Bytes from the input stream are read directly into the
179 * internal buffers of this streams.
180 *
181 * @param in the input stream to read from
182 * @return total number of bytes read from the input stream
183 * (and written to this stream)
184 * @throws IOException if an I/O error occurs while reading the input stream
185 * @since 1.4
186 */
187 public synchronized int write(InputStream in) throws IOException {
188 int readCount = 0;
189 int inBufferPos = count - filledBufferSum;
190 int n = in.read(currentBuffer, inBufferPos, currentBuffer.length - inBufferPos);
191 while (n != -1) {
192 readCount += n;
193 inBufferPos += n;
194 count += n;
195 if (inBufferPos == currentBuffer.length) {
196 needNewBuffer(currentBuffer.length);
197 inBufferPos = 0;
198 }
199 n = in.read(currentBuffer, inBufferPos, currentBuffer.length - inBufferPos);
200 }
201 return readCount;
202 }
203
204 /**
205 * Return the current size of the byte array.
206 * @return the current size of the byte array
207 */
208 public synchronized int size() {
209 return count;
210 }
211
212 /**
213 * Closing a <tt>ByteArrayOutputStream</tt> has no effect. The methods in
214 * this class can be called after the stream has been closed without
215 * generating an <tt>IOException</tt>.
216 *
217 * @throws IOException never (this method should not declare this exception
218 * but it has to now due to backwards compatability)
219 */
220 @Override
221 public void close() throws IOException {
222 //nop
223 }
224
225 /**
226 * @see java.io.ByteArrayOutputStream#reset()
227 */
228 public synchronized void reset() {
229 count = 0;
230 filledBufferSum = 0;
231 currentBufferIndex = 0;
232 currentBuffer = buffers.get(currentBufferIndex);
233 }
234
235 /**
236 * Writes the entire contents of this byte stream to the
237 * specified output stream.
238 *
239 * @param out the output stream to write to
240 * @throws IOException if an I/O error occurs, such as if the stream is closed
241 * @see java.io.ByteArrayOutputStream#writeTo(OutputStream)
242 */
243 public synchronized void writeTo(OutputStream out) throws IOException {
244 int remaining = count;
245 for (byte[] buf : buffers) {
246 int c = Math.min(buf.length, remaining);
247 out.write(buf, 0, c);
248 remaining -= c;
249 if (remaining == 0) {
250 break;
251 }
252 }
253 }
254
255 /**
256 * Fetches entire contents of an <code>InputStream</code> and represent
257 * same data as result InputStream.
258 * <p>
259 * This method is useful where,
260 * <ul>
261 * <li>Source InputStream is slow.</li>
262 * <li>It has network resources associated, so we cannot keep it open for
263 * long time.</li>
264 * <li>It has network timeout associated.</li>
265 * </ul>
266 * It can be used in favor of {@link #toByteArray()}, since it
267 * avoids unnecessary allocation and copy of byte[].<br>
268 * This method buffers the input internally, so there is no need to use a
269 * <code>BufferedInputStream</code>.
270 *
271 * @param input Stream to be fully buffered.
272 * @return A fully buffered stream.
273 * @throws IOException if an I/O error occurs
274 * @since 2.0
275 */
276 public static InputStream toBufferedInputStream(InputStream input)
277 throws IOException {
278 ByteArrayOutputStream output = new ByteArrayOutputStream();
279 output.write(input);
280 return output.toBufferedInputStream();
281 }
282
283 /**
284 * Gets the current contents of this byte stream as a Input Stream. The
285 * returned stream is backed by buffers of <code>this</code> stream,
286 * avoiding memory allocation and copy, thus saving space and time.<br>
287 *
288 * @return the current contents of this output stream.
289 * @see java.io.ByteArrayOutputStream#toByteArray()
290 * @see #reset()
291 * @since 2.0
292 */
293 private InputStream toBufferedInputStream() {
294 int remaining = count;
295 if (remaining == 0) {
296 return new ClosedInputStream();
297 }
298 List<ByteArrayInputStream> list = new ArrayList<ByteArrayInputStream>(buffers.size());
299 for (byte[] buf : buffers) {
300 int c = Math.min(buf.length, remaining);
301 list.add(new ByteArrayInputStream(buf, 0, c));
302 remaining -= c;
303 if (remaining == 0) {
304 break;
305 }
306 }
307 return new SequenceInputStream(Collections.enumeration(list));
308 }
309
310 /**
311 * Gets the curent contents of this byte stream as a byte array.
312 * The result is independent of this stream.
313 *
314 * @return the current contents of this output stream, as a byte array
315 * @see java.io.ByteArrayOutputStream#toByteArray()
316 */
317 public synchronized byte[] toByteArray() {
318 int remaining = count;
319 if (remaining == 0) {
320 return EMPTY_BYTE_ARRAY;
321 }
322 byte newbuf[] = new byte[remaining];
323 int pos = 0;
324 for (byte[] buf : buffers) {
325 int c = Math.min(buf.length, remaining);
326 System.arraycopy(buf, 0, newbuf, pos, c);
327 pos += c;
328 remaining -= c;
329 if (remaining == 0) {
330 break;
331 }
332 }
333 return newbuf;
334 }
335
336 /**
337 * Gets the curent contents of this byte stream as a string.
338 * @return the contents of the byte array as a String
339 * @see java.io.ByteArrayOutputStream#toString()
340 */
341 @Override
342 public String toString() {
343 return new String(toByteArray());
344 }
345
346 /**
347 * Gets the curent contents of this byte stream as a string
348 * using the specified encoding.
349 *
350 * @param enc the name of the character encoding
351 * @return the string converted from the byte array
352 * @throws UnsupportedEncodingException if the encoding is not supported
353 * @see java.io.ByteArrayOutputStream#toString(String)
354 */
355 public String toString(String enc) throws UnsupportedEncodingException {
356 return new String(toByteArray(), enc);
357 }
358
359 }