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