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.IOException; 020 import java.io.InputStream; 021 import java.io.OutputStream; 022 import java.io.UnsupportedEncodingException; 023 import java.util.ArrayList; 024 import java.util.List; 025 026 /** 027 * This class implements an output stream in which the data is 028 * written into a byte array. The buffer automatically grows as data 029 * is written to it. 030 * <p> 031 * The data can be retrieved using <code>toByteArray()</code> and 032 * <code>toString()</code>. 033 * <p> 034 * Closing a <tt>ByteArrayOutputStream</tt> has no effect. The methods in 035 * this class can be called after the stream has been closed without 036 * generating an <tt>IOException</tt>. 037 * <p> 038 * This is an alternative implementation of the java.io.ByteArrayOutputStream 039 * class. The original implementation only allocates 32 bytes at the beginning. 040 * As this class is designed for heavy duty it starts at 1024 bytes. In contrast 041 * to the original it doesn't reallocate the whole memory block but allocates 042 * additional buffers. This way no buffers need to be garbage collected and 043 * the contents don't have to be copied to the new buffer. This class is 044 * designed to behave exactly like the original. The only exception is the 045 * deprecated toString(int) method that has been ignored. 046 * 047 * @author <a href="mailto:jeremias@apache.org">Jeremias Maerki</a> 048 * @author Holger Hoffstatte 049 * @version $Id: ByteArrayOutputStream.java 610010 2008-01-08 14:50:59Z niallp $ 050 */ 051 public class ByteArrayOutputStream extends OutputStream { 052 053 /** A singleton empty byte array. */ 054 private static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; 055 056 /** The list of buffers, which grows and never reduces. */ 057 private List buffers = new ArrayList(); 058 /** The index of the current buffer. */ 059 private int currentBufferIndex; 060 /** The total count of bytes in all the filled buffers. */ 061 private int filledBufferSum; 062 /** The current buffer. */ 063 private byte[] currentBuffer; 064 /** The total count of bytes written. */ 065 private int count; 066 067 /** 068 * Creates a new byte array output stream. The buffer capacity is 069 * initially 1024 bytes, though its size increases if necessary. 070 */ 071 public ByteArrayOutputStream() { 072 this(1024); 073 } 074 075 /** 076 * Creates a new byte array output stream, with a buffer capacity of 077 * the specified size, in bytes. 078 * 079 * @param size the initial size 080 * @throws IllegalArgumentException if size is negative 081 */ 082 public ByteArrayOutputStream(int size) { 083 if (size < 0) { 084 throw new IllegalArgumentException( 085 "Negative initial size: " + size); 086 } 087 needNewBuffer(size); 088 } 089 090 /** 091 * Return the appropriate <code>byte[]</code> buffer 092 * specified by index. 093 * 094 * @param index the index of the buffer required 095 * @return the buffer 096 */ 097 private byte[] getBuffer(int index) { 098 return (byte[]) buffers.get(index); 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(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 }