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 * https://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 = -1; 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 currentBufferIndex++; 144 currentBuffer = buffers.get(currentBufferIndex); 145 } else { 146 // Creating new buffer 147 final int newBufferSize; 148 if (currentBuffer == null) { 149 // prevents 0 size buffers 150 newBufferSize = newCount > 0 ? newCount : DEFAULT_SIZE; 151 filledBufferSum = 0; 152 } else { 153 newBufferSize = Math.max(currentBuffer.length << 1, newCount - filledBufferSum); 154 filledBufferSum += currentBuffer.length; 155 } 156 currentBufferIndex++; 157 currentBuffer = IOUtils.byteArray(newBufferSize); 158 buffers.add(currentBuffer); 159 } 160 } 161 162 /** 163 * See {@link ByteArrayOutputStream#reset()}. 164 * 165 * @see ByteArrayOutputStream#reset() 166 */ 167 public abstract void reset(); 168 169 /** 170 * Implements a default reset behavior. 171 * 172 * @see ByteArrayOutputStream#reset() 173 */ 174 protected void resetImpl() { 175 count = 0; 176 filledBufferSum = 0; 177 currentBufferIndex = 0; 178 if (reuseBuffers) { 179 currentBuffer = buffers.get(currentBufferIndex); 180 } else { 181 //Throw away old buffers 182 currentBuffer = null; 183 final int size = buffers.get(0).length; 184 buffers.clear(); 185 needNewBuffer(size); 186 reuseBuffers = true; 187 } 188 } 189 190 /** 191 * Returns the current size of the byte array. 192 * 193 * @return the current size of the byte array 194 */ 195 public abstract int size(); 196 197 /** 198 * Gets the current contents of this byte stream as a byte array. 199 * The result is independent of this stream. 200 * 201 * @return the current contents of this output stream, as a byte array 202 * @see java.io.ByteArrayOutputStream#toByteArray() 203 */ 204 public abstract byte[] toByteArray(); 205 206 /** 207 * Gets the current contents of this byte stream as a byte array. 208 * The result is independent of this stream. 209 * 210 * @return the current contents of this output stream, as a byte array 211 * @see java.io.ByteArrayOutputStream#toByteArray() 212 */ 213 protected byte[] toByteArrayImpl() { 214 int remaining = count; 215 if (remaining == 0) { 216 return IOUtils.EMPTY_BYTE_ARRAY; 217 } 218 final byte[] newBuf = IOUtils.byteArray(remaining); 219 int pos = 0; 220 for (final byte[] buf : buffers) { 221 final int c = Math.min(buf.length, remaining); 222 System.arraycopy(buf, 0, newBuf, pos, c); 223 pos += c; 224 remaining -= c; 225 if (remaining == 0) { 226 break; 227 } 228 } 229 return newBuf; 230 } 231 232 /** 233 * Gets the current contents of this byte stream as an Input Stream. The 234 * returned stream is backed by buffers of {@code this} stream, 235 * avoiding memory allocation and copy, thus saving space and time.<br> 236 * 237 * @return the current contents of this output stream. 238 * @see java.io.ByteArrayOutputStream#toByteArray() 239 * @see #reset() 240 * @since 2.5 241 */ 242 public abstract InputStream toInputStream(); 243 244 /** 245 * Gets the current contents of this byte stream as an Input Stream. The 246 * returned stream is backed by buffers of {@code this} stream, 247 * avoiding memory allocation and copy, thus saving space and time.<br> 248 * 249 * @param <T> the type of the InputStream which makes up 250 * the {@link SequenceInputStream}. 251 * @param isConstructor A constructor for an InputStream which makes 252 * up the {@link SequenceInputStream}. 253 * 254 * @return the current contents of this output stream. 255 * @see java.io.ByteArrayOutputStream#toByteArray() 256 * @see #reset() 257 * @since 2.7 258 */ 259 @SuppressWarnings("resource") // The result InputStream MUST be managed by the call site. 260 protected <T extends InputStream> InputStream toInputStream(final InputStreamConstructor<T> isConstructor) { 261 int remaining = count; 262 if (remaining == 0) { 263 return ClosedInputStream.INSTANCE; 264 } 265 final List<T> list = new ArrayList<>(buffers.size()); 266 for (final byte[] buf : buffers) { 267 final int c = Math.min(buf.length, remaining); 268 list.add(isConstructor.construct(buf, 0, c)); 269 remaining -= c; 270 if (remaining == 0) { 271 break; 272 } 273 } 274 reuseBuffers = false; 275 return new SequenceInputStream(Collections.enumeration(list)); 276 } 277 278 /** 279 * Gets the current contents of this byte stream as a string using the virtual machine's {@link Charset#defaultCharset() default charset}. 280 * 281 * @return the contents of the byte array as a String 282 * @see java.io.ByteArrayOutputStream#toString() 283 * @see Charset#defaultCharset() 284 * @deprecated Use {@link #toString(String)} instead 285 */ 286 @Override 287 @Deprecated 288 public String toString() { 289 // make explicit the use of the default charset 290 return new String(toByteArray(), Charset.defaultCharset()); 291 } 292 293 /** 294 * Gets the current contents of this byte stream as a string 295 * using the specified encoding. 296 * 297 * @param charset the character encoding 298 * @return the string converted from the byte array 299 * @see java.io.ByteArrayOutputStream#toString(String) 300 * @since 2.5 301 */ 302 public String toString(final Charset charset) { 303 return new String(toByteArray(), charset); 304 } 305 306 /** 307 * Gets the current contents of this byte stream as a string 308 * using the specified encoding. 309 * 310 * @param enc the name of the character encoding 311 * @return the string converted from the byte array 312 * @throws UnsupportedEncodingException if the encoding is not supported 313 * @see java.io.ByteArrayOutputStream#toString(String) 314 */ 315 public String toString(final String enc) throws UnsupportedEncodingException { 316 return new String(toByteArray(), enc); 317 } 318 319 /** 320 * 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)}. 321 * 322 * @param b the data. 323 * @see #write(byte[], int, int) 324 * @since 2.19.0 325 */ 326 @Override 327 public void write(final byte b[]) { 328 write(b, 0, b.length); 329 } 330 331 @Override 332 public abstract void write(byte[] b, int off, int len); 333 334 /** 335 * Writes the bytes for given CharSequence encoded using a Charset. 336 * 337 * @param data The String to convert to bytes. not null. 338 * @param charset The {@link Charset} o encode the {@code String}, null means the default encoding. 339 * @return this instance. 340 * @since 2.19.0 341 */ 342 public T write(final CharSequence data, final Charset charset) { 343 write(data.toString().getBytes(Charsets.toCharset(charset))); 344 return asThis(); 345 } 346 347 /** 348 * Writes the entire contents of the specified input stream to this 349 * byte stream. Bytes from the input stream are read directly into the 350 * internal buffer of this stream. 351 * 352 * @param in the input stream to read from 353 * @return total number of bytes read from the input stream 354 * (and written to this stream) 355 * @throws IOException if an I/O error occurs while reading the input stream 356 * @since 1.4 357 */ 358 public abstract int write(InputStream in) throws IOException; 359 360 @Override 361 public abstract void write(int b); 362 363 /** 364 * Writes the bytes to the byte array. 365 * @param b the bytes to write 366 * @param off The start offset 367 * @param len The number of bytes to write 368 */ 369 protected void writeImpl(final byte[] b, final int off, final int len) { 370 final int newCount = count + len; 371 int remaining = len; 372 int inBufferPos = count - filledBufferSum; 373 while (remaining > 0) { 374 final int part = Math.min(remaining, currentBuffer.length - inBufferPos); 375 System.arraycopy(b, off + len - remaining, currentBuffer, inBufferPos, part); 376 remaining -= part; 377 if (remaining > 0) { 378 needNewBuffer(newCount); 379 inBufferPos = 0; 380 } 381 } 382 count = newCount; 383 } 384 385 /** 386 * Writes the entire contents of the specified input stream to this 387 * byte stream. Bytes from the input stream are read directly into the 388 * internal buffer of this stream. 389 * 390 * @param in the input stream to read from 391 * @return total number of bytes read from the input stream 392 * (and written to this stream) 393 * @throws IOException if an I/O error occurs while reading the input stream 394 * @since 2.7 395 */ 396 protected int writeImpl(final InputStream in) throws IOException { 397 int readCount = 0; 398 int inBufferPos = count - filledBufferSum; 399 int n = in.read(currentBuffer, inBufferPos, currentBuffer.length - inBufferPos); 400 while (n != EOF) { 401 readCount += n; 402 inBufferPos += n; 403 count += n; 404 if (inBufferPos == currentBuffer.length) { 405 needNewBuffer(currentBuffer.length); 406 inBufferPos = 0; 407 } 408 n = in.read(currentBuffer, inBufferPos, currentBuffer.length - inBufferPos); 409 } 410 return readCount; 411 } 412 413 /** 414 * Writes a byte to byte array. 415 * @param b the byte to write 416 */ 417 protected void writeImpl(final int b) { 418 int inBufferPos = count - filledBufferSum; 419 if (inBufferPos == currentBuffer.length) { 420 needNewBuffer(count + 1); 421 inBufferPos = 0; 422 } 423 currentBuffer[inBufferPos] = (byte) b; 424 count++; 425 } 426 427 /** 428 * Writes the entire contents of this byte stream to the 429 * specified output stream. 430 * 431 * @param out the output stream to write to 432 * @throws IOException if an I/O error occurs, such as if the stream is closed 433 * @see java.io.ByteArrayOutputStream#writeTo(OutputStream) 434 */ 435 public abstract void writeTo(OutputStream out) throws IOException; 436 437 /** 438 * Writes the entire contents of this byte stream to the 439 * specified output stream. 440 * 441 * @param out the output stream to write to 442 * @throws IOException if an I/O error occurs, such as if the stream is closed 443 * @see java.io.ByteArrayOutputStream#writeTo(OutputStream) 444 */ 445 protected void writeToImpl(final OutputStream out) throws IOException { 446 int remaining = count; 447 for (final byte[] buf : buffers) { 448 final int c = Math.min(buf.length, remaining); 449 out.write(buf, 0, c); 450 remaining -= c; 451 if (remaining == 0) { 452 break; 453 } 454 } 455 } 456 457}