1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 package org.apache.commons.io.output; 18 19 import java.io.IOException; 20 import java.io.InputStream; 21 import java.io.OutputStream; 22 import java.io.UnsupportedEncodingException; 23 import java.util.ArrayList; 24 import java.util.List; 25 26 /** 27 * This class implements an output stream in which the data is 28 * written into a byte array. The buffer automatically grows as data 29 * is written to it. 30 * <p> 31 * The data can be retrieved using <code>toByteArray()</code> and 32 * <code>toString()</code>. 33 * <p> 34 * Closing a <tt>ByteArrayOutputStream</tt> has no effect. The methods in 35 * this class can be called after the stream has been closed without 36 * generating an <tt>IOException</tt>. 37 * <p> 38 * This is an alternative implementation of the java.io.ByteArrayOutputStream 39 * class. The original implementation only allocates 32 bytes at the beginning. 40 * As this class is designed for heavy duty it starts at 1024 bytes. In contrast 41 * to the original it doesn't reallocate the whole memory block but allocates 42 * additional buffers. This way no buffers need to be garbage collected and 43 * the contents don't have to be copied to the new buffer. This class is 44 * designed to behave exactly like the original. The only exception is the 45 * deprecated toString(int) method that has been ignored. 46 * 47 * @author <a href="mailto:jeremias (at) apache.org">Jeremias Maerki</a> 48 * @author Holger Hoffstatte 49 * @version $Id: ByteArrayOutputStream.java 610010 2008-01-08 14:50:59Z niallp $ 50 */ 51 public class ByteArrayOutputStream extends OutputStream { 52 53 /** A singleton empty byte array. */ 54 private static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; 55 56 /** The list of buffers, which grows and never reduces. */ 57 private List buffers = new ArrayList(); 58 /** The index of the current buffer. */ 59 private int currentBufferIndex; 60 /** The total count of bytes in all the filled buffers. */ 61 private int filledBufferSum; 62 /** The current buffer. */ 63 private byte[] currentBuffer; 64 /** The total count of bytes written. */ 65 private int count; 66 67 /** 68 * Creates a new byte array output stream. The buffer capacity is 69 * initially 1024 bytes, though its size increases if necessary. 70 */ 71 public ByteArrayOutputStream() { 72 this(1024); 73 } 74 75 /** 76 * Creates a new byte array output stream, with a buffer capacity of 77 * the specified size, in bytes. 78 * 79 * @param size the initial size 80 * @throws IllegalArgumentException if size is negative 81 */ 82 public ByteArrayOutputStream(int size) { 83 if (size < 0) { 84 throw new IllegalArgumentException( 85 "Negative initial size: " + size); 86 } 87 needNewBuffer(size); 88 } 89 90 /** 91 * Return the appropriate <code>byte[]</code> buffer 92 * specified by index. 93 * 94 * @param index the index of the buffer required 95 * @return the buffer 96 */ 97 private byte[] getBuffer(int index) { 98 return (byte[]) buffers.get(index); 99 } 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(int newcount) { 108 if (currentBufferIndex < buffers.size() - 1) { 109 //Recycling old buffer 110 filledBufferSum += currentBuffer.length; 111 112 currentBufferIndex++; 113 currentBuffer = getBuffer(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 public void write(byte[] b, int off, 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 int newcount = count + len; 151 int remaining = len; 152 int inBufferPos = count - filledBufferSum; 153 while (remaining > 0) { 154 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 public synchronized void write(int b) { 171 int inBufferPos = count - filledBufferSum; 172 if (inBufferPos == currentBuffer.length) { 173 needNewBuffer(count + 1); 174 inBufferPos = 0; 175 } 176 currentBuffer[inBufferPos] = (byte) b; 177 count++; 178 } 179 180 /** 181 * Writes the entire contents of the specified input stream to this 182 * byte stream. Bytes from the input stream are read directly into the 183 * internal buffers of this streams. 184 * 185 * @param in the input stream to read from 186 * @return total number of bytes read from the input stream 187 * (and written to this stream) 188 * @throws IOException if an I/O error occurs while reading the input stream 189 * @since Commons IO 1.4 190 */ 191 public synchronized int write(InputStream in) throws IOException { 192 int readCount = 0; 193 int inBufferPos = count - filledBufferSum; 194 int n = in.read(currentBuffer, inBufferPos, currentBuffer.length - inBufferPos); 195 while (n != -1) { 196 readCount += n; 197 inBufferPos += n; 198 count += n; 199 if (inBufferPos == currentBuffer.length) { 200 needNewBuffer(currentBuffer.length); 201 inBufferPos = 0; 202 } 203 n = in.read(currentBuffer, inBufferPos, currentBuffer.length - inBufferPos); 204 } 205 return readCount; 206 } 207 208 /** 209 * Return the current size of the byte array. 210 * @return the current size of the byte array 211 */ 212 public synchronized int size() { 213 return count; 214 } 215 216 /** 217 * Closing a <tt>ByteArrayOutputStream</tt> has no effect. The methods in 218 * this class can be called after the stream has been closed without 219 * generating an <tt>IOException</tt>. 220 * 221 * @throws IOException never (this method should not declare this exception 222 * but it has to now due to backwards compatability) 223 */ 224 public void close() throws IOException { 225 //nop 226 } 227 228 /** 229 * @see java.io.ByteArrayOutputStream#reset() 230 */ 231 public synchronized void reset() { 232 count = 0; 233 filledBufferSum = 0; 234 currentBufferIndex = 0; 235 currentBuffer = getBuffer(currentBufferIndex); 236 } 237 238 /** 239 * Writes the entire contents of this byte stream to the 240 * specified output stream. 241 * 242 * @param out the output stream to write to 243 * @throws IOException if an I/O error occurs, such as if the stream is closed 244 * @see java.io.ByteArrayOutputStream#writeTo(OutputStream) 245 */ 246 public synchronized void writeTo(OutputStream out) throws IOException { 247 int remaining = count; 248 for (int i = 0; i < buffers.size(); i++) { 249 byte[] buf = getBuffer(i); 250 int c = Math.min(buf.length, remaining); 251 out.write(buf, 0, c); 252 remaining -= c; 253 if (remaining == 0) { 254 break; 255 } 256 } 257 } 258 259 /** 260 * Gets the curent contents of this byte stream as a byte array. 261 * The result is independent of this stream. 262 * 263 * @return the current contents of this output stream, as a byte array 264 * @see java.io.ByteArrayOutputStream#toByteArray() 265 */ 266 public synchronized byte[] toByteArray() { 267 int remaining = count; 268 if (remaining == 0) { 269 return EMPTY_BYTE_ARRAY; 270 } 271 byte newbuf[] = new byte[remaining]; 272 int pos = 0; 273 for (int i = 0; i < buffers.size(); i++) { 274 byte[] buf = getBuffer(i); 275 int c = Math.min(buf.length, remaining); 276 System.arraycopy(buf, 0, newbuf, pos, c); 277 pos += c; 278 remaining -= c; 279 if (remaining == 0) { 280 break; 281 } 282 } 283 return newbuf; 284 } 285 286 /** 287 * Gets the curent contents of this byte stream as a string. 288 * @return the contents of the byte array as a String 289 * @see java.io.ByteArrayOutputStream#toString() 290 */ 291 public String toString() { 292 return new String(toByteArray()); 293 } 294 295 /** 296 * Gets the curent contents of this byte stream as a string 297 * using the specified encoding. 298 * 299 * @param enc the name of the character encoding 300 * @return the string converted from the byte array 301 * @throws UnsupportedEncodingException if the encoding is not supported 302 * @see java.io.ByteArrayOutputStream#toString(String) 303 */ 304 public String toString(String enc) throws UnsupportedEncodingException { 305 return new String(toByteArray(), enc); 306 } 307 308 } 309