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    *      https://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 = -1;
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 {@code 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             currentBufferIndex++;
144             currentBuffer = buffers.get(currentBufferIndex);
145         } else {
146             // Creating new buffer
147             final int newBufferSize;
148             if (currentBuffer == null) {
149                 // prevents 0 size buffers
150                 newBufferSize = newCount > 0 ? newCount : DEFAULT_SIZE;
151                 filledBufferSum = 0;
152             } else {
153                 newBufferSize = Math.max(currentBuffer.length << 1, newCount - filledBufferSum);
154                 filledBufferSum += currentBuffer.length;
155             }
156             currentBufferIndex++;
157             currentBuffer = IOUtils.byteArray(newBufferSize);
158             buffers.add(currentBuffer);
159         }
160     }
161 
162     /**
163      * See {@link ByteArrayOutputStream#reset()}.
164      *
165      * @see ByteArrayOutputStream#reset()
166      */
167     public abstract void reset();
168 
169     /**
170      * Implements a default reset behavior.
171      *
172      * @see ByteArrayOutputStream#reset()
173      */
174     protected void resetImpl() {
175         count = 0;
176         filledBufferSum = 0;
177         currentBufferIndex = 0;
178         if (reuseBuffers) {
179             currentBuffer = buffers.get(currentBufferIndex);
180         } else {
181             //Throw away old buffers
182             currentBuffer = null;
183             final int size = buffers.get(0).length;
184             buffers.clear();
185             needNewBuffer(size);
186             reuseBuffers = true;
187         }
188     }
189 
190     /**
191      * Returns the current size of the byte array.
192      *
193      * @return the current size of the byte array.
194      */
195     public abstract int size();
196 
197     /**
198      * Gets the current contents of this byte stream as a byte array.
199      * The result is independent of this stream.
200      *
201      * @return the current contents of this output stream, as a byte array.
202      * @see java.io.ByteArrayOutputStream#toByteArray()
203      */
204     public abstract byte[] toByteArray();
205 
206     /**
207      * Gets the current contents of this byte stream as a byte array.
208      * The result is independent of this stream.
209      *
210      * @return the current contents of this output stream, as a byte array.
211      * @see java.io.ByteArrayOutputStream#toByteArray()
212      */
213     protected byte[] toByteArrayImpl() {
214         int remaining = count;
215         if (remaining == 0) {
216             return IOUtils.EMPTY_BYTE_ARRAY;
217         }
218         final byte[] newBuf = IOUtils.byteArray(remaining);
219         int pos = 0;
220         for (final byte[] buf : buffers) {
221             final int c = Math.min(buf.length, remaining);
222             System.arraycopy(buf, 0, newBuf, pos, c);
223             pos += c;
224             remaining -= c;
225             if (remaining == 0) {
226                 break;
227             }
228         }
229         return newBuf;
230     }
231 
232     /**
233      * Gets the current contents of this byte stream as an Input Stream. The
234      * returned stream is backed by buffers of {@code this} stream,
235      * avoiding memory allocation and copy, thus saving space and time.<br>
236      *
237      * @return the current contents of this output stream.
238      * @see java.io.ByteArrayOutputStream#toByteArray()
239      * @see #reset()
240      * @since 2.5
241      */
242     public abstract InputStream toInputStream();
243 
244     /**
245      * Gets the current contents of this byte stream as an Input Stream. The
246      * returned stream is backed by buffers of {@code this} stream,
247      * avoiding memory allocation and copy, thus saving space and time.<br>
248      *
249      * @param <T> the type of the InputStream which makes up
250      *            the {@link SequenceInputStream}.
251      * @param isConstructor A constructor for an InputStream which makes
252      *                     up the {@link SequenceInputStream}.
253      *
254      * @return the current contents of this output stream.
255      * @see java.io.ByteArrayOutputStream#toByteArray()
256      * @see #reset()
257      * @since 2.7
258      */
259     @SuppressWarnings("resource") // The result InputStream MUST be managed by the call site.
260     protected <T extends InputStream> InputStream toInputStream(final InputStreamConstructor<T> isConstructor) {
261         int remaining = count;
262         if (remaining == 0) {
263             return ClosedInputStream.INSTANCE;
264         }
265         final List<T> list = new ArrayList<>(buffers.size());
266         for (final byte[] buf : buffers) {
267             final int c = Math.min(buf.length, remaining);
268             list.add(isConstructor.construct(buf, 0, c));
269             remaining -= c;
270             if (remaining == 0) {
271                 break;
272             }
273         }
274         reuseBuffers = false;
275         return new SequenceInputStream(Collections.enumeration(list));
276     }
277 
278     /**
279      * Gets the current contents of this byte stream as a string using the virtual machine's {@linkplain Charset#defaultCharset() default charset}.
280      *
281      * @return the contents of the byte array as a String.
282      * @see java.io.ByteArrayOutputStream#toString()
283      * @see Charset#defaultCharset()
284      * @deprecated Use {@link #toString(String)} instead.
285      */
286     @Override
287     @Deprecated
288     public String toString() {
289         // make explicit the use of the default charset
290         return new String(toByteArray(), Charset.defaultCharset());
291     }
292 
293     /**
294      * Gets the current contents of this byte stream as a string
295      * using the specified encoding.
296      *
297      * @param charset  the character encoding.
298      * @return the string converted from the byte array.
299      * @see java.io.ByteArrayOutputStream#toString(String)
300      * @since 2.5
301      */
302     public String toString(final Charset charset) {
303         return new String(toByteArray(), charset);
304     }
305 
306     /**
307      * Gets the current contents of this byte stream as a string
308      * using the specified encoding.
309      *
310      * @param enc  the name of the character encoding.
311      * @return the string converted from the byte array.
312      * @throws UnsupportedEncodingException if the encoding is not supported.
313      * @see java.io.ByteArrayOutputStream#toString(String)
314      */
315     public String toString(final String enc) throws UnsupportedEncodingException {
316         return new String(toByteArray(), enc);
317     }
318 
319     /**
320      * 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)}.
321      *
322      * @param b the data.
323      * @see #write(byte[], int, int)
324      * @since 2.19.0
325      */
326     @Override
327     public void write(final byte b[]) {
328         write(b, 0, b.length);
329     }
330 
331     @Override
332     public abstract void write(byte[] b, int off, int len);
333 
334     /**
335      * Writes the bytes for given CharSequence encoded using a Charset.
336      *
337      * @param data    The String to convert to bytes. not null.
338      * @param charset The {@link Charset} o encode the {@code String}, null means the default encoding.
339      * @return {@code this} instance.
340      * @since 2.19.0
341      */
342     public T write(final CharSequence data, final Charset charset) {
343         write(data.toString().getBytes(Charsets.toCharset(charset)));
344         return asThis();
345     }
346 
347     /**
348      * Writes the entire contents of the specified input stream to this byte stream. Bytes from the input stream are read directly into the internal buffer of
349      * this stream.
350      *
351      * @param in the input stream to read from.
352      * @return total number of bytes read from the input stream (and written to this stream)
353      * @throws IOException if an I/O error occurs while reading the input stream.
354      * @since 1.4
355      */
356     public abstract int write(InputStream in) throws IOException;
357 
358     @Override
359     public abstract void write(int b);
360 
361     /**
362      * Writes the bytes to the byte array.
363      *
364      * @param b the bytes to write.
365      * @param off The start offset.
366      * @param len The number of bytes to write.
367      */
368     protected void writeImpl(final byte[] b, final int off, final int len) {
369         final int newCount = count + len;
370         int remaining = len;
371         int inBufferPos = count - filledBufferSum;
372         while (remaining > 0) {
373             final int part = Math.min(remaining, currentBuffer.length - inBufferPos);
374             System.arraycopy(b, off + len - remaining, currentBuffer, inBufferPos, part);
375             remaining -= part;
376             if (remaining > 0) {
377                 needNewBuffer(newCount);
378                 inBufferPos = 0;
379             }
380         }
381         count = newCount;
382     }
383 
384     /**
385      * Writes the entire contents of the specified input stream to this byte stream. Bytes from the input stream are read directly into the internal buffer of
386      * this stream.
387      *
388      * @param in the input stream to read from.
389      * @return total number of bytes read from the input stream (and written to this stream).
390      * @throws IOException if an I/O error occurs while reading the input stream.
391      * @since 2.7
392      */
393     protected int writeImpl(final InputStream in) throws IOException {
394         int readCount = 0;
395         int inBufferPos = count - filledBufferSum;
396         int n = in.read(currentBuffer, inBufferPos, currentBuffer.length - inBufferPos);
397         while (n != EOF) {
398             readCount += n;
399             inBufferPos += n;
400             count += n;
401             if (inBufferPos == currentBuffer.length) {
402                 needNewBuffer(currentBuffer.length);
403                 inBufferPos = 0;
404             }
405             n = in.read(currentBuffer, inBufferPos, currentBuffer.length - inBufferPos);
406         }
407         return readCount;
408     }
409 
410     /**
411      * Writes a byte to byte array.
412      *
413      * @param b the byte to write.
414      */
415     protected void writeImpl(final int b) {
416         int inBufferPos = count - filledBufferSum;
417         if (inBufferPos == currentBuffer.length) {
418             needNewBuffer(count + 1);
419             inBufferPos = 0;
420         }
421         currentBuffer[inBufferPos] = (byte) b;
422         count++;
423     }
424 
425     /**
426      * Writes the entire contents of this byte stream to the specified output stream.
427      *
428      * @param out the output stream to write to.
429      * @throws IOException if an I/O error occurs, such as if the stream is closed.
430      * @see java.io.ByteArrayOutputStream#writeTo(OutputStream)
431      */
432     public abstract void writeTo(OutputStream out) throws IOException;
433 
434     /**
435      * Writes the entire contents of this byte stream to the specified output stream.
436      *
437      * @param out the output stream to write to.
438      * @throws IOException if an I/O error occurs, such as if the stream is closed.
439      * @see java.io.ByteArrayOutputStream#writeTo(OutputStream)
440      */
441     protected void writeToImpl(final OutputStream out) throws IOException {
442         int remaining = count;
443         for (final byte[] buf : buffers) {
444             final int c = Math.min(buf.length, remaining);
445             out.write(buf, 0, c);
446             remaining -= c;
447             if (remaining == 0) {
448                 break;
449             }
450         }
451     }
452 
453 }