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