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;
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.assertNotNull;
22  import static org.junit.jupiter.api.Assertions.assertThrows;
23  
24  import java.io.ByteArrayInputStream;
25  import java.io.InputStream;
26  import java.io.InputStreamReader;
27  import java.io.OutputStream;
28  import java.io.OutputStreamWriter;
29  import java.io.Reader;
30  import java.io.Writer;
31  import java.net.URL;
32  import java.nio.charset.StandardCharsets;
33  import java.nio.file.Files;
34  import java.nio.file.Paths;
35  
36  import org.apache.commons.io.file.TempFile;
37  import org.apache.commons.io.input.NullInputStream;
38  import org.apache.commons.io.input.NullReader;
39  import org.apache.commons.io.output.ByteArrayOutputStream;
40  import org.apache.commons.io.output.NullOutputStream;
41  import org.apache.commons.io.output.NullWriter;
42  import org.apache.commons.io.test.TestUtils;
43  import org.apache.commons.io.test.ThrowOnCloseInputStream;
44  import org.apache.commons.io.test.ThrowOnFlushAndCloseOutputStream;
45  import org.junit.jupiter.api.Test;
46  
47  /**
48   * Tests {@link IOUtils} copy methods.
49   */
50  public class IOUtilsCopyTest {
51  
52      /*
53       * NOTE this is not particularly beautiful code. A better way to check for
54       * flush and close status would be to implement "trojan horse" wrapper
55       * implementations of the various stream classes, which set a flag when
56       * relevant methods are called. (JT)
57       */
58  
59      private static final int FILE_SIZE = 1024 * 4 + 1;
60  
61      private final byte[] inData = TestUtils.generateTestData(FILE_SIZE);
62  
63      @SuppressWarnings("resource") // 'in' is deliberately not closed
64      @Test
65      public void testCopy_byteArrayOutputStreamToInputStream() throws Exception {
66          final java.io.ByteArrayOutputStream out = new java.io.ByteArrayOutputStream();
67          out.write(inData);
68  
69          final InputStream in = IOUtils.copy(out);
70  
71          final byte[] inData2 = new byte[FILE_SIZE];
72          final int inSize = in.read(inData2);
73  
74          assertEquals(0, in.available(), "Not all bytes were read");
75          assertEquals(inData.length, inSize, "Sizes differ");
76          assertArrayEquals(inData, inData2, "Content differs");
77      }
78  
79      @Test
80      public void testCopy_byteArrayOutputStreamToInputStream_nullOutputStream() {
81          assertThrows(NullPointerException.class, () -> IOUtils.copy(null));
82      }
83  
84      @SuppressWarnings("resource") // 'in' is deliberately not closed
85      @Test
86      public void testCopy_inputStreamToOutputStream() throws Exception {
87          InputStream in = new ByteArrayInputStream(inData);
88          in = new ThrowOnCloseInputStream(in);
89  
90          final ByteArrayOutputStream baout = new ByteArrayOutputStream();
91          final OutputStream out = new ThrowOnFlushAndCloseOutputStream(baout, false, true);
92  
93          final int count = IOUtils.copy(in, out);
94  
95          assertEquals(0, in.available(), "Not all bytes were read");
96          assertEquals(inData.length, baout.size(), "Sizes differ");
97          assertArrayEquals(inData, baout.toByteArray(), "Content differs");
98          assertEquals(inData.length, count);
99      }
100 
101     /**
102      * Test Copying file > 2GB - see issue# IO-84
103      */
104     @Test
105     public void testCopy_inputStreamToOutputStream_IO84() throws Exception {
106         final long size = (long) Integer.MAX_VALUE + (long) 1;
107         final InputStream in = new NullInputStream(size);
108         final OutputStream out = NullOutputStream.INSTANCE;
109 
110         // Test copy() method
111         assertEquals(-1, IOUtils.copy(in, out));
112 
113         // reset the input
114         in.close();
115 
116         // Test copyLarge() method
117         assertEquals(size, IOUtils.copyLarge(in, out), "copyLarge()");
118     }
119 
120     @Test
121     public void testCopy_inputStreamToOutputStream_nullIn() {
122         final OutputStream out = new ByteArrayOutputStream();
123         assertThrows(NullPointerException.class, () -> IOUtils.copy((InputStream) null, out));
124     }
125 
126     @Test
127     public void testCopy_inputStreamToOutputStream_nullOut() {
128         final InputStream in = new ByteArrayInputStream(inData);
129         assertThrows(NullPointerException.class, () -> IOUtils.copy(in, (OutputStream) null));
130     }
131 
132     @Test
133     public void testCopy_inputStreamToOutputStreamWithBufferSize() throws Exception {
134         testCopy_inputStreamToOutputStreamWithBufferSize(1);
135         testCopy_inputStreamToOutputStreamWithBufferSize(2);
136         testCopy_inputStreamToOutputStreamWithBufferSize(4);
137         testCopy_inputStreamToOutputStreamWithBufferSize(8);
138         testCopy_inputStreamToOutputStreamWithBufferSize(16);
139         testCopy_inputStreamToOutputStreamWithBufferSize(32);
140         testCopy_inputStreamToOutputStreamWithBufferSize(64);
141         testCopy_inputStreamToOutputStreamWithBufferSize(128);
142         testCopy_inputStreamToOutputStreamWithBufferSize(256);
143         testCopy_inputStreamToOutputStreamWithBufferSize(512);
144         testCopy_inputStreamToOutputStreamWithBufferSize(1024);
145         testCopy_inputStreamToOutputStreamWithBufferSize(2048);
146         testCopy_inputStreamToOutputStreamWithBufferSize(4096);
147         testCopy_inputStreamToOutputStreamWithBufferSize(8192);
148         testCopy_inputStreamToOutputStreamWithBufferSize(16384);
149     }
150 
151     @SuppressWarnings("resource") // 'in' is deliberately not closed
152     private void testCopy_inputStreamToOutputStreamWithBufferSize(final int bufferSize) throws Exception {
153         InputStream in = new ByteArrayInputStream(inData);
154         in = new ThrowOnCloseInputStream(in);
155 
156         final ByteArrayOutputStream baout = new ByteArrayOutputStream();
157         final OutputStream out = new ThrowOnFlushAndCloseOutputStream(baout, false, true);
158 
159         final long count = IOUtils.copy(in, out, bufferSize);
160 
161         assertEquals(0, in.available(), "Not all bytes were read");
162         assertEquals(inData.length, baout.size(), "Sizes differ");
163         assertArrayEquals(inData, baout.toByteArray(), "Content differs");
164         assertEquals(inData.length, count);
165     }
166 
167     @SuppressWarnings({ "resource", "deprecation" }) // 'in' is deliberately not closed
168     @Test
169     public void testCopy_inputStreamToWriter() throws Exception {
170         InputStream in = new ByteArrayInputStream(inData);
171         in = new ThrowOnCloseInputStream(in);
172 
173         final ByteArrayOutputStream baout = new ByteArrayOutputStream();
174         final ThrowOnFlushAndCloseOutputStream out = new ThrowOnFlushAndCloseOutputStream(baout, true, true);
175         final Writer writer = new OutputStreamWriter(baout, StandardCharsets.US_ASCII);
176 
177         IOUtils.copy(in, writer); // deliberately testing deprecated method
178         out.off();
179         writer.flush();
180 
181         assertEquals(0, in.available(), "Not all bytes were read");
182         assertEquals(inData.length, baout.size(), "Sizes differ");
183         assertArrayEquals(inData, baout.toByteArray(), "Content differs");
184     }
185 
186     @SuppressWarnings("resource") // 'in' is deliberately not closed
187     @Test
188     public void testCopy_inputStreamToWriter_Encoding() throws Exception {
189         InputStream in = new ByteArrayInputStream(inData);
190         in = new ThrowOnCloseInputStream(in);
191 
192         final ByteArrayOutputStream baout = new ByteArrayOutputStream();
193         final ThrowOnFlushAndCloseOutputStream out = new ThrowOnFlushAndCloseOutputStream(baout, true, true);
194         final Writer writer = new OutputStreamWriter(baout, StandardCharsets.US_ASCII);
195 
196         IOUtils.copy(in, writer, "UTF8");
197         out.off();
198         writer.flush();
199 
200         assertEquals(0, in.available(), "Not all bytes were read");
201         byte[] bytes = baout.toByteArray();
202         bytes = new String(bytes, StandardCharsets.UTF_8).getBytes(StandardCharsets.US_ASCII);
203         assertArrayEquals(inData, bytes, "Content differs");
204     }
205 
206     @SuppressWarnings("resource") // 'in' is deliberately not closed
207     @Test
208     public void testCopy_inputStreamToWriter_Encoding_nullEncoding() throws Exception {
209         InputStream in = new ByteArrayInputStream(inData);
210         in = new ThrowOnCloseInputStream(in);
211 
212         final ByteArrayOutputStream baout = new ByteArrayOutputStream();
213         final ThrowOnFlushAndCloseOutputStream out = new ThrowOnFlushAndCloseOutputStream(baout, true, true);
214         final Writer writer = new OutputStreamWriter(baout, StandardCharsets.US_ASCII);
215 
216         IOUtils.copy(in, writer, (String) null);
217         out.off();
218         writer.flush();
219 
220         assertEquals(0, in.available(), "Not all bytes were read");
221         assertEquals(inData.length, baout.size(), "Sizes differ");
222         assertArrayEquals(inData, baout.toByteArray(), "Content differs");
223     }
224 
225     @Test
226     public void testCopy_inputStreamToWriter_Encoding_nullIn() {
227         final ByteArrayOutputStream baout = new ByteArrayOutputStream();
228         final OutputStream out = new ThrowOnFlushAndCloseOutputStream(baout, true, true);
229         final Writer writer = new OutputStreamWriter(out, StandardCharsets.US_ASCII);
230         assertThrows(NullPointerException.class, () -> IOUtils.copy(null, writer, "UTF8"));
231     }
232 
233     @Test
234     public void testCopy_inputStreamToWriter_Encoding_nullOut() {
235         final InputStream in = new ByteArrayInputStream(inData);
236         assertThrows(NullPointerException.class, () -> IOUtils.copy(in, null, "UTF8"));
237     }
238 
239     @SuppressWarnings("deprecation") // deliberately testing deprecated method
240     @Test
241     public void testCopy_inputStreamToWriter_nullIn() {
242         final ByteArrayOutputStream baout = new ByteArrayOutputStream();
243         final OutputStream out = new ThrowOnFlushAndCloseOutputStream(baout, true, true);
244         final Writer writer = new OutputStreamWriter(out, StandardCharsets.US_ASCII);
245         assertThrows(NullPointerException.class, () -> IOUtils.copy((InputStream) null, writer));
246     }
247 
248     @SuppressWarnings("deprecation") // deliberately testing deprecated method
249     @Test
250     public void testCopy_inputStreamToWriter_nullOut() {
251         final InputStream in = new ByteArrayInputStream(inData);
252         assertThrows(NullPointerException.class, () -> IOUtils.copy(in, (Writer) null)); // deliberately testing deprecated method
253     }
254 
255     @SuppressWarnings("resource") // 'in' is deliberately not closed
256     @Test
257     public void testCopy_readerToAppendable() throws Exception {
258         InputStream in = new ByteArrayInputStream(inData);
259         in = new ThrowOnCloseInputStream(in);
260         final Reader reader = new InputStreamReader(in, StandardCharsets.US_ASCII);
261 
262         final ByteArrayOutputStream baout = new ByteArrayOutputStream();
263         final ThrowOnFlushAndCloseOutputStream out = new ThrowOnFlushAndCloseOutputStream(baout, true, true);
264         final Writer writer = new OutputStreamWriter(baout, StandardCharsets.US_ASCII);
265 
266         final long count = IOUtils.copy(reader, (Appendable) writer);
267         out.off();
268         writer.flush();
269         assertEquals(inData.length, count, "The number of characters returned by copy is wrong");
270         assertEquals(inData.length, baout.size(), "Sizes differ");
271         assertArrayEquals(inData, baout.toByteArray(), "Content differs");
272     }
273 
274     @Test
275     public void testCopy_readerToAppendable_IO84() throws Exception {
276         final long size = (long) Integer.MAX_VALUE + (long) 1;
277         final Reader reader = new NullReader(size);
278         final NullWriter writer = new NullWriter();
279 
280         // Test copy() method
281         assertEquals(size, IOUtils.copy(reader, (Appendable) writer));
282 
283         // reset the input
284         reader.close();
285 
286         // Test copyLarge() method
287         assertEquals(size, IOUtils.copyLarge(reader, writer), "copy()");
288     }
289 
290     @Test
291     public void testCopy_readerToAppendable_nullIn() {
292         final ByteArrayOutputStream baout = new ByteArrayOutputStream();
293         final OutputStream out = new ThrowOnFlushAndCloseOutputStream(baout, true, true);
294         final Appendable writer = new OutputStreamWriter(out, StandardCharsets.US_ASCII);
295         assertThrows(NullPointerException.class, () -> IOUtils.copy(null, writer));
296     }
297 
298     @SuppressWarnings("resource") // 'in' is deliberately not closed
299     @Test
300     public void testCopy_readerToAppendable_nullOut() {
301         InputStream in = new ByteArrayInputStream(inData);
302         in = new ThrowOnCloseInputStream(in);
303         final Reader reader = new InputStreamReader(in, StandardCharsets.US_ASCII);
304         assertThrows(NullPointerException.class, () -> IOUtils.copy(reader, (Appendable) null));
305     }
306 
307     @SuppressWarnings({ "resource", "deprecation" }) // 'in' is deliberately not closed
308     @Test
309     public void testCopy_readerToOutputStream() throws Exception {
310         InputStream in = new ByteArrayInputStream(inData);
311         in = new ThrowOnCloseInputStream(in);
312         final Reader reader = new InputStreamReader(in, StandardCharsets.US_ASCII);
313 
314         final ByteArrayOutputStream baout = new ByteArrayOutputStream();
315         final OutputStream out = new ThrowOnFlushAndCloseOutputStream(baout, false, true);
316 
317         IOUtils.copy(reader, out); // deliberately testing deprecated method
318         //Note: this method *does* flush. It is equivalent to:
319         //  OutputStreamWriter _out = new OutputStreamWriter(fout);
320         //  IOUtils.copy( fin, _out, 4096 ); // copy( Reader, Writer, int );
321         //  _out.flush();
322         //  out = fout;
323 
324         // Note: rely on the method to flush
325         assertEquals(inData.length, baout.size(), "Sizes differ");
326         assertArrayEquals(inData, baout.toByteArray(), "Content differs");
327     }
328 
329     @SuppressWarnings("resource") // 'in' is deliberately not closed
330     @Test
331     public void testCopy_readerToOutputStream_Encoding() throws Exception {
332         InputStream in = new ByteArrayInputStream(inData);
333         in = new ThrowOnCloseInputStream(in);
334         final Reader reader = new InputStreamReader(in, StandardCharsets.US_ASCII);
335 
336         final ByteArrayOutputStream baout = new ByteArrayOutputStream();
337         final OutputStream out = new ThrowOnFlushAndCloseOutputStream(baout, false, true);
338 
339         IOUtils.copy(reader, out, "UTF16");
340         // note: this method *does* flush.
341         // note: we don't flush here; this IOUtils method does it for us
342 
343         byte[] bytes = baout.toByteArray();
344         bytes = new String(bytes, StandardCharsets.UTF_16).getBytes(StandardCharsets.US_ASCII);
345         assertArrayEquals(inData, bytes, "Content differs");
346     }
347 
348     @SuppressWarnings("resource") // 'in' is deliberately not closed
349     @Test
350     public void testCopy_readerToOutputStream_Encoding_nullEncoding() throws Exception {
351         InputStream in = new ByteArrayInputStream(inData);
352         in = new ThrowOnCloseInputStream(in);
353         final Reader reader = new InputStreamReader(in, StandardCharsets.US_ASCII);
354 
355         final ByteArrayOutputStream baout = new ByteArrayOutputStream();
356         final OutputStream out = new ThrowOnFlushAndCloseOutputStream(baout, false, true);
357 
358         IOUtils.copy(reader, out, (String) null);
359         // note: this method *does* flush.
360         // note: we don't flush here; this IOUtils method does it for us
361 
362         assertEquals(inData.length, baout.size(), "Sizes differ");
363         assertArrayEquals(inData, baout.toByteArray(), "Content differs");
364     }
365 
366     @Test
367     public void testCopy_readerToOutputStream_Encoding_nullIn() {
368         final ByteArrayOutputStream baout = new ByteArrayOutputStream();
369         final OutputStream out = new ThrowOnFlushAndCloseOutputStream(baout, true, true);
370         assertThrows(NullPointerException.class, () -> IOUtils.copy(null, out, "UTF16"));
371     }
372 
373     @SuppressWarnings("resource") // 'in' is deliberately not closed
374     @Test
375     public void testCopy_readerToOutputStream_Encoding_nullOut() {
376         InputStream in = new ByteArrayInputStream(inData);
377         in = new ThrowOnCloseInputStream(in);
378         final Reader reader = new InputStreamReader(in, StandardCharsets.US_ASCII);
379         assertThrows(NullPointerException.class, () -> IOUtils.copy(reader, null, "UTF16"));
380     }
381 
382     @SuppressWarnings("deprecation")
383     @Test
384     public void testCopy_readerToOutputStream_nullIn() { // deliberately testing deprecated method
385         final ByteArrayOutputStream baout = new ByteArrayOutputStream();
386         final OutputStream out = new ThrowOnFlushAndCloseOutputStream(baout, true, true);
387         assertThrows(NullPointerException.class, () -> IOUtils.copy((Reader) null, out));
388     }
389 
390     @SuppressWarnings({ "resource", "deprecation" }) // 'in' is deliberately not closed
391     @Test
392     public void testCopy_readerToOutputStream_nullOut() {
393         InputStream in = new ByteArrayInputStream(inData);
394         in = new ThrowOnCloseInputStream(in);
395         final Reader reader = new InputStreamReader(in, StandardCharsets.US_ASCII);
396         assertThrows(NullPointerException.class, () -> IOUtils.copy(reader, (OutputStream) null)); // deliberately testing deprecated method
397     }
398 
399     @SuppressWarnings("resource") // 'in' is deliberately not closed
400     @Test
401     public void testCopy_readerToWriter() throws Exception {
402         InputStream in = new ByteArrayInputStream(inData);
403         in = new ThrowOnCloseInputStream(in);
404         final Reader reader = new InputStreamReader(in, StandardCharsets.US_ASCII);
405 
406         final ByteArrayOutputStream baout = new ByteArrayOutputStream();
407         final ThrowOnFlushAndCloseOutputStream out = new ThrowOnFlushAndCloseOutputStream(baout, true, true);
408         final Writer writer = new OutputStreamWriter(baout, StandardCharsets.US_ASCII);
409 
410         final int count = IOUtils.copy(reader, writer);
411         out.off();
412         writer.flush();
413         assertEquals(inData.length, count, "The number of characters returned by copy is wrong");
414         assertEquals(inData.length, baout.size(), "Sizes differ");
415         assertArrayEquals(inData, baout.toByteArray(), "Content differs");
416     }
417 
418     /**
419      * Tests Copying file > 2GB  - see issue# IO-84
420      */
421     @Test
422     public void testCopy_readerToWriter_IO84() throws Exception {
423         final long size = (long) Integer.MAX_VALUE + (long) 1;
424         final Reader reader = new NullReader(size);
425         final Writer writer = new NullWriter();
426 
427         // Test copy() method
428         assertEquals(-1, IOUtils.copy(reader, writer));
429 
430         // reset the input
431         reader.close();
432 
433         // Test copyLarge() method
434         assertEquals(size, IOUtils.copyLarge(reader, writer), "copyLarge()");
435     }
436 
437     @Test
438     public void testCopy_readerToWriter_nullIn() {
439         final ByteArrayOutputStream baout = new ByteArrayOutputStream();
440         final OutputStream out = new ThrowOnFlushAndCloseOutputStream(baout, true, true);
441         final Writer writer = new OutputStreamWriter(out, StandardCharsets.US_ASCII);
442         assertThrows(NullPointerException.class, () -> IOUtils.copy((Reader) null, writer));
443     }
444 
445     @SuppressWarnings("resource") // 'in' is deliberately not closed
446     @Test
447     public void testCopy_readerToWriter_nullOut() {
448         InputStream in = new ByteArrayInputStream(inData);
449         in = new ThrowOnCloseInputStream(in);
450         final Reader reader = new InputStreamReader(in, StandardCharsets.US_ASCII);
451         assertThrows(NullPointerException.class, () -> IOUtils.copy(reader, (Writer) null));
452     }
453 
454     @Test
455     public void testCopy_URLToFile() throws Exception {
456         final String name = "/org/apache/commons/io/abitmorethan16k.txt";
457         final URL in = getClass().getResource(name);
458         assertNotNull(in, name);
459 
460         try (TempFile path = TempFile.create("testCopy_URLToFile", ".txt")) {
461             IOUtils.copy(in, path.toFile());
462             assertArrayEquals(Files.readAllBytes(Paths.get("src/test/resources" + name)), Files.readAllBytes(path.get()));
463         }
464     }
465 
466     @Test
467     public void testCopy_URLToOutputStream() throws Exception {
468         final String name = "/org/apache/commons/io/abitmorethan16k.txt";
469         final URL in = getClass().getResource(name);
470         assertNotNull(in, name);
471 
472         final ByteArrayOutputStream baout = new ByteArrayOutputStream();
473         IOUtils.copy(in, baout);
474 
475         assertArrayEquals(Files.readAllBytes(Paths.get("src/test/resources" + name)), baout.toByteArray());
476     }
477 
478 }