From 82bf04669fa90728ccae201e145dc941188e1b92 Mon Sep 17 00:00:00 2001 From: Dominik Stadler Date: Fri, 19 Mar 2021 09:40:05 +0000 Subject: [PATCH] Enhance javadoc and coverage for IOUtils git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1887801 13f79535-47bb-0310-9956-ffa450edef68 --- src/java/org/apache/poi/util/IOUtils.java | 18 ++ .../org/apache/poi/util/TestIOUtils.java | 224 ++++++++++++++++-- 2 files changed, 223 insertions(+), 19 deletions(-) diff --git a/src/java/org/apache/poi/util/IOUtils.java b/src/java/org/apache/poi/util/IOUtils.java index 8e69427553..974efdf64b 100644 --- a/src/java/org/apache/poi/util/IOUtils.java +++ b/src/java/org/apache/poi/util/IOUtils.java @@ -232,6 +232,13 @@ public final class IOUtils { /** * Helper method, just calls readFully(in, b, 0, b.length) + * + * @param in the stream from which the data is read. + * @param b the buffer into which the data is read. + * + * @return the number of bytes read or -1 if no bytes were read + * + * @throws IOException if reading from the stream fails */ public static int readFully(InputStream in, byte[] b) throws IOException { return readFully(in, b, 0, b.length); @@ -250,6 +257,10 @@ public final class IOUtils { * @param b the buffer into which the data is read. * @param off the start offset in array b at which the data is written. * @param len the maximum number of bytes to read. + * + * @return the number of bytes read or -1 if no bytes were read + * + * @throws IOException if reading from the stream fails */ public static int readFully(InputStream in, byte[] b, int off, int len) throws IOException { int total = 0; @@ -275,6 +286,13 @@ public final class IOUtils { * number of bytes read. If the end of the file isn't reached before the * buffer has no more remaining capacity, will return the number of bytes * that were read. + * + * @param channel The byte-channel to read data from + * @param b the buffer into which the data is read. + * + * @return the number of bytes read or -1 if no bytes were read + * + * @throws IOException if reading from the stream fails */ public static int readFully(ReadableByteChannel channel, ByteBuffer b) throws IOException { int total = 0; diff --git a/src/testcases/org/apache/poi/util/TestIOUtils.java b/src/testcases/org/apache/poi/util/TestIOUtils.java index c39efacfe2..4c9f6a1fb7 100644 --- a/src/testcases/org/apache/poi/util/TestIOUtils.java +++ b/src/testcases/org/apache/poi/util/TestIOUtils.java @@ -26,6 +26,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.io.EOFException; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; @@ -34,6 +35,7 @@ import java.io.InputStream; import java.io.OutputStream; import java.io.PushbackInputStream; import java.nio.ByteBuffer; +import java.nio.channels.ReadableByteChannel; import java.nio.charset.StandardCharsets; import java.util.Random; @@ -42,29 +44,25 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -/** - * Class to test IOUtils - */ final class TestIOUtils { - private static File TMP; - private static final long LENGTH = 300+new Random().nextInt(9000); + private static final long LENGTH = 300 + new Random().nextInt(9000); @BeforeAll public static void setUp() throws IOException { TMP = File.createTempFile("poi-ioutils-", ""); - OutputStream os = new FileOutputStream(TMP); - for (int i = 0; i < LENGTH; i++) { - os.write(0x01); + try (OutputStream os = new FileOutputStream(TMP)) { + for (int i = 0; i < LENGTH; i++) { + os.write(0x01); + } } - os.flush(); - os.close(); - } @AfterAll public static void tearDown() { - if (TMP != null) assertTrue(TMP.delete()); + if (TMP != null) { + assertTrue(TMP.delete()); + } } private static InputStream data123() { @@ -163,7 +161,7 @@ final class TestIOUtils { void testSkipFullyByteArray() throws IOException { ByteArrayOutputStream bos = new ByteArrayOutputStream(); try (InputStream is = new FileInputStream(TMP)) { - IOUtils.copy(is, bos); + assertEquals(LENGTH, IOUtils.copy(is, bos)); long skipped = IOUtils.skipFully(new ByteArrayInputStream(bos.toByteArray()), 20000L); assertEquals(LENGTH, skipped); } @@ -173,12 +171,41 @@ final class TestIOUtils { void testSkipFullyByteArrayGtIntMax() throws IOException { ByteArrayOutputStream bos = new ByteArrayOutputStream(); try (InputStream is = new FileInputStream(TMP)) { - IOUtils.copy(is, bos); + assertEquals(LENGTH, IOUtils.copy(is, bos)); long skipped = IOUtils.skipFully(new ByteArrayInputStream(bos.toByteArray()), Integer.MAX_VALUE + 20000L); assertEquals(LENGTH, skipped); } } + @Test + void testCopyToFile() throws IOException { + File dest = File.createTempFile("poi-ioutils-", ""); + try { + try (InputStream is = new FileInputStream(TMP)) { + assertEquals(LENGTH, IOUtils.copy(is, dest)); + } + + try (FileInputStream strOrig = new FileInputStream(TMP); + FileInputStream strDest = new FileInputStream(dest)) { + byte[] bytesOrig = new byte[(int)LENGTH]; + byte[] bytesDest = new byte[(int)LENGTH]; + IOUtils.readFully(strOrig, bytesOrig); + IOUtils.readFully(strDest, bytesDest); + assertArrayEquals(bytesOrig, bytesDest); + } + } finally { + assertTrue(dest.delete()); + } + } + + @Test + void testCopyToInvalidFile() throws IOException { + try (InputStream is = new FileInputStream(TMP)) { + assertThrows(RuntimeException.class, + () -> IOUtils.copy(is, new File("/notexisting/directory/structure"))); + } + } + @Test void testSkipFullyBug61294() throws IOException { long skipped = IOUtils.skipFully(new ByteArrayInputStream(new byte[0]), 1); @@ -292,7 +319,7 @@ final class TestIOUtils { } @Test - void testSetMaxOverrideOverLimitWithLength() throws IOException { + void testSetMaxOverrideOverLimitWithLength() { IOUtils.setByteArrayMaxOverride(2); try { ByteArrayInputStream stream = new ByteArrayInputStream("abc".getBytes(StandardCharsets.UTF_8)); @@ -324,31 +351,92 @@ final class TestIOUtils { @Test void testReadFully() throws IOException { byte[] bytes = new byte[2]; - IOUtils.readFully(new ByteArrayInputStream(new byte[] {1, 2, 3}), bytes, 0, 2); + assertEquals(2, IOUtils.readFully(new ByteArrayInputStream(new byte[] {1, 2, 3}), bytes, 0, 2)); assertArrayEquals(new byte[] {1,2}, bytes); } + @Test + void testReadFullyEOF() throws IOException { + byte[] bytes = new byte[2]; + assertEquals(2, IOUtils.readFully(new NullInputStream(2), bytes, 0, 4)); + assertArrayEquals(new byte[] {0,0}, bytes); + } + + @Test + void testReadFullyEOFZero() throws IOException { + byte[] bytes = new byte[2]; + assertEquals(-1, IOUtils.readFully(new NullInputStream(0), bytes, 0, 4)); + assertArrayEquals(new byte[] {0,0}, bytes); + } + @Test void testReadFullySimple() throws IOException { byte[] bytes = new byte[2]; - IOUtils.readFully(new ByteArrayInputStream(new byte[] {1, 2, 3}), bytes); + assertEquals(2, IOUtils.readFully(new ByteArrayInputStream(new byte[] {1, 2, 3}), bytes)); assertArrayEquals(new byte[] {1,2}, bytes); } @Test void testReadFullyOffset() throws IOException { byte[] bytes = new byte[3]; - IOUtils.readFully(new ByteArrayInputStream(new byte[] {1, 2, 3}), bytes, 1, 2); + assertEquals(2, IOUtils.readFully(new ByteArrayInputStream(new byte[] {1, 2, 3}), bytes, 1, 2)); assertArrayEquals(new byte[] {0, 1,2}, bytes); } @Test void testReadFullyAtLength() throws IOException { byte[] bytes = new byte[3]; - IOUtils.readFully(new ByteArrayInputStream(new byte[] {1, 2, 3}), bytes, 0, 3); + assertEquals(3, IOUtils.readFully(new ByteArrayInputStream(new byte[] {1, 2, 3}), bytes, 0, 3)); assertArrayEquals(new byte[] {1,2, 3}, bytes); } + + @Test + void testReadFullyChannel() throws IOException { + ByteBuffer bytes = ByteBuffer.allocate(2); + assertEquals(2, IOUtils.readFully(new SimpleByteChannel(new byte[]{1, 2, 3}), bytes)); + assertArrayEquals(new byte[] {1,2}, bytes.array()); + assertEquals(2, bytes.position()); + } + + @Test + void testReadFullyChannelEOF() throws IOException { + ByteBuffer bytes = ByteBuffer.allocate(2); + assertEquals(-1, IOUtils.readFully(new EOFByteChannel(false), bytes)); + assertArrayEquals(new byte[] {0,0}, bytes.array()); + assertEquals(0, bytes.position()); + } + + @Test + void testReadFullyChannelEOFException() { + ByteBuffer bytes = ByteBuffer.allocate(2); + assertThrows(IOException.class, + () -> IOUtils.readFully(new EOFByteChannel(true), bytes)); + } + + @Test + void testReadFullyChannelSimple() throws IOException { + ByteBuffer bytes = ByteBuffer.allocate(2); + assertEquals(2, IOUtils.readFully(new SimpleByteChannel(new byte[] {1, 2, 3}), bytes)); + assertArrayEquals(new byte[] {1,2}, bytes.array()); + assertEquals(2, bytes.position()); + } + + @Test + public void testChecksum() { + assertEquals(0L, IOUtils.calculateChecksum(new byte[0])); + assertEquals(3057449933L, IOUtils.calculateChecksum(new byte[] { 1, 2, 3, 4})); + } + + @Test + public void testChecksumStream() throws IOException { + assertEquals(0L, IOUtils.calculateChecksum(new NullInputStream(0))); + assertEquals(0L, IOUtils.calculateChecksum(new NullInputStream(1))); + assertEquals(3057449933L, IOUtils.calculateChecksum(new ByteArrayInputStream(new byte[] { 1, 2, 3, 4}))); + assertThrows(EOFException.class, + () -> IOUtils.calculateChecksum(new NullInputStream(1, true))); + } + /** * This returns 0 for the first call to skip and then reads * as requested. This tests that the fallback to read() works. @@ -386,4 +474,102 @@ final class TestIOUtils { return 100000; } } + + private static class EOFByteChannel implements ReadableByteChannel { + private final boolean throwException; + + public EOFByteChannel(boolean throwException) { + this.throwException = throwException; + } + + @Override + public int read(ByteBuffer dst) throws IOException { + if (throwException) { + throw new IOException("EOF"); + } + + return -1; + } + + @Override + public boolean isOpen() { + return false; + } + + @Override + public void close() throws IOException { + + } + } + + private static class SimpleByteChannel extends InputStream implements ReadableByteChannel { + private final byte[] bytes; + + public SimpleByteChannel(byte[] bytes) { + this.bytes = bytes; + } + + @Override + public int read() throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public int read(ByteBuffer dst) throws IOException { + int toRead = Math.min(bytes.length, dst.capacity()); + dst.put(bytes, 0, toRead); + return toRead; + } + + @Override + public boolean isOpen() { + return false; + } + } + + public class NullInputStream extends InputStream { + private final int bytes; + private final boolean exception; + + private int position; + + public NullInputStream(int bytes) { + this(bytes, false); + } + + public NullInputStream(int bytes, boolean exception) { + this.bytes = bytes; + this.exception = exception; + } + + @Override + public int read() throws IOException { + if (position >= bytes) { + return handleReturn(); + } + + position++; + return 0; + } + + private int handleReturn() throws EOFException { + if (exception) { + throw new EOFException(); + } else { + return -1; + } + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + int toRead = Math.min(b.length, len); + if (toRead > (bytes - position)) { + return handleReturn(); + } + toRead = Math.min(toRead, (bytes - position)); + + position += toRead; + return toRead; + } + } } -- 2.39.5