001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.io.output;
018
019import org.apache.commons.io.IOUtils;
020import org.apache.commons.io.input.ClosedInputStream;
021
022import java.io.InputStream;
023import java.io.IOException;
024import java.io.OutputStream;
025import java.io.SequenceInputStream;
026import java.io.UnsupportedEncodingException;
027import java.nio.charset.Charset;
028import java.util.ArrayList;
029import java.util.Collections;
030import java.util.List;
031
032import static org.apache.commons.io.IOUtils.EOF;
033
034/**
035 * This is the base class for implementing an output stream in which the data
036 * is written into a byte array. The buffer automatically grows as data
037 * is written to it.
038 * <p>
039 * The data can be retrieved using {@code toByteArray()} and
040 * {@code toString()}.
041 * Closing an {@code AbstractByteArrayOutputStream} has no effect. The methods in
042 * this class can be called after the stream has been closed without
043 * generating an {@code IOException}.
044 * </p>
045 * <p>
046 * This is the base for an alternative implementation of the
047 * {@link java.io.ByteArrayOutputStream} class. The original implementation
048 * only allocates 32 bytes at the beginning. As this class is designed for
049 * heavy duty it starts at {@value #DEFAULT_SIZE} bytes. In contrast to the original it doesn't
050 * reallocate the whole memory block but allocates additional buffers. This
051 * way no buffers need to be garbage collected and the contents don't have
052 * to be copied to the new buffer. This class is designed to behave exactly
053 * like the original. The only exception is the deprecated
054 * {@link java.io.ByteArrayOutputStream#toString(int)} method that has been
055 * ignored.
056 * </p>
057 *
058 * @since 2.7
059 */
060public abstract class AbstractByteArrayOutputStream extends OutputStream {
061
062    static final int DEFAULT_SIZE = 1024;
063
064    /** The list of buffers, which grows and never reduces. */
065    private final List<byte[]> buffers = new ArrayList<>();
066    /** The index of the current buffer. */
067    private int currentBufferIndex;
068    /** The total count of bytes in all the filled buffers. */
069    private int filledBufferSum;
070    /** The current buffer. */
071    private byte[] currentBuffer;
072    /** The total count of bytes written. */
073    protected int count;
074    /** Flag to indicate if the buffers can be reused after reset */
075    private boolean reuseBuffers = true;
076
077    /**
078     * Makes a new buffer available either by allocating
079     * a new one or re-cycling an existing one.
080     *
081     * @param newcount  the size of the buffer if one is created
082     */
083    protected void needNewBuffer(final int newcount) {
084        if (currentBufferIndex < buffers.size() - 1) {
085            //Recycling old buffer
086            filledBufferSum += currentBuffer.length;
087
088            currentBufferIndex++;
089            currentBuffer = buffers.get(currentBufferIndex);
090        } else {
091            //Creating new buffer
092            final int newBufferSize;
093            if (currentBuffer == null) {
094                newBufferSize = newcount;
095                filledBufferSum = 0;
096            } else {
097                newBufferSize = Math.max(
098                    currentBuffer.length << 1,
099                    newcount - filledBufferSum);
100                filledBufferSum += currentBuffer.length;
101            }
102
103            currentBufferIndex++;
104            currentBuffer = IOUtils.byteArray(newBufferSize);
105            buffers.add(currentBuffer);
106        }
107    }
108
109    /**
110     * Writes the bytes to the byte array.
111     * @param b the bytes to write
112     * @param off The start offset
113     * @param len The number of bytes to write
114     */
115    @Override
116    public abstract void write(final byte[] b, final int off, final int len);
117
118    /**
119     * Writes the bytes to the byte array.
120     * @param b the bytes to write
121     * @param off The start offset
122     * @param len The number of bytes to write
123     */
124    protected void writeImpl(final byte[] b, final int off, final int len) {
125        final int newcount = count + len;
126        int remaining = len;
127        int inBufferPos = count - filledBufferSum;
128        while (remaining > 0) {
129            final int part = Math.min(remaining, currentBuffer.length - inBufferPos);
130            System.arraycopy(b, off + len - remaining, currentBuffer, inBufferPos, part);
131            remaining -= part;
132            if (remaining > 0) {
133                needNewBuffer(newcount);
134                inBufferPos = 0;
135            }
136        }
137        count = newcount;
138    }
139
140    /**
141     * Write a byte to byte array.
142     * @param b the byte to write
143     */
144    @Override
145    public abstract void write(final int b);
146
147    /**
148     * Write a byte to byte array.
149     * @param b the byte to write
150     */
151    protected void writeImpl(final int b) {
152        int inBufferPos = count - filledBufferSum;
153        if (inBufferPos == currentBuffer.length) {
154            needNewBuffer(count + 1);
155            inBufferPos = 0;
156        }
157        currentBuffer[inBufferPos] = (byte) b;
158        count++;
159    }
160
161
162    /**
163     * Writes the entire contents of the specified input stream to this
164     * byte stream. Bytes from the input stream are read directly into the
165     * internal buffers of this streams.
166     *
167     * @param in the input stream to read from
168     * @return total number of bytes read from the input stream
169     *         (and written to this stream)
170     * @throws IOException if an I/O error occurs while reading the input stream
171     * @since 1.4
172     */
173    public abstract int write(final InputStream in) throws IOException;
174
175    /**
176     * Writes the entire contents of the specified input stream to this
177     * byte stream. Bytes from the input stream are read directly into the
178     * internal buffers of this streams.
179     *
180     * @param in the input stream to read from
181     * @return total number of bytes read from the input stream
182     *         (and written to this stream)
183     * @throws IOException if an I/O error occurs while reading the input stream
184     * @since 2.7
185     */
186    protected int writeImpl(final InputStream in) throws IOException {
187        int readCount = 0;
188        int inBufferPos = count - filledBufferSum;
189        int n = in.read(currentBuffer, inBufferPos, currentBuffer.length - inBufferPos);
190        while (n != EOF) {
191            readCount += n;
192            inBufferPos += n;
193            count += n;
194            if (inBufferPos == currentBuffer.length) {
195                needNewBuffer(currentBuffer.length);
196                inBufferPos = 0;
197            }
198            n = in.read(currentBuffer, inBufferPos, currentBuffer.length - inBufferPos);
199        }
200        return readCount;
201    }
202
203    /**
204     * Returns the current size of the byte array.
205     *
206     * @return the current size of the byte array
207     */
208    public abstract int size();
209
210    /**
211     * Closing a {@code ByteArrayOutputStream} has no effect. The methods in
212     * this class can be called after the stream has been closed without
213     * generating an {@code IOException}.
214     *
215     * @throws IOException never (this method should not declare this exception
216     * but it has to now due to backwards compatibility)
217     */
218    @Override
219    public void close() throws IOException {
220        //nop
221    }
222
223    /**
224     * @see java.io.ByteArrayOutputStream#reset()
225     */
226    public abstract void reset();
227
228    /**
229     * @see java.io.ByteArrayOutputStream#reset()
230     */
231    protected void resetImpl() {
232        count = 0;
233        filledBufferSum = 0;
234        currentBufferIndex = 0;
235        if (reuseBuffers) {
236            currentBuffer = buffers.get(currentBufferIndex);
237        } else {
238            //Throw away old buffers
239            currentBuffer = null;
240            final int size = buffers.get(0).length;
241            buffers.clear();
242            needNewBuffer(size);
243            reuseBuffers = true;
244        }
245    }
246
247    /**
248     * Writes the entire contents of this byte stream to the
249     * specified output stream.
250     *
251     * @param out  the output stream to write to
252     * @throws IOException if an I/O error occurs, such as if the stream is closed
253     * @see java.io.ByteArrayOutputStream#writeTo(OutputStream)
254     */
255    public abstract void writeTo(final OutputStream out) throws IOException;
256
257    /**
258     * Writes the entire contents of this byte stream to the
259     * specified output stream.
260     *
261     * @param out  the output stream to write to
262     * @throws IOException if an I/O error occurs, such as if the stream is closed
263     * @see java.io.ByteArrayOutputStream#writeTo(OutputStream)
264     */
265    protected void writeToImpl(final OutputStream out) throws IOException {
266        int remaining = count;
267        for (final byte[] buf : buffers) {
268            final int c = Math.min(buf.length, remaining);
269            out.write(buf, 0, c);
270            remaining -= c;
271            if (remaining == 0) {
272                break;
273            }
274        }
275    }
276
277    /**
278     * Gets the current contents of this byte stream as a Input Stream. The
279     * returned stream is backed by buffers of {@code this} stream,
280     * avoiding memory allocation and copy, thus saving space and time.<br>
281     *
282     * @return the current contents of this output stream.
283     * @see java.io.ByteArrayOutputStream#toByteArray()
284     * @see #reset()
285     * @since 2.5
286     */
287    public abstract InputStream toInputStream();
288
289    /**
290     * Gets the current contents of this byte stream as a Input Stream. The
291     * returned stream is backed by buffers of {@code this} stream,
292     * avoiding memory allocation and copy, thus saving space and time.<br>
293     *
294     * @param <T> the type of the InputStream which makes up
295     *            the {@link SequenceInputStream}.
296     * @param isConstructor A constructor for an InputStream which makes
297     *                     up the {@link SequenceInputStream}.
298     *
299     * @return the current contents of this output stream.
300     * @see java.io.ByteArrayOutputStream#toByteArray()
301     * @see #reset()
302     * @since 2.7
303     */
304    @SuppressWarnings("resource") // The result InputStream MUST be managed by the call site.
305    protected <T extends InputStream> InputStream toInputStream(
306            final InputStreamConstructor<T> isConstructor) {
307        int remaining = count;
308        if (remaining == 0) {
309            return ClosedInputStream.CLOSED_INPUT_STREAM;
310        }
311        final List<T> list = new ArrayList<>(buffers.size());
312        for (final byte[] buf : buffers) {
313            final int c = Math.min(buf.length, remaining);
314            list.add(isConstructor.construct(buf, 0, c));
315            remaining -= c;
316            if (remaining == 0) {
317                break;
318            }
319        }
320        reuseBuffers = false;
321        return new SequenceInputStream(Collections.enumeration(list));
322    }
323
324    /**
325     * Constructor for an InputStream subclass.
326     *
327     * @param <T> the type of the InputStream.
328     */
329    @FunctionalInterface
330    protected interface InputStreamConstructor<T extends InputStream> {
331
332        /**
333         * Construct an InputStream subclass.
334         *
335         * @param buf the buffer
336         * @param offset the offset into the buffer
337         * @param length the length of the buffer
338         *
339         * @return the InputStream subclass.
340         */
341        T construct(final byte[] buf, final int offset, final int length);
342    }
343
344    /**
345     * Gets the current contents of this byte stream as a byte array.
346     * The result is independent of this stream.
347     *
348     * @return the current contents of this output stream, as a byte array
349     * @see java.io.ByteArrayOutputStream#toByteArray()
350     */
351    public abstract byte[] toByteArray();
352
353    /**
354     * Gets the current contents of this byte stream as a byte array.
355     * The result is independent of this stream.
356     *
357     * @return the current contents of this output stream, as a byte array
358     * @see java.io.ByteArrayOutputStream#toByteArray()
359     */
360    protected byte[] toByteArrayImpl() {
361        int remaining = count;
362        if (remaining == 0) {
363            return IOUtils.EMPTY_BYTE_ARRAY;
364        }
365        final byte[] newbuf = IOUtils.byteArray(remaining);
366        int pos = 0;
367        for (final byte[] buf : buffers) {
368            final int c = Math.min(buf.length, remaining);
369            System.arraycopy(buf, 0, newbuf, pos, c);
370            pos += c;
371            remaining -= c;
372            if (remaining == 0) {
373                break;
374            }
375        }
376        return newbuf;
377    }
378
379    /**
380     * Gets the current contents of this byte stream as a string
381     * using the platform default charset.
382     * @return the contents of the byte array as a String
383     * @see java.io.ByteArrayOutputStream#toString()
384     * @deprecated 2.5 use {@link #toString(String)} instead
385     */
386    @Override
387    @Deprecated
388    public String toString() {
389        // make explicit the use of the default charset
390        return new String(toByteArray(), Charset.defaultCharset());
391    }
392
393    /**
394     * Gets the current contents of this byte stream as a string
395     * using the specified encoding.
396     *
397     * @param enc  the name of the character encoding
398     * @return the string converted from the byte array
399     * @throws UnsupportedEncodingException if the encoding is not supported
400     * @see java.io.ByteArrayOutputStream#toString(String)
401     */
402    public String toString(final String enc) throws UnsupportedEncodingException {
403        return new String(toByteArray(), enc);
404    }
405
406    /**
407     * Gets the current contents of this byte stream as a string
408     * using the specified encoding.
409     *
410     * @param charset  the character encoding
411     * @return the string converted from the byte array
412     * @see java.io.ByteArrayOutputStream#toString(String)
413     * @since 2.5
414     */
415    public String toString(final Charset charset) {
416        return new String(toByteArray(), charset);
417    }
418
419}