• 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 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          */
construct(final byte[] buffer, final int offset, final int length)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
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(final InputStreamConstructor<T> isConstructor) {
240         int remaining = count;
241         if (remaining == 0) {
242             return ClosedInputStream.INSTANCE;
243         }
244         final List<T> list = new ArrayList<>(buffers.size());
245         for (final byte[] buf : buffers) {
246             final int c = Math.min(buf.length, remaining);
247             list.add(isConstructor.construct(buf, 0, c));
248             remaining -= c;
249             if (remaining == 0) {
250                 break;
251             }
252         }
253         reuseBuffers = false;
254         return new SequenceInputStream(Collections.enumeration(list));
255     }
256 
257     /**
258      * Gets the current contents of this byte stream as a string
259      * using the platform default charset.
260      * @return the contents of the byte array as a String
261      * @see java.io.ByteArrayOutputStream#toString()
262      * @deprecated 2.5 use {@link #toString(String)} instead
263      */
264     @Override
265     @Deprecated
toString()266     public String toString() {
267         // make explicit the use of the default charset
268         return new String(toByteArray(), Charset.defaultCharset());
269     }
270 
271     /**
272      * Gets the current contents of this byte stream as a string
273      * using the specified encoding.
274      *
275      * @param charset  the character encoding
276      * @return the string converted from the byte array
277      * @see java.io.ByteArrayOutputStream#toString(String)
278      * @since 2.5
279      */
toString(final Charset charset)280     public String toString(final Charset charset) {
281         return new String(toByteArray(), charset);
282     }
283 
284     /**
285      * Gets the current contents of this byte stream as a string
286      * using the specified encoding.
287      *
288      * @param enc  the name of the character encoding
289      * @return the string converted from the byte array
290      * @throws UnsupportedEncodingException if the encoding is not supported
291      * @see java.io.ByteArrayOutputStream#toString(String)
292      */
toString(final String enc)293     public String toString(final String enc) throws UnsupportedEncodingException {
294         return new String(toByteArray(), enc);
295     }
296 
297     @Override
write(final byte[] b, final int off, final int len)298     public abstract void write(final byte[] b, final int off, final int len);
299 
300     /**
301      * Writes the entire contents of the specified input stream to this
302      * byte stream. Bytes from the input stream are read directly into the
303      * internal buffer of this stream.
304      *
305      * @param in the input stream to read from
306      * @return total number of bytes read from the input stream
307      *         (and written to this stream)
308      * @throws IOException if an I/O error occurs while reading the input stream
309      * @since 1.4
310      */
write(final InputStream in)311     public abstract int write(final InputStream in) throws IOException;
312 
313     @Override
write(final int b)314     public abstract void write(final int b);
315 
316     /**
317      * Writes the bytes to the byte array.
318      * @param b the bytes to write
319      * @param off The start offset
320      * @param len The number of bytes to write
321      */
writeImpl(final byte[] b, final int off, final int len)322     protected void writeImpl(final byte[] b, final int off, final int len) {
323         final int newCount = count + len;
324         int remaining = len;
325         int inBufferPos = count - filledBufferSum;
326         while (remaining > 0) {
327             final int part = Math.min(remaining, currentBuffer.length - inBufferPos);
328             System.arraycopy(b, off + len - remaining, currentBuffer, inBufferPos, part);
329             remaining -= part;
330             if (remaining > 0) {
331                 needNewBuffer(newCount);
332                 inBufferPos = 0;
333             }
334         }
335         count = newCount;
336     }
337 
338     /**
339      * Writes the entire contents of the specified input stream to this
340      * byte stream. Bytes from the input stream are read directly into the
341      * internal buffer of this stream.
342      *
343      * @param in the input stream to read from
344      * @return total number of bytes read from the input stream
345      *         (and written to this stream)
346      * @throws IOException if an I/O error occurs while reading the input stream
347      * @since 2.7
348      */
writeImpl(final InputStream in)349     protected int writeImpl(final InputStream in) throws IOException {
350         int readCount = 0;
351         int inBufferPos = count - filledBufferSum;
352         int n = in.read(currentBuffer, inBufferPos, currentBuffer.length - inBufferPos);
353         while (n != EOF) {
354             readCount += n;
355             inBufferPos += n;
356             count += n;
357             if (inBufferPos == currentBuffer.length) {
358                 needNewBuffer(currentBuffer.length);
359                 inBufferPos = 0;
360             }
361             n = in.read(currentBuffer, inBufferPos, currentBuffer.length - inBufferPos);
362         }
363         return readCount;
364     }
365 
366     /**
367      * Write a byte to byte array.
368      * @param b the byte to write
369      */
writeImpl(final int b)370     protected void writeImpl(final int b) {
371         int inBufferPos = count - filledBufferSum;
372         if (inBufferPos == currentBuffer.length) {
373             needNewBuffer(count + 1);
374             inBufferPos = 0;
375         }
376         currentBuffer[inBufferPos] = (byte) b;
377         count++;
378     }
379 
380     /**
381      * Writes the entire contents of this byte stream to the
382      * specified output stream.
383      *
384      * @param out  the output stream to write to
385      * @throws IOException if an I/O error occurs, such as if the stream is closed
386      * @see java.io.ByteArrayOutputStream#writeTo(OutputStream)
387      */
writeTo(final OutputStream out)388     public abstract void writeTo(final OutputStream out) throws IOException;
389 
390     /**
391      * Writes the entire contents of this byte stream to the
392      * specified output stream.
393      *
394      * @param out  the output stream to write to
395      * @throws IOException if an I/O error occurs, such as if the stream is closed
396      * @see java.io.ByteArrayOutputStream#writeTo(OutputStream)
397      */
writeToImpl(final OutputStream out)398     protected void writeToImpl(final OutputStream out) throws IOException {
399         int remaining = count;
400         for (final byte[] buf : buffers) {
401             final int c = Math.min(buf.length, remaining);
402             out.write(buf, 0, c);
403             remaining -= c;
404             if (remaining == 0) {
405                 break;
406             }
407         }
408     }
409 
410 }
411