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 }