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 */ 017package org.apache.commons.io.output; 018 019import static org.apache.commons.io.IOUtils.EOF; 020 021import java.io.ByteArrayInputStream; 022import java.io.IOException; 023import java.io.InputStream; 024import java.io.OutputStream; 025import java.io.SequenceInputStream; 026import java.io.UnsupportedEncodingException; 027import java.nio.charset.Charset; 028import java.util.ArrayList; 029import java.util.Collections; 030import java.util.List; 031 032import org.apache.commons.io.input.ClosedInputStream; 033 034/** 035 * This class implements an output stream in which the data is 036 * written into a byte array. The buffer automatically grows as data 037 * is written to it. 038 * <p> 039 * The data can be retrieved using <code>toByteArray()</code> and 040 * <code>toString()</code>. 041 * <p> 042 * Closing a {@code ByteArrayOutputStream} has no effect. The methods in 043 * this class can be called after the stream has been closed without 044 * generating an {@code IOException}. 045 * <p> 046 * This is an alternative implementation of the {@link java.io.ByteArrayOutputStream} 047 * class. The original implementation only allocates 32 bytes at the beginning. 048 * As this class is designed for heavy duty it starts at 1024 bytes. In contrast 049 * to the original it doesn't reallocate the whole memory block but allocates 050 * additional buffers. This way no buffers need to be garbage collected and 051 * the contents don't have to be copied to the new buffer. This class is 052 * designed to behave exactly like the original. The only exception is the 053 * deprecated toString(int) method that has been ignored. 054 * 055 * @version $Id: ByteArrayOutputStream.java 1612034 2014-07-20 06:35:19Z ggregory $ 056 */ 057public class ByteArrayOutputStream extends OutputStream { 058 059 /** A singleton empty byte array. */ 060 private static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; 061 062 /** The list of buffers, which grows and never reduces. */ 063 private final List<byte[]> buffers = new ArrayList<byte[]>(); 064 /** The index of the current buffer. */ 065 private int currentBufferIndex; 066 /** The total count of bytes in all the filled buffers. */ 067 private int filledBufferSum; 068 /** The current buffer. */ 069 private byte[] currentBuffer; 070 /** The total count of bytes written. */ 071 private int count; 072 /** Flag to indicate if the buffers can be reused after reset */ 073 private boolean reuseBuffers = true; 074 075 /** 076 * Creates a new byte array output stream. The buffer capacity is 077 * initially 1024 bytes, though its size increases if necessary. 078 */ 079 public ByteArrayOutputStream() { 080 this(1024); 081 } 082 083 /** 084 * Creates a new byte array output stream, with a buffer capacity of 085 * the specified size, in bytes. 086 * 087 * @param size the initial size 088 * @throws IllegalArgumentException if size is negative 089 */ 090 public ByteArrayOutputStream(final int size) { 091 if (size < 0) { 092 throw new IllegalArgumentException( 093 "Negative initial size: " + size); 094 } 095 synchronized (this) { 096 needNewBuffer(size); 097 } 098 } 099 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(final int newcount) { 107 if (currentBufferIndex < buffers.size() - 1) { 108 //Recycling old buffer 109 filledBufferSum += currentBuffer.length; 110 111 currentBufferIndex++; 112 currentBuffer = buffers.get(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 * Write the bytes to byte array. 134 * @param b the bytes to write 135 * @param off The start offset 136 * @param len The number of bytes to write 137 */ 138 @Override 139 public void write(final byte[] b, final int off, final 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 final int newcount = count + len; 151 int remaining = len; 152 int inBufferPos = count - filledBufferSum; 153 while (remaining > 0) { 154 final 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 @Override 171 public synchronized void write(final int b) { 172 int inBufferPos = count - filledBufferSum; 173 if (inBufferPos == currentBuffer.length) { 174 needNewBuffer(count + 1); 175 inBufferPos = 0; 176 } 177 currentBuffer[inBufferPos] = (byte) b; 178 count++; 179 } 180 181 /** 182 * Writes the entire contents of the specified input stream to this 183 * byte stream. Bytes from the input stream are read directly into the 184 * internal buffers of this streams. 185 * 186 * @param in the input stream to read from 187 * @return total number of bytes read from the input stream 188 * (and written to this stream) 189 * @throws IOException if an I/O error occurs while reading the input stream 190 * @since 1.4 191 */ 192 public synchronized int write(final InputStream in) throws IOException { 193 int readCount = 0; 194 int inBufferPos = count - filledBufferSum; 195 int n = in.read(currentBuffer, inBufferPos, currentBuffer.length - inBufferPos); 196 while (n != EOF) { 197 readCount += n; 198 inBufferPos += n; 199 count += n; 200 if (inBufferPos == currentBuffer.length) { 201 needNewBuffer(currentBuffer.length); 202 inBufferPos = 0; 203 } 204 n = in.read(currentBuffer, inBufferPos, currentBuffer.length - inBufferPos); 205 } 206 return readCount; 207 } 208 209 /** 210 * Return the current size of the byte array. 211 * @return the current size of the byte array 212 */ 213 public synchronized int size() { 214 return count; 215 } 216 217 /** 218 * Closing a {@code ByteArrayOutputStream} has no effect. The methods in 219 * this class can be called after the stream has been closed without 220 * generating an {@code IOException}. 221 * 222 * @throws IOException never (this method should not declare this exception 223 * but it has to now due to backwards compatibility) 224 */ 225 @Override 226 public void close() throws IOException { 227 //nop 228 } 229 230 /** 231 * @see java.io.ByteArrayOutputStream#reset() 232 */ 233 public synchronized void reset() { 234 count = 0; 235 filledBufferSum = 0; 236 currentBufferIndex = 0; 237 if (reuseBuffers) { 238 currentBuffer = buffers.get(currentBufferIndex); 239 } else { 240 //Throw away old buffers 241 currentBuffer = null; 242 int size = buffers.get(0).length; 243 buffers.clear(); 244 needNewBuffer(size); 245 reuseBuffers = true; 246 } 247 } 248 249 /** 250 * Writes the entire contents of this byte stream to the 251 * specified output stream. 252 * 253 * @param out the output stream to write to 254 * @throws IOException if an I/O error occurs, such as if the stream is closed 255 * @see java.io.ByteArrayOutputStream#writeTo(OutputStream) 256 */ 257 public synchronized void writeTo(final OutputStream out) throws IOException { 258 int remaining = count; 259 for (final byte[] buf : buffers) { 260 final int c = Math.min(buf.length, remaining); 261 out.write(buf, 0, c); 262 remaining -= c; 263 if (remaining == 0) { 264 break; 265 } 266 } 267 } 268 269 /** 270 * Fetches entire contents of an <code>InputStream</code> and represent 271 * same data as result InputStream. 272 * <p> 273 * This method is useful where, 274 * <ul> 275 * <li>Source InputStream is slow.</li> 276 * <li>It has network resources associated, so we cannot keep it open for 277 * long time.</li> 278 * <li>It has network timeout associated.</li> 279 * </ul> 280 * It can be used in favor of {@link #toByteArray()}, since it 281 * avoids unnecessary allocation and copy of byte[].<br> 282 * This method buffers the input internally, so there is no need to use a 283 * <code>BufferedInputStream</code>. 284 * 285 * @param input Stream to be fully buffered. 286 * @return A fully buffered stream. 287 * @throws IOException if an I/O error occurs 288 * @since 2.0 289 */ 290 public static InputStream toBufferedInputStream(final InputStream input) 291 throws IOException { 292 return toBufferedInputStream(input, 1024); 293 } 294 295 /** 296 * Fetches entire contents of an <code>InputStream</code> and represent 297 * same data as result InputStream. 298 * <p> 299 * This method is useful where, 300 * <ul> 301 * <li>Source InputStream is slow.</li> 302 * <li>It has network resources associated, so we cannot keep it open for 303 * long time.</li> 304 * <li>It has network timeout associated.</li> 305 * </ul> 306 * It can be used in favor of {@link #toByteArray()}, since it 307 * avoids unnecessary allocation and copy of byte[].<br> 308 * This method buffers the input internally, so there is no need to use a 309 * <code>BufferedInputStream</code>. 310 * 311 * @param input Stream to be fully buffered. 312 * @param size the initial buffer size 313 * @return A fully buffered stream. 314 * @throws IOException if an I/O error occurs 315 * @since 2.5 316 */ 317 public static InputStream toBufferedInputStream(final InputStream input, int size) 318 throws IOException { 319 // It does not matter if a ByteArrayOutputStream is not closed as close() is a no-op 320 @SuppressWarnings("resource") 321 final ByteArrayOutputStream output = new ByteArrayOutputStream(size); 322 output.write(input); 323 return output.toInputStream(); 324 } 325 326 /** 327 * Gets the current contents of this byte stream as a Input Stream. The 328 * returned stream is backed by buffers of <code>this</code> stream, 329 * avoiding memory allocation and copy, thus saving space and time.<br> 330 * 331 * @return the current contents of this output stream. 332 * @see java.io.ByteArrayOutputStream#toByteArray() 333 * @see #reset() 334 * @since 2.5 335 */ 336 public synchronized InputStream toInputStream() { 337 int remaining = count; 338 if (remaining == 0) { 339 return new ClosedInputStream(); 340 } 341 final List<ByteArrayInputStream> list = new ArrayList<ByteArrayInputStream>(buffers.size()); 342 for (final byte[] buf : buffers) { 343 final int c = Math.min(buf.length, remaining); 344 list.add(new ByteArrayInputStream(buf, 0, c)); 345 remaining -= c; 346 if (remaining == 0) { 347 break; 348 } 349 } 350 reuseBuffers = false; 351 return new SequenceInputStream(Collections.enumeration(list)); 352 } 353 354 /** 355 * Gets the curent contents of this byte stream as a byte array. 356 * The result is independent of this stream. 357 * 358 * @return the current contents of this output stream, as a byte array 359 * @see java.io.ByteArrayOutputStream#toByteArray() 360 */ 361 public synchronized byte[] toByteArray() { 362 int remaining = count; 363 if (remaining == 0) { 364 return EMPTY_BYTE_ARRAY; 365 } 366 final byte newbuf[] = new byte[remaining]; 367 int pos = 0; 368 for (final byte[] buf : buffers) { 369 final int c = Math.min(buf.length, remaining); 370 System.arraycopy(buf, 0, newbuf, pos, c); 371 pos += c; 372 remaining -= c; 373 if (remaining == 0) { 374 break; 375 } 376 } 377 return newbuf; 378 } 379 380 /** 381 * Gets the curent contents of this byte stream as a string 382 * using the platform default charset. 383 * @return the contents of the byte array as a String 384 * @see java.io.ByteArrayOutputStream#toString() 385 * @deprecated 2.5 use {@link #toString(String)} instead 386 */ 387 @Override 388 @Deprecated 389 public String toString() { 390 // make explicit the use of the default charset 391 return new String(toByteArray(), Charset.defaultCharset()); 392 } 393 394 /** 395 * Gets the curent contents of this byte stream as a string 396 * using the specified encoding. 397 * 398 * @param enc the name of the character encoding 399 * @return the string converted from the byte array 400 * @throws UnsupportedEncodingException if the encoding is not supported 401 * @see java.io.ByteArrayOutputStream#toString(String) 402 */ 403 public String toString(final String enc) throws UnsupportedEncodingException { 404 return new String(toByteArray(), enc); 405 } 406 407 /** 408 * Gets the curent contents of this byte stream as a string 409 * using the specified encoding. 410 * 411 * @param charset the character encoding 412 * @return the string converted from the byte array 413 * @see java.io.ByteArrayOutputStream#toString(String) 414 * @since 2.5 415 */ 416 public String toString(final Charset charset) { 417 return new String(toByteArray(), charset); 418 } 419 420}