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.Charsets;
32  import org.apache.commons.io.IOUtils;
33  import org.apache.commons.io.input.ClosedInputStream;
34  
35  /**
36   * This is the base class for implementing an output stream in which the data
37   * is written into a byte array. The buffer automatically grows as data
38   * is written to it.
39   * <p>
40   * The data can be retrieved using {@code toByteArray()} and
41   * {@code toString()}.
42   * Closing an {@link AbstractByteArrayOutputStream} has no effect. The methods in
43   * this class can be called after the stream has been closed without
44   * generating an {@link IOException}.
45   * </p>
46   * <p>
47   * This is the base for an alternative implementation of the
48   * {@link java.io.ByteArrayOutputStream} class. The original implementation
49   * only allocates 32 bytes at the beginning. As this class is designed for
50   * heavy duty it starts at {@value #DEFAULT_SIZE} bytes. In contrast to the original it doesn't
51   * reallocate the whole memory block but allocates additional buffers. This
52   * way no buffers need to be garbage collected and the contents don't have
53   * to be copied to the new buffer. This class is designed to behave exactly
54   * like the original. The only exception is the deprecated
55   * {@link java.io.ByteArrayOutputStream#toString(int)} method that has been
56   * ignored.
57   * </p>
58   *
59   * @param <T> The AbstractByteArrayOutputStream subclass
60   * @since 2.7
61   */
62  public abstract class AbstractByteArrayOutputStream<T extends AbstractByteArrayOutputStream<T>> extends OutputStream {
63  
64      /**
65       * Constructor for an InputStream subclass.
66       *
67       * @param <T> the type of the InputStream.
68       */
69      @FunctionalInterface
70      protected interface InputStreamConstructor<T extends InputStream> {
71  
72          /**
73           * Constructs an InputStream subclass.
74           *
75           * @param buffer the buffer
76           * @param offset the offset into the buffer
77           * @param length the length of the buffer
78           * @return the InputStream subclass.
79           */
80          T construct(byte[] buffer, int offset, int length);
81      }
82  
83      static final int DEFAULT_SIZE = 1024;
84  
85      /** The list of buffers, which grows and never reduces. */
86      private final List<byte[]> buffers = new ArrayList<>();
87  
88      /** The total count of bytes written. */
89      protected int count;
90  
91      /** The current buffer. */
92      private byte[] currentBuffer;
93  
94      /** The index of the current buffer. */
95      private int currentBufferIndex;
96  
97      /** The total count of bytes in all the filled buffers. */
98      private int filledBufferSum;
99  
100     /** Flag to indicate if the buffers can be reused after reset */
101     private boolean reuseBuffers = true;
102 
103     /**
104      * Constructs a new instance for subclasses.
105      */
106     public AbstractByteArrayOutputStream() {
107         // empty
108     }
109 
110     /**
111      * Returns this instance typed to {@code T}.
112      *
113      * @return this instance
114      */
115     @SuppressWarnings("unchecked")
116     protected T asThis() {
117         return (T) this;
118     }
119 
120     /**
121      * Does nothing.
122      *
123      * The methods in this class can be called after the stream has been closed without generating an {@link IOException}.
124      *
125      * @throws IOException never (this method should not declare this exception but it has to now due to backwards
126      *         compatibility)
127      */
128     @Override
129     public void close() throws IOException {
130         //nop
131     }
132 
133     /**
134      * Makes a new buffer available either by allocating
135      * a new one or re-cycling an existing one.
136      *
137      * @param newCount  the size of the buffer if one is created
138      */
139     protected void needNewBuffer(final int newCount) {
140         if (currentBufferIndex < buffers.size() - 1) {
141             // Recycling old buffer
142             filledBufferSum += currentBuffer.length;
143 
144             currentBufferIndex++;
145             currentBuffer = buffers.get(currentBufferIndex);
146         } else {
147             // Creating new buffer
148             final int newBufferSize;
149             if (currentBuffer == null) {
150                 newBufferSize = newCount;
151                 filledBufferSum = 0;
152             } else {
153                 newBufferSize = Math.max(currentBuffer.length << 1, newCount - filledBufferSum);
154                 filledBufferSum += currentBuffer.length;
155             }
156 
157             currentBufferIndex++;
158             currentBuffer = IOUtils.byteArray(newBufferSize);
159             buffers.add(currentBuffer);
160         }
161     }
162 
163     /**
164      * See {@link ByteArrayOutputStream#reset()}.
165      *
166      * @see ByteArrayOutputStream#reset()
167      */
168     public abstract void reset();
169 
170     /**
171      * Implements a default reset behavior.
172      *
173      * @see ByteArrayOutputStream#reset()
174      */
175     protected void resetImpl() {
176         count = 0;
177         filledBufferSum = 0;
178         currentBufferIndex = 0;
179         if (reuseBuffers) {
180             currentBuffer = buffers.get(currentBufferIndex);
181         } else {
182             //Throw away old buffers
183             currentBuffer = null;
184             final int size = buffers.get(0).length;
185             buffers.clear();
186             needNewBuffer(size);
187             reuseBuffers = true;
188         }
189     }
190 
191     /**
192      * Returns the current size of the byte array.
193      *
194      * @return the current size of the byte array
195      */
196     public abstract int size();
197 
198     /**
199      * Gets the current contents of this byte stream as a byte array.
200      * The result is independent of this stream.
201      *
202      * @return the current contents of this output stream, as a byte array
203      * @see java.io.ByteArrayOutputStream#toByteArray()
204      */
205     public abstract byte[] toByteArray();
206 
207     /**
208      * Gets the current contents of this byte stream as a byte array.
209      * The result is independent of this stream.
210      *
211      * @return the current contents of this output stream, as a byte array
212      * @see java.io.ByteArrayOutputStream#toByteArray()
213      */
214     protected byte[] toByteArrayImpl() {
215         int remaining = count;
216         if (remaining == 0) {
217             return IOUtils.EMPTY_BYTE_ARRAY;
218         }
219         final byte[] newBuf = IOUtils.byteArray(remaining);
220         int pos = 0;
221         for (final byte[] buf : buffers) {
222             final int c = Math.min(buf.length, remaining);
223             System.arraycopy(buf, 0, newBuf, pos, c);
224             pos += c;
225             remaining -= c;
226             if (remaining == 0) {
227                 break;
228             }
229         }
230         return newBuf;
231     }
232 
233     /**
234      * Gets the current contents of this byte stream as an Input Stream. The
235      * returned stream is backed by buffers of {@code this} stream,
236      * avoiding memory allocation and copy, thus saving space and time.<br>
237      *
238      * @return the current contents of this output stream.
239      * @see java.io.ByteArrayOutputStream#toByteArray()
240      * @see #reset()
241      * @since 2.5
242      */
243     public abstract InputStream toInputStream();
244 
245     /**
246      * Gets the current contents of this byte stream as an Input Stream. The
247      * returned stream is backed by buffers of {@code this} stream,
248      * avoiding memory allocation and copy, thus saving space and time.<br>
249      *
250      * @param <T> the type of the InputStream which makes up
251      *            the {@link SequenceInputStream}.
252      * @param isConstructor A constructor for an InputStream which makes
253      *                     up the {@link SequenceInputStream}.
254      *
255      * @return the current contents of this output stream.
256      * @see java.io.ByteArrayOutputStream#toByteArray()
257      * @see #reset()
258      * @since 2.7
259      */
260     @SuppressWarnings("resource") // The result InputStream MUST be managed by the call site.
261     protected <T extends InputStream> InputStream toInputStream(final InputStreamConstructor<T> isConstructor) {
262         int remaining = count;
263         if (remaining == 0) {
264             return ClosedInputStream.INSTANCE;
265         }
266         final List<T> list = new ArrayList<>(buffers.size());
267         for (final byte[] buf : buffers) {
268             final int c = Math.min(buf.length, remaining);
269             list.add(isConstructor.construct(buf, 0, c));
270             remaining -= c;
271             if (remaining == 0) {
272                 break;
273             }
274         }
275         reuseBuffers = false;
276         return new SequenceInputStream(Collections.enumeration(list));
277     }
278 
279     /**
280      * Gets the current contents of this byte stream as a string using the virtual machine's {@link Charset#defaultCharset() default charset}.
281      *
282      * @return the contents of the byte array as a String
283      * @see java.io.ByteArrayOutputStream#toString()
284      * @see Charset#defaultCharset()
285      * @deprecated Use {@link #toString(String)} instead
286      */
287     @Override
288     @Deprecated
289     public String toString() {
290         // make explicit the use of the default charset
291         return new String(toByteArray(), Charset.defaultCharset());
292     }
293 
294     /**
295      * Gets the current contents of this byte stream as a string
296      * using the specified encoding.
297      *
298      * @param charset  the character encoding
299      * @return the string converted from the byte array
300      * @see java.io.ByteArrayOutputStream#toString(String)
301      * @since 2.5
302      */
303     public String toString(final Charset charset) {
304         return new String(toByteArray(), charset);
305     }
306 
307     /**
308      * Gets the current contents of this byte stream as a string
309      * using the specified encoding.
310      *
311      * @param enc  the name of the character encoding
312      * @return the string converted from the byte array
313      * @throws UnsupportedEncodingException if the encoding is not supported
314      * @see java.io.ByteArrayOutputStream#toString(String)
315      */
316     public String toString(final String enc) throws UnsupportedEncodingException {
317         return new String(toByteArray(), enc);
318     }
319 
320     /**
321      * Writes {@code b.length} bytes from the given byte array to this output stream. This has same effect as {@code write(b, 0, b.length)}.
322      *
323      * @param b the data.
324      * @see #write(byte[], int, int)
325      * @since 2.19.0
326      */
327     @Override
328     public void write(final byte b[]) {
329         write(b, 0, b.length);
330     }
331 
332     @Override
333     public abstract void write(byte[] b, int off, int len);
334 
335     /**
336      * Writes the bytes for given CharSequence encoded using a Charset.
337      *
338      * @param data    The String to convert to bytes. not null.
339      * @param charset The {@link Charset} o encode the {@code String}, null means the default encoding.
340      * @return this instance.
341      * @since 2.19.0
342      */
343     public T write(final CharSequence data, final Charset charset) {
344         write(data.toString().getBytes(Charsets.toCharset(charset)));
345         return asThis();
346     }
347 
348     /**
349      * Writes the entire contents of the specified input stream to this
350      * byte stream. Bytes from the input stream are read directly into the
351      * internal buffer of this stream.
352      *
353      * @param in the input stream to read from
354      * @return total number of bytes read from the input stream
355      *         (and written to this stream)
356      * @throws IOException if an I/O error occurs while reading the input stream
357      * @since 1.4
358      */
359     public abstract int write(InputStream in) throws IOException;
360 
361     @Override
362     public abstract void write(int b);
363 
364     /**
365      * Writes the bytes to the byte array.
366      * @param b the bytes to write
367      * @param off The start offset
368      * @param len The number of bytes to write
369      */
370     protected void writeImpl(final byte[] b, final int off, final int len) {
371         final int newCount = count + len;
372         int remaining = len;
373         int inBufferPos = count - filledBufferSum;
374         while (remaining > 0) {
375             final int part = Math.min(remaining, currentBuffer.length - inBufferPos);
376             System.arraycopy(b, off + len - remaining, currentBuffer, inBufferPos, part);
377             remaining -= part;
378             if (remaining > 0) {
379                 needNewBuffer(newCount);
380                 inBufferPos = 0;
381             }
382         }
383         count = newCount;
384     }
385 
386     /**
387      * Writes the entire contents of the specified input stream to this
388      * byte stream. Bytes from the input stream are read directly into the
389      * internal buffer of this stream.
390      *
391      * @param in the input stream to read from
392      * @return total number of bytes read from the input stream
393      *         (and written to this stream)
394      * @throws IOException if an I/O error occurs while reading the input stream
395      * @since 2.7
396      */
397     protected int writeImpl(final InputStream in) throws IOException {
398         int readCount = 0;
399         int inBufferPos = count - filledBufferSum;
400         int n = in.read(currentBuffer, inBufferPos, currentBuffer.length - inBufferPos);
401         while (n != EOF) {
402             readCount += n;
403             inBufferPos += n;
404             count += n;
405             if (inBufferPos == currentBuffer.length) {
406                 needNewBuffer(currentBuffer.length);
407                 inBufferPos = 0;
408             }
409             n = in.read(currentBuffer, inBufferPos, currentBuffer.length - inBufferPos);
410         }
411         return readCount;
412     }
413 
414     /**
415      * Write a byte to byte array.
416      * @param b the byte to write
417      */
418     protected void writeImpl(final int b) {
419         int inBufferPos = count - filledBufferSum;
420         if (inBufferPos == currentBuffer.length) {
421             needNewBuffer(count + 1);
422             inBufferPos = 0;
423         }
424         currentBuffer[inBufferPos] = (byte) b;
425         count++;
426     }
427 
428     /**
429      * Writes the entire contents of this byte stream to the
430      * specified output stream.
431      *
432      * @param out  the output stream to write to
433      * @throws IOException if an I/O error occurs, such as if the stream is closed
434      * @see java.io.ByteArrayOutputStream#writeTo(OutputStream)
435      */
436     public abstract void writeTo(OutputStream out) throws IOException;
437 
438     /**
439      * Writes the entire contents of this byte stream to the
440      * specified output stream.
441      *
442      * @param out  the output stream to write to
443      * @throws IOException if an I/O error occurs, such as if the stream is closed
444      * @see java.io.ByteArrayOutputStream#writeTo(OutputStream)
445      */
446     protected void writeToImpl(final OutputStream out) throws IOException {
447         int remaining = count;
448         for (final byte[] buf : buffers) {
449             final int c = Math.min(buf.length, remaining);
450             out.write(buf, 0, c);
451             remaining -= c;
452             if (remaining == 0) {
453                 break;
454             }
455         }
456     }
457 
458 }