View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.io.output;
18  
19  import static org.junit.jupiter.api.Assertions.assertArrayEquals;
20  import static org.junit.jupiter.api.Assertions.assertEquals;
21  import static org.junit.jupiter.api.Assertions.assertSame;
22  import static org.junit.jupiter.api.Assertions.assertThrows;
23  import static org.junit.jupiter.api.Assertions.assertTrue;
24  import static org.junit.jupiter.api.Assertions.fail;
25  
26  import java.io.ByteArrayInputStream;
27  import java.io.IOException;
28  import java.io.InputStream;
29  import java.util.stream.Stream;
30  
31  import org.apache.commons.io.IOUtils;
32  import org.apache.commons.io.function.IOFunction;
33  import org.apache.commons.io.input.ClosedInputStream;
34  import org.junit.jupiter.params.ParameterizedTest;
35  import org.junit.jupiter.params.provider.Arguments;
36  import org.junit.jupiter.params.provider.MethodSource;
37  
38  /**
39   * Tests the alternative ByteArrayOutputStream implementations.
40   */
41  public class ByteArrayOutputStreamTest {
42  
43      private interface BAOSFactory<T extends AbstractByteArrayOutputStream> {
44          T newInstance();
45  
46          T newInstance(final int size);
47      }
48  
49      private static final class ByteArrayOutputStreamFactory implements BAOSFactory<ByteArrayOutputStream> {
50          @Override
51          public ByteArrayOutputStream newInstance() {
52              return new ByteArrayOutputStream();
53          }
54  
55          @Override
56          public ByteArrayOutputStream newInstance(final int size) {
57              return new ByteArrayOutputStream(size);
58          }
59      }
60  
61      private static final class UnsynchronizedByteArrayOutputStreamFactory implements BAOSFactory<UnsynchronizedByteArrayOutputStream> {
62          @Override
63          public UnsynchronizedByteArrayOutputStream newInstance() {
64              return new UnsynchronizedByteArrayOutputStream();
65          }
66  
67          @Override
68          public UnsynchronizedByteArrayOutputStream newInstance(final int size) {
69              return new UnsynchronizedByteArrayOutputStream(size);
70          }
71      }
72  
73      private static final byte[] DATA;
74  
75      static {
76          DATA = new byte[64];
77          for (byte i = 0; i < 64; i++) {
78              DATA[i] = i;
79          }
80      }
81  
82      private static Stream<Arguments> baosFactories() {
83          return Stream.of(Arguments.of(ByteArrayOutputStream.class.getSimpleName(), new ByteArrayOutputStreamFactory()),
84              Arguments.of(UnsynchronizedByteArrayOutputStream.class.getSimpleName(), new UnsynchronizedByteArrayOutputStreamFactory()));
85      }
86  
87      private static boolean byteCmp(final byte[] src, final byte[] cmp) {
88          for (int i = 0; i < cmp.length; i++) {
89              if (src[i] != cmp[i]) {
90                  return false;
91              }
92          }
93          return true;
94      }
95  
96      private static Stream<Arguments> toBufferedInputStreamFunctionFactories() {
97          final IOFunction<InputStream, InputStream> syncBaosToBufferedInputStream = ByteArrayOutputStream::toBufferedInputStream;
98          final IOFunction<InputStream, InputStream> syncBaosToBufferedInputStreamWithSize = is -> ByteArrayOutputStream.toBufferedInputStream(is, 1024);
99          final IOFunction<InputStream, InputStream> unSyncBaosToBufferedInputStream = UnsynchronizedByteArrayOutputStream::toBufferedInputStream;
100         final IOFunction<InputStream, InputStream> unSyncBaosToBufferedInputStreamWithSize = is -> UnsynchronizedByteArrayOutputStream.toBufferedInputStream(is,
101             1024);
102 
103         return Stream.of(Arguments.of("ByteArrayOutputStream.toBufferedInputStream(InputStream)", syncBaosToBufferedInputStream),
104             Arguments.of("ByteArrayOutputStream.toBufferedInputStream(InputStream, int)", syncBaosToBufferedInputStreamWithSize),
105             Arguments.of("UnsynchronizedByteArrayOutputStream.toBufferedInputStream(InputStream)", unSyncBaosToBufferedInputStream),
106             Arguments.of("UnsynchronizedByteArrayOutputStream.toBufferedInputStream(InputStream, int)", unSyncBaosToBufferedInputStreamWithSize));
107     }
108 
109     private void checkByteArrays(final byte[] expected, final byte[] actual) {
110         if (expected.length != actual.length) {
111             fail("Resulting byte arrays are not equally long");
112         }
113         if (!byteCmp(expected, actual)) {
114             fail("Resulting byte arrays are not equal");
115         }
116     }
117 
118     private void checkStreams(final AbstractByteArrayOutputStream actual, final java.io.ByteArrayOutputStream expected) {
119         assertEquals(expected.size(), actual.size(), "Sizes are not equal");
120         final byte[] buf = actual.toByteArray();
121         final byte[] refbuf = expected.toByteArray();
122         checkByteArrays(buf, refbuf);
123     }
124 
125     @ParameterizedTest(name = "[{index}] {0}")
126     @MethodSource("baosFactories")
127     public void testInvalidParameterizedConstruction(final String baosName, final BAOSFactory<?> baosFactory) {
128         assertThrows(IllegalArgumentException.class, () -> baosFactory.newInstance(-1));
129     }
130 
131     @ParameterizedTest(name = "[{index}] {0}")
132     @MethodSource("baosFactories")
133     public void testInvalidWriteLenUnder(final String baosName, final BAOSFactory<?> baosFactory) throws IOException {
134         try (AbstractByteArrayOutputStream baout = baosFactory.newInstance()) {
135             assertThrows(IndexOutOfBoundsException.class, () -> baout.write(new byte[1], 0, -1));
136         }
137     }
138 
139     @ParameterizedTest(name = "[{index}] {0}")
140     @MethodSource("baosFactories")
141     public void testInvalidWriteOffsetAndLenOver(final String baosName, final BAOSFactory<?> baosFactory) throws IOException {
142         try (AbstractByteArrayOutputStream baout = baosFactory.newInstance()) {
143             assertThrows(IndexOutOfBoundsException.class, () -> baout.write(new byte[1], 0, 2));
144         }
145     }
146 
147     @ParameterizedTest(name = "[{index}] {0}")
148     @MethodSource("baosFactories")
149     public void testInvalidWriteOffsetAndLenUnder(final String baosName, final BAOSFactory<?> baosFactory) throws IOException {
150         try (AbstractByteArrayOutputStream baout = baosFactory.newInstance()) {
151             assertThrows(IndexOutOfBoundsException.class, () -> baout.write(new byte[1], 1, -2));
152         }
153     }
154 
155     @ParameterizedTest(name = "[{index}] {0}")
156     @MethodSource("baosFactories")
157     public void testInvalidWriteOffsetOver(final String baosName, final BAOSFactory<?> baosFactory) throws IOException {
158         try (AbstractByteArrayOutputStream baout = baosFactory.newInstance()) {
159             assertThrows(IndexOutOfBoundsException.class, () -> baout.write(IOUtils.EMPTY_BYTE_ARRAY, 1, 0));
160         }
161     }
162 
163     @ParameterizedTest(name = "[{index}] {0}")
164     @MethodSource("baosFactories")
165     public void testInvalidWriteOffsetUnder(final String baosName, final BAOSFactory<?> baosFactory) throws IOException {
166         try (AbstractByteArrayOutputStream baout = baosFactory.newInstance()) {
167             assertThrows(IndexOutOfBoundsException.class, () -> baout.write(null, -1, 0));
168         }
169     }
170 
171     @ParameterizedTest(name = "[{index}] {0}")
172     @MethodSource("baosFactories")
173     public void testStream(final String baosName, final BAOSFactory<?> baosFactory) throws Exception {
174         int written;
175 
176         // The ByteArrayOutputStream is initialized with 32 bytes to match
177         // the original more closely for this test.
178         try (AbstractByteArrayOutputStream baout = baosFactory.newInstance(32);
179             final java.io.ByteArrayOutputStream ref = new java.io.ByteArrayOutputStream()) {
180 
181             // First three writes
182             written = writeData(baout, ref, new int[] {4, 10, 22});
183             assertEquals(36, written);
184             checkStreams(baout, ref);
185 
186             // Another two writes to see if there are any bad effects after toByteArray()
187             written = writeData(baout, ref, new int[] {20, 12});
188             assertEquals(32, written);
189             checkStreams(baout, ref);
190 
191             // Now reset the streams
192             baout.reset();
193             ref.reset();
194 
195             // Test again to see if reset() had any bad effects
196             written = writeData(baout, ref, new int[] {5, 47, 33, 60, 1, 0, 8});
197             assertEquals(155, written);
198             checkStreams(baout, ref);
199 
200             // Test the readFrom(InputStream) method
201             baout.reset();
202             written = baout.write(new ByteArrayInputStream(ref.toByteArray()));
203             assertEquals(155, written);
204             checkStreams(baout, ref);
205 
206             // Write the commons Byte[]OutputStream to a java.io.Byte[]OutputStream
207             // and vice-versa to test the writeTo() method.
208             try (AbstractByteArrayOutputStream baout1 = baosFactory.newInstance(32)) {
209                 ref.writeTo(baout1);
210                 final java.io.ByteArrayOutputStream ref1 = new java.io.ByteArrayOutputStream();
211                 baout.writeTo(ref1);
212                 checkStreams(baout1, ref1);
213 
214                 // Testing toString(String)
215                 final String baoutString = baout.toString("ASCII");
216                 final String refString = ref.toString("ASCII");
217                 assertEquals(refString, baoutString, "ASCII decoded String must be equal");
218 
219                 // Make sure that empty ByteArrayOutputStreams really don't create garbage
220                 // on toByteArray()
221                 try (AbstractByteArrayOutputStream baos1 = baosFactory.newInstance();
222                     final AbstractByteArrayOutputStream baos2 = baosFactory.newInstance()) {
223                     assertSame(baos1.toByteArray(), baos2.toByteArray());
224                 }
225             }
226         }
227     }
228 
229     @ParameterizedTest(name = "[{index}] {0}")
230     @MethodSource("toBufferedInputStreamFunctionFactories")
231     public void testToBufferedInputStream(final String baosName, final IOFunction<InputStream, InputStream> toBufferedInputStreamFunction) throws IOException {
232         final byte[] data = {(byte) 0xCA, (byte) 0xFE, (byte) 0xBA, (byte) 0xBE};
233 
234         try (ByteArrayInputStream bain = new ByteArrayInputStream(data)) {
235             assertEquals(data.length, bain.available());
236 
237             try (InputStream buffered = toBufferedInputStreamFunction.apply(bain)) {
238                 assertEquals(data.length, buffered.available());
239 
240                 assertArrayEquals(data, IOUtils.toByteArray(buffered));
241 
242             }
243         }
244     }
245 
246     @ParameterizedTest(name = "[{index}] {0}")
247     @MethodSource("toBufferedInputStreamFunctionFactories")
248     public void testToBufferedInputStreamEmpty(final String baosName, final IOFunction<InputStream, InputStream> toBufferedInputStreamFunction)
249         throws IOException {
250         try (ByteArrayInputStream bain = new ByteArrayInputStream(IOUtils.EMPTY_BYTE_ARRAY)) {
251             assertEquals(0, bain.available());
252 
253             try (InputStream buffered = toBufferedInputStreamFunction.apply(bain)) {
254                 assertEquals(0, buffered.available());
255 
256             }
257         }
258     }
259 
260     @ParameterizedTest(name = "[{index}] {0}")
261     @MethodSource("baosFactories")
262     public void testToInputStream(final String baosName, final BAOSFactory<?> baosFactory) throws IOException {
263         try (AbstractByteArrayOutputStream baout = baosFactory.newInstance();
264             final java.io.ByteArrayOutputStream ref = new java.io.ByteArrayOutputStream()) {
265 
266             // Write 8224 bytes
267             writeData(baout, ref, 32);
268             for (int i = 0; i < 128; i++) {
269                 writeData(baout, ref, 64);
270             }
271 
272             // Get data before more writes
273             try (InputStream in = baout.toInputStream()) {
274                 byte[] refData = ref.toByteArray();
275 
276                 // Write some more data
277                 writeData(baout, ref, new int[] {2, 4, 8, 16});
278 
279                 // Check original data
280                 byte[] baoutData = IOUtils.toByteArray(in);
281                 assertEquals(8224, baoutData.length);
282                 checkByteArrays(refData, baoutData);
283 
284                 // Check all data written
285                 try (InputStream in2 = baout.toInputStream()) {
286                     baoutData = IOUtils.toByteArray(in2);
287                 }
288                 refData = ref.toByteArray();
289                 assertEquals(8254, baoutData.length);
290                 checkByteArrays(refData, baoutData);
291             }
292         }
293     }
294 
295     @ParameterizedTest(name = "[{index}] {0}")
296     @MethodSource("baosFactories")
297     public void testToInputStreamEmpty(final String baosName, final BAOSFactory<?> baosFactory) throws IOException {
298         try (AbstractByteArrayOutputStream baout = baosFactory.newInstance();
299             // Get data before more writes
300             final InputStream in = baout.toInputStream()) {
301             assertEquals(0, in.available());
302             assertTrue(in instanceof ClosedInputStream);
303         }
304     }
305 
306     @ParameterizedTest(name = "[{index}] {0}")
307     @MethodSource("baosFactories")
308     public void testToInputStreamWithReset(final String baosName, final BAOSFactory<?> baosFactory) throws IOException {
309         // Make sure reset() do not destroy InputStream returned from toInputStream()
310         try (AbstractByteArrayOutputStream baout = baosFactory.newInstance();
311             final java.io.ByteArrayOutputStream ref = new java.io.ByteArrayOutputStream()) {
312 
313             // Write 8224 bytes
314             writeData(baout, ref, 32);
315             for (int i = 0; i < 128; i++) {
316                 writeData(baout, ref, 64);
317             }
318 
319             // Get data before reset
320             try (InputStream in = baout.toInputStream()) {
321                 byte[] refData = ref.toByteArray();
322 
323                 // Reset and write some new data
324                 baout.reset();
325                 ref.reset();
326                 writeData(baout, ref, new int[] {2, 4, 8, 16});
327 
328                 // Check original data
329                 byte[] baoutData = IOUtils.toByteArray(in);
330                 assertEquals(8224, baoutData.length);
331                 checkByteArrays(refData, baoutData);
332 
333                 // Check new data written after reset
334                 try (InputStream in2 = baout.toInputStream()) {
335                     baoutData = IOUtils.toByteArray(in2);
336                 }
337                 refData = ref.toByteArray();
338                 assertEquals(30, baoutData.length);
339                 checkByteArrays(refData, baoutData);
340             }
341         }
342     }
343 
344     @ParameterizedTest(name = "[{index}] {0}")
345     @MethodSource("baosFactories")
346     public void testWriteZero(final String baosName, final BAOSFactory<?> baosFactory) throws IOException {
347         try (AbstractByteArrayOutputStream baout = baosFactory.newInstance()) {
348             baout.write(IOUtils.EMPTY_BYTE_ARRAY, 0, 0);
349             assertTrue(true, "Dummy");
350         }
351     }
352 
353     private int writeData(final AbstractByteArrayOutputStream baout, final java.io.ByteArrayOutputStream ref, final int count) {
354         if (count > DATA.length) {
355             throw new IllegalArgumentException("Requesting too many bytes");
356         }
357         if (count == 0) {
358             baout.write(100);
359             ref.write(100);
360             return 1;
361         }
362         baout.write(DATA, 0, count);
363         ref.write(DATA, 0, count);
364         return count;
365     }
366 
367     private int writeData(final AbstractByteArrayOutputStream baout, final java.io.ByteArrayOutputStream ref, final int[] instructions) {
368         int written = 0;
369         for (final int instruction : instructions) {
370             written += writeData(baout, ref, instruction);
371         }
372         return written;
373     }
374 }