This is an automated email from the ASF dual-hosted git repository.

ggregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-io.git


The following commit(s) were added to refs/heads/master by this push:
     new 4aab769c Reduce boilerplate through new UncheckedIO class and friends 
in org.apache.commons.io.function.
4aab769c is described below

commit 4aab769c3279ea63b7bcdaa163c50761bc540f8c
Author: Gary Gregory <[email protected]>
AuthorDate: Fri May 13 17:55:10 2022 -0400

    Reduce boilerplate through new UncheckedIO class and friends in
    org.apache.commons.io.function.
---
 src/changes/changes.xml                            |   3 +
 src/main/java/org/apache/commons/io/FileUtils.java | 108 +++-----------
 .../java/org/apache/commons/io/UncheckedIO.java    | 158 +++++++++++++++++++++
 .../apache/commons/io/UncheckedIOExceptions.java   |   9 +-
 .../java/org/apache/commons/io/file/PathUtils.java |   8 +-
 .../apache/commons/io/function/IOBiFunction.java   |  81 +++++++++++
 .../org/apache/commons/io/function/IORunnable.java |  36 +++++
 .../apache/commons/io/function/IOTriFunction.java  |  83 +++++++++++
 .../commons/io/input/UncheckedBufferedReader.java  |  66 ++-------
 .../io/input/UncheckedFilterInputStream.java       |  48 ++-----
 .../commons/io/input/UncheckedFilterReader.java    |  60 ++------
 .../commons/io/output/UncheckedAppendableImpl.java |  22 +--
 .../commons/io/UncheckedIOExceptionsTest.java      |  16 ++-
 .../org/apache/commons/io/UncheckedIOTest.java     | 102 +++++++++++++
 .../commons/io/function/IOBiFunctionTest.java      |  92 ++++++++++++
 .../IORunnableTest.java}                           |  41 +++---
 .../commons/io/function/IOTriFunctionTest.java     | 112 +++++++++++++++
 17 files changed, 761 insertions(+), 284 deletions(-)

diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 936d9bb7..817b953d 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -353,6 +353,9 @@ The <action> type attribute can be add,update,fix,remove.
       <action issue="IO-680" dev="ggregory" type="add" due-to="XenoAmess, 
sebbASF, Gary Gregory">
         Add more tests for IOUtils.contentEqualsIgnoreEOL #137.
       </action>
+      <action dev="ggregory" type="add" due-to="Gary Gregory">
+        Reduce boilerplate through new UncheckedIO class and friends in 
org.apache.commons.io.function.
+      </action>
       <!-- UPDATE -->
       <action dev="ggregory" type="update" due-to="Dependabot">
         Bump actions/checkout from 2.3.4 to 3 #286, #298, #330.
diff --git a/src/main/java/org/apache/commons/io/FileUtils.java 
b/src/main/java/org/apache/commons/io/FileUtils.java
index a4cbdfe7..f5a04f16 100644
--- a/src/main/java/org/apache/commons/io/FileUtils.java
+++ b/src/main/java/org/apache/commons/io/FileUtils.java
@@ -1632,12 +1632,9 @@ public class FileUtils {
      * @since 2.8.0
      */
     public static boolean isFileNewer(final File file, final 
ChronoZonedDateTime<?> chronoZonedDateTime) {
+        Objects.requireNonNull(file, "file");
         Objects.requireNonNull(chronoZonedDateTime, "chronoZonedDateTime");
-        try {
-            return PathUtils.isNewer(file.toPath(), chronoZonedDateTime);
-        } catch (final IOException e) {
-            throw new UncheckedIOException(e);
-        }
+        return UncheckedIO.get(() -> PathUtils.isNewer(file.toPath(), 
chronoZonedDateTime));
     }
 
     /**
@@ -1666,11 +1663,7 @@ public class FileUtils {
      */
     public static boolean isFileNewer(final File file, final File reference) {
         requireExists(reference, "reference");
-        try {
-            return PathUtils.isNewer(file.toPath(), reference.toPath());
-        } catch (final IOException e) {
-            throw new UncheckedIOException(e);
-        }
+        return UncheckedIO.get(() -> PathUtils.isNewer(file.toPath(), 
reference.toPath()));
     }
 
     /**
@@ -1699,11 +1692,7 @@ public class FileUtils {
      */
     public static boolean isFileNewer(final File file, final Instant instant) {
         Objects.requireNonNull(instant, "instant");
-        try {
-            return PathUtils.isNewer(file.toPath(), instant);
-        } catch (final IOException e) {
-            throw new UncheckedIOException(e);
-        }
+        return UncheckedIO.get(() -> PathUtils.isNewer(file.toPath(), 
instant));
     }
 
     /**
@@ -1717,11 +1706,7 @@ public class FileUtils {
      */
     public static boolean isFileNewer(final File file, final long timeMillis) {
         Objects.requireNonNull(file, "file");
-        try {
-            return PathUtils.isNewer(file.toPath(), timeMillis);
-        } catch (final IOException e) {
-            throw new UncheckedIOException(e);
-        }
+        return UncheckedIO.get(() -> PathUtils.isNewer(file.toPath(), 
timeMillis));
     }
 
     /**
@@ -1854,11 +1839,7 @@ public class FileUtils {
      */
     public static boolean isFileOlder(final File file, final File reference) {
         requireExists(reference, "reference");
-        try {
-            return PathUtils.isOlder(file.toPath(), reference.toPath());
-        } catch (final IOException e) {
-            throw new UncheckedIOException(e);
-        }
+        return UncheckedIO.get(() -> PathUtils.isOlder(file.toPath(), 
reference.toPath()));
     }
 
     /**
@@ -1887,11 +1868,7 @@ public class FileUtils {
      */
     public static boolean isFileOlder(final File file, final Instant instant) {
         Objects.requireNonNull(instant, "instant");
-        try {
-            return PathUtils.isOlder(file.toPath(), instant);
-        } catch (final IOException e) {
-            throw new UncheckedIOException(e);
-        }
+        return UncheckedIO.get(() -> PathUtils.isOlder(file.toPath(), 
instant));
     }
 
     /**
@@ -1905,11 +1882,7 @@ public class FileUtils {
      */
     public static boolean isFileOlder(final File file, final long timeMillis) {
         Objects.requireNonNull(file, "file");
-        try {
-            return PathUtils.isOlder(file.toPath(), timeMillis);
-        } catch (final IOException e) {
-            throw new UncheckedIOException(e);
-        }
+        return UncheckedIO.get(() -> PathUtils.isOlder(file.toPath(), 
timeMillis));
     }
 
     /**
@@ -1984,11 +1957,7 @@ public class FileUtils {
      * @since 1.2
      */
     public static Iterator<File> iterateFiles(final File directory, final 
String[] extensions, final boolean recursive) {
-        try {
-            return StreamIterator.iterator(streamFiles(directory, recursive, 
extensions));
-        } catch (final IOException e) {
-            throw UncheckedIOExceptions.create(directory, e);
-        }
+        return UncheckedIO.apply(d -> StreamIterator.iterator(streamFiles(d, 
recursive, extensions)), directory);
     }
 
     /**
@@ -2084,11 +2053,7 @@ public class FileUtils {
         // https://bugs.openjdk.java.net/browse/JDK-8177809
         // File.lastModified() is losing milliseconds (always ends in 000)
         // This bug is in OpenJDK 8 and 9, and fixed in 10.
-        try {
-            return lastModified(file);
-        } catch (final IOException e) {
-            throw UncheckedIOExceptions.create(file, e);
-        }
+        return UncheckedIO.apply(FileUtils::lastModified, file);
     }
 
     /**
@@ -2217,12 +2182,9 @@ public class FileUtils {
      * @see org.apache.commons.io.filefilter.NameFileFilter
      */
     public static Collection<File> listFiles(final File directory, final 
IOFileFilter fileFilter, final IOFileFilter dirFilter) {
-        try {
-            final AccumulatorPathVisitor visitor = listAccumulate(directory, 
FileFileFilter.INSTANCE.and(fileFilter), dirFilter, 
FileVisitOption.FOLLOW_LINKS);
-            return 
visitor.getFileList().stream().map(Path::toFile).collect(Collectors.toList());
-        } catch (final IOException e) {
-            throw UncheckedIOExceptions.create(directory, e);
-        }
+        final AccumulatorPathVisitor visitor = UncheckedIO
+            .apply(d -> listAccumulate(d, 
FileFileFilter.INSTANCE.and(fileFilter), dirFilter, 
FileVisitOption.FOLLOW_LINKS), directory);
+        return 
visitor.getFileList().stream().map(Path::toFile).collect(Collectors.toList());
     }
 
     /**
@@ -2236,11 +2198,7 @@ public class FileUtils {
      * @return a collection of java.io.File with the matching files
      */
     public static Collection<File> listFiles(final File directory, final 
String[] extensions, final boolean recursive) {
-        try {
-            return toList(streamFiles(directory, recursive, extensions));
-        } catch (final IOException e) {
-            throw UncheckedIOExceptions.create(directory, e);
-        }
+        return UncheckedIO.apply(d -> toList(streamFiles(d, recursive, 
extensions)), directory);
     }
 
     /**
@@ -2263,14 +2221,11 @@ public class FileUtils {
      * @since 2.2
      */
     public static Collection<File> listFilesAndDirs(final File directory, 
final IOFileFilter fileFilter, final IOFileFilter dirFilter) {
-        try {
-            final AccumulatorPathVisitor visitor = listAccumulate(directory, 
fileFilter, dirFilter, FileVisitOption.FOLLOW_LINKS);
-            final List<Path> list = visitor.getFileList();
-            list.addAll(visitor.getDirList());
-            return 
list.stream().map(Path::toFile).collect(Collectors.toList());
-        } catch (final IOException e) {
-            throw UncheckedIOExceptions.create(directory, e);
-        }
+        final AccumulatorPathVisitor visitor = UncheckedIO.apply(d -> 
listAccumulate(d, fileFilter, dirFilter, FileVisitOption.FOLLOW_LINKS),
+            directory);
+        final List<Path> list = visitor.getFileList();
+        list.addAll(visitor.getDirList());
+        return list.stream().map(Path::toFile).collect(Collectors.toList());
     }
 
     /**
@@ -2921,11 +2876,7 @@ public class FileUtils {
      */
     public static long sizeOf(final File file) {
         requireExists(file, "file");
-        try {
-            return PathUtils.sizeOf(file.toPath());
-        } catch (final IOException e) {
-            throw new UncheckedIOException(e);
-        }
+        return UncheckedIO.get(() -> PathUtils.sizeOf(file.toPath()));
     }
 
     /**
@@ -2949,11 +2900,7 @@ public class FileUtils {
      */
     public static BigInteger sizeOfAsBigInteger(final File file) {
         requireExists(file, "file");
-        try {
-            return PathUtils.sizeOfAsBigInteger(file.toPath());
-        } catch (final IOException e) {
-            throw new UncheckedIOException(e);
-        }
+        return UncheckedIO.get(() -> 
PathUtils.sizeOfAsBigInteger(file.toPath()));
     }
 
     /**
@@ -2972,11 +2919,7 @@ public class FileUtils {
      */
     public static long sizeOfDirectory(final File directory) {
         requireDirectoryExists(directory, "directory");
-        try {
-            return PathUtils.sizeOfDirectory(directory.toPath());
-        } catch (final IOException e) {
-            throw new UncheckedIOException(e);
-        }
+        return UncheckedIO.get(() -> 
PathUtils.sizeOfDirectory(directory.toPath()));
     }
 
     /**
@@ -2990,12 +2933,7 @@ public class FileUtils {
      */
     public static BigInteger sizeOfDirectoryAsBigInteger(final File directory) 
{
         requireDirectoryExists(directory, "directory");
-        try {
-            return PathUtils.sizeOfDirectoryAsBigInteger(directory.toPath());
-        } catch (final IOException e) {
-            throw new UncheckedIOException(e);
-        }
-
+        return UncheckedIO.get(() -> 
PathUtils.sizeOfDirectoryAsBigInteger(directory.toPath()));
     }
 
     /**
diff --git a/src/main/java/org/apache/commons/io/UncheckedIO.java 
b/src/main/java/org/apache/commons/io/UncheckedIO.java
new file mode 100644
index 00000000..c8e0791b
--- /dev/null
+++ b/src/main/java/org/apache/commons/io/UncheckedIO.java
@@ -0,0 +1,158 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.io;
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+
+import org.apache.commons.io.function.IOBiFunction;
+import org.apache.commons.io.function.IOConsumer;
+import org.apache.commons.io.function.IOFunction;
+import org.apache.commons.io.function.IORunnable;
+import org.apache.commons.io.function.IOSupplier;
+import org.apache.commons.io.function.IOTriFunction;
+
+/**
+ * Helps use lambdas that throw {@link IOException} rethrow as {@link 
UncheckedIOException}.
+ *
+ * @since 2.12.0
+ */
+public class UncheckedIO {
+
+    /**
+     * Accepts an IO consumer with the given argument.
+     *
+     * @param <T> the return type of the operations.
+     * @param t the consumer argument.
+     * @param consumer Consumes the value.
+     * @throws UncheckedIOException if an I/O error occurs.
+     */
+    public static <T> void accept(final IOConsumer<T> consumer, final T t) {
+        try {
+            consumer.accept(t);
+        } catch (final IOException e) {
+            throw wrap(e);
+        }
+    }
+
+    /**
+     * Applies an IO bi-function with the given arguments.
+     *
+     * @param biFunction the function.
+     * @param <T> the first function argument type.
+     * @param <U> the second function argument type.
+     * @param <R> the return type.
+     *
+     * @param t the first function argument
+     * @param u the second function argument
+     * @return the function result
+     * @throws UncheckedIOException if an I/O error occurs.
+     */
+    public static <T, U, R> R apply(final IOBiFunction<T, U, R> biFunction, 
final T t, final U u) {
+        try {
+            return biFunction.apply(t, u);
+        } catch (final IOException e) {
+            throw wrap(e);
+        }
+    }
+
+    /**
+     * Applies an IO function with the given arguments.
+     *
+     * @param function the function.
+     * @param <T> the first function argument type.
+     * @param <R> the return type.
+     *
+     * @param t the first function argument
+     * @return the function result
+     * @throws UncheckedIOException if an I/O error occurs.
+     */
+    public static <T, R> R apply(final IOFunction<T, R> function, final T t) {
+        try {
+            return function.apply(t);
+        } catch (final IOException e) {
+            throw wrap(e);
+        }
+    }
+
+    /**
+     * Applies an IO tri-function with the given arguments.
+     *
+     * @param triFunction the function.
+     * @param <T> the first function argument type.
+     * @param <U> the second function argument type.
+     * @param <V> the third function argument type.
+     * @param <R> the return type.
+     *
+     * @param t the first function argument
+     * @param u the second function argument
+     * @param v the third function argument
+     * @return the function result
+     * @throws UncheckedIOException if an I/O error occurs.
+     */
+    public static <T, U, V, R> R apply(final IOTriFunction<T, U, V, R> 
triFunction, final T t, final U u, final V v) {
+        try {
+            return triFunction.apply(t, u, v);
+        } catch (final IOException e) {
+            throw wrap(e);
+        }
+    }
+
+    /**
+     * Creates a new UncheckedIOException for the given detail message.
+     * <p>
+     * This method exists because there is no String constructor in {@link 
UncheckedIOException}.
+     * </p>
+     *
+     * @param e The exception to wrap.
+     * @return a new {@link UncheckedIOException}.
+     */
+    private static UncheckedIOException wrap(final IOException e) {
+        return new UncheckedIOException(e);
+    }
+
+    /**
+     * Gets the result from an IO supplier.
+     *
+     * @param <T> the return type of the operations.
+     * @param supplier Supplies the return value.
+     * @return result from the supplier.
+     * @throws UncheckedIOException if an I/O error occurs.
+     */
+    public static <T> T get(final IOSupplier<T> supplier) {
+        try {
+            return supplier.get();
+        } catch (final IOException e) {
+            throw wrap(e);
+        }
+    }
+
+    /**
+     * Runs an IO runnable.
+     *
+     * @param runnable The runnable to run.
+     * @throws UncheckedIOException if an I/O error occurs.
+     */
+    public static void run(final IORunnable runnable) {
+        try {
+            runnable.run();
+        } catch (final IOException e) {
+            throw wrap(e);
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/io/UncheckedIOExceptions.java 
b/src/main/java/org/apache/commons/io/UncheckedIOExceptions.java
index 4173c0d4..f8f2c500 100644
--- a/src/main/java/org/apache/commons/io/UncheckedIOExceptions.java
+++ b/src/main/java/org/apache/commons/io/UncheckedIOExceptions.java
@@ -22,7 +22,7 @@ import java.io.UncheckedIOException;
 import java.util.Objects;
 
 /**
- * Helps use {@link UncheckedIOException}.
+ * Helps use lambdas that throw {@link IOException} rethrow as {@link 
UncheckedIOException}.
  *
  * @since 2.12.0
  */
@@ -47,13 +47,12 @@ public class UncheckedIOExceptions {
      * <p>
      * This method exists because there is no String constructor in {@link 
UncheckedIOException}.
      * </p>
-     *
-     * @param message the detail message.
      * @param e cause the {@code IOException}.
+     * @param message the detail message.
+     *
      * @return a new {@link UncheckedIOException}.
      */
-    public static UncheckedIOException create(final Object message, final 
IOException e) {
+    public static UncheckedIOException wrap(final IOException e, final Object 
message) {
         return new UncheckedIOException(Objects.toString(message), e);
     }
-
 }
diff --git a/src/main/java/org/apache/commons/io/file/PathUtils.java 
b/src/main/java/org/apache/commons/io/file/PathUtils.java
index ebcbf4df..ef45a8c5 100644
--- a/src/main/java/org/apache/commons/io/file/PathUtils.java
+++ b/src/main/java/org/apache/commons/io/file/PathUtils.java
@@ -69,7 +69,7 @@ import java.util.stream.Stream;
 import org.apache.commons.io.Charsets;
 import org.apache.commons.io.FileUtils;
 import org.apache.commons.io.IOUtils;
-import org.apache.commons.io.UncheckedIOExceptions;
+import org.apache.commons.io.UncheckedIO;
 import org.apache.commons.io.file.Counters.PathCounters;
 import org.apache.commons.io.filefilter.IOFileFilter;
 import org.apache.commons.io.function.IOFunction;
@@ -1136,7 +1136,7 @@ public final class PathUtils {
 
     /**
      * Reads the BasicFileAttributes from the given path. Returns null instead 
of throwing
-     * {@link UnsupportedOperationException}. Throws {@link 
UncheckedIOExceptions} instead of {@link IOException}.
+     * {@link UnsupportedOperationException}. Throws {@link UncheckedIO} 
instead of {@link IOException}.
      *
      * @param <A> The {@code BasicFileAttributes} type
      * @param path The Path to test.
@@ -1148,12 +1148,10 @@ public final class PathUtils {
      */
     public static <A extends BasicFileAttributes> A readAttributes(final Path 
path, final Class<A> type, final LinkOption... options) {
         try {
-            return path == null ? null : Files.readAttributes(path, type, 
options);
+            return path == null ? null : 
UncheckedIO.apply(Files::readAttributes, path, type, options);
         } catch (final UnsupportedOperationException e) {
             // For example, on Windows.
             return null;
-        } catch (final IOException e) {
-            throw UncheckedIOExceptions.create(path, e);
         }
     }
 
diff --git a/src/main/java/org/apache/commons/io/function/IOBiFunction.java 
b/src/main/java/org/apache/commons/io/function/IOBiFunction.java
new file mode 100644
index 00000000..c3e8e556
--- /dev/null
+++ b/src/main/java/org/apache/commons/io/function/IOBiFunction.java
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.io.function;
+
+import java.io.IOException;
+import java.util.Objects;
+import java.util.function.Function;
+
+/**
+ * Represents a function that accepts two arguments and produces a result. 
This is the two-arity specialization of
+ * {@link IOFunction}.
+ *
+ * <p>
+ * This is a <a href="package-summary.html">functional interface</a> whose 
functional method is
+ * {@link #apply(Object, Object)}.
+ *
+ * @param <T> the type of the first argument to the function
+ * @param <U> the type of the second argument to the function
+ * @param <R> the type of the result of the function
+ *
+ * @see Function
+ * @since 2.12.0
+ */
+@FunctionalInterface
+public interface IOBiFunction<T, U, R> {
+
+    /**
+     * Applies this function to the given arguments.
+     *
+     * @param t the first function argument
+     * @param u the second function argument
+     * @return the function result
+     * @throws IOException if an I/O error occurs.
+     */
+    R apply(T t, U u) throws IOException;
+
+    /**
+     * Returns a composed function that first applies this function to its 
input, and then applies the {@code after}
+     * function to the result. If evaluation of either function throws an 
exception, it is relayed to the caller of the
+     * composed function.
+     *
+     * @param <V> the type of output of the {@code after} function, and of the 
composed function
+     * @param after the function to apply after this function is applied
+     * @return a composed function that first applies this function and then 
applies the {@code after} function
+     * @throws NullPointerException if after is null
+     */
+    default <V> IOBiFunction<T, U, V> andThen(final Function<? super R, ? 
extends V> after) {
+        Objects.requireNonNull(after);
+        return (final T t, final U u) -> after.apply(apply(t, u));
+    }
+
+    /**
+     * Returns a composed function that first applies this function to its 
input, and then applies the {@code after}
+     * function to the result. If evaluation of either function throws an 
exception, it is relayed to the caller of the
+     * composed function.
+     *
+     * @param <V> the type of output of the {@code after} function, and of the 
composed function
+     * @param after the function to apply after this function is applied
+     * @return a composed function that first applies this function and then 
applies the {@code after} function
+     * @throws NullPointerException if after is null
+     */
+    default <V> IOBiFunction<T, U, V> andThen(final IOFunction<? super R, ? 
extends V> after) {
+        Objects.requireNonNull(after);
+        return (final T t, final U u) -> after.apply(apply(t, u));
+    }
+}
diff --git a/src/main/java/org/apache/commons/io/function/IORunnable.java 
b/src/main/java/org/apache/commons/io/function/IORunnable.java
new file mode 100644
index 00000000..01403ef7
--- /dev/null
+++ b/src/main/java/org/apache/commons/io/function/IORunnable.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.io.function;
+
+import java.io.IOException;
+
+/**
+ * Like {@link Runnable} but throws {@link IOException}.
+ *
+ * @since 2.12.0
+ */
+@FunctionalInterface
+public interface IORunnable {
+
+    /**
+     * Like {@link Runnable#run()} but throws {@link IOException}.
+     *
+     * @throws IOException if an I/O error occurs.
+     */
+    void run() throws IOException;
+}
diff --git a/src/main/java/org/apache/commons/io/function/IOTriFunction.java 
b/src/main/java/org/apache/commons/io/function/IOTriFunction.java
new file mode 100644
index 00000000..a7c4e11b
--- /dev/null
+++ b/src/main/java/org/apache/commons/io/function/IOTriFunction.java
@@ -0,0 +1,83 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.io.function;
+
+import java.io.IOException;
+import java.util.Objects;
+import java.util.function.Function;
+
+/**
+ * Represents a function that accepts three arguments and produces a result. 
This is the three-arity specialization of
+ * {@link IOFunction}.
+ *
+ * <p>
+ * This is a <a href="package-summary.html">functional interface</a> whose 
functional method is
+ * {@link #apply(Object, Object, Object)}.
+ * </p>
+ *
+ * @param <T> the type of the first argument to the function
+ * @param <U> the type of the second argument to the function
+ * @param <V> the type of the third argument to the function
+ * @param <R> the type of the result of the function
+ *
+ * @see Function
+ * @since 2.12.0
+ */
+@FunctionalInterface
+public interface IOTriFunction<T, U, V, R> {
+
+    /**
+     * Applies this function to the given arguments.
+     *
+     * @param t the first function argument
+     * @param u the second function argument
+     * @param v the third function argument
+     * @return the function result
+     * @throws IOException if an I/O error occurs.
+     */
+    R apply(T t, U u, V v) throws IOException;
+
+    /**
+     * Returns a composed function that first applies this function to its 
input, and then applies the {@code after}
+     * function to the result. If evaluation of either function throws an 
exception, it is relayed to the caller of the
+     * composed function.
+     *
+     * @param <W> the type of output of the {@code after} function, and of the 
composed function
+     * @param after the function to apply after this function is applied
+     * @return a composed function that first applies this function and then 
applies the {@code after} function
+     * @throws NullPointerException if after is null
+     */
+    default <W> IOTriFunction<T, U, V, W> andThen(final IOFunction<? super R, 
? extends W> after) {
+        Objects.requireNonNull(after);
+        return (final T t, final U u, final V v) -> after.apply(apply(t, u, 
v));
+    }
+
+    /**
+     * Returns a composed function that first applies this function to its 
input, and then applies the {@code after}
+     * function to the result. If evaluation of either function throws an 
exception, it is relayed to the caller of the
+     * composed function.
+     *
+     * @param <W> the type of output of the {@code after} function, and of the 
composed function
+     * @param after the function to apply after this function is applied
+     * @return a composed function that first applies this function and then 
applies the {@code after} function
+     * @throws NullPointerException if after is null
+     */
+    default <W> IOTriFunction<T, U, V, W> andThen(final Function<? super R, ? 
extends W> after) {
+        Objects.requireNonNull(after);
+        return (final T t, final U u, final V v) -> after.apply(apply(t, u, 
v));
+    }
+}
diff --git 
a/src/main/java/org/apache/commons/io/input/UncheckedBufferedReader.java 
b/src/main/java/org/apache/commons/io/input/UncheckedBufferedReader.java
index f6c6a9c3..a73b73cd 100644
--- a/src/main/java/org/apache/commons/io/input/UncheckedBufferedReader.java
+++ b/src/main/java/org/apache/commons/io/input/UncheckedBufferedReader.java
@@ -23,6 +23,8 @@ import java.io.Reader;
 import java.io.UncheckedIOException;
 import java.nio.CharBuffer;
 
+import org.apache.commons.io.UncheckedIO;
+
 /**
  * A {@link BufferedReader} that throws {@link UncheckedIOException} instead 
of {@link IOException}.
  *
@@ -70,11 +72,7 @@ public class UncheckedBufferedReader extends BufferedReader {
      */
     @Override
     public void close() throws UncheckedIOException {
-        try {
-            super.close();
-        } catch (final IOException e) {
-            throw uncheck(e);
-        }
+        UncheckedIO.run(super::close);
     }
 
     /**
@@ -82,11 +80,7 @@ public class UncheckedBufferedReader extends BufferedReader {
      */
     @Override
     public void mark(final int readAheadLimit) throws UncheckedIOException {
-        try {
-            super.mark(readAheadLimit);
-        } catch (final IOException e) {
-            throw uncheck(e);
-        }
+        UncheckedIO.accept(super::mark, readAheadLimit);
     }
 
     /**
@@ -94,11 +88,7 @@ public class UncheckedBufferedReader extends BufferedReader {
      */
     @Override
     public int read() throws UncheckedIOException {
-        try {
-            return super.read();
-        } catch (final IOException e) {
-            throw uncheck(e);
-        }
+        return UncheckedIO.get(super::read);
     }
 
     /**
@@ -106,11 +96,7 @@ public class UncheckedBufferedReader extends BufferedReader 
{
      */
     @Override
     public int read(final char[] cbuf) throws UncheckedIOException {
-        try {
-            return super.read(cbuf);
-        } catch (final IOException e) {
-            throw uncheck(e);
-        }
+        return UncheckedIO.apply(super::read, cbuf);
     }
 
     /**
@@ -118,11 +104,7 @@ public class UncheckedBufferedReader extends 
BufferedReader {
      */
     @Override
     public int read(final char[] cbuf, final int off, final int len) throws 
UncheckedIOException {
-        try {
-            return super.read(cbuf, off, len);
-        } catch (final IOException e) {
-            throw uncheck(e);
-        }
+        return UncheckedIO.apply(super::read, cbuf, off, len);
     }
 
     /**
@@ -130,11 +112,7 @@ public class UncheckedBufferedReader extends 
BufferedReader {
      */
     @Override
     public int read(final CharBuffer target) throws UncheckedIOException {
-        try {
-            return super.read(target);
-        } catch (final IOException e) {
-            throw uncheck(e);
-        }
+        return UncheckedIO.apply(super::read, target);
     }
 
     /**
@@ -142,11 +120,7 @@ public class UncheckedBufferedReader extends 
BufferedReader {
      */
     @Override
     public String readLine() throws UncheckedIOException {
-        try {
-            return super.readLine();
-        } catch (final IOException e) {
-            throw uncheck(e);
-        }
+        return UncheckedIO.get(super::readLine);
     }
 
     /**
@@ -154,11 +128,7 @@ public class UncheckedBufferedReader extends 
BufferedReader {
      */
     @Override
     public boolean ready() throws UncheckedIOException {
-        try {
-            return super.ready();
-        } catch (final IOException e) {
-            throw uncheck(e);
-        }
+        return UncheckedIO.get(super::ready);
     }
 
     /**
@@ -166,11 +136,7 @@ public class UncheckedBufferedReader extends 
BufferedReader {
      */
     @Override
     public void reset() throws UncheckedIOException {
-        try {
-            super.reset();
-        } catch (final IOException e) {
-            throw uncheck(e);
-        }
+        UncheckedIO.run(super::reset);
     }
 
     /**
@@ -178,15 +144,7 @@ public class UncheckedBufferedReader extends 
BufferedReader {
      */
     @Override
     public long skip(final long n) throws UncheckedIOException {
-        try {
-            return super.skip(n);
-        } catch (final IOException e) {
-            throw uncheck(e);
-        }
-    }
-
-    private UncheckedIOException uncheck(final IOException e) {
-        return new UncheckedIOException(e);
+        return UncheckedIO.apply(super::skip, n);
     }
 
 }
diff --git 
a/src/main/java/org/apache/commons/io/input/UncheckedFilterInputStream.java 
b/src/main/java/org/apache/commons/io/input/UncheckedFilterInputStream.java
index 85f3d6c0..9f0fccfa 100644
--- a/src/main/java/org/apache/commons/io/input/UncheckedFilterInputStream.java
+++ b/src/main/java/org/apache/commons/io/input/UncheckedFilterInputStream.java
@@ -24,6 +24,8 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.io.UncheckedIOException;
 
+import org.apache.commons.io.UncheckedIO;
+
 /**
  * A {@link BufferedReader} that throws {@link UncheckedIOException} instead 
of {@link IOException}.
  *
@@ -60,11 +62,7 @@ public class UncheckedFilterInputStream extends 
FilterInputStream {
      */
     @Override
     public int available() throws UncheckedIOException {
-        try {
-            return super.available();
-        } catch (final IOException e) {
-            throw uncheck(e);
-        }
+        return UncheckedIO.get(super::available);
     }
 
     /**
@@ -72,11 +70,7 @@ public class UncheckedFilterInputStream extends 
FilterInputStream {
      */
     @Override
     public void close() throws UncheckedIOException {
-        try {
-            super.close();
-        } catch (final IOException e) {
-            throw uncheck(e);
-        }
+        UncheckedIO.run(super::close);
     }
 
     /**
@@ -84,11 +78,7 @@ public class UncheckedFilterInputStream extends 
FilterInputStream {
      */
     @Override
     public int read() throws UncheckedIOException {
-        try {
-            return super.read();
-        } catch (final IOException e) {
-            throw uncheck(e);
-        }
+        return UncheckedIO.get(super::read);
     }
 
     /**
@@ -96,11 +86,7 @@ public class UncheckedFilterInputStream extends 
FilterInputStream {
      */
     @Override
     public int read(final byte[] b) throws UncheckedIOException {
-        try {
-            return super.read(b);
-        } catch (final IOException e) {
-            throw uncheck(e);
-        }
+        return UncheckedIO.apply(super::read, b);
     }
 
     /**
@@ -108,11 +94,7 @@ public class UncheckedFilterInputStream extends 
FilterInputStream {
      */
     @Override
     public int read(final byte[] b, final int off, final int len) throws 
UncheckedIOException {
-        try {
-            return super.read(b, off, len);
-        } catch (final IOException e) {
-            throw uncheck(e);
-        }
+        return UncheckedIO.apply(super::read, b, off, len);
     }
 
     /**
@@ -120,11 +102,7 @@ public class UncheckedFilterInputStream extends 
FilterInputStream {
      */
     @Override
     public synchronized void reset() throws UncheckedIOException {
-        try {
-            super.reset();
-        } catch (final IOException e) {
-            throw uncheck(e);
-        }
+        UncheckedIO.run(super::reset);
     }
 
     /**
@@ -132,15 +110,7 @@ public class UncheckedFilterInputStream extends 
FilterInputStream {
      */
     @Override
     public long skip(final long n) throws UncheckedIOException {
-        try {
-            return super.skip(n);
-        } catch (final IOException e) {
-            throw uncheck(e);
-        }
-    }
-
-    private UncheckedIOException uncheck(final IOException e) {
-        return new UncheckedIOException(e);
+        return UncheckedIO.apply(super::skip, n);
     }
 
 }
diff --git 
a/src/main/java/org/apache/commons/io/input/UncheckedFilterReader.java 
b/src/main/java/org/apache/commons/io/input/UncheckedFilterReader.java
index 285bc94d..f0a68a12 100644
--- a/src/main/java/org/apache/commons/io/input/UncheckedFilterReader.java
+++ b/src/main/java/org/apache/commons/io/input/UncheckedFilterReader.java
@@ -23,6 +23,8 @@ import java.io.Reader;
 import java.io.UncheckedIOException;
 import java.nio.CharBuffer;
 
+import org.apache.commons.io.UncheckedIO;
+
 /**
  * A {@link FilterReader} that throws {@link UncheckedIOException} instead of 
{@link IOException}.
  *
@@ -59,11 +61,7 @@ public class UncheckedFilterReader extends FilterReader {
      */
     @Override
     public void close() throws UncheckedIOException {
-        try {
-            super.close();
-        } catch (final IOException e) {
-            throw uncheck(e);
-        }
+        UncheckedIO.run(super::close);
     }
 
     /**
@@ -71,11 +69,7 @@ public class UncheckedFilterReader extends FilterReader {
      */
     @Override
     public void mark(final int readAheadLimit) throws UncheckedIOException {
-        try {
-            super.mark(readAheadLimit);
-        } catch (final IOException e) {
-            throw uncheck(e);
-        }
+        UncheckedIO.accept(super::mark, readAheadLimit);
     }
 
     /**
@@ -83,11 +77,7 @@ public class UncheckedFilterReader extends FilterReader {
      */
     @Override
     public int read() throws UncheckedIOException {
-        try {
-            return super.read();
-        } catch (final IOException e) {
-            throw uncheck(e);
-        }
+        return UncheckedIO.get(super::read);
     }
 
     /**
@@ -95,11 +85,7 @@ public class UncheckedFilterReader extends FilterReader {
      */
     @Override
     public int read(final char[] cbuf) throws UncheckedIOException {
-        try {
-            return super.read(cbuf);
-        } catch (final IOException e) {
-            throw uncheck(e);
-        }
+        return UncheckedIO.apply(super::read, cbuf);
     }
 
     /**
@@ -107,11 +93,7 @@ public class UncheckedFilterReader extends FilterReader {
      */
     @Override
     public int read(final char[] cbuf, final int off, final int len) throws 
UncheckedIOException {
-        try {
-            return super.read(cbuf, off, len);
-        } catch (final IOException e) {
-            throw uncheck(e);
-        }
+        return UncheckedIO.apply(super::read, cbuf, off, len);
     }
 
     /**
@@ -119,11 +101,7 @@ public class UncheckedFilterReader extends FilterReader {
      */
     @Override
     public int read(final CharBuffer target) throws UncheckedIOException {
-        try {
-            return super.read(target);
-        } catch (final IOException e) {
-            throw uncheck(e);
-        }
+        return UncheckedIO.apply(super::read, target);
     }
 
     /**
@@ -131,11 +109,7 @@ public class UncheckedFilterReader extends FilterReader {
      */
     @Override
     public boolean ready() throws UncheckedIOException {
-        try {
-            return super.ready();
-        } catch (final IOException e) {
-            throw uncheck(e);
-        }
+        return UncheckedIO.get(super::ready);
     }
 
     /**
@@ -143,11 +117,7 @@ public class UncheckedFilterReader extends FilterReader {
      */
     @Override
     public void reset() throws UncheckedIOException {
-        try {
-            super.reset();
-        } catch (final IOException e) {
-            throw uncheck(e);
-        }
+        UncheckedIO.run(super::reset);
     }
 
     /**
@@ -155,15 +125,7 @@ public class UncheckedFilterReader extends FilterReader {
      */
     @Override
     public long skip(final long n) throws UncheckedIOException {
-        try {
-            return super.skip(n);
-        } catch (final IOException e) {
-            throw uncheck(e);
-        }
-    }
-
-    private UncheckedIOException uncheck(final IOException e) {
-        return new UncheckedIOException(e);
+        return UncheckedIO.apply(super::skip, n);
     }
 
 }
diff --git 
a/src/main/java/org/apache/commons/io/output/UncheckedAppendableImpl.java 
b/src/main/java/org/apache/commons/io/output/UncheckedAppendableImpl.java
index 8776fb34..48aa3e00 100644
--- a/src/main/java/org/apache/commons/io/output/UncheckedAppendableImpl.java
+++ b/src/main/java/org/apache/commons/io/output/UncheckedAppendableImpl.java
@@ -21,7 +21,7 @@ import java.io.IOException;
 import java.io.UncheckedIOException;
 import java.util.Objects;
 
-import org.apache.commons.io.UncheckedIOExceptions;
+import org.apache.commons.io.UncheckedIO;
 
 /**
  * An {@link Appendable} implementation that throws {@link 
UncheckedIOException} instead of {@link IOException}.
@@ -41,31 +41,19 @@ class UncheckedAppendableImpl implements 
UncheckedAppendable {
 
     @Override
     public UncheckedAppendable append(final char c) {
-        try {
-            appendable.append(c);
-        } catch (final IOException e) {
-            throw UncheckedIOExceptions.create(c, e);
-        }
+        UncheckedIO.apply(appendable::append, c);
         return this;
     }
 
     @Override
     public UncheckedAppendable append(final CharSequence csq) {
-        try {
-            appendable.append(csq);
-        } catch (final IOException e) {
-            throw UncheckedIOExceptions.create(csq, e);
-        }
+        UncheckedIO.apply(appendable::append, csq);
         return this;
     }
 
     @Override
     public UncheckedAppendable append(final CharSequence csq, final int start, 
final int end) {
-        try {
-            appendable.append(csq, start, end);
-        } catch (final IOException e) {
-            throw UncheckedIOExceptions.create(csq, e);
-        }
+        UncheckedIO.apply(appendable::append, csq, start, end);
         return this;
     }
 
@@ -74,4 +62,4 @@ class UncheckedAppendableImpl implements UncheckedAppendable {
         return appendable.toString();
     }
 
-}
\ No newline at end of file
+}
diff --git a/src/test/java/org/apache/commons/io/UncheckedIOExceptionsTest.java 
b/src/test/java/org/apache/commons/io/UncheckedIOExceptionsTest.java
index 09c393c2..160d38ea 100644
--- a/src/test/java/org/apache/commons/io/UncheckedIOExceptionsTest.java
+++ b/src/test/java/org/apache/commons/io/UncheckedIOExceptionsTest.java
@@ -24,8 +24,14 @@ import java.io.UncheckedIOException;
 
 import org.junit.jupiter.api.Test;
 
+/**
+ * Tests {@link UncheckedIO}.
+ */
 public class UncheckedIOExceptionsTest {
 
+    /**
+     * Tests {@link UncheckedIOExceptions#create(Object)}.
+     */
     @Test
     public void testCreate() {
         final Object message = "test";
@@ -35,20 +41,20 @@ public class UncheckedIOExceptionsTest {
             assertEquals(message, e.getMessage());
             assertEquals(message, e.getCause().getMessage());
         }
-
     }
 
+    /**
+     * Tests {@link UncheckedIOExceptions#wrap(IOException, Object)}.
+     */
     @Test
-    public void testCreateWithException() {
+    public void testWrap() {
         final Object message1 = "test1";
         final Object message2 = "test2";
-        final IOException ioe = new IOException(message2.toString());
         try {
-            throw UncheckedIOExceptions.create(message1, ioe);
+            throw UncheckedIOExceptions.wrap(new 
IOException(message2.toString()), message1);
         } catch (final UncheckedIOException e) {
             assertEquals(message1, e.getMessage());
             assertEquals(message2, e.getCause().getMessage());
         }
-
     }
 }
diff --git a/src/test/java/org/apache/commons/io/UncheckedIOTest.java 
b/src/test/java/org/apache/commons/io/UncheckedIOTest.java
new file mode 100644
index 00000000..6628d83d
--- /dev/null
+++ b/src/test/java/org/apache/commons/io/UncheckedIOTest.java
@@ -0,0 +1,102 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.io;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.io.ByteArrayInputStream;
+
+import org.apache.commons.io.function.IOBiFunction;
+import org.apache.commons.io.function.IOConsumer;
+import org.apache.commons.io.function.IOFunction;
+import org.apache.commons.io.function.IORunnable;
+import org.apache.commons.io.function.IOSupplier;
+import org.apache.commons.io.function.IOTriFunction;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests {@link UncheckedIO}.
+ */
+public class UncheckedIOTest {
+
+    private static final byte[] BYTES = {'a', 'b'};
+
+    private ByteArrayInputStream newInputStream() {
+        return new ByteArrayInputStream(BYTES);
+    }
+
+    /**
+     * Tests {@link UncheckedIO#accept(IOConsumer, Object)}.
+     */
+    @Test
+    public void testAccept() {
+        final ByteArrayInputStream stream = newInputStream();
+        UncheckedIO.accept(n -> stream.skip(n), 1);
+        assertEquals('b', UncheckedIO.get(stream::read).intValue());
+    }
+
+    /**
+     * Tests {@link UncheckedIO#apply(IOFunction, Object)}.
+     */
+    @Test
+    public void testApply1() {
+        final ByteArrayInputStream stream = newInputStream();
+        assertEquals(1, UncheckedIO.apply(n -> stream.skip(n), 1).intValue());
+        assertEquals('b', UncheckedIO.get(stream::read).intValue());
+    }
+
+    /**
+     * Tests {@link UncheckedIO#apply(IOBiFunction, Object, Object)}.
+     */
+    @Test
+    public void testApply2() {
+        final ByteArrayInputStream stream = newInputStream();
+        final byte[] buf = new byte[BYTES.length];
+        assertEquals(1, UncheckedIO.apply((o, l) -> stream.read(buf, o, l), 0, 
1).intValue());
+        assertEquals('a', buf[0]);
+    }
+
+    /**
+     * Tests {@link UncheckedIO#apply(IOTriFunction, Object, Object, Object)}.
+     */
+    @Test
+    public void testApply3() {
+        final ByteArrayInputStream stream = newInputStream();
+        final byte[] buf = new byte[BYTES.length];
+        assertEquals(1, UncheckedIO.apply((b, o, l) -> stream.read(b, o, l), 
buf, 0, 1).intValue());
+        assertEquals('a', buf[0]);
+    }
+
+    /**
+     * Tests {@link UncheckedIO#get(IOSupplier)}.
+     */
+    @Test
+    public void testGet() {
+        assertEquals('a', UncheckedIO.get(() -> 
newInputStream().read()).intValue());
+    }
+
+    /**
+     * Tests {@link UncheckedIO#run(IORunnable)}.
+     */
+    @Test
+    public void testRun() {
+        final ByteArrayInputStream stream = newInputStream();
+        UncheckedIO.run(() -> stream.skip(1));
+        assertEquals('b', UncheckedIO.get(stream::read).intValue());
+    }
+}
diff --git a/src/test/java/org/apache/commons/io/function/IOBiFunctionTest.java 
b/src/test/java/org/apache/commons/io/function/IOBiFunctionTest.java
new file mode 100644
index 00000000..ac75d502
--- /dev/null
+++ b/src/test/java/org/apache/commons/io/function/IOBiFunctionTest.java
@@ -0,0 +1,92 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.io.function;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.LinkOption;
+import java.nio.file.Path;
+import java.util.function.Function;
+
+import org.apache.commons.io.file.PathUtils;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests {@link IOBiFunction}.
+ */
+public class IOBiFunctionTest {
+
+    @SuppressWarnings("unused")
+    private boolean not(final boolean value) throws IOException {
+        return !value;
+    }
+
+    /**
+     * Tests {@link IOBiFunction#andThen(Function)}.
+     *
+     * @throws IOException thrown on test failure
+     */
+    @Test
+    public void testAndThenFunction() throws IOException {
+        final IOBiFunction<Path, LinkOption[], Boolean> isDirectory = 
Files::isDirectory;
+        final Function<Boolean, Boolean> not = b -> !b;
+        assertEquals(true, isDirectory.apply(PathUtils.current(), 
PathUtils.EMPTY_LINK_OPTION_ARRAY));
+        final IOBiFunction<Path, LinkOption[], Boolean> andThen = 
isDirectory.andThen(not);
+        assertEquals(false, andThen.apply(PathUtils.current(), 
PathUtils.EMPTY_LINK_OPTION_ARRAY));
+    }
+
+    /**
+     * Tests {@link IOBiFunction#andThen(IOFunction)}.
+     *
+     * @throws IOException thrown on test failure
+     */
+    @Test
+    public void testAndThenIOFunction() throws IOException {
+        final IOBiFunction<Path, LinkOption[], Boolean> isDirectory = 
Files::isDirectory;
+        final IOFunction<Boolean, Boolean> not = this::not;
+        assertEquals(true, isDirectory.apply(PathUtils.current(), 
PathUtils.EMPTY_LINK_OPTION_ARRAY));
+        final IOBiFunction<Path, LinkOption[], Boolean> andThen = 
isDirectory.andThen(not);
+        assertEquals(false, andThen.apply(PathUtils.current(), 
PathUtils.EMPTY_LINK_OPTION_ARRAY));
+    }
+
+    /**
+     * Tests {@link IOBiFunction#apply(Object, Object)}.
+     *
+     * @throws IOException thrown on test failure
+     */
+    @Test
+    public void testApply() throws IOException {
+        final IOBiFunction<Path, LinkOption[], Boolean> isDirectory = 
Files::isDirectory;
+        assertEquals(true, isDirectory.apply(PathUtils.current(), 
PathUtils.EMPTY_LINK_OPTION_ARRAY));
+    }
+
+    /**
+     * Tests {@link IOBiFunction#apply(Object, Object)}.
+     */
+    @Test
+    public void testApplyThrowsException() {
+        final IOBiFunction<Path, LinkOption[], Boolean> isDirectory = (t, u) 
-> {
+            throw new IOException("Boom!");
+        };
+        assertThrows(IOException.class, () -> 
isDirectory.apply(PathUtils.current(), PathUtils.EMPTY_LINK_OPTION_ARRAY));
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/io/UncheckedIOExceptionsTest.java 
b/src/test/java/org/apache/commons/io/function/IORunnableTest.java
similarity index 51%
copy from src/test/java/org/apache/commons/io/UncheckedIOExceptionsTest.java
copy to src/test/java/org/apache/commons/io/function/IORunnableTest.java
index 09c393c2..2a77d6ea 100644
--- a/src/test/java/org/apache/commons/io/UncheckedIOExceptionsTest.java
+++ b/src/test/java/org/apache/commons/io/function/IORunnableTest.java
@@ -15,40 +15,31 @@
  * limitations under the License.
  */
 
-package org.apache.commons.io;
+package org.apache.commons.io.function;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 
 import java.io.IOException;
-import java.io.UncheckedIOException;
+import java.util.concurrent.atomic.AtomicReference;
 
 import org.junit.jupiter.api.Test;
 
-public class UncheckedIOExceptionsTest {
+/**
+ * Tests {@link IORunnable}.
+ */
+public class IORunnableTest {
 
+    /**
+     * Tests {@link IORunnable#run()}.
+     *
+     * @throws IOException thrown on test failure
+     */
     @Test
-    public void testCreate() {
-        final Object message = "test";
-        try {
-            throw UncheckedIOExceptions.create(message);
-        } catch (final UncheckedIOException e) {
-            assertEquals(message, e.getMessage());
-            assertEquals(message, e.getCause().getMessage());
-        }
-
+    public void testAccept() throws IOException {
+        final AtomicReference<String> ref = new AtomicReference<>();
+        final IORunnable runnable = () -> ref.set("A1");
+        runnable.run();
+        assertEquals("A1", ref.get());
     }
 
-    @Test
-    public void testCreateWithException() {
-        final Object message1 = "test1";
-        final Object message2 = "test2";
-        final IOException ioe = new IOException(message2.toString());
-        try {
-            throw UncheckedIOExceptions.create(message1, ioe);
-        } catch (final UncheckedIOException e) {
-            assertEquals(message1, e.getMessage());
-            assertEquals(message2, e.getCause().getMessage());
-        }
-
-    }
 }
diff --git 
a/src/test/java/org/apache/commons/io/function/IOTriFunctionTest.java 
b/src/test/java/org/apache/commons/io/function/IOTriFunctionTest.java
new file mode 100644
index 00000000..9f5ce36e
--- /dev/null
+++ b/src/test/java/org/apache/commons/io/function/IOTriFunctionTest.java
@@ -0,0 +1,112 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.io.function;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.io.IOException;
+import java.math.BigInteger;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Function;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests {@link IOTriFunction}.
+ */
+public class IOTriFunctionTest {
+
+    /**
+     * Tests {@link IOTriFunction#apply(Object, Object, Object)}.
+     *
+     * @throws IOException thrown on test failure
+     */
+    @Test
+    public void testAccept() throws IOException {
+        final AtomicReference<Character> ref1 = new AtomicReference<>();
+        final AtomicReference<Short> ref2 = new AtomicReference<>();
+        final AtomicReference<String> ref3 = new AtomicReference<>();
+        final IOTriFunction<AtomicReference<Character>, 
AtomicReference<Short>, AtomicReference<String>, String> tri = (t, u, v) -> {
+            ref1.set(Character.valueOf('a'));
+            ref2.set(Short.valueOf((short) 1));
+            ref3.set("z");
+            return "ABC";
+        };
+        assertEquals("ABC", tri.apply(ref1, ref2, ref3));
+        assertEquals(Character.valueOf('a'), ref1.get());
+        assertEquals(Short.valueOf((short) 1), ref2.get());
+        assertEquals("z", ref3.get());
+    }
+
+    /**
+     * Tests {@link IOTriFunction#andThen(Function)}.
+     *
+     * @throws IOException thrown on test failure
+     */
+    @Test
+    public void testAndThenFunction() throws IOException {
+        final AtomicReference<Character> ref1 = new AtomicReference<>();
+        final AtomicReference<Short> ref2 = new AtomicReference<>();
+        final AtomicReference<String> ref3 = new AtomicReference<>();
+        final IOTriFunction<AtomicReference<Character>, 
AtomicReference<Short>, AtomicReference<String>, String> tri = (t, u, v) -> {
+            ref1.set(Character.valueOf('a'));
+            ref2.set(Short.valueOf((short) 1));
+            ref3.set("z");
+            return "9";
+        };
+        final Function<String, BigInteger> after = t -> {
+            ref1.set(Character.valueOf('b'));
+            ref2.set(Short.valueOf((short) 2));
+            ref3.set("zz");
+            return BigInteger.valueOf(Long.parseLong(t)).add(BigInteger.ONE);
+        };
+        assertEquals(BigInteger.TEN, tri.andThen(after).apply(ref1, ref2, 
ref3));
+        assertEquals(Character.valueOf('b'), ref1.get());
+        assertEquals(Short.valueOf((short) 2), ref2.get());
+        assertEquals("zz", ref3.get());
+    }
+
+    /**
+     * Tests {@link IOTriFunction#andThen(IOFunction)}.
+     *
+     * @throws IOException thrown on test failure
+     */
+    @Test
+    public void testAndThenIOFunction() throws IOException {
+        final AtomicReference<Character> ref1 = new AtomicReference<>();
+        final AtomicReference<Short> ref2 = new AtomicReference<>();
+        final AtomicReference<String> ref3 = new AtomicReference<>();
+        final IOTriFunction<AtomicReference<Character>, 
AtomicReference<Short>, AtomicReference<String>, String> tri = (t, u, v) -> {
+            ref1.set(Character.valueOf('a'));
+            ref2.set(Short.valueOf((short) 1));
+            ref3.set("z");
+            return "9";
+        };
+        final IOFunction<String, BigInteger> after = t -> {
+            ref1.set(Character.valueOf('b'));
+            ref2.set(Short.valueOf((short) 2));
+            ref3.set("zz");
+            return BigInteger.valueOf(Long.parseLong(t)).add(BigInteger.ONE);
+        };
+        assertEquals(BigInteger.TEN, tri.andThen(after).apply(ref1, ref2, 
ref3));
+        assertEquals(Character.valueOf('b'), ref1.get());
+        assertEquals(Short.valueOf((short) 2), ref2.get());
+        assertEquals("zz", ref3.get());
+    }
+
+}

Reply via email to