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 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 {@link 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 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
349      * byte stream. Bytes from the input stream are read directly into the
350      * internal buffer of this stream.
351      *
352      * @param in the input stream to read from
353      * @return total number of bytes read from the input stream
354      *         (and written to this stream)
355      * @throws IOException if an I/O error occurs while reading the input stream
356      * @since 1.4
357      */
358     public abstract int write(InputStream in) throws IOException;
359 
360     @Override
361     public abstract void write(int b);
362 
363     /**
364      * Writes the bytes to the byte array.
365      * @param b the bytes to write
366      * @param off The start offset
367      * @param len The number of bytes to write
368      */
369     protected void writeImpl(final byte[] b, final int off, final int len) {
370         final int newCount = count + len;
371         int remaining = len;
372         int inBufferPos = count - filledBufferSum;
373         while (remaining > 0) {
374             final int part = Math.min(remaining, currentBuffer.length - inBufferPos);
375             System.arraycopy(b, off + len - remaining, currentBuffer, inBufferPos, part);
376             remaining -= part;
377             if (remaining > 0) {
378                 needNewBuffer(newCount);
379                 inBufferPos = 0;
380             }
381         }
382         count = newCount;
383     }
384 
385     /**
386      * Writes the entire contents of the specified input stream to this
387      * byte stream. Bytes from the input stream are read directly into the
388      * internal buffer of this stream.
389      *
390      * @param in the input stream to read from
391      * @return total number of bytes read from the input stream
392      *         (and written to this stream)
393      * @throws IOException if an I/O error occurs while reading the input stream
394      * @since 2.7
395      */
396     protected int writeImpl(final InputStream in) throws IOException {
397         int readCount = 0;
398         int inBufferPos = count - filledBufferSum;
399         int n = in.read(currentBuffer, inBufferPos, currentBuffer.length - inBufferPos);
400         while (n != EOF) {
401             readCount += n;
402             inBufferPos += n;
403             count += n;
404             if (inBufferPos == currentBuffer.length) {
405                 needNewBuffer(currentBuffer.length);
406                 inBufferPos = 0;
407             }
408             n = in.read(currentBuffer, inBufferPos, currentBuffer.length - inBufferPos);
409         }
410         return readCount;
411     }
412 
413     /**
414      * Writes a byte to byte array.
415      * @param b the byte to write
416      */
417     protected void writeImpl(final int b) {
418         int inBufferPos = count - filledBufferSum;
419         if (inBufferPos == currentBuffer.length) {
420             needNewBuffer(count + 1);
421             inBufferPos = 0;
422         }
423         currentBuffer[inBufferPos] = (byte) b;
424         count++;
425     }
426 
427     /**
428      * Writes the entire contents of this byte stream to the
429      * specified output stream.
430      *
431      * @param out  the output stream to write to
432      * @throws IOException if an I/O error occurs, such as if the stream is closed
433      * @see java.io.ByteArrayOutputStream#writeTo(OutputStream)
434      */
435     public abstract void writeTo(OutputStream out) throws IOException;
436 
437     /**
438      * Writes the entire contents of this byte stream to the
439      * specified output stream.
440      *
441      * @param out  the output stream to write to
442      * @throws IOException if an I/O error occurs, such as if the stream is closed
443      * @see java.io.ByteArrayOutputStream#writeTo(OutputStream)
444      */
445     protected void writeToImpl(final OutputStream out) throws IOException {
446         int remaining = count;
447         for (final byte[] buf : buffers) {
448             final int c = Math.min(buf.length, remaining);
449             out.write(buf, 0, c);
450             remaining -= c;
451             if (remaining == 0) {
452                 break;
453             }
454         }
455     }
456 
457 }