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.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 buffer 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           */
79          T construct(final byte[] buffer, 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
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 }