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 <[email protected]>
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;
+ }
}