This is an automated email from the ASF dual-hosted git repository.

ggregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-io.git


The following commit(s) were added to refs/heads/master by this push:
     new c2c2709  Refactor ByteArrayOutputStream into synchronized and 
non-synchronized versions (#108)
c2c2709 is described below

commit c2c2709cf7a24498292e65d2e058d7b4e7da9edb
Author: Adam Retter <adam.ret...@googlemail.com>
AuthorDate: Wed Apr 8 02:15:54 2020 +0200

    Refactor ByteArrayOutputStream into synchronized and non-synchronized 
versions (#108)
    
    * Split ByteArrayOutputStream into synchronized and non-synchronized 
versions
    
    * Improve the test coverage of AbstractByteArrayOutputStream and sub-classes
    
    * Address review comments by @aherbert
    
    * Address review comments by @garydgregory
    
    * Address further review comments by @garydgregory
    
    * Remove </p> tags, breaks the javadoc build
    
    * Address review comments by @aherbert
    
    * Improve coverage of tests
---
 pom.xml                                            |   4 +
 ...eam.java => AbstractByteArrayOutputStream.java} | 235 ++++++++---------
 .../commons/io/output/ByteArrayOutputStream.java   | 292 ++-------------------
 .../UnsynchronizedByteArrayOutputStream.java       | 163 ++++++++++++
 .../io/output/ByteArrayOutputStreamTestCase.java   | 211 +++++++++++++--
 5 files changed, 482 insertions(+), 423 deletions(-)

diff --git a/pom.xml b/pom.xml
index 8e1c921..076916f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -209,6 +209,10 @@ file comparators, endian transformation classes, and much 
more.
       <email>alban.peignier at free.fr</email>
     </contributor>
     <contributor>
+      <name>Adam Retter</name>
+      <organization>Evolved Binary</organization>
+    </contributor>
+    <contributor>
       <name>Ian Springer</name>
     </contributor>
     <contributor>
diff --git 
a/src/main/java/org/apache/commons/io/output/ByteArrayOutputStream.java 
b/src/main/java/org/apache/commons/io/output/AbstractByteArrayOutputStream.java
similarity index 64%
copy from src/main/java/org/apache/commons/io/output/ByteArrayOutputStream.java
copy to 
src/main/java/org/apache/commons/io/output/AbstractByteArrayOutputStream.java
index c84721a..03d5127 100644
--- a/src/main/java/org/apache/commons/io/output/ByteArrayOutputStream.java
+++ 
b/src/main/java/org/apache/commons/io/output/AbstractByteArrayOutputStream.java
@@ -16,11 +16,11 @@
  */
 package org.apache.commons.io.output;
 
-import static org.apache.commons.io.IOUtils.EOF;
+import org.apache.commons.io.input.ClosedInputStream;
 
 import java.io.ByteArrayInputStream;
-import java.io.IOException;
 import java.io.InputStream;
+import java.io.IOException;
 import java.io.OutputStream;
 import java.io.SequenceInputStream;
 import java.io.UnsupportedEncodingException;
@@ -29,30 +29,35 @@ import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 
-import org.apache.commons.io.input.ClosedInputStream;
+import static org.apache.commons.io.IOUtils.EOF;
 
 /**
- * This class implements an output stream in which the data is
- * written into a byte array. The buffer automatically grows as data
+ * This is the base class for implementing an output stream in which the data
+ * is written into a byte array. The buffer automatically grows as data
  * is written to it.
  * <p>
  * The data can be retrieved using <code>toByteArray()</code> and
  * <code>toString()</code>.
- * <p>
- * Closing a {@code ByteArrayOutputStream} has no effect. The methods in
+ * Closing an {@code AbstractByteArrayOutputStream} has no effect. The methods 
in
  * this class can be called after the stream has been closed without
  * generating an {@code IOException}.
+ * </p>
  * <p>
- * This is an alternative implementation of the {@link 
java.io.ByteArrayOutputStream}
- * class. The original implementation only allocates 32 bytes at the beginning.
- * As this class is designed for heavy duty it starts at 1024 bytes. In 
contrast
- * to the original it doesn't reallocate the whole memory block but allocates
- * additional buffers. This way no buffers need to be garbage collected and
- * the contents don't have to be copied to the new buffer. This class is
- * designed to behave exactly like the original. The only exception is the
- * deprecated toString(int) method that has been ignored.
+ * This is the base for an alternative implementation of the
+ * {@link java.io.ByteArrayOutputStream} class. The original implementation
+ * only allocates 32 bytes at the beginning. As this class is designed for
+ * heavy duty it starts at 1024 bytes. In contrast to the original it doesn't
+ * reallocate the whole memory block but allocates additional buffers. This
+ * way no buffers need to be garbage collected and the contents don't have
+ * to be copied to the new buffer. This class is designed to behave exactly
+ * like the original. The only exception is the deprecated
+ * {@link java.io.ByteArrayOutputStream#toString(int)} method that has been
+ * ignored.
+ * </p>
+ *
+ * @since 2.7
  */
-public class ByteArrayOutputStream extends OutputStream {
+public abstract class AbstractByteArrayOutputStream extends OutputStream {
 
     static final int DEFAULT_SIZE = 1024;
 
@@ -68,42 +73,17 @@ public class ByteArrayOutputStream extends OutputStream {
     /** The current buffer. */
     private byte[] currentBuffer;
     /** The total count of bytes written. */
-    private int count;
+    protected int count;
     /** Flag to indicate if the buffers can be reused after reset */
     private boolean reuseBuffers = true;
 
     /**
-     * Creates a new byte array output stream. The buffer capacity is
-     * initially 1024 bytes, though its size increases if necessary.
-     */
-    public ByteArrayOutputStream() {
-        this(DEFAULT_SIZE);
-    }
-
-    /**
-     * Creates a new byte array output stream, with a buffer capacity of
-     * the specified size, in bytes.
-     *
-     * @param size  the initial size
-     * @throws IllegalArgumentException if size is negative
-     */
-    public ByteArrayOutputStream(final int size) {
-        if (size < 0) {
-            throw new IllegalArgumentException(
-                "Negative initial size: " + size);
-        }
-        synchronized (this) {
-            needNewBuffer(size);
-        }
-    }
-
-    /**
      * Makes a new buffer available either by allocating
      * a new one or re-cycling an existing one.
      *
      * @param newcount  the size of the buffer if one is created
      */
-    private void needNewBuffer(final int newcount) {
+    protected void needNewBuffer(final int newcount) {
         if (currentBufferIndex < buffers.size() - 1) {
             //Recycling old buffer
             filledBufferSum += currentBuffer.length;
@@ -130,37 +110,34 @@ public class ByteArrayOutputStream extends OutputStream {
     }
 
     /**
-     * Write the bytes to byte array.
+     * Writes the bytes to the byte array.
      * @param b the bytes to write
      * @param off The start offset
      * @param len The number of bytes to write
      */
     @Override
-    public void write(final byte[] b, final int off, final int len) {
-        if ((off < 0)
-                || (off > b.length)
-                || (len < 0)
-                || ((off + len) > b.length)
-                || ((off + len) < 0)) {
-            throw new IndexOutOfBoundsException();
-        } else if (len == 0) {
-            return;
-        }
-        synchronized (this) {
-            final int newcount = count + len;
-            int remaining = len;
-            int inBufferPos = count - filledBufferSum;
-            while (remaining > 0) {
-                final int part = Math.min(remaining, currentBuffer.length - 
inBufferPos);
-                System.arraycopy(b, off + len - remaining, currentBuffer, 
inBufferPos, part);
-                remaining -= part;
-                if (remaining > 0) {
-                    needNewBuffer(newcount);
-                    inBufferPos = 0;
-                }
+    public abstract void write(final byte[] b, final int off, final int len);
+
+    /**
+     * Writes the bytes to the byte array.
+     * @param b the bytes to write
+     * @param off The start offset
+     * @param len The number of bytes to write
+     */
+    protected void writeImpl(final byte[] b, final int off, final int len) {
+        final int newcount = count + len;
+        int remaining = len;
+        int inBufferPos = count - filledBufferSum;
+        while (remaining > 0) {
+            final int part = Math.min(remaining, currentBuffer.length - 
inBufferPos);
+            System.arraycopy(b, off + len - remaining, currentBuffer, 
inBufferPos, part);
+            remaining -= part;
+            if (remaining > 0) {
+                needNewBuffer(newcount);
+                inBufferPos = 0;
             }
-            count = newcount;
         }
+        count = newcount;
     }
 
     /**
@@ -168,7 +145,13 @@ public class ByteArrayOutputStream extends OutputStream {
      * @param b the byte to write
      */
     @Override
-    public synchronized void write(final int b) {
+    public abstract void write(final int b);
+
+    /**
+     * Write a byte to byte array.
+     * @param b the byte to write
+     */
+    protected void writeImpl(final int b) {
         int inBufferPos = count - filledBufferSum;
         if (inBufferPos == currentBuffer.length) {
             needNewBuffer(count + 1);
@@ -178,6 +161,7 @@ public class ByteArrayOutputStream extends OutputStream {
         count++;
     }
 
+
     /**
      * Writes the entire contents of the specified input stream to this
      * byte stream. Bytes from the input stream are read directly into the
@@ -189,7 +173,20 @@ public class ByteArrayOutputStream extends OutputStream {
      * @throws IOException if an I/O error occurs while reading the input 
stream
      * @since 1.4
      */
-    public synchronized int write(final InputStream in) throws IOException {
+    public abstract int write(final InputStream in) throws IOException;
+
+    /**
+     * Writes the entire contents of the specified input stream to this
+     * byte stream. Bytes from the input stream are read directly into the
+     * internal buffers of this streams.
+     *
+     * @param in the input stream to read from
+     * @return total number of bytes read from the input stream
+     *         (and written to this stream)
+     * @throws IOException if an I/O error occurs while reading the input 
stream
+     * @since 2.7
+     */
+    protected int writeImpl(final InputStream in) throws IOException {
         int readCount = 0;
         int inBufferPos = count - filledBufferSum;
         int n = in.read(currentBuffer, inBufferPos, currentBuffer.length - 
inBufferPos);
@@ -207,12 +204,11 @@ public class ByteArrayOutputStream extends OutputStream {
     }
 
     /**
-     * Return the current size of the byte array.
+     * Returns the current size of the byte array.
+     *
      * @return the current size of the byte array
      */
-    public synchronized int size() {
-        return count;
-    }
+    public abstract int size();
 
     /**
      * Closing a {@code ByteArrayOutputStream} has no effect. The methods in
@@ -230,7 +226,12 @@ public class ByteArrayOutputStream extends OutputStream {
     /**
      * @see java.io.ByteArrayOutputStream#reset()
      */
-    public synchronized void reset() {
+    public abstract void reset();
+
+    /**
+     * @see java.io.ByteArrayOutputStream#reset()
+     */
+    protected void resetImpl() {
         count = 0;
         filledBufferSum = 0;
         currentBufferIndex = 0;
@@ -254,7 +255,17 @@ public class ByteArrayOutputStream extends OutputStream {
      * @throws IOException if an I/O error occurs, such as if the stream is 
closed
      * @see java.io.ByteArrayOutputStream#writeTo(OutputStream)
      */
-    public synchronized void writeTo(final OutputStream out) throws 
IOException {
+    public abstract void writeTo(final OutputStream out) throws IOException;
+
+    /**
+     * Writes the entire contents of this byte stream to the
+     * specified output stream.
+     *
+     * @param out  the output stream to write to
+     * @throws IOException if an I/O error occurs, such as if the stream is 
closed
+     * @see java.io.ByteArrayOutputStream#writeTo(OutputStream)
+     */
+    protected void writeToImpl(final OutputStream out) throws IOException {
         int remaining = count;
         for (final byte[] buf : buffers) {
             final int c = Math.min(buf.length, remaining);
@@ -267,61 +278,16 @@ public class ByteArrayOutputStream extends OutputStream {
     }
 
     /**
-     * Fetches entire contents of an <code>InputStream</code> and represent
-     * same data as result InputStream.
-     * <p>
-     * This method is useful where,
-     * <ul>
-     * <li>Source InputStream is slow.</li>
-     * <li>It has network resources associated, so we cannot keep it open for
-     * long time.</li>
-     * <li>It has network timeout associated.</li>
-     * </ul>
-     * It can be used in favor of {@link #toByteArray()}, since it
-     * avoids unnecessary allocation and copy of byte[].<br>
-     * This method buffers the input internally, so there is no need to use a
-     * <code>BufferedInputStream</code>.
-     *
-     * @param input Stream to be fully buffered.
-     * @return A fully buffered stream.
-     * @throws IOException if an I/O error occurs
-     * @since 2.0
-     */
-    public static InputStream toBufferedInputStream(final InputStream input)
-            throws IOException {
-        return toBufferedInputStream(input, 1024);
-    }
-
-    /**
-     * Fetches entire contents of an <code>InputStream</code> and represent
-     * same data as result InputStream.
-     * <p>
-     * This method is useful where,
-     * <ul>
-     * <li>Source InputStream is slow.</li>
-     * <li>It has network resources associated, so we cannot keep it open for
-     * long time.</li>
-     * <li>It has network timeout associated.</li>
-     * </ul>
-     * It can be used in favor of {@link #toByteArray()}, since it
-     * avoids unnecessary allocation and copy of byte[].<br>
-     * This method buffers the input internally, so there is no need to use a
-     * <code>BufferedInputStream</code>.
+     * Gets the current contents of this byte stream as a Input Stream. The
+     * returned stream is backed by buffers of <code>this</code> stream,
+     * avoiding memory allocation and copy, thus saving space and time.<br>
      *
-     * @param input Stream to be fully buffered.
-     * @param size the initial buffer size
-     * @return A fully buffered stream.
-     * @throws IOException if an I/O error occurs
+     * @return the current contents of this output stream.
+     * @see java.io.ByteArrayOutputStream#toByteArray()
+     * @see #reset()
      * @since 2.5
      */
-    public static InputStream toBufferedInputStream(final InputStream input, 
final int size)
-            throws IOException {
-        // It does not matter if a ByteArrayOutputStream is not closed as 
close() is a no-op
-        @SuppressWarnings("resource")
-        final ByteArrayOutputStream output = new ByteArrayOutputStream(size);
-        output.write(input);
-        return output.toInputStream();
-    }
+    public abstract InputStream toInputStream();
 
     /**
      * Gets the current contents of this byte stream as a Input Stream. The
@@ -331,9 +297,9 @@ public class ByteArrayOutputStream extends OutputStream {
      * @return the current contents of this output stream.
      * @see java.io.ByteArrayOutputStream#toByteArray()
      * @see #reset()
-     * @since 2.5
+     * @since 2.7
      */
-    public synchronized InputStream toInputStream() {
+    protected InputStream toInputStreamImpl() {
         int remaining = count;
         if (remaining == 0) {
             return new ClosedInputStream();
@@ -358,7 +324,16 @@ public class ByteArrayOutputStream extends OutputStream {
      * @return the current contents of this output stream, as a byte array
      * @see java.io.ByteArrayOutputStream#toByteArray()
      */
-    public synchronized byte[] toByteArray() {
+    public abstract byte[] toByteArray();
+
+    /**
+     * Gets the current contents of this byte stream as a byte array.
+     * The result is independent of this stream.
+     *
+     * @return the current contents of this output stream, as a byte array
+     * @see java.io.ByteArrayOutputStream#toByteArray()
+     */
+    protected byte[] toByteArrayImpl() {
         int remaining = count;
         if (remaining == 0) {
             return EMPTY_BYTE_ARRAY;
diff --git 
a/src/main/java/org/apache/commons/io/output/ByteArrayOutputStream.java 
b/src/main/java/org/apache/commons/io/output/ByteArrayOutputStream.java
index c84721a..ecd47f3 100644
--- a/src/main/java/org/apache/commons/io/output/ByteArrayOutputStream.java
+++ b/src/main/java/org/apache/commons/io/output/ByteArrayOutputStream.java
@@ -16,61 +16,17 @@
  */
 package org.apache.commons.io.output;
 
-import static org.apache.commons.io.IOUtils.EOF;
-
-import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
-import java.io.SequenceInputStream;
-import java.io.UnsupportedEncodingException;
-import java.nio.charset.Charset;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-import org.apache.commons.io.input.ClosedInputStream;
 
 /**
- * This class implements an output stream in which the data is
- * written into a byte array. The buffer automatically grows as data
- * is written to it.
- * <p>
- * The data can be retrieved using <code>toByteArray()</code> and
- * <code>toString()</code>.
- * <p>
- * Closing a {@code ByteArrayOutputStream} has no effect. The methods in
- * this class can be called after the stream has been closed without
- * generating an {@code IOException}.
- * <p>
- * This is an alternative implementation of the {@link 
java.io.ByteArrayOutputStream}
- * class. The original implementation only allocates 32 bytes at the beginning.
- * As this class is designed for heavy duty it starts at 1024 bytes. In 
contrast
- * to the original it doesn't reallocate the whole memory block but allocates
- * additional buffers. This way no buffers need to be garbage collected and
- * the contents don't have to be copied to the new buffer. This class is
- * designed to behave exactly like the original. The only exception is the
- * deprecated toString(int) method that has been ignored.
+ * Implements a ThreadSafe version of
+ * {@link AbstractByteArrayOutputStream} using instance
+ * synchronisation.
  */
-public class ByteArrayOutputStream extends OutputStream {
-
-    static final int DEFAULT_SIZE = 1024;
-
-    /** A singleton empty byte array. */
-    private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
-
-    /** The list of buffers, which grows and never reduces. */
-    private final List<byte[]> buffers = new ArrayList<>();
-    /** The index of the current buffer. */
-    private int currentBufferIndex;
-    /** The total count of bytes in all the filled buffers. */
-    private int filledBufferSum;
-    /** The current buffer. */
-    private byte[] currentBuffer;
-    /** The total count of bytes written. */
-    private int count;
-    /** Flag to indicate if the buffers can be reused after reset */
-    private boolean reuseBuffers = true;
+//@ThreadSafe
+public class ByteArrayOutputStream extends AbstractByteArrayOutputStream {
 
     /**
      * Creates a new byte array output stream. The buffer capacity is
@@ -97,44 +53,6 @@ public class ByteArrayOutputStream extends OutputStream {
         }
     }
 
-    /**
-     * Makes a new buffer available either by allocating
-     * a new one or re-cycling an existing one.
-     *
-     * @param newcount  the size of the buffer if one is created
-     */
-    private void needNewBuffer(final int newcount) {
-        if (currentBufferIndex < buffers.size() - 1) {
-            //Recycling old buffer
-            filledBufferSum += currentBuffer.length;
-
-            currentBufferIndex++;
-            currentBuffer = buffers.get(currentBufferIndex);
-        } else {
-            //Creating new buffer
-            int newBufferSize;
-            if (currentBuffer == null) {
-                newBufferSize = newcount;
-                filledBufferSum = 0;
-            } else {
-                newBufferSize = Math.max(
-                    currentBuffer.length << 1,
-                    newcount - filledBufferSum);
-                filledBufferSum += currentBuffer.length;
-            }
-
-            currentBufferIndex++;
-            currentBuffer = new byte[newBufferSize];
-            buffers.add(currentBuffer);
-        }
-    }
-
-    /**
-     * Write the bytes to byte array.
-     * @param b the bytes to write
-     * @param off The start offset
-     * @param len The number of bytes to write
-     */
     @Override
     public void write(final byte[] b, final int off, final int len) {
         if ((off < 0)
@@ -147,123 +65,36 @@ public class ByteArrayOutputStream extends OutputStream {
             return;
         }
         synchronized (this) {
-            final int newcount = count + len;
-            int remaining = len;
-            int inBufferPos = count - filledBufferSum;
-            while (remaining > 0) {
-                final int part = Math.min(remaining, currentBuffer.length - 
inBufferPos);
-                System.arraycopy(b, off + len - remaining, currentBuffer, 
inBufferPos, part);
-                remaining -= part;
-                if (remaining > 0) {
-                    needNewBuffer(newcount);
-                    inBufferPos = 0;
-                }
-            }
-            count = newcount;
+            writeImpl(b, off, len);
         }
     }
 
-    /**
-     * Write a byte to byte array.
-     * @param b the byte to write
-     */
     @Override
     public synchronized void write(final int b) {
-        int inBufferPos = count - filledBufferSum;
-        if (inBufferPos == currentBuffer.length) {
-            needNewBuffer(count + 1);
-            inBufferPos = 0;
-        }
-        currentBuffer[inBufferPos] = (byte) b;
-        count++;
+        writeImpl(b);
     }
 
-    /**
-     * Writes the entire contents of the specified input stream to this
-     * byte stream. Bytes from the input stream are read directly into the
-     * internal buffers of this streams.
-     *
-     * @param in the input stream to read from
-     * @return total number of bytes read from the input stream
-     *         (and written to this stream)
-     * @throws IOException if an I/O error occurs while reading the input 
stream
-     * @since 1.4
-     */
+    @Override
     public synchronized int write(final InputStream in) throws IOException {
-        int readCount = 0;
-        int inBufferPos = count - filledBufferSum;
-        int n = in.read(currentBuffer, inBufferPos, currentBuffer.length - 
inBufferPos);
-        while (n != EOF) {
-            readCount += n;
-            inBufferPos += n;
-            count += n;
-            if (inBufferPos == currentBuffer.length) {
-                needNewBuffer(currentBuffer.length);
-                inBufferPos = 0;
-            }
-            n = in.read(currentBuffer, inBufferPos, currentBuffer.length - 
inBufferPos);
-        }
-        return readCount;
+        return writeImpl(in);
     }
 
-    /**
-     * Return the current size of the byte array.
-     * @return the current size of the byte array
-     */
+    @Override
     public synchronized int size() {
         return count;
     }
 
     /**
-     * Closing a {@code ByteArrayOutputStream} has no effect. The methods in
-     * this class can be called after the stream has been closed without
-     * generating an {@code IOException}.
-     *
-     * @throws IOException never (this method should not declare this exception
-     * but it has to now due to backwards compatibility)
-     */
-    @Override
-    public void close() throws IOException {
-        //nop
-    }
-
-    /**
      * @see java.io.ByteArrayOutputStream#reset()
      */
+    @Override
     public synchronized void reset() {
-        count = 0;
-        filledBufferSum = 0;
-        currentBufferIndex = 0;
-        if (reuseBuffers) {
-            currentBuffer = buffers.get(currentBufferIndex);
-        } else {
-            //Throw away old buffers
-            currentBuffer = null;
-            final int size = buffers.get(0).length;
-            buffers.clear();
-            needNewBuffer(size);
-            reuseBuffers = true;
-        }
+        resetImpl();
     }
 
-    /**
-     * Writes the entire contents of this byte stream to the
-     * specified output stream.
-     *
-     * @param out  the output stream to write to
-     * @throws IOException if an I/O error occurs, such as if the stream is 
closed
-     * @see java.io.ByteArrayOutputStream#writeTo(OutputStream)
-     */
+    @Override
     public synchronized void writeTo(final OutputStream out) throws 
IOException {
-        int remaining = count;
-        for (final byte[] buf : buffers) {
-            final int c = Math.min(buf.length, remaining);
-            out.write(buf, 0, c);
-            remaining -= c;
-            if (remaining == 0) {
-                break;
-            }
-        }
+        writeToImpl(out);
     }
 
     /**
@@ -271,6 +102,7 @@ public class ByteArrayOutputStream extends OutputStream {
      * same data as result InputStream.
      * <p>
      * This method is useful where,
+     * </p>
      * <ul>
      * <li>Source InputStream is slow.</li>
      * <li>It has network resources associated, so we cannot keep it open for
@@ -297,6 +129,7 @@ public class ByteArrayOutputStream extends OutputStream {
      * same data as result InputStream.
      * <p>
      * This method is useful where,
+     * </p>
      * <ul>
      * <li>Source InputStream is slow.</li>
      * <li>It has network resources associated, so we cannot keep it open for
@@ -323,98 +156,13 @@ public class ByteArrayOutputStream extends OutputStream {
         return output.toInputStream();
     }
 
-    /**
-     * Gets the current contents of this byte stream as a Input Stream. The
-     * returned stream is backed by buffers of <code>this</code> stream,
-     * avoiding memory allocation and copy, thus saving space and time.<br>
-     *
-     * @return the current contents of this output stream.
-     * @see java.io.ByteArrayOutputStream#toByteArray()
-     * @see #reset()
-     * @since 2.5
-     */
+    @Override
     public synchronized InputStream toInputStream() {
-        int remaining = count;
-        if (remaining == 0) {
-            return new ClosedInputStream();
-        }
-        final List<ByteArrayInputStream> list = new 
ArrayList<>(buffers.size());
-        for (final byte[] buf : buffers) {
-            final int c = Math.min(buf.length, remaining);
-            list.add(new ByteArrayInputStream(buf, 0, c));
-            remaining -= c;
-            if (remaining == 0) {
-                break;
-            }
-        }
-        reuseBuffers = false;
-        return new SequenceInputStream(Collections.enumeration(list));
+        return toInputStreamImpl();
     }
 
-    /**
-     * Gets the current contents of this byte stream as a byte array.
-     * The result is independent of this stream.
-     *
-     * @return the current contents of this output stream, as a byte array
-     * @see java.io.ByteArrayOutputStream#toByteArray()
-     */
-    public synchronized byte[] toByteArray() {
-        int remaining = count;
-        if (remaining == 0) {
-            return EMPTY_BYTE_ARRAY;
-        }
-        final byte newbuf[] = new byte[remaining];
-        int pos = 0;
-        for (final byte[] buf : buffers) {
-            final int c = Math.min(buf.length, remaining);
-            System.arraycopy(buf, 0, newbuf, pos, c);
-            pos += c;
-            remaining -= c;
-            if (remaining == 0) {
-                break;
-            }
-        }
-        return newbuf;
-    }
-
-    /**
-     * Gets the current contents of this byte stream as a string
-     * using the platform default charset.
-     * @return the contents of the byte array as a String
-     * @see java.io.ByteArrayOutputStream#toString()
-     * @deprecated 2.5 use {@link #toString(String)} instead
-     */
     @Override
-    @Deprecated
-    public String toString() {
-        // make explicit the use of the default charset
-        return new String(toByteArray(), Charset.defaultCharset());
-    }
-
-    /**
-     * Gets the current contents of this byte stream as a string
-     * using the specified encoding.
-     *
-     * @param enc  the name of the character encoding
-     * @return the string converted from the byte array
-     * @throws UnsupportedEncodingException if the encoding is not supported
-     * @see java.io.ByteArrayOutputStream#toString(String)
-     */
-    public String toString(final String enc) throws 
UnsupportedEncodingException {
-        return new String(toByteArray(), enc);
-    }
-
-    /**
-     * Gets the current contents of this byte stream as a string
-     * using the specified encoding.
-     *
-     * @param charset  the character encoding
-     * @return the string converted from the byte array
-     * @see java.io.ByteArrayOutputStream#toString(String)
-     * @since 2.5
-     */
-    public String toString(final Charset charset) {
-        return new String(toByteArray(), charset);
+    public synchronized byte[] toByteArray() {
+        return toByteArrayImpl();
     }
-
 }
diff --git 
a/src/main/java/org/apache/commons/io/output/UnsynchronizedByteArrayOutputStream.java
 
b/src/main/java/org/apache/commons/io/output/UnsynchronizedByteArrayOutputStream.java
new file mode 100644
index 0000000..d138ea6
--- /dev/null
+++ 
b/src/main/java/org/apache/commons/io/output/UnsynchronizedByteArrayOutputStream.java
@@ -0,0 +1,163 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.io.output;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * Implements a version of {@link AbstractByteArrayOutputStream}
+ * <b>without</b> any concurrent thread safety.
+ *
+ * @since 2.7
+ */
+//@NotThreadSafe
+public final class UnsynchronizedByteArrayOutputStream extends 
AbstractByteArrayOutputStream {
+
+    /**
+     * Creates a new byte array output stream. The buffer capacity is
+     * initially 1024 bytes, though its size increases if necessary.
+     */
+    public UnsynchronizedByteArrayOutputStream() {
+        this(DEFAULT_SIZE);
+    }
+
+    /**
+     * Creates a new byte array output stream, with a buffer capacity of
+     * the specified size, in bytes.
+     *
+     * @param size  the initial size
+     * @throws IllegalArgumentException if size is negative
+     */
+    public UnsynchronizedByteArrayOutputStream(final int size) {
+        if (size < 0) {
+            throw new IllegalArgumentException(
+                "Negative initial size: " + size);
+        }
+        needNewBuffer(size);
+    }
+
+    @Override
+    public void write(final byte[] b, final int off, final int len) {
+        if ((off < 0)
+                || (off > b.length)
+                || (len < 0)
+                || ((off + len) > b.length)
+                || ((off + len) < 0)) {
+            throw new IndexOutOfBoundsException();
+        } else if (len == 0) {
+            return;
+        }
+        writeImpl(b, off, len);
+    }
+
+    @Override
+    public void write(final int b) {
+        writeImpl(b);
+    }
+
+    @Override
+    public int write(final InputStream in) throws IOException {
+        return writeImpl(in);
+    }
+
+    @Override
+    public int size() {
+        return count;
+    }
+
+    /**
+     * @see java.io.ByteArrayOutputStream#reset()
+     */
+    @Override
+    public void reset() {
+        resetImpl();
+    }
+
+    @Override
+    public void writeTo(final OutputStream out) throws IOException {
+        writeToImpl(out);
+    }
+
+    /**
+     * Fetches entire contents of an <code>InputStream</code> and represent
+     * same data as result InputStream.
+     * <p>
+     * This method is useful where,
+     * </p>
+     * <ul>
+     * <li>Source InputStream is slow.</li>
+     * <li>It has network resources associated, so we cannot keep it open for
+     * long time.</li>
+     * <li>It has network timeout associated.</li>
+     * </ul>
+     * It can be used in favor of {@link #toByteArray()}, since it
+     * avoids unnecessary allocation and copy of byte[].<br>
+     * This method buffers the input internally, so there is no need to use a
+     * <code>BufferedInputStream</code>.
+     *
+     * @param input Stream to be fully buffered.
+     * @return A fully buffered stream.
+     * @throws IOException if an I/O error occurs
+     */
+    public static InputStream toBufferedInputStream(final InputStream input)
+            throws IOException {
+        return toBufferedInputStream(input, 1024);
+    }
+
+    /**
+     * Fetches entire contents of an <code>InputStream</code> and represent
+     * same data as result InputStream.
+     * <p>
+     * This method is useful where,
+     * </p>
+     * <ul>
+     * <li>Source InputStream is slow.</li>
+     * <li>It has network resources associated, so we cannot keep it open for
+     * long time.</li>
+     * <li>It has network timeout associated.</li>
+     * </ul>
+     * It can be used in favor of {@link #toByteArray()}, since it
+     * avoids unnecessary allocation and copy of byte[].<br>
+     * This method buffers the input internally, so there is no need to use a
+     * <code>BufferedInputStream</code>.
+     *
+     * @param input Stream to be fully buffered.
+     * @param size the initial buffer size
+     * @return A fully buffered stream.
+     * @throws IOException if an I/O error occurs
+     */
+    public static InputStream toBufferedInputStream(final InputStream input, 
final int size)
+            throws IOException {
+        // It does not matter if a ByteArrayOutputStream is not closed as 
close() is a no-op
+        @SuppressWarnings("resource")
+        final UnsynchronizedByteArrayOutputStream output = new 
UnsynchronizedByteArrayOutputStream(size);
+        output.write(input);
+        return output.toInputStream();
+    }
+
+    @Override
+    public InputStream toInputStream() {
+        return toInputStreamImpl();
+    }
+
+    @Override
+    public byte[] toByteArray() {
+        return toByteArrayImpl();
+    }
+}
diff --git 
a/src/test/java/org/apache/commons/io/output/ByteArrayOutputStreamTestCase.java 
b/src/test/java/org/apache/commons/io/output/ByteArrayOutputStreamTestCase.java
index 037e112..917a993 100644
--- 
a/src/test/java/org/apache/commons/io/output/ByteArrayOutputStreamTestCase.java
+++ 
b/src/test/java/org/apache/commons/io/output/ByteArrayOutputStreamTestCase.java
@@ -16,19 +16,26 @@
  */
 package org.apache.commons.io.output;
 
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertSame;
-import static org.junit.jupiter.api.Assertions.fail;
-
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.util.stream.Stream;
 
 import org.apache.commons.io.IOUtils;
-import org.junit.jupiter.api.Test;
+import org.apache.commons.io.input.ClosedInputStream;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
 
 /**
- * Basic unit tests for the alternative ByteArrayOutputStream implementation.
+ * Basic unit tests for the alternative ByteArrayOutputStream implementations.
  */
 public class ByteArrayOutputStreamTestCase {
 
@@ -41,7 +48,7 @@ public class ByteArrayOutputStreamTestCase {
         }
     }
 
-    private int writeData(final ByteArrayOutputStream baout,
+    private int writeData(final AbstractByteArrayOutputStream baout,
                 final java.io.ByteArrayOutputStream ref,
                 final int count) {
         if (count > DATA.length) {
@@ -58,7 +65,7 @@ public class ByteArrayOutputStreamTestCase {
         }
     }
 
-    private int writeData(final ByteArrayOutputStream baout,
+    private int writeData(final AbstractByteArrayOutputStream baout,
                 final java.io.ByteArrayOutputStream ref,
                 final int[] instructions) {
         int written = 0;
@@ -87,7 +94,7 @@ public class ByteArrayOutputStreamTestCase {
     }
 
     private void checkStreams(
-            final ByteArrayOutputStream actual,
+            final AbstractByteArrayOutputStream actual,
             final java.io.ByteArrayOutputStream expected) {
         assertEquals(expected.size(), actual.size(), "Sizes are not equal");
         final byte[] buf = actual.toByteArray();
@@ -95,9 +102,114 @@ public class ByteArrayOutputStreamTestCase {
         checkByteArrays(buf, refbuf);
     }
 
-    @Test
-    public void testToInputStream() throws IOException {
-        final ByteArrayOutputStream baout = new ByteArrayOutputStream();
+    @ParameterizedTest(name = "[{index}] {0}")
+    @MethodSource("baosFactories")
+    public void testWriteZero(final String baosName, final BAOSFactory 
baosFactory) {
+        final AbstractByteArrayOutputStream baout = baosFactory.instance();
+        baout.write(new byte[0], 0, 0);
+    }
+
+    @ParameterizedTest(name = "[{index}] {0}")
+    @MethodSource("baosFactories")
+    public void testInvalidWriteOffsetUnder(final String baosName, final 
BAOSFactory baosFactory) {
+        final AbstractByteArrayOutputStream baout = baosFactory.instance();
+        assertThrows(IndexOutOfBoundsException.class, () ->
+                baout.write(null, -1, 0)
+        );
+    }
+
+    @ParameterizedTest(name = "[{index}] {0}")
+    @MethodSource("baosFactories")
+    public void testInvalidWriteOffsetOver(final String baosName, final 
BAOSFactory baosFactory) {
+        final AbstractByteArrayOutputStream baout = baosFactory.instance();
+        assertThrows(IndexOutOfBoundsException.class, () ->
+                baout.write(new byte[0], 1, 0)
+        );
+    }
+
+    @ParameterizedTest(name = "[{index}] {0}")
+    @MethodSource("baosFactories")
+    public void testInvalidWriteLenUnder(final String baosName, final 
BAOSFactory baosFactory) {
+        final AbstractByteArrayOutputStream baout = baosFactory.instance();
+        assertThrows(IndexOutOfBoundsException.class, () ->
+                baout.write(new byte[1], 0, -1)
+        );
+    }
+
+    @ParameterizedTest(name = "[{index}] {0}")
+    @MethodSource("baosFactories")
+    public void testInvalidWriteOffsetAndLenUnder(final String baosName, final 
BAOSFactory baosFactory) {
+        final AbstractByteArrayOutputStream baout = baosFactory.instance();
+        assertThrows(IndexOutOfBoundsException.class, () ->
+                baout.write(new byte[1], 1, -2)
+        );
+    }
+
+    @ParameterizedTest(name = "[{index}] {0}")
+    @MethodSource("baosFactories")
+    public void testInvalidWriteOffsetAndLenOver(final String baosName, final 
BAOSFactory baosFactory) {
+        final AbstractByteArrayOutputStream baout = baosFactory.instance();
+        assertThrows(IndexOutOfBoundsException.class, () ->
+                baout.write(new byte[1], 0, 2)
+        );
+    }
+
+    @ParameterizedTest(name = "[{index}] {0}")
+    @MethodSource("baosFactories")
+    public void testInvalidParameterizedConstruction(final String baosName, 
final BAOSFactory baosFactory) {
+        assertThrows(IllegalArgumentException.class, () ->
+                baosFactory.instance(-1)
+        );
+    }
+
+    @ParameterizedTest(name = "[{index}] {0}")
+    @MethodSource("baosFactories")
+    public void testToInputStreamEmpty(final String baosName, final 
BAOSFactory baosFactory) throws IOException {
+        final AbstractByteArrayOutputStream baout = baosFactory.instance();
+
+        //Get data before more writes
+        final InputStream in = baout.toInputStream();
+        assertEquals(0, in.available());
+        assertTrue(in instanceof ClosedInputStream);
+
+        in.close();
+        baout.close();
+    }
+
+    @ParameterizedTest(name = "[{index}] {0}")
+    @MethodSource("toBufferedInputStreamFunctionFactories")
+    public void testToBufferedInputStreamEmpty(final String baosName, final 
IOFunction<InputStream, InputStream> toBufferedInputStreamFunction) throws 
IOException {
+        final ByteArrayInputStream bain = new ByteArrayInputStream(new 
byte[0]);
+        assertEquals(0, bain.available());
+
+        final InputStream buffered = toBufferedInputStreamFunction.apply(bain);
+        assertEquals(0, buffered.available());
+
+        buffered.close();
+        bain.close();
+    }
+
+    @ParameterizedTest(name = "[{index}] {0}")
+    @MethodSource("toBufferedInputStreamFunctionFactories")
+    public void testToBufferedInputStream(final String baosName, final 
IOFunction<InputStream, InputStream> toBufferedInputStreamFunction) throws 
IOException {
+        final byte data[] = {(byte)0xCA, (byte)0xFE, (byte)0xBA, (byte)0xBE};
+
+        final ByteArrayInputStream bain = new ByteArrayInputStream(data);
+        assertEquals(data.length, bain.available());
+
+        final InputStream buffered = toBufferedInputStreamFunction.apply(bain);
+        assertEquals(data.length, buffered.available());
+
+        assertArrayEquals(data, IOUtils.toByteArray(buffered));
+
+        buffered.close();
+        bain.close();
+    }
+
+    @ParameterizedTest(name = "[{index}] {0}")
+    @MethodSource("baosFactories")
+    public void testToInputStream(final String baosName, final BAOSFactory 
baosFactory) throws IOException {
+        final AbstractByteArrayOutputStream baout = baosFactory.instance();
         final java.io.ByteArrayOutputStream ref = new 
java.io.ByteArrayOutputStream();
 
         //Write 8224 bytes
@@ -127,10 +239,11 @@ public class ByteArrayOutputStreamTestCase {
         in.close();
     }
 
-    @Test
-    public void testToInputStreamWithReset() throws IOException {
+    @ParameterizedTest(name = "[{index}] {0}")
+    @MethodSource("baosFactories")
+    public void testToInputStreamWithReset(final String baosName, final 
BAOSFactory baosFactory) throws IOException {
         //Make sure reset() do not destroy InputStream returned from 
toInputStream()
-        final ByteArrayOutputStream baout = new ByteArrayOutputStream();
+        final AbstractByteArrayOutputStream baout = baosFactory.instance();
         final java.io.ByteArrayOutputStream ref = new 
java.io.ByteArrayOutputStream();
 
         //Write 8224 bytes
@@ -162,13 +275,14 @@ public class ByteArrayOutputStreamTestCase {
         in.close();
     }
 
-    @Test
-    public void testStream() throws Exception {
+    @ParameterizedTest(name = "[{index}] {0}")
+    @MethodSource("baosFactories")
+    public void testStream(final String baosName, final BAOSFactory 
baosFactory) throws Exception {
         int written;
 
         //The ByteArrayOutputStream is initialized with 32 bytes to match
         //the original more closely for this test.
-        final ByteArrayOutputStream baout = new ByteArrayOutputStream(32);
+        final AbstractByteArrayOutputStream baout = baosFactory.instance(32);
         final java.io.ByteArrayOutputStream ref = new 
java.io.ByteArrayOutputStream();
 
         //First three writes
@@ -198,7 +312,7 @@ public class ByteArrayOutputStreamTestCase {
 
         //Write the commons Byte[]OutputStream to a java.io.Byte[]OutputStream
         //and vice-versa to test the writeTo() method.
-        final ByteArrayOutputStream baout1 = new ByteArrayOutputStream(32);
+        final AbstractByteArrayOutputStream baout1 = baosFactory.instance(32);
         ref.writeTo(baout1);
         final java.io.ByteArrayOutputStream ref1 = new 
java.io.ByteArrayOutputStream();
         baout.writeTo(ref1);
@@ -211,13 +325,68 @@ public class ByteArrayOutputStreamTestCase {
 
         //Make sure that empty ByteArrayOutputStreams really don't create 
garbage
         //on toByteArray()
-        final ByteArrayOutputStream baos1 = new ByteArrayOutputStream();
-        final ByteArrayOutputStream baos2 = new ByteArrayOutputStream();
+        final AbstractByteArrayOutputStream baos1 = baosFactory.instance();
+        final AbstractByteArrayOutputStream baos2 = baosFactory.instance();
         assertSame(baos1.toByteArray(), baos2.toByteArray());
         baos1.close();
         baos2.close();
         baout.close();
         baout1.close();
     }
+
+    private static Stream<Arguments> baosFactories() {
+        return Stream.of(
+                Arguments.of(ByteArrayOutputStream.class.getSimpleName(), new 
ByteArrayOutputStreamFactory()),
+                
Arguments.of(UnsynchronizedByteArrayOutputStream.class.getSimpleName(), new 
UnsynchronizedByteArrayOutputStreamFactory())
+        );
+    }
+
+    private static class ByteArrayOutputStreamFactory implements BAOSFactory {
+        @Override
+        public AbstractByteArrayOutputStream instance() {
+            return new ByteArrayOutputStream();
+        }
+
+        @Override
+        public AbstractByteArrayOutputStream instance(final int size) {
+            return new ByteArrayOutputStream(size);
+        }
+    }
+
+    private static class UnsynchronizedByteArrayOutputStreamFactory implements 
BAOSFactory {
+        @Override
+        public AbstractByteArrayOutputStream instance() {
+            return new UnsynchronizedByteArrayOutputStream();
+        }
+
+        @Override
+        public AbstractByteArrayOutputStream instance(final int size) {
+            return new UnsynchronizedByteArrayOutputStream(size);
+        }
+    }
+
+    private interface BAOSFactory<T extends AbstractByteArrayOutputStream> {
+        AbstractByteArrayOutputStream instance();
+        AbstractByteArrayOutputStream instance(final int size);
+    }
+
+    private static Stream<Arguments> toBufferedInputStreamFunctionFactories() {
+        final IOFunction<InputStream, InputStream> 
syncBaosToBufferedInputStream = ByteArrayOutputStream::toBufferedInputStream;
+        final IOFunction<InputStream, InputStream> 
syncBaosToBufferedInputStreamWithSize = is -> 
ByteArrayOutputStream.toBufferedInputStream(is, 1024);
+        final IOFunction<InputStream, InputStream> 
unSyncBaosToBufferedInputStream = 
UnsynchronizedByteArrayOutputStream::toBufferedInputStream;
+        final IOFunction<InputStream, InputStream> 
unSyncBaosToBufferedInputStreamWithSize = is -> 
UnsynchronizedByteArrayOutputStream.toBufferedInputStream(is, 1024);
+
+        return Stream.of(
+            
Arguments.of("ByteArrayOutputStream.toBufferedInputStream(InputStream)", 
syncBaosToBufferedInputStream),
+            
Arguments.of("ByteArrayOutputStream.toBufferedInputStream(InputStream, int)", 
syncBaosToBufferedInputStreamWithSize),
+            
Arguments.of("UnsynchronizedByteArrayOutputStream.toBufferedInputStream(InputStream)",
 unSyncBaosToBufferedInputStream),
+            
Arguments.of("UnsynchronizedByteArrayOutputStream.toBufferedInputStream(InputStream,
 int)", unSyncBaosToBufferedInputStreamWithSize)
+        );
+    }
+
+    @FunctionalInterface
+    private interface IOFunction<T, R> {
+        R apply(final T t) throws IOException;
+    }
 }
 

Reply via email to