AbstractByteArrayOutputStream.java

  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. import static org.apache.commons.io.IOUtils.EOF;

  19. import java.io.IOException;
  20. import java.io.InputStream;
  21. import java.io.OutputStream;
  22. import java.io.SequenceInputStream;
  23. import java.io.UnsupportedEncodingException;
  24. import java.nio.charset.Charset;
  25. import java.util.ArrayList;
  26. import java.util.Collections;
  27. import java.util.List;

  28. import org.apache.commons.io.Charsets;
  29. import org.apache.commons.io.IOUtils;
  30. import org.apache.commons.io.input.ClosedInputStream;

  31. /**
  32.  * This is the base class for implementing an output stream in which the data
  33.  * is written into a byte array. The buffer automatically grows as data
  34.  * is written to it.
  35.  * <p>
  36.  * The data can be retrieved using {@code toByteArray()} and
  37.  * {@code toString()}.
  38.  * Closing an {@link AbstractByteArrayOutputStream} has no effect. The methods in
  39.  * this class can be called after the stream has been closed without
  40.  * generating an {@link IOException}.
  41.  * </p>
  42.  * <p>
  43.  * This is the base for an alternative implementation of the
  44.  * {@link java.io.ByteArrayOutputStream} class. The original implementation
  45.  * only allocates 32 bytes at the beginning. As this class is designed for
  46.  * heavy duty it starts at {@value #DEFAULT_SIZE} bytes. In contrast to the original it doesn't
  47.  * reallocate the whole memory block but allocates additional buffers. This
  48.  * way no buffers need to be garbage collected and the contents don't have
  49.  * to be copied to the new buffer. This class is designed to behave exactly
  50.  * like the original. The only exception is the deprecated
  51.  * {@link java.io.ByteArrayOutputStream#toString(int)} method that has been
  52.  * ignored.
  53.  * </p>
  54.  *
  55.  * @param <T> The AbstractByteArrayOutputStream subclass
  56.  * @since 2.7
  57.  */
  58. public abstract class AbstractByteArrayOutputStream<T extends AbstractByteArrayOutputStream<T>> extends OutputStream {

  59.     /**
  60.      * Constructor for an InputStream subclass.
  61.      *
  62.      * @param <T> the type of the InputStream.
  63.      */
  64.     @FunctionalInterface
  65.     protected interface InputStreamConstructor<T extends InputStream> {

  66.         /**
  67.          * Constructs an InputStream subclass.
  68.          *
  69.          * @param buffer the buffer
  70.          * @param offset the offset into the buffer
  71.          * @param length the length of the buffer
  72.          * @return the InputStream subclass.
  73.          */
  74.         T construct(byte[] buffer, int offset, int length);
  75.     }

  76.     static final int DEFAULT_SIZE = 1024;

  77.     /** The list of buffers, which grows and never reduces. */
  78.     private final List<byte[]> buffers = new ArrayList<>();

  79.     /** The total count of bytes written. */
  80.     protected int count;

  81.     /** The current buffer. */
  82.     private byte[] currentBuffer;

  83.     /** The index of the current buffer. */
  84.     private int currentBufferIndex;

  85.     /** The total count of bytes in all the filled buffers. */
  86.     private int filledBufferSum;

  87.     /** Flag to indicate if the buffers can be reused after reset */
  88.     private boolean reuseBuffers = true;

  89.     /**
  90.      * Constructs a new instance for subclasses.
  91.      */
  92.     public AbstractByteArrayOutputStream() {
  93.         // empty
  94.     }

  95.     /**
  96.      * Returns this instance typed to {@code T}.
  97.      *
  98.      * @return this instance
  99.      */
  100.     @SuppressWarnings("unchecked")
  101.     protected T asThis() {
  102.         return (T) this;
  103.     }

  104.     /**
  105.      * Does nothing.
  106.      *
  107.      * The methods in this class can be called after the stream has been closed without generating an {@link IOException}.
  108.      *
  109.      * @throws IOException never (this method should not declare this exception but it has to now due to backwards
  110.      *         compatibility)
  111.      */
  112.     @Override
  113.     public void close() throws IOException {
  114.         //nop
  115.     }

  116.     /**
  117.      * Makes a new buffer available either by allocating
  118.      * a new one or re-cycling an existing one.
  119.      *
  120.      * @param newCount  the size of the buffer if one is created
  121.      */
  122.     protected void needNewBuffer(final int newCount) {
  123.         if (currentBufferIndex < buffers.size() - 1) {
  124.             // Recycling old buffer
  125.             filledBufferSum += currentBuffer.length;

  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.             currentBufferIndex++;
  139.             currentBuffer = IOUtils.byteArray(newBufferSize);
  140.             buffers.add(currentBuffer);
  141.         }
  142.     }

  143.     /**
  144.      * See {@link ByteArrayOutputStream#reset()}.
  145.      *
  146.      * @see ByteArrayOutputStream#reset()
  147.      */
  148.     public abstract void reset();

  149.     /**
  150.      * Implements a default reset behavior.
  151.      *
  152.      * @see ByteArrayOutputStream#reset()
  153.      */
  154.     protected void resetImpl() {
  155.         count = 0;
  156.         filledBufferSum = 0;
  157.         currentBufferIndex = 0;
  158.         if (reuseBuffers) {
  159.             currentBuffer = buffers.get(currentBufferIndex);
  160.         } else {
  161.             //Throw away old buffers
  162.             currentBuffer = null;
  163.             final int size = buffers.get(0).length;
  164.             buffers.clear();
  165.             needNewBuffer(size);
  166.             reuseBuffers = true;
  167.         }
  168.     }

  169.     /**
  170.      * Returns the current size of the byte array.
  171.      *
  172.      * @return the current size of the byte array
  173.      */
  174.     public abstract int size();

  175.     /**
  176.      * Gets the current contents of this byte stream as a byte array.
  177.      * The result is independent of this stream.
  178.      *
  179.      * @return the current contents of this output stream, as a byte array
  180.      * @see java.io.ByteArrayOutputStream#toByteArray()
  181.      */
  182.     public abstract byte[] toByteArray();

  183.     /**
  184.      * Gets the current contents of this byte stream as a byte array.
  185.      * The result is independent of this stream.
  186.      *
  187.      * @return the current contents of this output stream, as a byte array
  188.      * @see java.io.ByteArrayOutputStream#toByteArray()
  189.      */
  190.     protected byte[] toByteArrayImpl() {
  191.         int remaining = count;
  192.         if (remaining == 0) {
  193.             return IOUtils.EMPTY_BYTE_ARRAY;
  194.         }
  195.         final byte[] newBuf = IOUtils.byteArray(remaining);
  196.         int pos = 0;
  197.         for (final byte[] buf : buffers) {
  198.             final int c = Math.min(buf.length, remaining);
  199.             System.arraycopy(buf, 0, newBuf, pos, c);
  200.             pos += c;
  201.             remaining -= c;
  202.             if (remaining == 0) {
  203.                 break;
  204.             }
  205.         }
  206.         return newBuf;
  207.     }

  208.     /**
  209.      * Gets the current contents of this byte stream as an Input Stream. The
  210.      * returned stream is backed by buffers of {@code this} stream,
  211.      * avoiding memory allocation and copy, thus saving space and time.<br>
  212.      *
  213.      * @return the current contents of this output stream.
  214.      * @see java.io.ByteArrayOutputStream#toByteArray()
  215.      * @see #reset()
  216.      * @since 2.5
  217.      */
  218.     public abstract InputStream toInputStream();

  219.     /**
  220.      * Gets the current contents of this byte stream as an Input Stream. The
  221.      * returned stream is backed by buffers of {@code this} stream,
  222.      * avoiding memory allocation and copy, thus saving space and time.<br>
  223.      *
  224.      * @param <T> the type of the InputStream which makes up
  225.      *            the {@link SequenceInputStream}.
  226.      * @param isConstructor A constructor for an InputStream which makes
  227.      *                     up the {@link SequenceInputStream}.
  228.      *
  229.      * @return the current contents of this output stream.
  230.      * @see java.io.ByteArrayOutputStream#toByteArray()
  231.      * @see #reset()
  232.      * @since 2.7
  233.      */
  234.     @SuppressWarnings("resource") // The result InputStream MUST be managed by the call site.
  235.     protected <T extends InputStream> InputStream toInputStream(final InputStreamConstructor<T> isConstructor) {
  236.         int remaining = count;
  237.         if (remaining == 0) {
  238.             return ClosedInputStream.INSTANCE;
  239.         }
  240.         final List<T> list = new ArrayList<>(buffers.size());
  241.         for (final byte[] buf : buffers) {
  242.             final int c = Math.min(buf.length, remaining);
  243.             list.add(isConstructor.construct(buf, 0, c));
  244.             remaining -= c;
  245.             if (remaining == 0) {
  246.                 break;
  247.             }
  248.         }
  249.         reuseBuffers = false;
  250.         return new SequenceInputStream(Collections.enumeration(list));
  251.     }

  252.     /**
  253.      * Gets the current contents of this byte stream as a string using the virtual machine's {@link Charset#defaultCharset() default charset}.
  254.      *
  255.      * @return the contents of the byte array as a String
  256.      * @see java.io.ByteArrayOutputStream#toString()
  257.      * @see Charset#defaultCharset()
  258.      * @deprecated Use {@link #toString(String)} instead
  259.      */
  260.     @Override
  261.     @Deprecated
  262.     public String toString() {
  263.         // make explicit the use of the default charset
  264.         return new String(toByteArray(), Charset.defaultCharset());
  265.     }

  266.     /**
  267.      * Gets the current contents of this byte stream as a string
  268.      * using the specified encoding.
  269.      *
  270.      * @param charset  the character encoding
  271.      * @return the string converted from the byte array
  272.      * @see java.io.ByteArrayOutputStream#toString(String)
  273.      * @since 2.5
  274.      */
  275.     public String toString(final Charset charset) {
  276.         return new String(toByteArray(), charset);
  277.     }

  278.     /**
  279.      * Gets the current contents of this byte stream as a string
  280.      * using the specified encoding.
  281.      *
  282.      * @param enc  the name of the character encoding
  283.      * @return the string converted from the byte array
  284.      * @throws UnsupportedEncodingException if the encoding is not supported
  285.      * @see java.io.ByteArrayOutputStream#toString(String)
  286.      */
  287.     public String toString(final String enc) throws UnsupportedEncodingException {
  288.         return new String(toByteArray(), enc);
  289.     }

  290.     /**
  291.      * 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)}.
  292.      *
  293.      * @param b the data.
  294.      * @see #write(byte[], int, int)
  295.      * @since 2.19.0
  296.      */
  297.     @Override
  298.     public void write(final byte b[]) {
  299.         write(b, 0, b.length);
  300.     }

  301.     @Override
  302.     public abstract void write(byte[] b, int off, int len);

  303.     /**
  304.      * Writes the bytes for given CharSequence encoded using a Charset.
  305.      *
  306.      * @param data    The String to convert to bytes. not null.
  307.      * @param charset The {@link Charset} o encode the {@code String}, null means the default encoding.
  308.      * @return this instance.
  309.      * @since 2.19.0
  310.      */
  311.     public T write(final CharSequence data, final Charset charset) {
  312.         write(data.toString().getBytes(Charsets.toCharset(charset)));
  313.         return asThis();
  314.     }

  315.     /**
  316.      * Writes the entire contents of the specified input stream to this
  317.      * byte stream. Bytes from the input stream are read directly into the
  318.      * internal buffer of this stream.
  319.      *
  320.      * @param in the input stream to read from
  321.      * @return total number of bytes read from the input stream
  322.      *         (and written to this stream)
  323.      * @throws IOException if an I/O error occurs while reading the input stream
  324.      * @since 1.4
  325.      */
  326.     public abstract int write(InputStream in) throws IOException;

  327.     @Override
  328.     public abstract void write(int b);

  329.     /**
  330.      * Writes the bytes to the byte array.
  331.      * @param b the bytes to write
  332.      * @param off The start offset
  333.      * @param len The number of bytes to write
  334.      */
  335.     protected void writeImpl(final byte[] b, final int off, final int len) {
  336.         final int newCount = count + len;
  337.         int remaining = len;
  338.         int inBufferPos = count - filledBufferSum;
  339.         while (remaining > 0) {
  340.             final int part = Math.min(remaining, currentBuffer.length - inBufferPos);
  341.             System.arraycopy(b, off + len - remaining, currentBuffer, inBufferPos, part);
  342.             remaining -= part;
  343.             if (remaining > 0) {
  344.                 needNewBuffer(newCount);
  345.                 inBufferPos = 0;
  346.             }
  347.         }
  348.         count = newCount;
  349.     }

  350.     /**
  351.      * Writes the entire contents of the specified input stream to this
  352.      * byte stream. Bytes from the input stream are read directly into the
  353.      * internal buffer of this stream.
  354.      *
  355.      * @param in the input stream to read from
  356.      * @return total number of bytes read from the input stream
  357.      *         (and written to this stream)
  358.      * @throws IOException if an I/O error occurs while reading the input stream
  359.      * @since 2.7
  360.      */
  361.     protected int writeImpl(final InputStream in) throws IOException {
  362.         int readCount = 0;
  363.         int inBufferPos = count - filledBufferSum;
  364.         int n = in.read(currentBuffer, inBufferPos, currentBuffer.length - inBufferPos);
  365.         while (n != EOF) {
  366.             readCount += n;
  367.             inBufferPos += n;
  368.             count += n;
  369.             if (inBufferPos == currentBuffer.length) {
  370.                 needNewBuffer(currentBuffer.length);
  371.                 inBufferPos = 0;
  372.             }
  373.             n = in.read(currentBuffer, inBufferPos, currentBuffer.length - inBufferPos);
  374.         }
  375.         return readCount;
  376.     }

  377.     /**
  378.      * Write a byte to byte array.
  379.      * @param b the byte to write
  380.      */
  381.     protected void writeImpl(final int b) {
  382.         int inBufferPos = count - filledBufferSum;
  383.         if (inBufferPos == currentBuffer.length) {
  384.             needNewBuffer(count + 1);
  385.             inBufferPos = 0;
  386.         }
  387.         currentBuffer[inBufferPos] = (byte) b;
  388.         count++;
  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.      */
  398.     public abstract void writeTo(OutputStream out) throws IOException;

  399.     /**
  400.      * Writes the entire contents of this byte stream to the
  401.      * specified output stream.
  402.      *
  403.      * @param out  the output stream to write to
  404.      * @throws IOException if an I/O error occurs, such as if the stream is closed
  405.      * @see java.io.ByteArrayOutputStream#writeTo(OutputStream)
  406.      */
  407.     protected void writeToImpl(final OutputStream out) throws IOException {
  408.         int remaining = count;
  409.         for (final byte[] buf : buffers) {
  410.             final int c = Math.min(buf.length, remaining);
  411.             out.write(buf, 0, c);
  412.             remaining -= c;
  413.             if (remaining == 0) {
  414.                 break;
  415.             }
  416.         }
  417.     }

  418. }