diff --git a/src/main/java/org/apache/commons/io/FileUtils.java b/src/main/java/org/apache/commons/io/FileUtils.java index 523c2c1fd26..a24ab813a73 100644 --- a/src/main/java/org/apache/commons/io/FileUtils.java +++ b/src/main/java/org/apache/commons/io/FileUtils.java @@ -2290,7 +2290,7 @@ public static LineIterator lineIterator(final File file, final String charsetNam inputStream = Files.newInputStream(file.toPath()); return IOUtils.lineIterator(inputStream, charsetName); } catch (final IOException | RuntimeException ex) { - IOUtils.closeQuietly(inputStream, ex::addSuppressed); + IOUtils.closeQuietly(inputStream, ex); throw ex; } } diff --git a/src/main/java/org/apache/commons/io/IOUtils.java b/src/main/java/org/apache/commons/io/IOUtils.java index 09a018b036d..8a4057ae514 100644 --- a/src/main/java/org/apache/commons/io/IOUtils.java +++ b/src/main/java/org/apache/commons/io/IOUtils.java @@ -780,7 +780,7 @@ public static void close(final URLConnection conn) { * @param closeable the object to close, may be null. */ private static void closeQ(final Closeable closeable) { - closeQuietly(closeable, null); + closeQuietly(closeable, (Consumer) null); } /** @@ -824,7 +824,40 @@ private static void closeQ(final Closeable closeable) { * @see Throwable#addSuppressed(Throwable) */ public static void closeQuietly(final Closeable closeable) { - closeQuietly(closeable, null); + closeQuietly(closeable, (Consumer) null); + } + + /** + * Closes a {@link Closeable} unconditionally and adds any exception thrown by the {@code close()} to the given Throwable. + * + *

+ * For example: + *

+ * + *
+     * Closeable closeable = ...;
+     * try {
+     *     // process closeable
+     *     closeable.close();
+     * } catch (Exception e) {
+     *     // error handling
+     *     throw IOUtils.closeQuietly(closeable, e);
+     * }
+     * 
+ *

+ * Also consider using a try-with-resources statement where appropriate. + *

+ * + * @param The Throwable type. + * @param closeable The object to close, may be null or already closed. + * @param throwable Add the exception throw by the closeable to the given Throwable. + * @return The given Throwable. + * @since 2.22.0 + * @see Throwable#addSuppressed(Throwable) + */ + public static T closeQuietly(final Closeable closeable, final T throwable) { + closeQuietly(closeable, (Consumer) throwable::addSuppressed); + return throwable; } /** diff --git a/src/main/java/org/apache/commons/io/LineIterator.java b/src/main/java/org/apache/commons/io/LineIterator.java index 293847e7a72..2e1bf0169ab 100644 --- a/src/main/java/org/apache/commons/io/LineIterator.java +++ b/src/main/java/org/apache/commons/io/LineIterator.java @@ -135,8 +135,7 @@ public boolean hasNext() { } } } catch (final IOException ioe) { - IOUtils.closeQuietly(this, ioe::addSuppressed); - throw new IllegalStateException(ioe); + throw new IllegalStateException(IOUtils.closeQuietly(this, ioe)); } } diff --git a/src/main/java/org/apache/commons/io/output/FileWriterWithEncoding.java b/src/main/java/org/apache/commons/io/output/FileWriterWithEncoding.java index 42d378cc0a5..1bb15a37a19 100644 --- a/src/main/java/org/apache/commons/io/output/FileWriterWithEncoding.java +++ b/src/main/java/org/apache/commons/io/output/FileWriterWithEncoding.java @@ -188,11 +188,7 @@ private static OutputStreamWriter initWriter(final File file, final Object encod } return new OutputStreamWriter(outputStream, (String) encoding); } catch (final IOException | RuntimeException ex) { - try { - IOUtils.close(outputStream); - } catch (final IOException e) { - ex.addSuppressed(e); - } + IOUtils.closeQuietly(outputStream, ex); if (!fileExistedAlready) { FileUtils.deleteQuietly(file); } diff --git a/src/test/java/org/apache/commons/io/IOUtilsTest.java b/src/test/java/org/apache/commons/io/IOUtilsTest.java index 033fdffdd76..df90ab66e11 100644 --- a/src/test/java/org/apache/commons/io/IOUtilsTest.java +++ b/src/test/java/org/apache/commons/io/IOUtilsTest.java @@ -20,6 +20,7 @@ import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNotSame; import static org.junit.jupiter.api.Assertions.assertNull; @@ -539,6 +540,18 @@ void testCloseQuietly_AllCloseableIOException() { assertDoesNotThrow(() -> IOUtils.closeQuietly((Iterable) null)); } + @SuppressWarnings("resource") + @Test + void testCloseQuietly_CloseableIOExceptionAddSuppressed() { + final Throwable e = new Exception("test").fillInStackTrace(); + assertEquals(0, e.getSuppressed().length); + assertSame(e, IOUtils.closeQuietly(new BrokenInputStream(new EOFException("Suppressed").fillInStackTrace()), e)); + assertEquals(1, e.getSuppressed().length); + final Throwable suppressed0 = e.getSuppressed()[0]; + assertInstanceOf(EOFException.class, suppressed0); + assertEquals("Suppressed", suppressed0.getMessage()); + } + @Test void testCloseQuietly_CloseableException() { // IOException