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 * @author <a href="mailto:jeremias@apache.org">Jeremias Maerki</a> 053 * @author Holger Hoffstatte 054 * @version $Id: ByteArrayOutputStream.java 1153506 2011-08-03 14:32:38Z 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 }