    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 static org.apache.commons.io.IOUtils.EOF;
    020
    021import java.io.IOException;
    022import java.io.InputStream;
    023import java.io.OutputStream;
    024import java.io.SequenceInputStream;
    025import java.io.UnsupportedEncodingException;
    026import java.nio.charset.Charset;
    027import java.util.ArrayList;
    028import java.util.Collections;
    029import java.util.List;
    030
    031import org.apache.commons.io.Charsets;
    032import org.apache.commons.io.IOUtils;
    033import org.apache.commons.io.input.ClosedInputStream;
    034
    035/**
    036 * This is the base class for implementing an output stream in which the data
    037 * is written into a byte array. The buffer automatically grows as data
    038 * is written to it.
    039 * <p>
    040 * The data can be retrieved using {@code toByteArray()} and
    041 * {@code toString()}.
    042 * Closing an {@link AbstractByteArrayOutputStream} has no effect. The methods in
    043 * this class can be called after the stream has been closed without
    044 * generating an {@link IOException}.
    045 * </p>
    046 * <p>
    047 * This is the base for an alternative implementation of the
    048 * {@link java.io.ByteArrayOutputStream} class. The original implementation
    049 * only allocates 32 bytes at the beginning. As this class is designed for
    050 * heavy duty it starts at {@value #DEFAULT_SIZE} bytes. In contrast to the original it doesn't
    051 * reallocate the whole memory block but allocates additional buffers. This
    052 * way no buffers need to be garbage collected and the contents don't have
    053 * to be copied to the new buffer. This class is designed to behave exactly
    054 * like the original. The only exception is the deprecated
    055 * {@link java.io.ByteArrayOutputStream#toString(int)} method that has been
    056 * ignored.
    057 * </p>
    058 *
    059 * @param <T> The AbstractByteArrayOutputStream subclass
    060 * @since 2.7
    061 */
    062public abstract class AbstractByteArrayOutputStream<T extends AbstractByteArrayOutputStream<T>> extends OutputStream {
    063
    064    /**
    065     * Constructor for an InputStream subclass.
    066     *
    067     * @param <T> the type of the InputStream.
    068     */
    069    @FunctionalInterface
    070    protected interface InputStreamConstructor<T extends InputStream> {
    071
    072        /**
    073         * Constructs an InputStream subclass.
    074         *
    075         * @param buffer the buffer
    076         * @param offset the offset into the buffer
    077         * @param length the length of the buffer
    078         * @return the InputStream subclass.
    079         */
    080        T construct(byte[] buffer, int offset, int length);
    081    }
    082
    083    static final int DEFAULT_SIZE = 1024;
    084
    085    /** The list of buffers, which grows and never reduces. */
    086    private final List<byte[]> buffers = new ArrayList<>();
    087
    088    /** The total count of bytes written. */
    089    protected int count;
    090
    091    /** The current buffer. */
    092    private byte[] currentBuffer;
    093
    094    /** The index of the current buffer. */
    095    private int currentBufferIndex;
    096
    097    /** The total count of bytes in all the filled buffers. */
    098    private int filledBufferSum;
    099
    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
    144            currentBufferIndex++;
    145            currentBuffer = buffers.get(currentBufferIndex);
    146        } else {
    147            // Creating new buffer
    148            final int newBufferSize;
    149            if (currentBuffer == null) {
    150                newBufferSize = newCount;
    151                filledBufferSum = 0;
    152            } else {
    153                newBufferSize = Math.max(currentBuffer.length << 1, newCount - filledBufferSum);
    154                filledBufferSum += currentBuffer.length;
    155            }
    156
    157            currentBufferIndex++;
    158            currentBuffer = IOUtils.byteArray(newBufferSize);
    159            buffers.add(currentBuffer);
    160        }
    161    }
    162
    163    /**
    164     * See {@link ByteArrayOutputStream#reset()}.
    165     *
    166     * @see ByteArrayOutputStream#reset()
    167     */
    168    public abstract void reset();
    169
    170    /**
    171     * Implements a default reset behavior.
    172     *
    173     * @see ByteArrayOutputStream#reset()
    174     */
    175    protected void resetImpl() {
    176        count = 0;
    177        filledBufferSum = 0;
    178        currentBufferIndex = 0;
    179        if (reuseBuffers) {
    180            currentBuffer = buffers.get(currentBufferIndex);
    181        } else {
    182            //Throw away old buffers
    183            currentBuffer = null;
    184            final int size = buffers.get(0).length;
    185            buffers.clear();
    186            needNewBuffer(size);
    187            reuseBuffers = true;
    188        }
    189    }
    190
    191    /**
    192     * Returns the current size of the byte array.
    193     *
    194     * @return the current size of the byte array
    195     */
    196    public abstract int size();
    197
    198    /**
    199     * Gets the current contents of this byte stream as a byte array.
    200     * The result is independent of this stream.
    201     *
    202     * @return the current contents of this output stream, as a byte array
    203     * @see java.io.ByteArrayOutputStream#toByteArray()
    204     */
    205    public abstract byte[] toByteArray();
    206
    207    /**
    208     * Gets the current contents of this byte stream as a byte array.
    209     * The result is independent of this stream.
    210     *
    211     * @return the current contents of this output stream, as a byte array
    212     * @see java.io.ByteArrayOutputStream#toByteArray()
    213     */
    214    protected byte[] toByteArrayImpl() {
    215        int remaining = count;
    216        if (remaining == 0) {
    217            return IOUtils.EMPTY_BYTE_ARRAY;
    218        }
    219        final byte[] newBuf = IOUtils.byteArray(remaining);
    220        int pos = 0;
    221        for (final byte[] buf : buffers) {
    222            final int c = Math.min(buf.length, remaining);
    223            System.arraycopy(buf, 0, newBuf, pos, c);
    224            pos += c;
    225            remaining -= c;
    226            if (remaining == 0) {
    227                break;
    228            }
    229        }
    230        return newBuf;
    231    }
    232
    233    /**
    234     * Gets the current contents of this byte stream as an Input Stream. The
    235     * returned stream is backed by buffers of {@code this} stream,
    236     * avoiding memory allocation and copy, thus saving space and time.<br>
    237     *
    238     * @return the current contents of this output stream.
    239     * @see java.io.ByteArrayOutputStream#toByteArray()
    240     * @see #reset()
    241     * @since 2.5
    242     */
    243    public abstract InputStream toInputStream();
    244
    245    /**
    246     * Gets the current contents of this byte stream as an Input Stream. The
    247     * returned stream is backed by buffers of {@code this} stream,
    248     * avoiding memory allocation and copy, thus saving space and time.<br>
    249     *
    250     * @param <T> the type of the InputStream which makes up
    251     *            the {@link SequenceInputStream}.
    252     * @param isConstructor A constructor for an InputStream which makes
    253     *                     up the {@link SequenceInputStream}.
    254     *
    255     * @return the current contents of this output stream.
    256     * @see java.io.ByteArrayOutputStream#toByteArray()
    257     * @see #reset()
    258     * @since 2.7
    259     */
    260    @SuppressWarnings("resource") // The result InputStream MUST be managed by the call site.
    261    protected <T extends InputStream> InputStream toInputStream(final InputStreamConstructor<T> isConstructor) {
    262        int remaining = count;
    263        if (remaining == 0) {
    264            return ClosedInputStream.INSTANCE;
    265        }
    266        final List<T> list = new ArrayList<>(buffers.size());
    267        for (final byte[] buf : buffers) {
    268            final int c = Math.min(buf.length, remaining);
    269            list.add(isConstructor.construct(buf, 0, c));
    270            remaining -= c;
    271            if (remaining == 0) {
    272                break;
    273            }
    274        }
    275        reuseBuffers = false;
    276        return new SequenceInputStream(Collections.enumeration(list));
    277    }
    278
    279    /**
    280     * Gets the current contents of this byte stream as a string using the virtual machine's {@link Charset#defaultCharset() default charset}.
    281     *
    282     * @return the contents of the byte array as a String
    283     * @see java.io.ByteArrayOutputStream#toString()
    284     * @see Charset#defaultCharset()
    285     * @deprecated Use {@link #toString(String)} instead
    286     */
    287    @Override
    288    @Deprecated
    289    public String toString() {
    290        // make explicit the use of the default charset
    291        return new String(toByteArray(), Charset.defaultCharset());
    292    }
    293
    294    /**
    295     * Gets the current contents of this byte stream as a string
    296     * using the specified encoding.
    297     *
    298     * @param charset  the character encoding
    299     * @return the string converted from the byte array
    300     * @see java.io.ByteArrayOutputStream#toString(String)
    301     * @since 2.5
    302     */
    303    public String toString(final Charset charset) {
    304        return new String(toByteArray(), charset);
    305    }
    306
    307    /**
    308     * Gets the current contents of this byte stream as a string
    309     * using the specified encoding.
    310     *
    311     * @param enc  the name of the character encoding
    312     * @return the string converted from the byte array
    313     * @throws UnsupportedEncodingException if the encoding is not supported
    314     * @see java.io.ByteArrayOutputStream#toString(String)
    315     */
    316    public String toString(final String enc) throws UnsupportedEncodingException {
    317        return new String(toByteArray(), enc);
    318    }
    319
    320    /**
    321     * 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)}.
    322     *
    323     * @param b the data.
    324     * @see #write(byte[], int, int)
    325     * @since 2.19.0
    326     */
    327    @Override
    328    public void write(final byte b[]) {
    329        write(b, 0, b.length);
    330    }
    331
    332    @Override
    333    public abstract void write(byte[] b, int off, int len);
    334
    335    /**
    336     * Writes the bytes for given CharSequence encoded using a Charset.
    337     *
    338     * @param data    The String to convert to bytes. not null.
    339     * @param charset The {@link Charset} o encode the {@code String}, null means the default encoding.
    340     * @return this instance.
    341     * @since 2.19.0
    342     */
    343    public T write(final CharSequence data, final Charset charset) {
    344        write(data.toString().getBytes(Charsets.toCharset(charset)));
    345        return asThis();
    346    }
    347
    348    /**
    349     * Writes the entire contents of the specified input stream to this
    350     * byte stream. Bytes from the input stream are read directly into the
    351     * internal buffer of this stream.
    352     *
    353     * @param in the input stream to read from
    354     * @return total number of bytes read from the input stream
    355     *         (and written to this stream)
    356     * @throws IOException if an I/O error occurs while reading the input stream
    357     * @since 1.4
    358     */
    359    public abstract int write(InputStream in) throws IOException;
    360
    361    @Override
    362    public abstract void write(int b);
    363
    364    /**
    365     * Writes the bytes to the byte array.
    366     * @param b the bytes to write
    367     * @param off The start offset
    368     * @param len The number of bytes to write
    369     */
    370    protected void writeImpl(final byte[] b, final int off, final int len) {
    371        final int newCount = count + len;
    372        int remaining = len;
    373        int inBufferPos = count - filledBufferSum;
    374        while (remaining > 0) {
    375            final int part = Math.min(remaining, currentBuffer.length - inBufferPos);
    376            System.arraycopy(b, off + len - remaining, currentBuffer, inBufferPos, part);
    377            remaining -= part;
    378            if (remaining > 0) {
    379                needNewBuffer(newCount);
    380                inBufferPos = 0;
    381            }
    382        }
    383        count = newCount;
    384    }
    385
    386    /**
    387     * Writes the entire contents of the specified input stream to this
    388     * byte stream. Bytes from the input stream are read directly into the
    389     * internal buffer of this stream.
    390     *
    391     * @param in the input stream to read from
    392     * @return total number of bytes read from the input stream
    393     *         (and written to this stream)
    394     * @throws IOException if an I/O error occurs while reading the input stream
    395     * @since 2.7
    396     */
    397    protected int writeImpl(final InputStream in) throws IOException {
    398        int readCount = 0;
    399        int inBufferPos = count - filledBufferSum;
    400        int n = in.read(currentBuffer, inBufferPos, currentBuffer.length - inBufferPos);
    401        while (n != EOF) {
    402            readCount += n;
    403            inBufferPos += n;
    404            count += n;
    405            if (inBufferPos == currentBuffer.length) {
    406                needNewBuffer(currentBuffer.length);
    407                inBufferPos = 0;
    408            }
    409            n = in.read(currentBuffer, inBufferPos, currentBuffer.length - inBufferPos);
    410        }
    411        return readCount;
    412    }
    413
    414    /**
    415     * Write a byte to byte array.
    416     * @param b the byte to write
    417     */
    418    protected void writeImpl(final int b) {
    419        int inBufferPos = count - filledBufferSum;
    420        if (inBufferPos == currentBuffer.length) {
    421            needNewBuffer(count + 1);
    422            inBufferPos = 0;
    423        }
    424        currentBuffer[inBufferPos] = (byte) b;
    425        count++;
    426    }
    427
    428    /**
    429     * Writes the entire contents of this byte stream to the
    430     * specified output stream.
    431     *
    432     * @param out  the output stream to write to
    433     * @throws IOException if an I/O error occurs, such as if the stream is closed
    434     * @see java.io.ByteArrayOutputStream#writeTo(OutputStream)
    435     */
    436    public abstract void writeTo(OutputStream out) throws IOException;
    437
    438    /**
    439     * Writes the entire contents of this byte stream to the
    440     * specified output stream.
    441     *
    442     * @param out  the output stream to write to
    443     * @throws IOException if an I/O error occurs, such as if the stream is closed
    444     * @see java.io.ByteArrayOutputStream#writeTo(OutputStream)
    445     */
    446    protected void writeToImpl(final OutputStream out) throws IOException {
    447        int remaining = count;
    448        for (final byte[] buf : buffers) {
    449            final int c = Math.min(buf.length, remaining);
    450            out.write(buf, 0, c);
    451            remaining -= c;
    452            if (remaining == 0) {
    453                break;
    454            }
    455        }
    456    }
    457
    458}