diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 791a08b05d7..b74dca11eef 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -54,6 +54,8 @@ The type attribute can be add,update,fix,remove. Add ThrottledInputStream.Builder.setMaxBytes(long, ChronoUnit). Add IOIterable. ReversedLinesFileReader implements IOIterable<String>. + Add AbstractByteArrayOutputStream.write(CharSequence, Charset). + Add AbstractByteArrayOutputStream.write(byte[]). diff --git a/src/main/java/org/apache/commons/io/output/AbstractByteArrayOutputStream.java b/src/main/java/org/apache/commons/io/output/AbstractByteArrayOutputStream.java index f3d08d0e706..30e259c28eb 100644 --- a/src/main/java/org/apache/commons/io/output/AbstractByteArrayOutputStream.java +++ b/src/main/java/org/apache/commons/io/output/AbstractByteArrayOutputStream.java @@ -28,6 +28,7 @@ import java.util.Collections; import java.util.List; +import org.apache.commons.io.Charsets; import org.apache.commons.io.IOUtils; import org.apache.commons.io.input.ClosedInputStream; @@ -55,9 +56,10 @@ * ignored. *

* + * @param The AbstractByteArrayOutputStream subclass * @since 2.7 */ -public abstract class AbstractByteArrayOutputStream extends OutputStream { +public abstract class AbstractByteArrayOutputStream> extends OutputStream { /** * Constructor for an InputStream subclass. @@ -83,18 +85,18 @@ protected interface InputStreamConstructor { /** The list of buffers, which grows and never reduces. */ private final List buffers = new ArrayList<>(); + /** The total count of bytes written. */ + protected int count; + + /** The current buffer. */ + private byte[] currentBuffer; + /** 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. */ - protected int count; - /** Flag to indicate if the buffers can be reused after reset */ private boolean reuseBuffers = true; @@ -105,6 +107,16 @@ public AbstractByteArrayOutputStream() { // empty } + /* + * Returns this instance typed to {@code T}. + * + * @return this instance + */ + @SuppressWarnings("unchecked") + protected T asThis() { + return (T) this; + } + /** * Does nothing. * @@ -304,9 +316,34 @@ public String toString(final String enc) throws UnsupportedEncodingException { return new String(toByteArray(), enc); } + /** + * 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)}. + * + * @param b the data. + * @see #write(byte[], int, int) + * @since 2.19.0 + */ + @Override + public void write(final byte b[]) { + write(b, 0, b.length); + } + @Override public abstract void write(final byte[] b, final int off, final int len); + /** + * Writes the bytes for given CharSequence encoded using a Charset. + * + * @param data The String to convert to bytes. not null. + * @param charset The {@link Charset} o encode the {@code String}, null means the default encoding. + * @return this instance. + * @since 2.19.0 + */ + public T write(final CharSequence data, final Charset charset) { + write(data.toString().getBytes(Charsets.toCharset(charset))); + return asThis(); + } + /** * Writes the entire contents of the specified input stream to this * byte stream. Bytes from the input stream are read directly into the 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 b2b310ab79c..285c3f45d84 100644 --- a/src/main/java/org/apache/commons/io/output/ByteArrayOutputStream.java +++ b/src/main/java/org/apache/commons/io/output/ByteArrayOutputStream.java @@ -25,7 +25,7 @@ * Implements a ThreadSafe version of {@link AbstractByteArrayOutputStream} using instance synchronization. */ //@ThreadSafe -public class ByteArrayOutputStream extends AbstractByteArrayOutputStream { +public class ByteArrayOutputStream extends AbstractByteArrayOutputStream { /** * Fetches entire contents of an {@link InputStream} and represent diff --git a/src/main/java/org/apache/commons/io/output/UnsynchronizedByteArrayOutputStream.java b/src/main/java/org/apache/commons/io/output/UnsynchronizedByteArrayOutputStream.java index 429d8162cf0..cc0b455c039 100644 --- a/src/main/java/org/apache/commons/io/output/UnsynchronizedByteArrayOutputStream.java +++ b/src/main/java/org/apache/commons/io/output/UnsynchronizedByteArrayOutputStream.java @@ -36,7 +36,7 @@ * @since 2.7 */ //@NotThreadSafe -public final class UnsynchronizedByteArrayOutputStream extends AbstractByteArrayOutputStream { +public final class UnsynchronizedByteArrayOutputStream extends AbstractByteArrayOutputStream { // @formatter:off /** diff --git a/src/test/java/org/apache/commons/io/output/ByteArrayOutputStreamTest.java b/src/test/java/org/apache/commons/io/output/ByteArrayOutputStreamTest.java index 9e9e4cfb2e5..f700892ce50 100644 --- a/src/test/java/org/apache/commons/io/output/ByteArrayOutputStreamTest.java +++ b/src/test/java/org/apache/commons/io/output/ByteArrayOutputStreamTest.java @@ -27,6 +27,8 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; import java.util.stream.Stream; import org.apache.commons.io.IOUtils; @@ -41,7 +43,8 @@ */ public class ByteArrayOutputStreamTest { - private interface BAOSFactory { + private interface BAOSFactory> { + T newInstance(); T newInstance(final int size); @@ -71,18 +74,18 @@ public UnsynchronizedByteArrayOutputStream newInstance(final int size) { } } - private static final byte[] DATA; + private static final byte[] ASCII_DATA; static { - DATA = new byte[64]; - for (byte i = 0; i < 64; i++) { - DATA[i] = i; + ASCII_DATA = new byte[64]; + for (byte i = 0; i < ASCII_DATA.length; i++) { + ASCII_DATA[i] = (byte) (char) ('0' + i); } } private static Stream baosFactories() { return Stream.of(Arguments.of(ByteArrayOutputStream.class.getSimpleName(), new ByteArrayOutputStreamFactory()), - Arguments.of(UnsynchronizedByteArrayOutputStream.class.getSimpleName(), new UnsynchronizedByteArrayOutputStreamFactory())); + Arguments.of(UnsynchronizedByteArrayOutputStream.class.getSimpleName(), new UnsynchronizedByteArrayOutputStreamFactory())); } private static boolean byteCmp(final byte[] src, final byte[] cmp) { @@ -99,12 +102,12 @@ private static Stream toBufferedInputStreamFunctionFactories() { final IOFunction syncBaosToBufferedInputStreamWithSize = is -> ByteArrayOutputStream.toBufferedInputStream(is, 1024); final IOFunction unSyncBaosToBufferedInputStream = UnsynchronizedByteArrayOutputStream::toBufferedInputStream; final IOFunction unSyncBaosToBufferedInputStreamWithSize = is -> UnsynchronizedByteArrayOutputStream.toBufferedInputStream(is, - 1024); + 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)); + Arguments.of("ByteArrayOutputStream.toBufferedInputStream(InputStream, int)", syncBaosToBufferedInputStreamWithSize), + Arguments.of("UnsynchronizedByteArrayOutputStream.toBufferedInputStream(InputStream)", unSyncBaosToBufferedInputStream), + Arguments.of("UnsynchronizedByteArrayOutputStream.toBufferedInputStream(InputStream, int)", unSyncBaosToBufferedInputStreamWithSize)); } private void checkByteArrays(final byte[] expected, final byte[] actual) { @@ -116,7 +119,7 @@ private void checkByteArrays(final byte[] expected, final byte[] actual) { } } - private void checkStreams(final AbstractByteArrayOutputStream actual, final java.io.ByteArrayOutputStream expected) { + private void checkStreams(final AbstractByteArrayOutputStream actual, final java.io.ByteArrayOutputStream expected) { assertEquals(expected.size(), actual.size(), "Sizes are not equal"); final byte[] buf = actual.toByteArray(); final byte[] refbuf = expected.toByteArray(); @@ -132,7 +135,7 @@ public void testInvalidParameterizedConstruction(final String baosName, final BA @ParameterizedTest(name = "[{index}] {0}") @MethodSource("baosFactories") public void testInvalidWriteLenUnder(final String baosName, final BAOSFactory baosFactory) throws IOException { - try (AbstractByteArrayOutputStream baout = baosFactory.newInstance()) { + try (AbstractByteArrayOutputStream baout = baosFactory.newInstance()) { assertThrows(IndexOutOfBoundsException.class, () -> baout.write(new byte[1], 0, -1)); } } @@ -140,7 +143,7 @@ public void testInvalidWriteLenUnder(final String baosName, final BAOSFactory @ParameterizedTest(name = "[{index}] {0}") @MethodSource("baosFactories") public void testInvalidWriteOffsetAndLenOver(final String baosName, final BAOSFactory baosFactory) throws IOException { - try (AbstractByteArrayOutputStream baout = baosFactory.newInstance()) { + try (AbstractByteArrayOutputStream baout = baosFactory.newInstance()) { assertThrows(IndexOutOfBoundsException.class, () -> baout.write(new byte[1], 0, 2)); } } @@ -148,7 +151,7 @@ public void testInvalidWriteOffsetAndLenOver(final String baosName, final BAOSFa @ParameterizedTest(name = "[{index}] {0}") @MethodSource("baosFactories") public void testInvalidWriteOffsetAndLenUnder(final String baosName, final BAOSFactory baosFactory) throws IOException { - try (AbstractByteArrayOutputStream baout = baosFactory.newInstance()) { + try (AbstractByteArrayOutputStream baout = baosFactory.newInstance()) { assertThrows(IndexOutOfBoundsException.class, () -> baout.write(new byte[1], 1, -2)); } } @@ -156,7 +159,7 @@ public void testInvalidWriteOffsetAndLenUnder(final String baosName, final BAOSF @ParameterizedTest(name = "[{index}] {0}") @MethodSource("baosFactories") public void testInvalidWriteOffsetOver(final String baosName, final BAOSFactory baosFactory) throws IOException { - try (AbstractByteArrayOutputStream baout = baosFactory.newInstance()) { + try (AbstractByteArrayOutputStream baout = baosFactory.newInstance()) { assertThrows(IndexOutOfBoundsException.class, () -> baout.write(IOUtils.EMPTY_BYTE_ARRAY, 1, 0)); } } @@ -164,73 +167,15 @@ public void testInvalidWriteOffsetOver(final String baosName, final BAOSFactory< @ParameterizedTest(name = "[{index}] {0}") @MethodSource("baosFactories") public void testInvalidWriteOffsetUnder(final String baosName, final BAOSFactory baosFactory) throws IOException { - try (AbstractByteArrayOutputStream baout = baosFactory.newInstance()) { + try (AbstractByteArrayOutputStream baout = baosFactory.newInstance()) { assertThrows(IndexOutOfBoundsException.class, () -> baout.write(null, -1, 0)); } } - @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. - try (AbstractByteArrayOutputStream baout = baosFactory.newInstance(32); - final java.io.ByteArrayOutputStream ref = new java.io.ByteArrayOutputStream()) { - - // First three writes - written = writeData(baout, ref, new int[] {4, 10, 22}); - assertEquals(36, written); - checkStreams(baout, ref); - - // Another two writes to see if there are any bad effects after toByteArray() - written = writeData(baout, ref, new int[] {20, 12}); - assertEquals(32, written); - checkStreams(baout, ref); - - // Now reset the streams - baout.reset(); - ref.reset(); - - // Test again to see if reset() had any bad effects - written = writeData(baout, ref, new int[] {5, 47, 33, 60, 1, 0, 8}); - assertEquals(155, written); - checkStreams(baout, ref); - - // Test the readFrom(InputStream) method - baout.reset(); - written = baout.write(new ByteArrayInputStream(ref.toByteArray())); - assertEquals(155, written); - checkStreams(baout, ref); - - // Write the commons Byte[]OutputStream to a java.io.Byte[]OutputStream - // and vice-versa to test the writeTo() method. - try (AbstractByteArrayOutputStream baout1 = baosFactory.newInstance(32)) { - ref.writeTo(baout1); - final java.io.ByteArrayOutputStream ref1 = new java.io.ByteArrayOutputStream(); - baout.writeTo(ref1); - checkStreams(baout1, ref1); - - // Testing toString(String) - final String baoutString = baout.toString("ASCII"); - final String refString = ref.toString("ASCII"); - assertEquals(refString, baoutString, "ASCII decoded String must be equal"); - - // Make sure that empty ByteArrayOutputStreams really don't create garbage - // on toByteArray() - try (AbstractByteArrayOutputStream baos1 = baosFactory.newInstance(); - final AbstractByteArrayOutputStream baos2 = baosFactory.newInstance()) { - assertSame(baos1.toByteArray(), baos2.toByteArray()); - } - } - } - } - @ParameterizedTest(name = "[{index}] {0}") @MethodSource("toBufferedInputStreamFunctionFactories") public void testToBufferedInputStream(final String baosName, final IOFunction toBufferedInputStreamFunction) throws IOException { - final byte[] data = {(byte) 0xCA, (byte) 0xFE, (byte) 0xBA, (byte) 0xBE}; + final byte[] data = { (byte) 0xCA, (byte) 0xFE, (byte) 0xBA, (byte) 0xBE }; try (ByteArrayInputStream bain = new ByteArrayInputStream(data)) { assertEquals(data.length, bain.available()); @@ -247,7 +192,7 @@ public void testToBufferedInputStream(final String baosName, final IOFunction toBufferedInputStreamFunction) - throws IOException { + throws IOException { try (ByteArrayInputStream bain = new ByteArrayInputStream(IOUtils.EMPTY_BYTE_ARRAY)) { assertEquals(0, bain.available()); @@ -261,13 +206,13 @@ public void testToBufferedInputStreamEmpty(final String baosName, final IOFuncti @ParameterizedTest(name = "[{index}] {0}") @MethodSource("baosFactories") public void testToInputStream(final String baosName, final BAOSFactory baosFactory) throws IOException { - try (AbstractByteArrayOutputStream baout = baosFactory.newInstance(); - final java.io.ByteArrayOutputStream ref = new java.io.ByteArrayOutputStream()) { + try (AbstractByteArrayOutputStream baout = baosFactory.newInstance(); + final java.io.ByteArrayOutputStream ref = new java.io.ByteArrayOutputStream()) { // Write 8224 bytes - writeData(baout, ref, 32); + writeByteArrayIndex(baout, ref, 32); for (int i = 0; i < 128; i++) { - writeData(baout, ref, 64); + writeByteArrayIndex(baout, ref, 64); } // Get data before more writes @@ -275,7 +220,7 @@ public void testToInputStream(final String baosName, final BAOSFactory baosFa byte[] refData = ref.toByteArray(); // Write some more data - writeData(baout, ref, new int[] {2, 4, 8, 16}); + writeByteArrayIndex(baout, ref, new int[] { 2, 4, 8, 16 }); // Check original data byte[] baoutData = IOUtils.toByteArray(in); @@ -296,9 +241,9 @@ public void testToInputStream(final String baosName, final BAOSFactory baosFa @ParameterizedTest(name = "[{index}] {0}") @MethodSource("baosFactories") public void testToInputStreamEmpty(final String baosName, final BAOSFactory baosFactory) throws IOException { - try (AbstractByteArrayOutputStream baout = baosFactory.newInstance(); - // Get data before more writes - final InputStream in = baout.toInputStream()) { + try (AbstractByteArrayOutputStream baout = baosFactory.newInstance(); + // Get data before more writes + final InputStream in = baout.toInputStream()) { assertEquals(0, in.available()); assertInstanceOf(ClosedInputStream.class, in); } @@ -308,13 +253,13 @@ public void testToInputStreamEmpty(final String baosName, final BAOSFactory b @MethodSource("baosFactories") public void testToInputStreamWithReset(final String baosName, final BAOSFactory baosFactory) throws IOException { // Make sure reset() do not destroy InputStream returned from toInputStream() - try (AbstractByteArrayOutputStream baout = baosFactory.newInstance(); - final java.io.ByteArrayOutputStream ref = new java.io.ByteArrayOutputStream()) { + try (AbstractByteArrayOutputStream baout = baosFactory.newInstance(); + final java.io.ByteArrayOutputStream ref = new java.io.ByteArrayOutputStream()) { // Write 8224 bytes - writeData(baout, ref, 32); + writeByteArrayIndex(baout, ref, 32); for (int i = 0; i < 128; i++) { - writeData(baout, ref, 64); + writeByteArrayIndex(baout, ref, 64); } // Get data before reset @@ -324,7 +269,7 @@ public void testToInputStreamWithReset(final String baosName, final BAOSFactory< // Reset and write some new data baout.reset(); ref.reset(); - writeData(baout, ref, new int[] {2, 4, 8, 16}); + writeByteArrayIndex(baout, ref, new int[] { 2, 4, 8, 16 }); // Check original data byte[] baoutData = IOUtils.toByteArray(in); @@ -342,17 +287,251 @@ public void testToInputStreamWithReset(final String baosName, final BAOSFactory< } } + @ParameterizedTest(name = "[{index}] {0}") + @MethodSource("baosFactories") + public void testWriteByte(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. + try (AbstractByteArrayOutputStream baout = baosFactory.newInstance(32); + final java.io.ByteArrayOutputStream ref = new java.io.ByteArrayOutputStream()) { + + // First three writes + written = writeByte(baout, ref, new int[] { 4, 10, 22 }); + assertEquals(36, written); + checkStreams(baout, ref); + + // Another two writes to see if there are any bad effects after toByteArray() + written = writeByte(baout, ref, new int[] { 20, 12 }); + assertEquals(32, written); + checkStreams(baout, ref); + + // Now reset the streams + baout.reset(); + ref.reset(); + + // Test again to see if reset() had any bad effects + written = writeByte(baout, ref, new int[] { 5, 47, 33, 60, 1, 0, 8 }); + assertEquals(155, written); + checkStreams(baout, ref); + + // Test the readFrom(InputStream) method + baout.reset(); + written = baout.write(new ByteArrayInputStream(ref.toByteArray())); + assertEquals(155, written); + checkStreams(baout, ref); + + // Write the commons Byte[]OutputStream to a java.io.Byte[]OutputStream + // and vice-versa to test the writeTo() method. + try (AbstractByteArrayOutputStream baout1 = baosFactory.newInstance(32)) { + ref.writeTo(baout1); + final java.io.ByteArrayOutputStream ref1 = new java.io.ByteArrayOutputStream(); + baout.writeTo(ref1); + checkStreams(baout1, ref1); + + // Testing toString(String) + final String baoutString = baout.toString("ASCII"); + final String refString = ref.toString("ASCII"); + assertEquals(refString, baoutString, "ASCII decoded String must be equal"); + + // Make sure that empty ByteArrayOutputStreams really don't create garbage + // on toByteArray() + try (AbstractByteArrayOutputStream baos1 = baosFactory.newInstance(); + final AbstractByteArrayOutputStream baos2 = baosFactory.newInstance()) { + assertSame(baos1.toByteArray(), baos2.toByteArray()); + } + } + } + } + + // writeStringCharset + + @ParameterizedTest(name = "[{index}] {0}") + @MethodSource("baosFactories") + public void testWriteByteArray(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. + try (AbstractByteArrayOutputStream baout = baosFactory.newInstance(32); + final java.io.ByteArrayOutputStream ref = new java.io.ByteArrayOutputStream()) { + + // First three writes + written = writeByteArray(baout, ref, new int[] { 4, 10, 22 }); + assertEquals(36, written); + checkStreams(baout, ref); + + // Another two writes to see if there are any bad effects after toByteArray() + written = writeByteArray(baout, ref, new int[] { 20, 12 }); + assertEquals(32, written); + checkStreams(baout, ref); + + // Now reset the streams + baout.reset(); + ref.reset(); + + // Test again to see if reset() had any bad effects + written = writeByteArray(baout, ref, new int[] { 5, 47, 33, 60, 1, 0, 8 }); + assertEquals(155, written); + checkStreams(baout, ref); + + // Test the readFrom(InputStream) method + baout.reset(); + written = baout.write(new ByteArrayInputStream(ref.toByteArray())); + assertEquals(155, written); + checkStreams(baout, ref); + + // Write the commons Byte[]OutputStream to a java.io.Byte[]OutputStream + // and vice-versa to test the writeTo() method. + try (AbstractByteArrayOutputStream baout1 = baosFactory.newInstance(32)) { + ref.writeTo(baout1); + final java.io.ByteArrayOutputStream ref1 = new java.io.ByteArrayOutputStream(); + baout.writeTo(ref1); + checkStreams(baout1, ref1); + + // Testing toString(String) + final String baoutString = baout.toString("ASCII"); + final String refString = ref.toString("ASCII"); + assertEquals(refString, baoutString, "ASCII decoded String must be equal"); + + // Make sure that empty ByteArrayOutputStreams really don't create garbage + // on toByteArray() + try (AbstractByteArrayOutputStream baos1 = baosFactory.newInstance(); + final AbstractByteArrayOutputStream baos2 = baosFactory.newInstance()) { + assertSame(baos1.toByteArray(), baos2.toByteArray()); + } + } + } + } + + @ParameterizedTest(name = "[{index}] {0}") + @MethodSource("baosFactories") + public void testWriteByteArrayIndex(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. + try (AbstractByteArrayOutputStream baout = baosFactory.newInstance(32); + final java.io.ByteArrayOutputStream ref = new java.io.ByteArrayOutputStream()) { + + // First three writes + written = writeByteArrayIndex(baout, ref, new int[] { 4, 10, 22 }); + assertEquals(36, written); + checkStreams(baout, ref); + + // Another two writes to see if there are any bad effects after toByteArray() + written = writeByteArrayIndex(baout, ref, new int[] { 20, 12 }); + assertEquals(32, written); + checkStreams(baout, ref); + + // Now reset the streams + baout.reset(); + ref.reset(); + + // Test again to see if reset() had any bad effects + written = writeByteArrayIndex(baout, ref, new int[] { 5, 47, 33, 60, 1, 0, 8 }); + assertEquals(155, written); + checkStreams(baout, ref); + + // Test the readFrom(InputStream) method + baout.reset(); + written = baout.write(new ByteArrayInputStream(ref.toByteArray())); + assertEquals(155, written); + checkStreams(baout, ref); + + // Write the commons Byte[]OutputStream to a java.io.Byte[]OutputStream + // and vice-versa to test the writeTo() method. + try (AbstractByteArrayOutputStream baout1 = baosFactory.newInstance(32)) { + ref.writeTo(baout1); + final java.io.ByteArrayOutputStream ref1 = new java.io.ByteArrayOutputStream(); + baout.writeTo(ref1); + checkStreams(baout1, ref1); + + // Testing toString(String) + final String baoutString = baout.toString("ASCII"); + final String refString = ref.toString("ASCII"); + assertEquals(refString, baoutString, "ASCII decoded String must be equal"); + + // Make sure that empty ByteArrayOutputStreams really don't create garbage + // on toByteArray() + try (AbstractByteArrayOutputStream baos1 = baosFactory.newInstance(); + final AbstractByteArrayOutputStream baos2 = baosFactory.newInstance()) { + assertSame(baos1.toByteArray(), baos2.toByteArray()); + } + } + } + } + + @ParameterizedTest(name = "[{index}] {0}") + @MethodSource("baosFactories") + public void testWriteStringCharset(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. + try (AbstractByteArrayOutputStream baout = baosFactory.newInstance(32); + final java.io.ByteArrayOutputStream ref = new java.io.ByteArrayOutputStream()) { + + // First three writes + written = writeStringCharset(baout, ref, new int[] { 4, 10, 22 }); + assertEquals(36, written); + checkStreams(baout, ref); + + // Another two writes to see if there are any bad effects after toByteArray() + written = writeStringCharset(baout, ref, new int[] { 20, 12 }); + assertEquals(32, written); + checkStreams(baout, ref); + + // Now reset the streams + baout.reset(); + ref.reset(); + + // Test again to see if reset() had any bad effects + written = writeStringCharset(baout, ref, new int[] { 5, 47, 33, 60, 1, 0, 8 }); + assertEquals(155, written); + checkStreams(baout, ref); + + // Test the readFrom(InputStream) method + baout.reset(); + written = baout.write(new ByteArrayInputStream(ref.toByteArray())); + assertEquals(155, written); + checkStreams(baout, ref); + + // Write the commons Byte[]OutputStream to a java.io.Byte[]OutputStream + // and vice-versa to test the writeTo() method. + try (AbstractByteArrayOutputStream baout1 = baosFactory.newInstance(32)) { + ref.writeTo(baout1); + final java.io.ByteArrayOutputStream ref1 = new java.io.ByteArrayOutputStream(); + baout.writeTo(ref1); + checkStreams(baout1, ref1); + + // Testing toString(String) + final String baoutString = baout.toString("ASCII"); + final String refString = ref.toString("ASCII"); + assertEquals(refString, baoutString, "ASCII decoded String must be equal"); + + // Make sure that empty ByteArrayOutputStreams really don't create garbage + // on toByteArray() + try (AbstractByteArrayOutputStream baos1 = baosFactory.newInstance(); + final AbstractByteArrayOutputStream baos2 = baosFactory.newInstance()) { + assertSame(baos1.toByteArray(), baos2.toByteArray()); + } + } + } + } + @ParameterizedTest(name = "[{index}] {0}") @MethodSource("baosFactories") public void testWriteZero(final String baosName, final BAOSFactory baosFactory) throws IOException { - try (AbstractByteArrayOutputStream baout = baosFactory.newInstance()) { + try (AbstractByteArrayOutputStream baout = baosFactory.newInstance()) { baout.write(IOUtils.EMPTY_BYTE_ARRAY, 0, 0); assertTrue(true, "Dummy"); } } - private int writeData(final AbstractByteArrayOutputStream baout, final java.io.ByteArrayOutputStream ref, final int count) { - if (count > DATA.length) { + private int writeByte(final AbstractByteArrayOutputStream baout, final java.io.ByteArrayOutputStream ref, final int count) { + if (count > ASCII_DATA.length) { throw new IllegalArgumentException("Requesting too many bytes"); } if (count == 0) { @@ -360,15 +539,94 @@ private int writeData(final AbstractByteArrayOutputStream baout, final java.io.B ref.write(100); return 1; } - baout.write(DATA, 0, count); - ref.write(DATA, 0, count); + for (int i = 0; i < count; i++) { + baout.write(ASCII_DATA[i]); + ref.write(ASCII_DATA[i]); + } + return count; + } + + private int writeByte(final AbstractByteArrayOutputStream baout, final java.io.ByteArrayOutputStream ref, final int[] instructions) throws IOException { + int written = 0; + for (final int instruction : instructions) { + written += writeByte(baout, ref, instruction); + } + return written; + } + + private int writeByteArray(final AbstractByteArrayOutputStream baout, final java.io.ByteArrayOutputStream ref, final int count) throws IOException { + if (count > ASCII_DATA.length) { + throw new IllegalArgumentException("Requesting too many bytes"); + } + if (count == 0) { + // length 1 data + baout.write(new byte[] { 100 }); + ref.write(new byte[] { 100 }); + return 1; + } + baout.write(Arrays.copyOf(ASCII_DATA, count)); + ref.write(Arrays.copyOf(ASCII_DATA, count)); + return count; + } + + private int writeByteArray(final AbstractByteArrayOutputStream baout, final java.io.ByteArrayOutputStream ref, final int[] instructions) + throws IOException { + int written = 0; + for (final int instruction : instructions) { + written += writeByteArray(baout, ref, instruction); + } + return written; + } + + private int writeByteArrayIndex(final AbstractByteArrayOutputStream baout, final java.io.ByteArrayOutputStream ref, final int count) { + if (count > ASCII_DATA.length) { + throw new IllegalArgumentException("Requesting too many bytes"); + } + if (count == 0) { + // length 1 data + baout.write(100); + ref.write(100); + return 1; + } + baout.write(ASCII_DATA, 0, count); + ref.write(ASCII_DATA, 0, count); + return count; + } + + private int writeByteArrayIndex(final AbstractByteArrayOutputStream baout, final java.io.ByteArrayOutputStream ref, final int[] instructions) { + int written = 0; + for (final int instruction : instructions) { + written += writeByteArrayIndex(baout, ref, instruction); + } + return written; + } + + private int writeStringCharset(final AbstractByteArrayOutputStream baout, final java.io.ByteArrayOutputStream ref, final int count) throws IOException { + if (count > ASCII_DATA.length) { + throw new IllegalArgumentException("Requesting too many bytes"); + } + if (count == 0) { + // length 1 data + final String data = "a"; + baout.write(data, StandardCharsets.UTF_8); + ref.write(data.getBytes(StandardCharsets.UTF_8)); + return 1; + } + final String data = new String(ASCII_DATA, StandardCharsets.UTF_8).substring(0, count); + assertEquals(count, data.length(), () -> String.format("[%,d]:'%s'", count, data)); + baout.write(data, StandardCharsets.UTF_8); + final byte[] bytes = data.getBytes(StandardCharsets.UTF_8); + // For now, test assumes 1-1 size mapping from chars to bytes. + assertEquals(count, bytes.length, () -> String.format("[%,d]:'%s'", count, data)); + ref.write(bytes); return count; } - private int writeData(final AbstractByteArrayOutputStream baout, final java.io.ByteArrayOutputStream ref, final int[] instructions) { + private int writeStringCharset(final AbstractByteArrayOutputStream baout, final java.io.ByteArrayOutputStream ref, final int[] instructions) + throws IOException { int written = 0; for (final int instruction : instructions) { - written += writeData(baout, ref, instruction); + written += writeStringCharset(baout, ref, instruction); } return written; } diff --git a/src/test/java/org/apache/commons/io/output/ProxyOutputStreamTest.java b/src/test/java/org/apache/commons/io/output/ProxyOutputStreamTest.java index 8c3a2ec5a15..d5c86c67f6e 100644 --- a/src/test/java/org/apache/commons/io/output/ProxyOutputStreamTest.java +++ b/src/test/java/org/apache/commons/io/output/ProxyOutputStreamTest.java @@ -17,9 +17,12 @@ package org.apache.commons.io.output; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; -import java.io.IOException; import java.io.OutputStream; +import java.util.concurrent.atomic.AtomicBoolean; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -29,18 +32,26 @@ */ public class ProxyOutputStreamTest { + private final AtomicBoolean hit = new AtomicBoolean(); + private ByteArrayOutputStream original; private OutputStream proxied; @BeforeEach public void setUp() { - original = new ByteArrayOutputStream(){ + original = new ByteArrayOutputStream() { + + @Override + public void write(final byte[] ba) { + hit.set(true); + super.write(ba); + } + @Override - public void write(final byte[] ba) throws IOException { - if (ba != null){ - super.write(ba); - } + public synchronized void write(final int ba) { + hit.set(true); + super.write(ba); } }; proxied = new ProxyOutputStream(original); @@ -48,15 +59,20 @@ public void write(final byte[] ba) throws IOException { @Test public void testWrite() throws Exception { + assertFalse(hit.get()); proxied.write('y'); + assertTrue(hit.get()); assertEquals(1, original.size()); assertEquals('y', original.toByteArray()[0]); } @Test - public void testWriteNullBaSucceeds() throws Exception { + public void testWriteNullArrayProxiesToUnderlying() throws Exception { + assertFalse(hit.get()); final byte[] ba = null; - original.write(ba); - proxied.write(ba); + assertThrows(NullPointerException.class, () -> original.write(ba)); + assertTrue(hit.get()); + assertThrows(NullPointerException.class, () -> proxied.write(ba)); + assertTrue(hit.get()); } }