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 org.apache.commons.io.input.ClosedInputStream; 020 021import java.io.InputStream; 022import java.io.IOException; 023import java.io.OutputStream; 024import java.io.SequenceInputStream; 025import java.io.UnsupportedEncodingException; 026import java.nio.charset.Charset; 027import java.util.ArrayList; 028import java.util.Collections; 029import java.util.List; 030 031import static org.apache.commons.io.IOUtils.EOF; 032 033/** 034 * This is the base class for implementing an output stream in which the data 035 * is written into a byte array. The buffer automatically grows as data 036 * is written to it. 037 * <p> 038 * The data can be retrieved using <code>toByteArray()</code> and 039 * <code>toString()</code>. 040 * Closing an {@code AbstractByteArrayOutputStream} has no effect. The methods in 041 * this class can be called after the stream has been closed without 042 * generating an {@code IOException}. 043 * </p> 044 * <p> 045 * This is the base for an alternative implementation of the 046 * {@link java.io.ByteArrayOutputStream} class. The original implementation 047 * only allocates 32 bytes at the beginning. As this class is designed for 048 * heavy duty it starts at {@value #DEFAULT_SIZE} bytes. In contrast to the original it doesn't 049 * reallocate the whole memory block but allocates additional buffers. This 050 * way no buffers need to be garbage collected and the contents don't have 051 * to be copied to the new buffer. This class is designed to behave exactly 052 * like the original. The only exception is the deprecated 053 * {@link java.io.ByteArrayOutputStream#toString(int)} method that has been 054 * ignored. 055 * </p> 056 * 057 * @since 2.7 058 */ 059public abstract class AbstractByteArrayOutputStream extends OutputStream { 060 061 static final int DEFAULT_SIZE = 1024; 062 063 /** A singleton empty byte array. */ 064 private static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; 065 066 /** The list of buffers, which grows and never reduces. */ 067 private final List<byte[]> buffers = new ArrayList<>(); 068 /** The index of the current buffer. */ 069 private int currentBufferIndex; 070 /** The total count of bytes in all the filled buffers. */ 071 private int filledBufferSum; 072 /** The current buffer. */ 073 private byte[] currentBuffer; 074 /** The total count of bytes written. */ 075 protected int count; 076 /** Flag to indicate if the buffers can be reused after reset */ 077 private boolean reuseBuffers = true; 078 079 /** 080 * Makes a new buffer available either by allocating 081 * a new one or re-cycling an existing one. 082 * 083 * @param newcount the size of the buffer if one is created 084 */ 085 protected void needNewBuffer(final int newcount) { 086 if (currentBufferIndex < buffers.size() - 1) { 087 //Recycling old buffer 088 filledBufferSum += currentBuffer.length; 089 090 currentBufferIndex++; 091 currentBuffer = buffers.get(currentBufferIndex); 092 } else { 093 //Creating new buffer 094 int newBufferSize; 095 if (currentBuffer == null) { 096 newBufferSize = newcount; 097 filledBufferSum = 0; 098 } else { 099 newBufferSize = Math.max( 100 currentBuffer.length << 1, 101 newcount - filledBufferSum); 102 filledBufferSum += currentBuffer.length; 103 } 104 105 currentBufferIndex++; 106 currentBuffer = new byte[newBufferSize]; 107 buffers.add(currentBuffer); 108 } 109 } 110 111 /** 112 * Writes the bytes to the byte array. 113 * @param b the bytes to write 114 * @param off The start offset 115 * @param len The number of bytes to write 116 */ 117 @Override 118 public abstract void write(final byte[] b, final int off, final int len); 119 120 /** 121 * Writes the bytes to the byte array. 122 * @param b the bytes to write 123 * @param off The start offset 124 * @param len The number of bytes to write 125 */ 126 protected void writeImpl(final byte[] b, final int off, final int len) { 127 final int newcount = count + len; 128 int remaining = len; 129 int inBufferPos = count - filledBufferSum; 130 while (remaining > 0) { 131 final int part = Math.min(remaining, currentBuffer.length - inBufferPos); 132 System.arraycopy(b, off + len - remaining, currentBuffer, inBufferPos, part); 133 remaining -= part; 134 if (remaining > 0) { 135 needNewBuffer(newcount); 136 inBufferPos = 0; 137 } 138 } 139 count = newcount; 140 } 141 142 /** 143 * Write a byte to byte array. 144 * @param b the byte to write 145 */ 146 @Override 147 public abstract void write(final int b); 148 149 /** 150 * Write a byte to byte array. 151 * @param b the byte to write 152 */ 153 protected void writeImpl(final int b) { 154 int inBufferPos = count - filledBufferSum; 155 if (inBufferPos == currentBuffer.length) { 156 needNewBuffer(count + 1); 157 inBufferPos = 0; 158 } 159 currentBuffer[inBufferPos] = (byte) b; 160 count++; 161 } 162 163 164 /** 165 * Writes the entire contents of the specified input stream to this 166 * byte stream. Bytes from the input stream are read directly into the 167 * internal buffers of this streams. 168 * 169 * @param in the input stream to read from 170 * @return total number of bytes read from the input stream 171 * (and written to this stream) 172 * @throws IOException if an I/O error occurs while reading the input stream 173 * @since 1.4 174 */ 175 public abstract int write(final InputStream in) throws IOException; 176 177 /** 178 * Writes the entire contents of the specified input stream to this 179 * byte stream. Bytes from the input stream are read directly into the 180 * internal buffers of this streams. 181 * 182 * @param in the input stream to read from 183 * @return total number of bytes read from the input stream 184 * (and written to this stream) 185 * @throws IOException if an I/O error occurs while reading the input stream 186 * @since 2.7 187 */ 188 protected int writeImpl(final InputStream in) throws IOException { 189 int readCount = 0; 190 int inBufferPos = count - filledBufferSum; 191 int n = in.read(currentBuffer, inBufferPos, currentBuffer.length - inBufferPos); 192 while (n != EOF) { 193 readCount += n; 194 inBufferPos += n; 195 count += n; 196 if (inBufferPos == currentBuffer.length) { 197 needNewBuffer(currentBuffer.length); 198 inBufferPos = 0; 199 } 200 n = in.read(currentBuffer, inBufferPos, currentBuffer.length - inBufferPos); 201 } 202 return readCount; 203 } 204 205 /** 206 * Returns the current size of the byte array. 207 * 208 * @return the current size of the byte array 209 */ 210 public abstract int size(); 211 212 /** 213 * Closing a {@code ByteArrayOutputStream} has no effect. The methods in 214 * this class can be called after the stream has been closed without 215 * generating an {@code IOException}. 216 * 217 * @throws IOException never (this method should not declare this exception 218 * but it has to now due to backwards compatibility) 219 */ 220 @Override 221 public void close() throws IOException { 222 //nop 223 } 224 225 /** 226 * @see java.io.ByteArrayOutputStream#reset() 227 */ 228 public abstract void reset(); 229 230 /** 231 * @see java.io.ByteArrayOutputStream#reset() 232 */ 233 protected void resetImpl() { 234 count = 0; 235 filledBufferSum = 0; 236 currentBufferIndex = 0; 237 if (reuseBuffers) { 238 currentBuffer = buffers.get(currentBufferIndex); 239 } else { 240 //Throw away old buffers 241 currentBuffer = null; 242 final int size = buffers.get(0).length; 243 buffers.clear(); 244 needNewBuffer(size); 245 reuseBuffers = true; 246 } 247 } 248 249 /** 250 * Writes the entire contents of this byte stream to the 251 * specified output stream. 252 * 253 * @param out the output stream to write to 254 * @throws IOException if an I/O error occurs, such as if the stream is closed 255 * @see java.io.ByteArrayOutputStream#writeTo(OutputStream) 256 */ 257 public abstract void writeTo(final OutputStream out) throws IOException; 258 259 /** 260 * Writes the entire contents of this byte stream to the 261 * specified output stream. 262 * 263 * @param out the output stream to write to 264 * @throws IOException if an I/O error occurs, such as if the stream is closed 265 * @see java.io.ByteArrayOutputStream#writeTo(OutputStream) 266 */ 267 protected void writeToImpl(final OutputStream out) throws IOException { 268 int remaining = count; 269 for (final byte[] buf : buffers) { 270 final int c = Math.min(buf.length, remaining); 271 out.write(buf, 0, c); 272 remaining -= c; 273 if (remaining == 0) { 274 break; 275 } 276 } 277 } 278 279 /** 280 * Gets the current contents of this byte stream as a Input Stream. The 281 * returned stream is backed by buffers of <code>this</code> stream, 282 * avoiding memory allocation and copy, thus saving space and time.<br> 283 * 284 * @return the current contents of this output stream. 285 * @see java.io.ByteArrayOutputStream#toByteArray() 286 * @see #reset() 287 * @since 2.5 288 */ 289 public abstract InputStream toInputStream(); 290 291 /** 292 * Gets the current contents of this byte stream as a Input Stream. The 293 * returned stream is backed by buffers of <code>this</code> stream, 294 * avoiding memory allocation and copy, thus saving space and time.<br> 295 * 296 * @param <T> the type of the InputStream which makes up 297 * the {@link SequenceInputStream}. 298 * @param isConstructor A constructor for an InputStream which makes 299 * up the {@link SequenceInputStream}. 300 * 301 * @return the current contents of this output stream. 302 * @see java.io.ByteArrayOutputStream#toByteArray() 303 * @see #reset() 304 * @since 2.7 305 */ 306 protected <T extends InputStream> InputStream toInputStream( 307 final InputStreamConstructor<T> isConstructor) { 308 int remaining = count; 309 if (remaining == 0) { 310 return ClosedInputStream.CLOSED_INPUT_STREAM; 311 } 312 final List<T> list = new ArrayList<>(buffers.size()); 313 for (final byte[] buf : buffers) { 314 final int c = Math.min(buf.length, remaining); 315 list.add(isConstructor.construct(buf, 0, c)); 316 remaining -= c; 317 if (remaining == 0) { 318 break; 319 } 320 } 321 reuseBuffers = false; 322 return new SequenceInputStream(Collections.enumeration(list)); 323 } 324 325 /** 326 * Constructor for an InputStream subclass. 327 * 328 * @param <T> the type of the InputStream. 329 */ 330 @FunctionalInterface 331 protected interface InputStreamConstructor<T extends InputStream> { 332 333 /** 334 * Construct an InputStream subclass. 335 * 336 * @param buf the buffer 337 * @param offset the offset into the buffer 338 * @param length the length of the buffer 339 * 340 * @return the InputStream subclass. 341 */ 342 T construct(final byte buf[], final int offset, final int length); 343 } 344 345 /** 346 * Gets the current contents of this byte stream as a byte array. 347 * The result is independent of this stream. 348 * 349 * @return the current contents of this output stream, as a byte array 350 * @see java.io.ByteArrayOutputStream#toByteArray() 351 */ 352 public abstract byte[] toByteArray(); 353 354 /** 355 * Gets the current contents of this byte stream as a byte array. 356 * The result is independent of this stream. 357 * 358 * @return the current contents of this output stream, as a byte array 359 * @see java.io.ByteArrayOutputStream#toByteArray() 360 */ 361 protected byte[] toByteArrayImpl() { 362 int remaining = count; 363 if (remaining == 0) { 364 return EMPTY_BYTE_ARRAY; 365 } 366 final byte newbuf[] = new byte[remaining]; 367 int pos = 0; 368 for (final byte[] buf : buffers) { 369 final int c = Math.min(buf.length, remaining); 370 System.arraycopy(buf, 0, newbuf, pos, c); 371 pos += c; 372 remaining -= c; 373 if (remaining == 0) { 374 break; 375 } 376 } 377 return newbuf; 378 } 379 380 /** 381 * Gets the current contents of this byte stream as a string 382 * using the platform default charset. 383 * @return the contents of the byte array as a String 384 * @see java.io.ByteArrayOutputStream#toString() 385 * @deprecated 2.5 use {@link #toString(String)} instead 386 */ 387 @Override 388 @Deprecated 389 public String toString() { 390 // make explicit the use of the default charset 391 return new String(toByteArray(), Charset.defaultCharset()); 392 } 393 394 /** 395 * Gets the current contents of this byte stream as a string 396 * using the specified encoding. 397 * 398 * @param enc the name of the character encoding 399 * @return the string converted from the byte array 400 * @throws UnsupportedEncodingException if the encoding is not supported 401 * @see java.io.ByteArrayOutputStream#toString(String) 402 */ 403 public String toString(final String enc) throws UnsupportedEncodingException { 404 return new String(toByteArray(), enc); 405 } 406 407 /** 408 * Gets the current contents of this byte stream as a string 409 * using the specified encoding. 410 * 411 * @param charset the character encoding 412 * @return the string converted from the byte array 413 * @see java.io.ByteArrayOutputStream#toString(String) 414 * @since 2.5 415 */ 416 public String toString(final Charset charset) { 417 return new String(toByteArray(), charset); 418 } 419 420}