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