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.codehaus.plexus.archiver.zip; 18 19 import java.io.ByteArrayInputStream; 20 import java.io.IOException; 21 import java.io.InputStream; 22 import java.io.OutputStream; 23 import java.io.SequenceInputStream; 24 import java.io.UnsupportedEncodingException; 25 import java.nio.charset.Charset; 26 import java.util.ArrayList; 27 import java.util.Collections; 28 import java.util.List; 29 30 import org.apache.commons.io.input.ClosedInputStream; 31 32 /** 33 * This class implements an output stream in which the data is 34 * written into a byte array. The buffer automatically grows as data 35 * is written to it. 36 * <p> 37 * The data can be retrieved using <code>toByteArray()</code> and 38 * <code>toString()</code>. 39 * <p> 40 * Closing a {@code ByteArrayOutputStream} has no effect. The methods in 41 * this class can be called after the stream has been closed without 42 * generating an {@code IOException}. 43 * <p> 44 * This is an alternative implementation of the {@link java.io.ByteArrayOutputStream} 45 * class. The original implementation only allocates 32 bytes at the beginning. 46 * As this class is designed for heavy duty it starts at 1024 bytes. In contrast 47 * to the original it doesn't reallocate the whole memory block but allocates 48 * additional buffers. This way no buffers need to be garbage collected and 49 * the contents don't have to be copied to the new buffer. This class is 50 * designed to behave exactly like the original. The only exception is the 51 * deprecated toString(int) method that has been ignored. 52 */ 53 public class ByteArrayOutputStream extends OutputStream { 54 55 /** 56 * A singleton empty byte array. 57 */ 58 private static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; 59 60 /** 61 * The list of buffers, which grows and never reduces. 62 */ 63 private final List<byte[]> buffers = new ArrayList<byte[]>(); 64 65 /** 66 * The index of the current buffer. 67 */ 68 private int currentBufferIndex; 69 70 /** 71 * The total count of bytes in all the filled buffers. 72 */ 73 private int filledBufferSum; 74 75 /** 76 * The current buffer. 77 */ 78 private byte[] currentBuffer; 79 80 /** 81 * The total count of bytes written. 82 */ 83 private int count; 84 85 /** 86 * Flag to indicate if the buffers can be reused after reset 87 */ 88 private boolean reuseBuffers = true; 89 90 /** 91 * Creates a new byte array output stream. The buffer capacity is 92 * initially 1024 bytes, though its size increases if necessary. 93 */ 94 public ByteArrayOutputStream() { 95 this(1024); 96 } 97 98 /** 99 * Creates a new byte array output stream, with a buffer capacity of 100 * the specified size, in bytes. 101 * 102 * @param size the initial size 103 * 104 * @throws IllegalArgumentException if size is negative 105 */ 106 public ByteArrayOutputStream(final int size) { 107 if (size < 0) { 108 throw new IllegalArgumentException("Negative initial size: " + size); 109 } 110 synchronized (this) { 111 needNewBuffer(size); 112 } 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 private 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 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 = new byte[newBufferSize]; 141 buffers.add(currentBuffer); 142 } 143 } 144 145 /** 146 * Write the bytes to byte array. 147 * 148 * @param b the bytes to write 149 * @param off The start offset 150 * @param len The number of bytes to write 151 */ 152 @Override 153 public void write(final byte[] b, final int off, final int len) { 154 if ((off < 0) || (off > b.length) || (len < 0) || ((off + len) > b.length) || ((off + len) < 0)) { 155 throw new IndexOutOfBoundsException(); 156 } else if (len == 0) { 157 return; 158 } 159 synchronized (this) { 160 final int newcount = count + len; 161 int remaining = len; 162 int inBufferPos = count - filledBufferSum; 163 while (remaining > 0) { 164 final int part = Math.min(remaining, currentBuffer.length - inBufferPos); 165 System.arraycopy(b, off + len - remaining, currentBuffer, inBufferPos, part); 166 remaining -= part; 167 if (remaining > 0) { 168 needNewBuffer(newcount); 169 inBufferPos = 0; 170 } 171 } 172 count = newcount; 173 } 174 } 175 176 /** 177 * Write a byte to byte array. 178 * 179 * @param b the byte to write 180 */ 181 @Override 182 public synchronized void write(final int b) { 183 int inBufferPos = count - filledBufferSum; 184 if (inBufferPos == currentBuffer.length) { 185 needNewBuffer(count + 1); 186 inBufferPos = 0; 187 } 188 currentBuffer[inBufferPos] = (byte) b; 189 count++; 190 } 191 192 /** 193 * Writes the entire contents of the specified input stream to this 194 * byte stream. Bytes from the input stream are read directly into the 195 * internal buffers of this streams. 196 * 197 * @param in the input stream to read from 198 * 199 * @return total number of bytes read from the input stream 200 * (and written to this stream) 201 * 202 * @throws java.io.IOException if an I/O error occurs while reading the input stream 203 * @since 1.4 204 */ 205 public synchronized int write(final InputStream in) throws IOException { 206 int readCount = 0; 207 int inBufferPos = count - filledBufferSum; 208 int n = in.read(currentBuffer, inBufferPos, currentBuffer.length - inBufferPos); 209 while (n != -1) { 210 readCount += n; 211 inBufferPos += n; 212 count += n; 213 if (inBufferPos == currentBuffer.length) { 214 needNewBuffer(currentBuffer.length); 215 inBufferPos = 0; 216 } 217 n = in.read(currentBuffer, inBufferPos, currentBuffer.length - inBufferPos); 218 } 219 return readCount; 220 } 221 222 /** 223 * Return the current size of the byte array. 224 * 225 * @return the current size of the byte array 226 */ 227 public synchronized int size() { 228 return count; 229 } 230 231 /** 232 * Closing a {@code ByteArrayOutputStream} has no effect. The methods in 233 * this class can be called after the stream has been closed without 234 * generating an {@code IOException}. 235 * 236 * @throws java.io.IOException never (this method should not declare this exception 237 * but it has to now due to backwards compatibility) 238 */ 239 @Override 240 public void close() throws IOException { 241 // nop 242 } 243 244 /** 245 * @see java.io.ByteArrayOutputStream#reset() 246 */ 247 public synchronized void reset() { 248 count = 0; 249 filledBufferSum = 0; 250 currentBufferIndex = 0; 251 if (reuseBuffers) { 252 currentBuffer = buffers.get(currentBufferIndex); 253 } else { 254 // Throw away old buffers 255 currentBuffer = null; 256 int size = buffers.get(0).length; 257 buffers.clear(); 258 needNewBuffer(size); 259 reuseBuffers = true; 260 } 261 } 262 263 /** 264 * Writes the entire contents of this byte stream to the 265 * specified output stream. 266 * 267 * @param out the output stream to write to 268 * 269 * @throws java.io.IOException if an I/O error occurs, such as if the stream is closed 270 * @see java.io.ByteArrayOutputStream#writeTo(java.io.OutputStream) 271 */ 272 public synchronized void writeTo(final OutputStream out) throws IOException { 273 int remaining = count; 274 for (final byte[] buf : buffers) { 275 final int c = Math.min(buf.length, remaining); 276 out.write(buf, 0, c); 277 remaining -= c; 278 if (remaining == 0) { 279 break; 280 } 281 } 282 } 283 284 /** 285 * Fetches entire contents of an <code>InputStream</code> and represent 286 * same data as result InputStream. 287 * <p> 288 * This method is useful where, 289 * <ul> 290 * <li>Source InputStream is slow.</li> 291 * <li>It has network resources associated, so we cannot keep it open for 292 * long time.</li> 293 * <li>It has network timeout associated.</li> 294 * </ul> 295 * It can be used in favor of {@link #toByteArray()}, since it 296 * avoids unnecessary allocation and copy of byte[].<br> 297 * This method buffers the input internally, so there is no need to use a 298 * <code>BufferedInputStream</code>. 299 * 300 * @param input Stream to be fully buffered. 301 * 302 * @return A fully buffered stream. 303 * 304 * @throws java.io.IOException if an I/O error occurs 305 * @since 2.0 306 */ 307 public static InputStream toBufferedInputStream(final InputStream input) throws IOException { 308 return toBufferedInputStream(input, 1024); 309 } 310 311 /** 312 * Fetches entire contents of an <code>InputStream</code> and represent 313 * same data as result InputStream. 314 * <p> 315 * This method is useful where, 316 * <ul> 317 * <li>Source InputStream is slow.</li> 318 * <li>It has network resources associated, so we cannot keep it open for 319 * long time.</li> 320 * <li>It has network timeout associated.</li> 321 * </ul> 322 * It can be used in favor of {@link #toByteArray()}, since it 323 * avoids unnecessary allocation and copy of byte[].<br> 324 * This method buffers the input internally, so there is no need to use a 325 * <code>BufferedInputStream</code>. 326 * 327 * @param input Stream to be fully buffered. 328 * @param size the initial buffer size 329 * 330 * @return A fully buffered stream. 331 * 332 * @throws java.io.IOException if an I/O error occurs 333 * @since 2.5 334 */ 335 public static InputStream toBufferedInputStream(final InputStream input, int size) throws IOException { 336 // It does not matter if a ByteArrayOutputStream is not closed as close() is a no-op 337 @SuppressWarnings("resource") 338 final ByteArrayOutputStream output = new ByteArrayOutputStream(size); 339 output.write(input); 340 return output.toInputStream(); 341 } 342 343 /** 344 * Gets the current contents of this byte stream as a Input Stream. The 345 * returned stream is backed by buffers of <code>this</code> stream, 346 * avoiding memory allocation and copy, thus saving space and time.<br> 347 * 348 * @return the current contents of this output stream. 349 * 350 * @see java.io.ByteArrayOutputStream#toByteArray() 351 * @see #reset() 352 * @since 2.5 353 */ 354 public synchronized InputStream toInputStream() { 355 int remaining = count; 356 if (remaining == 0) { 357 return new ClosedInputStream(); 358 } 359 final List<ByteArrayInputStream> list = new ArrayList<ByteArrayInputStream>(buffers.size()); 360 for (final byte[] buf : buffers) { 361 final int c = Math.min(buf.length, remaining); 362 list.add(new ByteArrayInputStream(buf, 0, c)); 363 remaining -= c; 364 if (remaining == 0) { 365 break; 366 } 367 } 368 reuseBuffers = false; 369 return new SequenceInputStream(Collections.enumeration(list)); 370 } 371 372 /** 373 * Gets the curent contents of this byte stream as a byte array. 374 * The result is independent of this stream. 375 * 376 * @return the current contents of this output stream, as a byte array 377 * 378 * @see java.io.ByteArrayOutputStream#toByteArray() 379 */ 380 public synchronized byte[] toByteArray() { 381 int remaining = count; 382 if (remaining == 0) { 383 return EMPTY_BYTE_ARRAY; 384 } 385 final byte newbuf[] = new byte[remaining]; 386 int pos = 0; 387 for (final byte[] buf : buffers) { 388 final int c = Math.min(buf.length, remaining); 389 System.arraycopy(buf, 0, newbuf, pos, c); 390 pos += c; 391 remaining -= c; 392 if (remaining == 0) { 393 break; 394 } 395 } 396 return newbuf; 397 } 398 399 /** 400 * Gets the curent contents of this byte stream as a string 401 * using the platform default charset. 402 * 403 * @return the contents of the byte array as a String 404 * 405 * @see java.io.ByteArrayOutputStream#toString() 406 * @deprecated 2.5 use {@link #toString(String)} instead 407 */ 408 @Override 409 @Deprecated 410 public String toString() { 411 // make explicit the use of the default charset 412 return new String(toByteArray(), Charset.defaultCharset()); 413 } 414 415 /** 416 * Gets the curent contents of this byte stream as a string 417 * using the specified encoding. 418 * 419 * @param enc the name of the character encoding 420 * 421 * @return the string converted from the byte array 422 * 423 * @throws java.io.UnsupportedEncodingException if the encoding is not supported 424 * @see java.io.ByteArrayOutputStream#toString(String) 425 */ 426 public String toString(final String enc) throws UnsupportedEncodingException { 427 return new String(toByteArray(), enc); 428 } 429 430 /** 431 * Gets the curent contents of this byte stream as a string 432 * using the specified encoding. 433 * 434 * @param charset the character encoding 435 * 436 * @return the string converted from the byte array 437 * 438 * @see java.io.ByteArrayOutputStream#toString(String) 439 * @since 2.5 440 */ 441 public String toString(final Charset charset) { 442 return new String(toByteArray(), charset); 443 } 444 }