View Javadoc
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 }