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