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 static org.apache.commons.io.IOUtils.EOF; 20 21 import java.io.IOException; 22 import java.io.InputStream; 23 import java.io.OutputStream; 24 import java.io.SequenceInputStream; 25 import java.io.UnsupportedEncodingException; 26 import java.nio.charset.Charset; 27 import java.util.ArrayList; 28 import java.util.Collections; 29 import java.util.List; 30 31 import org.apache.commons.io.IOUtils; 32 import org.apache.commons.io.input.ClosedInputStream; 33 34 /** 35 * This is the base class for implementing an output stream in which the data 36 * is written into a byte array. The buffer automatically grows as data 37 * is written to it. 38 * <p> 39 * The data can be retrieved using {@code toByteArray()} and 40 * {@code toString()}. 41 * Closing an {@link AbstractByteArrayOutputStream} has no effect. The methods in 42 * this class can be called after the stream has been closed without 43 * generating an {@link IOException}. 44 * </p> 45 * <p> 46 * This is the base for an alternative implementation of the 47 * {@link java.io.ByteArrayOutputStream} class. The original implementation 48 * only allocates 32 bytes at the beginning. As this class is designed for 49 * heavy duty it starts at {@value #DEFAULT_SIZE} bytes. In contrast to the original it doesn't 50 * reallocate the whole memory block but allocates additional buffers. This 51 * way no buffers need to be garbage collected and the contents don't have 52 * to be copied to the new buffer. This class is designed to behave exactly 53 * like the original. The only exception is the deprecated 54 * {@link java.io.ByteArrayOutputStream#toString(int)} method that has been 55 * ignored. 56 * </p> 57 * 58 * @since 2.7 59 */ 60 public abstract class AbstractByteArrayOutputStream extends OutputStream { 61 62 /** 63 * Constructor for an InputStream subclass. 64 * 65 * @param <T> the type of the InputStream. 66 */ 67 @FunctionalInterface 68 protected interface InputStreamConstructor<T extends InputStream> { 69 70 /** 71 * Constructs an InputStream subclass. 72 * 73 * @param buf the buffer 74 * @param offset the offset into the buffer 75 * @param length the length of the buffer 76 * 77 * @return the InputStream subclass. 78 */ construct(final byte[] buf, final int offset, final int length)79 T construct(final byte[] buf, final int offset, final int length); 80 } 81 82 static final int DEFAULT_SIZE = 1024; 83 84 /** The list of buffers, which grows and never reduces. */ 85 private final List<byte[]> buffers = new ArrayList<>(); 86 87 /** The index of the current buffer. */ 88 private int currentBufferIndex; 89 90 /** The total count of bytes in all the filled buffers. */ 91 private int filledBufferSum; 92 93 /** The current buffer. */ 94 private byte[] currentBuffer; 95 96 /** The total count of bytes written. */ 97 protected int count; 98 99 /** Flag to indicate if the buffers can be reused after reset */ 100 private boolean reuseBuffers = true; 101 102 /** 103 * Does nothing. 104 * 105 * The methods in this class can be called after the stream has been closed without generating an {@link IOException}. 106 * 107 * @throws IOException never (this method should not declare this exception but it has to now due to backwards 108 * compatibility) 109 */ 110 @Override close()111 public void close() throws IOException { 112 //nop 113 } 114 115 /** 116 * Makes a new buffer available either by allocating 117 * a new one or re-cycling an existing one. 118 * 119 * @param newCount the size of the buffer if one is created 120 */ needNewBuffer(final int newCount)121 protected void needNewBuffer(final int newCount) { 122 if (currentBufferIndex < buffers.size() - 1) { 123 // Recycling old buffer 124 filledBufferSum += currentBuffer.length; 125 126 currentBufferIndex++; 127 currentBuffer = buffers.get(currentBufferIndex); 128 } else { 129 // Creating new buffer 130 final int newBufferSize; 131 if (currentBuffer == null) { 132 newBufferSize = newCount; 133 filledBufferSum = 0; 134 } else { 135 newBufferSize = Math.max(currentBuffer.length << 1, newCount - filledBufferSum); 136 filledBufferSum += currentBuffer.length; 137 } 138 139 currentBufferIndex++; 140 currentBuffer = IOUtils.byteArray(newBufferSize); 141 buffers.add(currentBuffer); 142 } 143 } 144 145 /** 146 * @see java.io.ByteArrayOutputStream#reset() 147 */ reset()148 public abstract void reset(); 149 150 /** 151 * @see java.io.ByteArrayOutputStream#reset() 152 */ resetImpl()153 protected void resetImpl() { 154 count = 0; 155 filledBufferSum = 0; 156 currentBufferIndex = 0; 157 if (reuseBuffers) { 158 currentBuffer = buffers.get(currentBufferIndex); 159 } else { 160 //Throw away old buffers 161 currentBuffer = null; 162 final int size = buffers.get(0).length; 163 buffers.clear(); 164 needNewBuffer(size); 165 reuseBuffers = true; 166 } 167 } 168 169 /** 170 * Returns the current size of the byte array. 171 * 172 * @return the current size of the byte array 173 */ size()174 public abstract int size(); 175 176 /** 177 * Gets the current contents of this byte stream as a byte array. 178 * The result is independent of this stream. 179 * 180 * @return the current contents of this output stream, as a byte array 181 * @see java.io.ByteArrayOutputStream#toByteArray() 182 */ toByteArray()183 public abstract byte[] toByteArray(); 184 185 /** 186 * Gets the current contents of this byte stream as a byte array. 187 * The result is independent of this stream. 188 * 189 * @return the current contents of this output stream, as a byte array 190 * @see java.io.ByteArrayOutputStream#toByteArray() 191 */ toByteArrayImpl()192 protected byte[] toByteArrayImpl() { 193 int remaining = count; 194 if (remaining == 0) { 195 return IOUtils.EMPTY_BYTE_ARRAY; 196 } 197 final byte[] newBuf = IOUtils.byteArray(remaining); 198 int pos = 0; 199 for (final byte[] buf : buffers) { 200 final int c = Math.min(buf.length, remaining); 201 System.arraycopy(buf, 0, newBuf, pos, c); 202 pos += c; 203 remaining -= c; 204 if (remaining == 0) { 205 break; 206 } 207 } 208 return newBuf; 209 } 210 211 /** 212 * Gets the current contents of this byte stream as an Input Stream. The 213 * returned stream is backed by buffers of {@code this} stream, 214 * avoiding memory allocation and copy, thus saving space and time.<br> 215 * 216 * @return the current contents of this output stream. 217 * @see java.io.ByteArrayOutputStream#toByteArray() 218 * @see #reset() 219 * @since 2.5 220 */ toInputStream()221 public abstract InputStream toInputStream(); 222 223 /** 224 * Gets the current contents of this byte stream as an Input Stream. The 225 * returned stream is backed by buffers of {@code this} stream, 226 * avoiding memory allocation and copy, thus saving space and time.<br> 227 * 228 * @param <T> the type of the InputStream which makes up 229 * the {@link SequenceInputStream}. 230 * @param isConstructor A constructor for an InputStream which makes 231 * up the {@link SequenceInputStream}. 232 * 233 * @return the current contents of this output stream. 234 * @see java.io.ByteArrayOutputStream#toByteArray() 235 * @see #reset() 236 * @since 2.7 237 */ 238 @SuppressWarnings("resource") // The result InputStream MUST be managed by the call site. toInputStream( final InputStreamConstructor<T> isConstructor)239 protected <T extends InputStream> InputStream toInputStream( 240 final InputStreamConstructor<T> isConstructor) { 241 int remaining = count; 242 if (remaining == 0) { 243 return ClosedInputStream.INSTANCE; 244 } 245 final List<T> list = new ArrayList<>(buffers.size()); 246 for (final byte[] buf : buffers) { 247 final int c = Math.min(buf.length, remaining); 248 list.add(isConstructor.construct(buf, 0, c)); 249 remaining -= c; 250 if (remaining == 0) { 251 break; 252 } 253 } 254 reuseBuffers = false; 255 return new SequenceInputStream(Collections.enumeration(list)); 256 } 257 258 /** 259 * Gets the current contents of this byte stream as a string 260 * using the platform default charset. 261 * @return the contents of the byte array as a String 262 * @see java.io.ByteArrayOutputStream#toString() 263 * @deprecated 2.5 use {@link #toString(String)} instead 264 */ 265 @Override 266 @Deprecated toString()267 public String toString() { 268 // make explicit the use of the default charset 269 return new String(toByteArray(), Charset.defaultCharset()); 270 } 271 272 /** 273 * Gets the current contents of this byte stream as a string 274 * using the specified encoding. 275 * 276 * @param charset the character encoding 277 * @return the string converted from the byte array 278 * @see java.io.ByteArrayOutputStream#toString(String) 279 * @since 2.5 280 */ toString(final Charset charset)281 public String toString(final Charset charset) { 282 return new String(toByteArray(), charset); 283 } 284 285 /** 286 * Gets the current contents of this byte stream as a string 287 * using the specified encoding. 288 * 289 * @param enc the name of the character encoding 290 * @return the string converted from the byte array 291 * @throws UnsupportedEncodingException if the encoding is not supported 292 * @see java.io.ByteArrayOutputStream#toString(String) 293 */ toString(final String enc)294 public String toString(final String enc) throws UnsupportedEncodingException { 295 return new String(toByteArray(), enc); 296 } 297 298 @Override write(final byte[] b, final int off, final int len)299 public abstract void write(final byte[] b, final int off, final int len); 300 301 /** 302 * Writes the entire contents of the specified input stream to this 303 * byte stream. Bytes from the input stream are read directly into the 304 * internal buffer of this stream. 305 * 306 * @param in the input stream to read from 307 * @return total number of bytes read from the input stream 308 * (and written to this stream) 309 * @throws IOException if an I/O error occurs while reading the input stream 310 * @since 1.4 311 */ write(final InputStream in)312 public abstract int write(final InputStream in) throws IOException; 313 314 @Override write(final int b)315 public abstract void write(final int b); 316 317 /** 318 * Writes the bytes to the byte array. 319 * @param b the bytes to write 320 * @param off The start offset 321 * @param len The number of bytes to write 322 */ writeImpl(final byte[] b, final int off, final int len)323 protected void writeImpl(final byte[] b, final int off, final int len) { 324 final int newCount = count + len; 325 int remaining = len; 326 int inBufferPos = count - filledBufferSum; 327 while (remaining > 0) { 328 final int part = Math.min(remaining, currentBuffer.length - inBufferPos); 329 System.arraycopy(b, off + len - remaining, currentBuffer, inBufferPos, part); 330 remaining -= part; 331 if (remaining > 0) { 332 needNewBuffer(newCount); 333 inBufferPos = 0; 334 } 335 } 336 count = newCount; 337 } 338 339 /** 340 * Writes the entire contents of the specified input stream to this 341 * byte stream. Bytes from the input stream are read directly into the 342 * internal buffer of this stream. 343 * 344 * @param in the input stream to read from 345 * @return total number of bytes read from the input stream 346 * (and written to this stream) 347 * @throws IOException if an I/O error occurs while reading the input stream 348 * @since 2.7 349 */ writeImpl(final InputStream in)350 protected int writeImpl(final InputStream in) throws IOException { 351 int readCount = 0; 352 int inBufferPos = count - filledBufferSum; 353 int n = in.read(currentBuffer, inBufferPos, currentBuffer.length - inBufferPos); 354 while (n != EOF) { 355 readCount += n; 356 inBufferPos += n; 357 count += n; 358 if (inBufferPos == currentBuffer.length) { 359 needNewBuffer(currentBuffer.length); 360 inBufferPos = 0; 361 } 362 n = in.read(currentBuffer, inBufferPos, currentBuffer.length - inBufferPos); 363 } 364 return readCount; 365 } 366 367 /** 368 * Write a byte to byte array. 369 * @param b the byte to write 370 */ writeImpl(final int b)371 protected void writeImpl(final int b) { 372 int inBufferPos = count - filledBufferSum; 373 if (inBufferPos == currentBuffer.length) { 374 needNewBuffer(count + 1); 375 inBufferPos = 0; 376 } 377 currentBuffer[inBufferPos] = (byte) b; 378 count++; 379 } 380 381 /** 382 * Writes the entire contents of this byte stream to the 383 * specified output stream. 384 * 385 * @param out the output stream to write to 386 * @throws IOException if an I/O error occurs, such as if the stream is closed 387 * @see java.io.ByteArrayOutputStream#writeTo(OutputStream) 388 */ writeTo(final OutputStream out)389 public abstract void writeTo(final OutputStream out) throws IOException; 390 391 /** 392 * Writes the entire contents of this byte stream to the 393 * specified output stream. 394 * 395 * @param out the output stream to write to 396 * @throws IOException if an I/O error occurs, such as if the stream is closed 397 * @see java.io.ByteArrayOutputStream#writeTo(OutputStream) 398 */ writeToImpl(final OutputStream out)399 protected void writeToImpl(final OutputStream out) throws IOException { 400 int remaining = count; 401 for (final byte[] buf : buffers) { 402 final int c = Math.min(buf.length, remaining); 403 out.write(buf, 0, c); 404 remaining -= c; 405 if (remaining == 0) { 406 break; 407 } 408 } 409 } 410 411 } 412