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