You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by gg...@apache.org on 2022/09/22 12:48:36 UTC

[commons-io] branch master updated (ed9c1ea3 -> d503c604)

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

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


    from ed9c1ea3 Remove unused exception in test
     new 4ea91d20 Add IOBaseStream & IOStream
     new d503c604 Use GitHub cache for CodeQL

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .github/workflows/codeql-analysis.yml              |   6 +
 src/changes/changes.xml                            |   2 +-
 .../apache/commons/io/function/IOBaseStream.java   | 154 ++++++
 ...eratorAdapter.java => IOBaseStreamAdapter.java} |  31 +-
 .../org/apache/commons/io/function/IOStream.java   | 597 +++++++++++++++++++++
 ...pliteratorAdapter.java => IOStreamAdapter.java} |  24 +-
 .../org/apache/commons/io/function/IOStreams.java  |  62 +--
 .../commons/io/function/UncheckedIOBaseStream.java |  88 +++
 .../commons/io/input/ObservableInputStream.java    |   3 +-
 .../org/apache/commons/io/function/EraseTest.java  |  97 ++++
 .../commons/io/function/IOBaseStreamTest.java      | 349 ++++++++++++
 .../apache/commons/io/function/IOIntStream.java}   |  10 +-
 .../commons/io/function/IOIntStreamAdapter.java}   |  24 +-
 .../apache/commons/io/function/IOStreamTest.java   | 540 +++++++++++++++++++
 .../apache/commons/io/function/IOSupplierTest.java |   4 +-
 .../commons/io/function/PathBaseStream.java}       |  11 +-
 .../apache/commons/io/function/PathStream.java}    |  11 +-
 .../apache/commons/io/function/TestConstants.java  |  12 +-
 .../org/apache/commons/io/function/TestUtils.java  |  47 +-
 .../apache/commons/io/function/UncheckTest.java    |  38 +-
 20 files changed, 1985 insertions(+), 125 deletions(-)
 create mode 100644 src/main/java/org/apache/commons/io/function/IOBaseStream.java
 copy src/main/java/org/apache/commons/io/function/{IOSpliteratorAdapter.java => IOBaseStreamAdapter.java} (65%)
 create mode 100644 src/main/java/org/apache/commons/io/function/IOStream.java
 copy src/main/java/org/apache/commons/io/function/{IOSpliteratorAdapter.java => IOStreamAdapter.java} (60%)
 create mode 100644 src/main/java/org/apache/commons/io/function/UncheckedIOBaseStream.java
 create mode 100644 src/test/java/org/apache/commons/io/function/EraseTest.java
 create mode 100644 src/test/java/org/apache/commons/io/function/IOBaseStreamTest.java
 copy src/{main/java/org/apache/commons/io/function/package-info.java => test/java/org/apache/commons/io/function/IOIntStream.java} (74%)
 copy src/{main/java/org/apache/commons/io/function/IOSpliteratorAdapter.java => test/java/org/apache/commons/io/function/IOIntStreamAdapter.java} (59%)
 create mode 100644 src/test/java/org/apache/commons/io/function/IOStreamTest.java
 copy src/{main/java/org/apache/commons/io/function/package-info.java => test/java/org/apache/commons/io/function/PathBaseStream.java} (83%)
 copy src/{main/java/org/apache/commons/io/function/package-info.java => test/java/org/apache/commons/io/function/PathStream.java} (86%)


[commons-io] 01/02: Add IOBaseStream & IOStream

Posted by gg...@apache.org.
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

commit 4ea91d20dfa730bf2ce2120f7bdfbe0384d0c29a
Author: Gary Gregory <ga...@gmail.com>
AuthorDate: Mon Sep 19 17:28:40 2022 -0400

    Add IOBaseStream & IOStream
---
 src/changes/changes.xml                            |   2 +-
 .../apache/commons/io/function/IOBaseStream.java   | 154 ++++++
 .../commons/io/function/IOBaseStreamAdapter.java   |  52 ++
 .../org/apache/commons/io/function/IOStream.java   | 597 +++++++++++++++++++++
 .../commons/io/function/IOStreamAdapter.java       |  45 ++
 .../org/apache/commons/io/function/IOStreams.java  |  62 +--
 .../commons/io/function/UncheckedIOBaseStream.java |  88 +++
 .../commons/io/input/ObservableInputStream.java    |   3 +-
 .../org/apache/commons/io/function/EraseTest.java  |  97 ++++
 .../commons/io/function/IOBaseStreamTest.java      | 349 ++++++++++++
 .../apache/commons/io/function/IOIntStream.java    |  27 +
 .../commons/io/function/IOIntStreamAdapter.java    |  41 ++
 .../apache/commons/io/function/IOStreamTest.java   | 540 +++++++++++++++++++
 .../apache/commons/io/function/IOSupplierTest.java |   4 +-
 .../apache/commons/io/function/PathBaseStream.java |  28 +
 .../org/apache/commons/io/function/PathStream.java |  28 +
 .../apache/commons/io/function/TestConstants.java  |  12 +-
 .../org/apache/commons/io/function/TestUtils.java  |  47 +-
 .../apache/commons/io/function/UncheckTest.java    |  38 +-
 19 files changed, 2133 insertions(+), 81 deletions(-)

diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 7e2488b3..f10ca8b9 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -428,7 +428,7 @@ The <action> type attribute can be add,update,fix,remove.
         Add PathUtils.getLastModifiedFileTime(*).
       </action>
       <action dev="ggregory" type="add" due-to="Gary Gregory">
-        Add IOBiFunction, IOTriFunction, IOQuadFunction, IOPredicate, IOIterator, IOSpliterator, FilesUncheck.
+        Add IOBiFunction, IOTriFunction, IOQuadFunction, IOPredicate, IOIterator, IOSpliterator, IOBaseStream, IOStream, FilesUncheck.
       </action>
       <action dev="ggregory" type="add" due-to="Gary Gregory">
         Add IOUtils.consume(Reader).
diff --git a/src/main/java/org/apache/commons/io/function/IOBaseStream.java b/src/main/java/org/apache/commons/io/function/IOBaseStream.java
new file mode 100644
index 00000000..6ac9bee2
--- /dev/null
+++ b/src/main/java/org/apache/commons/io/function/IOBaseStream.java
@@ -0,0 +1,154 @@
+/*
+ * 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.Closeable;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.util.stream.BaseStream;
+import java.util.stream.Stream;
+
+/**
+ * Like {@link BaseStream} but throws {@link IOException}.
+ *
+ * @param <T> the type of the stream elements.
+ * @param <S> the type of the IO stream extending {@code IOBaseStream}.
+ * @param <B> the type of the stream extending {@code BaseStream}.
+ * @since 2.12.0
+ */
+public interface IOBaseStream<T, S extends IOBaseStream<T, S, B>, B extends BaseStream<T, B>> extends Closeable {
+
+    /**
+     * Creates a {@link BaseStream} for this instance that throws {@link UncheckedIOException} instead of
+     * {@link IOException}.
+     *
+     * @return an {@link UncheckedIOException} {@link BaseStream}.
+     */
+    @SuppressWarnings("unchecked")
+    default BaseStream<T, B> asBaseStream() {
+        return new UncheckedIOBaseStream<>((S) this);
+    }
+
+    /**
+     * Like {@link BaseStream#close()}.
+     *
+     * @see BaseStream#close()
+     */
+    @Override
+    default void close() {
+        unwrap().close();
+    }
+
+    /**
+     * Like {@link BaseStream#isParallel()}.
+     *
+     * @return See {@link BaseStream#isParallel() delegate}.
+     * @see BaseStream#isParallel()
+     */
+    @SuppressWarnings("resource") // for unwrap()
+    default boolean isParallel() {
+        return unwrap().isParallel();
+    }
+
+    /**
+     * Like {@link BaseStream#iterator()}.
+     *
+     * @return See {@link BaseStream#iterator() delegate}.
+     * @see BaseStream#iterator()
+     */
+    @SuppressWarnings("resource") // for unwrap()
+    default IOIterator<T> iterator() {
+        return IOIteratorAdapter.adapt(unwrap().iterator());
+    }
+
+    /**
+     * Like {@link BaseStream#onClose(Runnable)}.
+     *
+     * @param closeHandler See {@link BaseStream#onClose(Runnable) delegate}.
+     * @return See {@link BaseStream#onClose(Runnable) delegate}.
+     * @throws IOException if an I/O error occurs.
+     * @see BaseStream#onClose(Runnable)
+     */
+    @SuppressWarnings({"unused", "resource"}) // throws IOException, unwrap()
+    default S onClose(final IORunnable closeHandler) throws IOException {
+        return wrap(unwrap().onClose(() -> Erase.run(closeHandler)));
+    }
+
+    /**
+     * Like {@link BaseStream#parallel()}.
+     *
+     * @return See {@link BaseStream#parallel() delegate}.
+     * @see BaseStream#parallel()
+     */
+    @SuppressWarnings({"resource", "unchecked"}) // for unwrap(), this
+    default S parallel() {
+        return isParallel() ? (S) this : wrap(unwrap().parallel());
+    }
+
+    /**
+     * Like {@link BaseStream#sequential()}.
+     *
+     * @return See {@link BaseStream#sequential() delegate}.
+     * @see BaseStream#sequential()
+     */
+    @SuppressWarnings({"resource", "unchecked"}) // for unwrap(), this
+    default S sequential() {
+        return isParallel() ? wrap(unwrap().sequential()) : (S) this;
+    }
+
+    /**
+     * Like {@link BaseStream#spliterator()}.
+     *
+     * @return See {@link BaseStream#spliterator() delegate}.
+     * @see BaseStream#spliterator()
+     */
+    @SuppressWarnings("resource") // for unwrap()
+    default IOSpliterator<T> spliterator() {
+        return IOSpliteratorAdapter.adapt(unwrap().spliterator());
+    }
+
+    /**
+     * Like {@link BaseStream#unordered()}.
+     *
+     * @return See {@link BaseStream#unordered() delegate}.
+     * @see java.util.stream.BaseStream#unordered()
+     */
+    @SuppressWarnings("resource") // for unwrap()
+    default S unordered() {
+        return wrap(unwrap().unordered());
+    }
+
+    /**
+     * Unwraps this instance and returns the underlying {@link Stream}.
+     * <p>
+     * Implementations may not have anything to unwrap and that behavior is undefined for now.
+     * </p>
+     *
+     * @return the underlying stream.
+     */
+    B unwrap();
+
+    /**
+     * Wraps a {@link Stream}.
+     *
+     * @param delegate The delegate.
+     * @return An IO stream.
+     */
+    S wrap(B delegate);
+
+}
diff --git a/src/main/java/org/apache/commons/io/function/IOBaseStreamAdapter.java b/src/main/java/org/apache/commons/io/function/IOBaseStreamAdapter.java
new file mode 100644
index 00000000..c1413dad
--- /dev/null
+++ b/src/main/java/org/apache/commons/io/function/IOBaseStreamAdapter.java
@@ -0,0 +1,52 @@
+/*
+ * 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.util.Objects;
+import java.util.stream.BaseStream;
+
+/**
+ * Abstracts an {@link IOBaseStream} implementation.
+ *
+ * Keep package-private for now.
+ *
+ * @param <T> the type of the stream elements.
+ * @param <S> the type of the stream extending {@code IOBaseStream}.
+ */
+abstract class IOBaseStreamAdapter<T, S extends IOBaseStream<T, S, B>, B extends BaseStream<T, B>> implements IOBaseStream<T, S, B> {
+
+    /**
+     * The underlying base stream.
+     */
+    private final B delegate;
+
+    /**
+     * Constructs an instance.
+     *
+     * @param delegate the delegate.
+     */
+    IOBaseStreamAdapter(final B delegate) {
+        this.delegate = Objects.requireNonNull(delegate, "delegate");
+    }
+
+    @Override
+    public B unwrap() {
+        return delegate;
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/io/function/IOStream.java b/src/main/java/org/apache/commons/io/function/IOStream.java
new file mode 100644
index 00000000..116a7a42
--- /dev/null
+++ b/src/main/java/org/apache/commons/io/function/IOStream.java
@@ -0,0 +1,597 @@
+/*
+ * 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.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Spliterator;
+import java.util.Spliterators;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.BiFunction;
+import java.util.function.IntFunction;
+import java.util.function.ToDoubleFunction;
+import java.util.function.ToIntFunction;
+import java.util.function.ToLongFunction;
+import java.util.function.UnaryOperator;
+import java.util.stream.Collector;
+import java.util.stream.DoubleStream;
+import java.util.stream.IntStream;
+import java.util.stream.LongStream;
+import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
+
+import org.apache.commons.io.IOExceptionList;
+
+/**
+ * Like {@link Stream} but throws {@link IOException}.
+ *
+ * @param <T> the type of the stream elements.
+ * @since 2.12.0
+ */
+public interface IOStream<T> extends IOBaseStream<T, IOStream<T>, Stream<T>> {
+
+    /**
+     * Constructs a new IOStream for the given Stream.
+     *
+     * @param <T> the type of the stream elements.
+     * @param stream The stream to delegate.
+     * @return a new IOStream.
+     */
+    static <T> IOStream<T> adapt(final Stream<T> stream) {
+        return IOStreamAdapter.adapt(stream);
+    }
+
+    /**
+     * This class' version of {@link Stream#empty()}.
+     *
+     * @param <T> the type of the stream elements
+     * @return an empty sequential {@code IOStreamImpl}.
+     * @see Stream#empty()
+     */
+    static <T> IOStream<T> empty() {
+        return IOStreamAdapter.adapt(Stream.empty());
+    }
+
+    /**
+     * Performs an action for each element gathering any exceptions.
+     *
+     * @param action The action to apply to each element.
+     * @throws IOExceptionList if any I/O errors occur.
+     */
+    default void forAll(final IOConsumer<T> action) throws IOExceptionList {
+        forAll(action, (i, e) -> e);
+    }
+
+    /**
+     * Performs an action for each element gathering any exceptions.
+     *
+     * @param action The action to apply to each element.
+     * @param exSupplier The exception supplier.
+     * @throws IOExceptionList if any I/O errors occur.
+     */
+    default void forAll(final IOConsumer<T> action, final BiFunction<Integer, IOException, IOException> exSupplier) throws IOExceptionList {
+        final AtomicReference<List<IOException>> causeList = new AtomicReference<>();
+        final AtomicInteger index = new AtomicInteger();
+        final IOConsumer<T> safeAction = IOStreams.toIOConsumer(action);
+        unwrap().forEach(e -> {
+            try {
+                safeAction.accept(e);
+            } catch (final IOException innerEx) {
+                if (causeList.get() == null) {
+                    // Only allocate if required
+                    causeList.set(new ArrayList<>());
+                }
+                if (exSupplier != null) {
+                    causeList.get().add(exSupplier.apply(index.get(), innerEx));
+                }
+            }
+            index.incrementAndGet();
+        });
+        IOExceptionList.checkEmpty(causeList.get(), null);
+    }
+
+    /**
+     * Like {@link Stream#iterate(Object, UnaryOperator)} but for IO.
+     *
+     * @param <T> the type of stream elements.
+     * @param seed the initial element.
+     * @param f a function to be applied to the previous element to produce a new element.
+     * @return a new sequential {@code IOStream}.
+     */
+    static <T> IOStream<T> iterate(final T seed, final IOUnaryOperator<T> f) {
+        Objects.requireNonNull(f);
+        final Iterator<T> iterator = new Iterator<T>() {
+            @SuppressWarnings("unchecked")
+            T t = (T) IOStreams.NONE;
+
+            @Override
+            public boolean hasNext() {
+                return true;
+            }
+
+            @Override
+            public T next() {
+                return t = t == IOStreams.NONE ? seed : Erase.apply(f, t);
+            }
+        };
+        return adapt(StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, Spliterator.ORDERED | Spliterator.IMMUTABLE), false));
+    }
+
+    /**
+     * Null-safe version of {@link StreamSupport#stream(java.util.Spliterator, boolean)}.
+     *
+     * Copied from Apache Commons Lang.
+     *
+     * @param <T> the type of stream elements.
+     * @param values the elements of the new stream, may be {@code null}.
+     * @return the new stream on {@code values} or {@link Stream#empty()}.
+     */
+    @SuppressWarnings("resource") // call to #empty()
+    static <T> IOStream<T> of(final Iterable<T> values) {
+        return values == null ? empty() : adapt(StreamSupport.stream(values.spliterator(), false));
+    }
+
+    /**
+     * Null-safe version of {@link Stream#of(Object[])} for an IO stream.
+     *
+     * @param <T> the type of stream elements.
+     * @param values the elements of the new stream, may be {@code null}.
+     * @return the new stream on {@code values} or {@link Stream#empty()}.
+     */
+    @SuppressWarnings("resource")
+    @SafeVarargs // Creating a stream from an array is safe
+    static <T> IOStream<T> of(final T... values) {
+        return values == null || values.length == 0 ? empty() : adapt(Arrays.stream(values));
+    }
+
+    /**
+     * Returns a sequential {@code IOStreamImpl} containing a single element.
+     *
+     * @param t the single element
+     * @param <T> the type of stream elements
+     * @return a singleton sequential stream
+     */
+    static <T> IOStream<T> of(final T t) {
+        return adapt(Stream.of(t));
+    }
+
+    /**
+     * Like {@link Stream#allMatch(java.util.function.Predicate)} but throws {@link IOException}.
+     *
+     * @param predicate {@link Stream#allMatch(java.util.function.Predicate)}.
+     * @return Like {@link Stream#allMatch(java.util.function.Predicate)}.
+     * @throws IOException if an I/O error occurs.
+     */
+    @SuppressWarnings("unused") // thrown by Erase.
+    default boolean allMatch(final IOPredicate<? super T> predicate) throws IOException {
+        return unwrap().allMatch(t -> Erase.test(predicate, t));
+    }
+
+    /**
+     * Like {@link Stream#anyMatch(java.util.function.Predicate)} but throws {@link IOException}.
+     *
+     * @param predicate {@link Stream#anyMatch(java.util.function.Predicate)}.
+     * @return Like {@link Stream#anyMatch(java.util.function.Predicate)}.
+     * @throws IOException if an I/O error occurs.
+     */
+    @SuppressWarnings("unused") // thrown by Erase.
+    default boolean anyMatch(final IOPredicate<? super T> predicate) throws IOException {
+        return unwrap().anyMatch(t -> Erase.test(predicate, t));
+    }
+
+    /**
+     * TODO Package-private for now, needs IOCollector?
+     *
+     * Adding this method now and an IO version later is an issue because call sites would have to type-cast to pick one. It
+     * would be ideal to have only one.
+     *
+     * Like {@link Stream#collect(Collector)}.
+     *
+     * Package private for now.
+     *
+     * @param <R> Like {@link Stream#collect(Collector)}.
+     * @param <A> Like {@link Stream#collect(Collector)}.
+     * @param collector Like {@link Stream#collect(Collector)}.
+     * @return Like {@link Stream#collect(Collector)}.
+     */
+    default <R, A> R collect(final Collector<? super T, A, R> collector) {
+        return unwrap().collect(collector);
+    }
+
+    /**
+     * Like
+     * {@link Stream#collect(java.util.function.Supplier, java.util.function.BiConsumer, java.util.function.BiConsumer)}.
+     *
+     * @param <R> Like
+     *        {@link Stream#collect(java.util.function.Supplier, java.util.function.BiConsumer, java.util.function.BiConsumer)}.
+     * @param supplier Like
+     *        {@link Stream#collect(java.util.function.Supplier, java.util.function.BiConsumer, java.util.function.BiConsumer)}.
+     * @param accumulator Like
+     *        {@link Stream#collect(java.util.function.Supplier, java.util.function.BiConsumer, java.util.function.BiConsumer)}.
+     * @param combiner Like
+     *        {@link Stream#collect(java.util.function.Supplier, java.util.function.BiConsumer, java.util.function.BiConsumer)}.
+     * @return Like
+     *         {@link Stream#collect(java.util.function.Supplier, java.util.function.BiConsumer, java.util.function.BiConsumer)}.
+     * @throws IOException if an I/O error occurs.
+     */
+    @SuppressWarnings("unused") // thrown by Erase.
+    default <R> R collect(final IOSupplier<R> supplier, final IOBiConsumer<R, ? super T> accumulator, final IOBiConsumer<R, R> combiner) throws IOException {
+        return unwrap().collect(() -> Erase.get(supplier), (t, u) -> Erase.accept(accumulator, t, u), (t, u) -> Erase.accept(combiner, t, u));
+    }
+
+    /**
+     * Like {@link Stream#count()}.
+     *
+     * @return Like {@link Stream#count()}.
+     */
+    default long count() {
+        return unwrap().count();
+    }
+
+    /**
+     * Like {@link Stream#distinct()}.
+     *
+     * @return Like {@link Stream#distinct()}.
+     */
+    default IOStream<T> distinct() {
+        return adapt(unwrap().distinct());
+    }
+
+    /**
+     * Like {@link Stream#filter(java.util.function.Predicate)}.
+     *
+     * @param predicate Like {@link Stream#filter(java.util.function.Predicate)}.
+     * @return Like {@link Stream#filter(java.util.function.Predicate)}.
+     * @throws IOException if an I/O error occurs.
+     */
+    @SuppressWarnings("unused") // thrown by Erase.
+    default IOStream<T> filter(final IOPredicate<? super T> predicate) throws IOException {
+        return adapt(unwrap().filter(t -> Erase.test(predicate, t)));
+    }
+
+    /**
+     * Like {@link Stream#findAny()}.
+     *
+     * @return Like {@link Stream#findAny()}.
+     */
+    default Optional<T> findAny() {
+        return unwrap().findAny();
+    }
+
+    /**
+     * Like {@link Stream#findFirst()}.
+     *
+     * @return Like {@link Stream#findFirst()}.
+     */
+    default Optional<T> findFirst() {
+        return unwrap().findFirst();
+    }
+
+    /**
+     * Like {@link Stream#flatMap(java.util.function.Function)}.
+     *
+     * @param <R> Like {@link Stream#flatMap(java.util.function.Function)}.
+     * @param mapper Like {@link Stream#flatMap(java.util.function.Function)}.
+     * @return Like {@link Stream#flatMap(java.util.function.Function)}.
+     * @throws IOException if an I/O error occurs.
+     */
+    @SuppressWarnings("unused") // thrown by Erase.
+    default <R> IOStream<R> flatMap(final IOFunction<? super T, ? extends IOStream<? extends R>> mapper) throws IOException {
+        return adapt(unwrap().flatMap(t -> Erase.apply(mapper, t).unwrap()));
+    }
+
+    /**
+     * TODO Package-private for now, needs IODoubleStream?
+     *
+     * Adding this method now and an IO version later is an issue because call sites would have to type-cast to pick one. It
+     * would be ideal to have only one.
+     *
+     * Like {@link Stream#flatMapToDouble(java.util.function.Function)}.
+     *
+     * @param mapper Like {@link Stream#flatMapToDouble(java.util.function.Function)}.
+     * @return Like {@link Stream#flatMapToDouble(java.util.function.Function)}.
+     * @throws IOException if an I/O error occurs.
+     */
+    @SuppressWarnings("unused") // thrown by Erase.
+    default DoubleStream flatMapToDouble(final IOFunction<? super T, ? extends DoubleStream> mapper) throws IOException {
+        return unwrap().flatMapToDouble(t -> Erase.apply(mapper, t));
+    }
+
+    /**
+     * TODO Package-private for now, needs IOIntStream?
+     *
+     * Adding this method now and an IO version later is an issue because call sites would have to type-cast to pick one. It
+     * would be ideal to have only one.
+     *
+     * Like {@link Stream#flatMapToInt(java.util.function.Function)}.
+     *
+     * @param mapper Like {@link Stream#flatMapToInt(java.util.function.Function)}.
+     * @return Like {@link Stream#flatMapToInt(java.util.function.Function)}.
+     * @throws IOException if an I/O error occurs.
+     */
+    @SuppressWarnings("unused") // thrown by Erase.
+    default IntStream flatMapToInt(final IOFunction<? super T, ? extends IntStream> mapper) throws IOException {
+        return unwrap().flatMapToInt(t -> Erase.apply(mapper, t));
+    }
+
+    /**
+     * TODO Package-private for now, needs IOLongStream?
+     *
+     * Adding this method now and an IO version later is an issue because call sites would have to type-cast to pick one. It
+     * would be ideal to have only one.
+     *
+     * Like {@link Stream#flatMapToLong(java.util.function.Function)}.
+     *
+     * @param mapper Like {@link Stream#flatMapToLong(java.util.function.Function)}.
+     * @return Like {@link Stream#flatMapToLong(java.util.function.Function)}.
+     * @throws IOException if an I/O error occurs.
+     */
+    @SuppressWarnings("unused") // thrown by Erase.
+    default LongStream flatMapToLong(final IOFunction<? super T, ? extends LongStream> mapper) throws IOException {
+        return unwrap().flatMapToLong(t -> Erase.apply(mapper, t));
+    }
+
+    /**
+     * Like {@link Stream#forEach(java.util.function.Consumer)} but throws {@link IOException}.
+     *
+     * @param action Like {@link Stream#forEach(java.util.function.Consumer)}.
+     * @throws IOException if an I/O error occurs.
+     */
+    @SuppressWarnings("unused") // thrown by Erase.
+    default void forEach(final IOConsumer<? super T> action) throws IOException {
+        unwrap().forEach(e -> Erase.accept(action, e));
+    }
+
+    /**
+     * Like {@link Stream#forEachOrdered(java.util.function.Consumer)}.
+     *
+     * @param action Like {@link Stream#forEachOrdered(java.util.function.Consumer)}.
+     * @throws IOException if an I/O error occurs.
+     */
+    @SuppressWarnings("unused") // thrown by Erase.
+    default void forEachOrdered(final IOConsumer<? super T> action) throws IOException {
+        unwrap().forEachOrdered(e -> Erase.accept(action, e));
+    }
+
+    /**
+     * Like {@link Stream#limit(long)}.
+     *
+     * @param maxSize Like {@link Stream#limit(long)}.
+     * @return Like {@link Stream#limit(long)}.
+     */
+    default IOStream<T> limit(final long maxSize) {
+        return adapt(unwrap().limit(maxSize));
+    }
+
+    /**
+     * Like {@link Stream#map(java.util.function.Function)}.
+     *
+     * @param <R> Like {@link Stream#map(java.util.function.Function)}.
+     * @param mapper Like {@link Stream#map(java.util.function.Function)}.
+     * @return Like {@link Stream#map(java.util.function.Function)}.
+     * @throws IOException if an I/O error occurs.
+     */
+    @SuppressWarnings("unused") // thrown by Erase.
+    default <R> IOStream<R> map(final IOFunction<? super T, ? extends R> mapper) throws IOException {
+        return adapt(unwrap().map(t -> Erase.apply(mapper, t)));
+    }
+
+    /**
+     * TODO Package-private for now, needs IOToDoubleFunction?
+     *
+     * Adding this method now and an IO version later is an issue because call sites would have to type-cast to pick one. It
+     * would be ideal to have only one.
+     *
+     * Like {@link Stream#mapToDouble(ToDoubleFunction)}.
+     *
+     * Package private for now.
+     *
+     * @param mapper Like {@link Stream#mapToDouble(ToDoubleFunction)}.
+     * @return Like {@link Stream#mapToDouble(ToDoubleFunction)}.
+     */
+    default DoubleStream mapToDouble(final ToDoubleFunction<? super T> mapper) {
+        return unwrap().mapToDouble(mapper);
+    }
+
+    /**
+     * TODO Package-private for now, needs IOToIntFunction?
+     *
+     * Adding this method now and an IO version later is an issue because call sites would have to type-cast to pick one. It
+     * would be ideal to have only one.
+     *
+     * Like {@link Stream#mapToInt(ToIntFunction)}.
+     *
+     * Package private for now.
+     *
+     * @param mapper Like {@link Stream#mapToInt(ToIntFunction)}.
+     * @return Like {@link Stream#mapToInt(ToIntFunction)}.
+     */
+    default IntStream mapToInt(final ToIntFunction<? super T> mapper) {
+        return unwrap().mapToInt(mapper);
+    }
+
+    /**
+     * TODO Package-private for now, needs IOToLongFunction?
+     *
+     * Adding this method now and an IO version later is an issue because call sites would have to type-cast to pick one. It
+     * would be ideal to have only one.
+     *
+     * Like {@link Stream#mapToLong(ToLongFunction)}.
+     *
+     * Package private for now.
+     *
+     * @param mapper Like {@link Stream#mapToLong(ToLongFunction)}.
+     * @return Like {@link Stream#mapToLong(ToLongFunction)}.
+     */
+    default LongStream mapToLong(final ToLongFunction<? super T> mapper) {
+        return unwrap().mapToLong(mapper);
+    }
+
+    /**
+     * Like {@link Stream#max(java.util.Comparator)}.
+     *
+     * @param comparator Like {@link Stream#max(java.util.Comparator)}.
+     * @return Like {@link Stream#max(java.util.Comparator)}.
+     * @throws IOException if an I/O error occurs.
+     */
+    @SuppressWarnings("unused") // thrown by Erase.
+    default Optional<T> max(final IOComparator<? super T> comparator) throws IOException {
+        return unwrap().max((t, u) -> Erase.compare(comparator, t, u));
+    }
+
+    /**
+     * Like {@link Stream#min(java.util.Comparator)}.
+     *
+     * @param comparator Like {@link Stream#min(java.util.Comparator)}.
+     * @return Like {@link Stream#min(java.util.Comparator)}.
+     * @throws IOException if an I/O error occurs.
+     */
+    @SuppressWarnings("unused") // thrown by Erase.
+    default Optional<T> min(final IOComparator<? super T> comparator) throws IOException {
+        return unwrap().min((t, u) -> Erase.compare(comparator, t, u));
+    }
+
+    /**
+     * Like {@link Stream#noneMatch(java.util.function.Predicate)}.
+     *
+     * @param predicate Like {@link Stream#noneMatch(java.util.function.Predicate)}.
+     * @return Like {@link Stream#noneMatch(java.util.function.Predicate)}.
+     * @throws IOException if an I/O error occurs.
+     */
+    @SuppressWarnings("unused") // thrown by Erase.
+    default boolean noneMatch(final IOPredicate<? super T> predicate) throws IOException {
+        return unwrap().noneMatch(t -> Erase.test(predicate, t));
+    }
+
+    /**
+     * Like {@link Stream#peek(java.util.function.Consumer)}.
+     *
+     * @param action Like {@link Stream#peek(java.util.function.Consumer)}.
+     * @return Like {@link Stream#peek(java.util.function.Consumer)}.
+     * @throws IOException if an I/O error occurs.
+     */
+    @SuppressWarnings("unused") // thrown by Erase.
+    default IOStream<T> peek(final IOConsumer<? super T> action) throws IOException {
+        return adapt(unwrap().peek(t -> Erase.accept(action, t)));
+    }
+
+    /**
+     * Like {@link Stream#reduce(java.util.function.BinaryOperator)}.
+     *
+     * @param accumulator Like {@link Stream#reduce(java.util.function.BinaryOperator)}.
+     * @return Like {@link Stream#reduce(java.util.function.BinaryOperator)}.
+     * @throws IOException if an I/O error occurs.
+     */
+    @SuppressWarnings("unused") // thrown by Erase.
+    default Optional<T> reduce(final IOBinaryOperator<T> accumulator) throws IOException {
+        return unwrap().reduce((t, u) -> Erase.apply(accumulator, t, u));
+    }
+
+    /**
+     * Like {@link Stream#reduce(Object, java.util.function.BinaryOperator)}.
+     *
+     * @param identity Like {@link Stream#reduce(Object, java.util.function.BinaryOperator)}.
+     * @param accumulator Like {@link Stream#reduce(Object, java.util.function.BinaryOperator)}.
+     * @return Like {@link Stream#reduce(Object, java.util.function.BinaryOperator)}.
+     * @throws IOException if an I/O error occurs.
+     */
+    @SuppressWarnings("unused") // thrown by Erase.
+    default T reduce(final T identity, final IOBinaryOperator<T> accumulator) throws IOException {
+        return unwrap().reduce(identity, (t, u) -> Erase.apply(accumulator, t, u));
+    }
+
+    /**
+     * Like {@link Stream#reduce(Object, BiFunction, java.util.function.BinaryOperator)}.
+     *
+     * @param <U> Like {@link Stream#reduce(Object, BiFunction, java.util.function.BinaryOperator)}.
+     * @param identity Like {@link Stream#reduce(Object, BiFunction, java.util.function.BinaryOperator)}.
+     * @param accumulator Like {@link Stream#reduce(Object, BiFunction, java.util.function.BinaryOperator)}.
+     * @param combiner Like {@link Stream#reduce(Object, BiFunction, java.util.function.BinaryOperator)}.
+     * @return Like {@link Stream#reduce(Object, BiFunction, java.util.function.BinaryOperator)}.
+     * @throws IOException if an I/O error occurs.
+     */
+    @SuppressWarnings("unused") // thrown by Erase.
+    default <U> U reduce(final U identity, final IOBiFunction<U, ? super T, U> accumulator, final IOBinaryOperator<U> combiner) throws IOException {
+        return unwrap().reduce(identity, (t, u) -> Erase.apply(accumulator, t, u), (t, u) -> Erase.apply(combiner, t, u));
+    }
+
+    /**
+     * Like {@link Stream#skip(long)}.
+     *
+     * @param n Like {@link Stream#skip(long)}.
+     * @return Like {@link Stream#skip(long)}.
+     */
+    default IOStream<T> skip(final long n) {
+        return adapt(unwrap().skip(n));
+    }
+
+    /**
+     * Like {@link Stream#sorted()}.
+     *
+     * @return Like {@link Stream#sorted()}.
+     */
+    default IOStream<T> sorted() {
+        return adapt(unwrap().sorted());
+    }
+
+    /**
+     * Like {@link Stream#sorted(java.util.Comparator)}.
+     *
+     * @param comparator Like {@link Stream#sorted(java.util.Comparator)}.
+     * @return Like {@link Stream#sorted(java.util.Comparator)}.
+     * @throws IOException if an I/O error occurs.
+     */
+    @SuppressWarnings("unused") // thrown by Erase.
+    default IOStream<T> sorted(final IOComparator<? super T> comparator) throws IOException {
+        return adapt(unwrap().sorted((t, u) -> Erase.compare(comparator, t, u)));
+    }
+
+    /**
+     * Like {@link Stream#toArray()}.
+     *
+     * @return {@link Stream#toArray()}.
+     */
+    default Object[] toArray() {
+        return unwrap().toArray();
+    }
+
+    /**
+     * TODO Package-private for now, needs IOIntFunction?
+     *
+     * Adding this method now and an IO version later is an issue because call sites would have to type-cast to pick one. It
+     * would be ideal to have only one.
+     *
+     * Like {@link Stream#toArray(IntFunction)}.
+     *
+     * Package private for now.
+     *
+     * @param <A> Like {@link Stream#toArray(IntFunction)}.
+     * @param generator Like {@link Stream#toArray(IntFunction)}.
+     * @return Like {@link Stream#toArray(IntFunction)}.
+     */
+    default <A> A[] toArray(final IntFunction<A[]> generator) {
+        return unwrap().toArray(generator);
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/io/function/IOStreamAdapter.java b/src/main/java/org/apache/commons/io/function/IOStreamAdapter.java
new file mode 100644
index 00000000..aed21a7f
--- /dev/null
+++ b/src/main/java/org/apache/commons/io/function/IOStreamAdapter.java
@@ -0,0 +1,45 @@
+/*
+ * 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.util.stream.Stream;
+
+/**
+ * Adapts an {@link Stream} as an {@link IOStream}.
+ *
+ * Keep package-private for now.
+ *
+ * @param <T> the type of the stream elements.
+ */
+final class IOStreamAdapter<T> extends IOBaseStreamAdapter<T, IOStream<T>, Stream<T>> implements IOStream<T> {
+
+    @SuppressWarnings("resource")
+    static <T> IOStream<T> adapt(final Stream<T> delegate) {
+        return delegate != null ? new IOStreamAdapter<>(delegate) : IOStream.empty();
+    }
+
+    private IOStreamAdapter(final Stream<T> delegate) {
+        super(delegate);
+    }
+
+    @Override
+    public IOStream<T> wrap(final Stream<T> delegate) {
+        return unwrap() == delegate ? this : adapt(delegate);
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/io/function/IOStreams.java b/src/main/java/org/apache/commons/io/function/IOStreams.java
index 5dfd107f..70cac787 100644
--- a/src/main/java/org/apache/commons/io/function/IOStreams.java
+++ b/src/main/java/org/apache/commons/io/function/IOStreams.java
@@ -18,67 +18,34 @@
 package org.apache.commons.io.function;
 
 import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.BiFunction;
 import java.util.stream.Stream;
 import java.util.stream.StreamSupport;
 
 import org.apache.commons.io.IOExceptionList;
+import org.apache.commons.io.IOIndexedException;
 
 /**
  * Keep this code package-private for now.
  */
-class IOStreams {
+final class IOStreams {
 
-    /**
-     * Accepts and throws an IOException.
-     *
-     * @param <T> The consumer type.
-     * @param consumer The consumer to accept.
-     * @param t the input argument.
-     * @throws IOException if an I/O error occurs; erased for the compiler.
-     */
-    static <T> void accept(final IOConsumer<T> consumer, T t) {
-        try {
-            consumer.accept(t);
-        } catch (IOException ex) {
-            rethrow(ex);
-        }
-    }
+    static final Object NONE = new Object();
 
     static <T> void forAll(final Stream<T> stream, final IOConsumer<T> action) throws IOExceptionList {
         forAll(stream, action, (i, e) -> e);
     }
 
+    @SuppressWarnings("resource") // adapt()
     static <T> void forAll(final Stream<T> stream, final IOConsumer<T> action, final BiFunction<Integer, IOException, IOException> exSupplier)
         throws IOExceptionList {
-        final AtomicReference<List<IOException>> causeList = new AtomicReference<>();
-        final AtomicInteger index = new AtomicInteger();
-        final IOConsumer<T> actualAction = toIOConsumer(action);
-        of(stream).forEach(e -> {
-            try {
-                actualAction.accept(e);
-            } catch (IOException ex) {
-                if (causeList.get() == null) {
-                    // Only allocate if required
-                    causeList.set(new ArrayList<>());
-                }
-                if (exSupplier != null) {
-                    causeList.get().add(exSupplier.apply(index.get(), ex));
-                }
-            }
-            index.incrementAndGet();
-        });
-        IOExceptionList.checkEmpty(causeList.get(), null);
+        IOStream.adapt(stream).forAll(action, IOIndexedException::new);
     }
 
     @SuppressWarnings("unused") // IOStreams.rethrow() throws
     static <T> void forEach(final Stream<T> stream, final IOConsumer<T> action) throws IOException {
         final IOConsumer<T> actualAction = toIOConsumer(action);
-        of(stream).forEach(e -> accept(actualAction, e));
+        of(stream).forEach(e -> Erase.accept(actualAction, e));
     }
 
     /**
@@ -112,20 +79,11 @@ class IOStreams {
         return values == null ? Stream.empty() : Stream.of(values);
     }
 
-    /**
-     * Throws the given throwable.
-     *
-     * @param <T> The throwable cast type.
-     * @param throwable The throwable to rethrow.
-     * @return nothing because we throw.
-     * @throws T Always thrown.
-     */
-    @SuppressWarnings("unchecked")
-    static <T extends Throwable> RuntimeException rethrow(final Throwable throwable) throws T {
-        throw (T) throwable; // hack
-    }
-
     static <T> IOConsumer<T> toIOConsumer(final IOConsumer<T> action) {
         return action != null ? action : IOConsumer.noop();
     }
+
+    private IOStreams() {
+        // no instances
+    }
 }
diff --git a/src/main/java/org/apache/commons/io/function/UncheckedIOBaseStream.java b/src/main/java/org/apache/commons/io/function/UncheckedIOBaseStream.java
new file mode 100644
index 00000000..3e033ee0
--- /dev/null
+++ b/src/main/java/org/apache/commons/io/function/UncheckedIOBaseStream.java
@@ -0,0 +1,88 @@
+/*
+ * 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.io.UncheckedIOException;
+import java.util.Iterator;
+import java.util.Spliterator;
+import java.util.stream.BaseStream;
+
+/**
+ * An {@link BaseStream} for a {@link IOBaseStream} that throws {@link UncheckedIOException} instead of
+ * {@link IOException}.
+ *
+ * Keep package-private for now.
+ *
+ * @param <T> the type of the stream elements.
+ * @param <S> the type of the IO stream extending {@code IOBaseStream}.
+ * @param <B> the type of the stream extending {@code BaseStream}.
+ */
+class UncheckedIOBaseStream<T, S extends IOBaseStream<T, S, B>, B extends BaseStream<T, B>> implements BaseStream<T, B> {
+
+    private final S delegate;
+
+    UncheckedIOBaseStream(final S delegate) {
+        this.delegate = delegate;
+    }
+
+    @Override
+    public void close() {
+        delegate.close();
+    }
+
+    @Override
+    public boolean isParallel() {
+        return delegate.isParallel();
+    }
+
+    @Override
+    public Iterator<T> iterator() {
+        return delegate.iterator().asIterator();
+    }
+
+    @SuppressWarnings("resource")
+    @Override
+    public B onClose(final Runnable closeHandler) {
+        return Uncheck.apply(delegate::onClose, () -> closeHandler.run()).unwrap();
+    }
+
+    @SuppressWarnings("resource")
+    @Override
+    public B parallel() {
+        return delegate.parallel().unwrap();
+    }
+
+    @SuppressWarnings("resource")
+    @Override
+    public B sequential() {
+        return delegate.sequential().unwrap();
+    }
+
+    @Override
+    public Spliterator<T> spliterator() {
+        return delegate.spliterator().unwrap();
+    }
+
+    @SuppressWarnings("resource")
+    @Override
+    public B unordered() {
+        return delegate.unordered().unwrap();
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/io/input/ObservableInputStream.java b/src/main/java/org/apache/commons/io/input/ObservableInputStream.java
index ad4653ba..259f64c9 100644
--- a/src/main/java/org/apache/commons/io/input/ObservableInputStream.java
+++ b/src/main/java/org/apache/commons/io/input/ObservableInputStream.java
@@ -23,7 +23,6 @@ import java.io.InputStream;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
-import java.util.Objects;
 
 import org.apache.commons.io.IOUtils;
 import org.apache.commons.io.function.IOConsumer;
@@ -175,7 +174,7 @@ public class ObservableInputStream extends ProxyInputStream {
     }
 
     private void forEachObserver(final IOConsumer<Observer> action) throws IOException {
-        IOConsumer.forAll(Objects.requireNonNull(action), observers);
+        IOConsumer.forAll(action, observers);
     }
 
     /**
diff --git a/src/test/java/org/apache/commons/io/function/EraseTest.java b/src/test/java/org/apache/commons/io/function/EraseTest.java
new file mode 100644
index 00000000..cabaebb9
--- /dev/null
+++ b/src/test/java/org/apache/commons/io/function/EraseTest.java
@@ -0,0 +1,97 @@
+/*
+ * 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 static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.IOException;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests {@code Erase}.
+ */
+class EraseTest {
+
+    private final AtomicInteger intRef = new AtomicInteger();
+    private final AtomicBoolean boolRef = new AtomicBoolean();
+
+    @Test
+    void testAcceptIOBiConsumerOfTUTU() {
+        Erase.accept((e, f) -> boolRef.set(intRef.compareAndSet(0, e)), 1, true);
+        assertEquals(1, intRef.get());
+        assertTrue(boolRef.get());
+        assertThrows(IOException.class, () -> Erase.accept(TestUtils.throwingIOBiConsumer(), null, 1));
+    }
+
+    @Test
+    void testAcceptIOConsumerOfTT() {
+        Erase.accept(e -> intRef.compareAndSet(0, e), 1);
+        assertEquals(1, intRef.get());
+        assertThrows(IOException.class, () -> Erase.accept(TestUtils.throwingIOConsumer(), 1));
+    }
+
+    @Test
+    void testApplyIOBiFunctionOfQsuperTQsuperUQextendsRTU() {
+        assertTrue(Erase.<Integer, Boolean, Boolean>apply((i, b) -> boolRef.compareAndSet(false, intRef.compareAndSet(0, i.intValue())), 1, Boolean.TRUE));
+        assertThrows(IOException.class, () -> Erase.apply(TestUtils.throwingIOBiFunction(), 1, Boolean.TRUE));
+    }
+
+    @Test
+    void testApplyIOFunctionOfQsuperTQextendsRT() {
+        assertTrue(Erase.<Integer, Boolean>apply(e -> intRef.compareAndSet(0, e), 1));
+        assertThrows(IOException.class, () -> Erase.apply(TestUtils.throwingIOFunction(), 1));
+    }
+
+    @Test
+    void testCompare() {
+        assertEquals(0, Erase.compare(String::compareTo, "A", "A"));
+        assertEquals(-1, Erase.compare(String::compareTo, "A", "B"));
+        assertEquals(1, Erase.compare(String::compareTo, "B", "A"));
+        assertThrows(IOException.class, () -> Erase.compare(TestUtils.throwingIOComparator(), null, null));
+    }
+
+    @Test
+    void testGet() {
+        assertEquals(0, Erase.get(() -> intRef.get()));
+        assertThrows(IOException.class, () -> Erase.get(TestUtils.throwingIOSupplier()));
+    }
+
+    @Test
+    void testRethrow() {
+        assertThrows(IOException.class, () -> Erase.rethrow(new IOException()));
+    }
+
+    @Test
+    void testRun() {
+        Erase.run(() -> intRef.set(1));
+        assertEquals(1, intRef.get());
+        assertThrows(IOException.class, () -> Erase.run(TestUtils.throwingIORunnable()));
+    }
+
+    @Test
+    void testTest() {
+        assertTrue(Erase.test(e -> intRef.compareAndSet(0, e), 1));
+        assertThrows(IOException.class, () -> Erase.test(TestUtils.throwingIOPredicate(), 1));
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/io/function/IOBaseStreamTest.java b/src/test/java/org/apache/commons/io/function/IOBaseStreamTest.java
new file mode 100644
index 00000000..a77e55f2
--- /dev/null
+++ b/src/test/java/org/apache/commons/io/function/IOBaseStreamTest.java
@@ -0,0 +1,349 @@
+/*
+ * 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.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.assertSame;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.stream.BaseStream;
+import java.util.stream.Stream;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests {@link IOBaseStream}.
+ */
+public class IOBaseStreamTest {
+
+    /**
+     * Implements IOBaseStream with generics.
+     */
+    private static class IOBaseStreamFixture<T, S extends IOBaseStreamFixture<T, S, B>, B extends BaseStream<T, B>> implements IOBaseStream<T, S, B> {
+
+        private final B baseStream;
+
+        private IOBaseStreamFixture(final B baseStream) {
+            this.baseStream = baseStream;
+        }
+
+        @Override
+        public B unwrap() {
+            return baseStream;
+        }
+
+        @SuppressWarnings("unchecked") // We are this here
+        @Override
+        public S wrap(final B delegate) {
+            return delegate == baseStream ? (S) this : (S) new IOBaseStreamFixture<T, S, B>(delegate);
+        }
+
+    }
+
+    /**
+     * Implements IOBaseStream with a concrete type.
+     */
+    private static class IOBaseStreamPathFixture<B extends BaseStream<Path, B>> extends IOBaseStreamFixture<Path, IOBaseStreamPathFixture<B>, B> {
+
+        private IOBaseStreamPathFixture(final B baseStream) {
+            super(baseStream);
+        }
+
+        @Override
+        public IOBaseStreamPathFixture<B> wrap(final B delegate) {
+            return delegate == unwrap() ? this : new IOBaseStreamPathFixture<>(delegate);
+        }
+
+    }
+
+    private static class MyRuntimeException extends RuntimeException {
+
+        private static final long serialVersionUID = 1L;
+
+        public MyRuntimeException(final String message) {
+            super(message);
+        }
+
+    }
+
+    /** Sanity check */
+    private BaseStream<Path, ? extends BaseStream<Path, ?>> baseStream;
+
+    /** Generic version */
+    private IOBaseStreamFixture<Path, ? extends IOBaseStreamFixture<Path, ?, ?>, ?> ioBaseStream;
+
+    /** Concrete version */
+    private IOBaseStreamPathFixture<? extends BaseStream<Path, ?>> ioBaseStreamPath;
+
+    /** Adapter version */
+    private IOStream<Path> ioBaseStreamAdapter;
+
+    @BeforeEach
+    public void beforeEach() {
+        baseStream = createStreamOfPaths();
+        ioBaseStream = createIOBaseStream();
+        ioBaseStreamPath = createIOBaseStreamPath();
+        ioBaseStreamAdapter = createIOBaseStreamApapter();
+    }
+
+    private IOBaseStreamFixture<Path, ?, Stream<Path>> createIOBaseStream() {
+        return new IOBaseStreamFixture<>(createStreamOfPaths());
+    }
+
+    private IOStream<Path> createIOBaseStreamApapter() {
+        return IOStreamAdapter.adapt(createStreamOfPaths());
+    }
+
+    private IOBaseStreamPathFixture<Stream<Path>> createIOBaseStreamPath() {
+        return new IOBaseStreamPathFixture<>(createStreamOfPaths());
+    }
+
+    private Stream<Path> createStreamOfPaths() {
+        return Stream.of(TestConstants.ABS_PATH_A, TestConstants.ABS_PATH_B);
+    }
+
+    @Test
+    @AfterEach
+    public void testClose() {
+        baseStream.close();
+        ioBaseStream.close();
+        ioBaseStreamPath.close();
+        ioBaseStream.asBaseStream().close();
+        ioBaseStreamPath.asBaseStream().close();
+    }
+
+    @SuppressWarnings("resource") // @AfterEach
+    @Test
+    public void testIsParallel() {
+        assertFalse(baseStream.isParallel());
+        assertFalse(ioBaseStream.isParallel());
+        assertFalse(ioBaseStream.asBaseStream().isParallel());
+        assertFalse(ioBaseStreamPath.asBaseStream().isParallel());
+        assertFalse(ioBaseStreamPath.isParallel());
+    }
+
+    @SuppressWarnings("resource") // @AfterEach
+    @Test
+    public void testIteratorPathIO() throws IOException {
+        final AtomicReference<Path> ref = new AtomicReference<>();
+        ioBaseStream.iterator().forEachRemaining(e -> ref.set(e.toRealPath()));
+        assertEquals(TestConstants.ABS_PATH_B.toRealPath(), ref.get());
+        //
+        ioBaseStreamPath.asBaseStream().iterator().forEachRemaining(e -> ref.set(e.getFileName()));
+        assertEquals(TestConstants.ABS_PATH_B.getFileName(), ref.get());
+    }
+
+    @SuppressWarnings("resource") // @AfterEach
+    @Test
+    public void testIteratorSimple() throws IOException {
+        final AtomicInteger ref = new AtomicInteger();
+        baseStream.iterator().forEachRemaining(e -> ref.incrementAndGet());
+        assertEquals(2, ref.get());
+        ioBaseStream.iterator().forEachRemaining(e -> ref.incrementAndGet());
+        assertEquals(4, ref.get());
+        ioBaseStreamPath.asBaseStream().iterator().forEachRemaining(e -> ref.incrementAndGet());
+        assertEquals(6, ref.get());
+    }
+
+    @SuppressWarnings("resource")
+    @Test
+    public void testOnClose() {
+        // Stream
+        testOnClose(baseStream);
+        testOnClose(ioBaseStream.asBaseStream());
+        testOnClose(ioBaseStreamPath.asBaseStream());
+    }
+
+    @SuppressWarnings("resource")
+    private <T, S extends BaseStream<T, S>> void testOnClose(final BaseStream<T, S> stream) {
+        final AtomicReference<String> refA = new AtomicReference<>();
+        final AtomicReference<String> refB = new AtomicReference<>();
+        stream.onClose(() -> refA.set("A"));
+        stream.onClose(() -> {
+            throw new MyRuntimeException("B");
+        });
+        stream.onClose(() -> {
+            throw new MyRuntimeException("C");
+        });
+        stream.onClose(() -> refB.set("D"));
+        final MyRuntimeException e = assertThrows(MyRuntimeException.class, stream::close);
+        assertEquals("A", refA.get());
+        assertEquals("D", refB.get());
+        assertEquals("B", e.getMessage());
+        final Throwable[] suppressed = e.getSuppressed();
+        assertNotNull(suppressed);
+        assertEquals(1, suppressed.length);
+        assertEquals("C", suppressed[0].getMessage());
+    }
+
+    @SuppressWarnings("resource")
+    @Test
+    public void testParallel() throws IOException {
+        final AtomicInteger ref = new AtomicInteger();
+        baseStream.parallel().iterator().forEachRemaining(e -> ref.incrementAndGet());
+        assertEquals(2, ref.get());
+        ioBaseStream.parallel().iterator().forEachRemaining(e -> ref.incrementAndGet());
+        assertEquals(4, ref.get());
+        final BaseStream<Path, ?> parallel = ioBaseStreamPath.asBaseStream().parallel();
+        parallel.iterator().forEachRemaining(e -> ref.incrementAndGet());
+        assertEquals(6, ref.get());
+        assertTrue(parallel.isParallel());
+    }
+
+    @SuppressWarnings("resource") // @AfterEach
+    @Test
+    public void testParallelParallel() {
+        try (final IOBaseStream<?, ?, ?> stream = createIOBaseStream()) {
+            testParallelParallel(stream);
+        }
+        try (final IOBaseStream<?, ?, ?> stream = createIOBaseStreamPath()) {
+            testParallelParallel(stream);
+        }
+        try (final IOBaseStream<?, ?, ?> stream = createIOBaseStream()) {
+            testParallelParallel(stream);
+        }
+        try (final IOBaseStreamFixture<Path, ?, Stream<Path>> stream = createIOBaseStream()) {
+            testParallelParallel(stream.asBaseStream());
+        }
+    }
+
+    @SuppressWarnings("resource")
+    private void testParallelParallel(final BaseStream<?, ?> stream) {
+        final BaseStream<?, ?> seq = stream.sequential();
+        assertFalse(seq.isParallel());
+        final BaseStream<?, ?> p1 = seq.parallel();
+        assertTrue(p1.isParallel());
+        final BaseStream<?, ?> p2 = p1.parallel();
+        assertTrue(p1.isParallel());
+        assertSame(p1, p2);
+    }
+
+    @SuppressWarnings("resource")
+    private void testParallelParallel(final IOBaseStream<?, ?, ?> stream) {
+        final IOBaseStream<?, ?, ?> seq = stream.sequential();
+        assertFalse(seq.isParallel());
+        final IOBaseStream<?, ?, ?> p1 = seq.parallel();
+        assertTrue(p1.isParallel());
+        final IOBaseStream<?, ?, ?> p2 = p1.parallel();
+        assertTrue(p1.isParallel());
+        assertSame(p1, p2);
+    }
+
+    @SuppressWarnings("resource")
+    @Test
+    public void testSequential() throws IOException {
+        final AtomicInteger ref = new AtomicInteger();
+        baseStream.sequential().iterator().forEachRemaining(e -> ref.incrementAndGet());
+        assertEquals(2, ref.get());
+        ioBaseStream.sequential().iterator().forEachRemaining(e -> ref.incrementAndGet());
+        assertEquals(4, ref.get());
+        ioBaseStreamPath.asBaseStream().sequential().iterator().forEachRemaining(e -> ref.incrementAndGet());
+        assertEquals(6, ref.get());
+    }
+
+    @SuppressWarnings("resource") // @AfterEach
+    @Test
+    public void testSequentialSequential() {
+        try (final IOBaseStream<?, ?, ?> stream = createIOBaseStream()) {
+            testSequentialSequential(stream);
+        }
+        try (final IOBaseStream<?, ?, ?> stream = createIOBaseStreamPath()) {
+            testSequentialSequential(stream);
+        }
+        try (final IOBaseStream<?, ?, ?> stream = createIOBaseStream()) {
+            testSequentialSequential(stream.asBaseStream());
+        }
+    }
+
+    @SuppressWarnings("resource")
+    private void testSequentialSequential(final BaseStream<?, ?> stream) {
+        final BaseStream<?, ?> p = stream.parallel();
+        assertTrue(p.isParallel());
+        final BaseStream<?, ?> seq1 = p.sequential();
+        assertFalse(seq1.isParallel());
+        final BaseStream<?, ?> seq2 = seq1.sequential();
+        assertFalse(seq1.isParallel());
+        assertSame(seq1, seq2);
+    }
+
+    @SuppressWarnings("resource")
+    private void testSequentialSequential(final IOBaseStream<?, ?, ?> stream) {
+        final IOBaseStream<?, ?, ?> p = stream.parallel();
+        assertTrue(p.isParallel());
+        final IOBaseStream<?, ?, ?> seq1 = p.sequential();
+        assertFalse(seq1.isParallel());
+        final IOBaseStream<?, ?, ?> seq2 = seq1.sequential();
+        assertFalse(seq1.isParallel());
+        assertSame(seq1, seq2);
+    }
+
+    @SuppressWarnings("resource") // @AfterEach
+    @Test
+    public void testSpliterator() {
+        final AtomicInteger ref = new AtomicInteger();
+        baseStream.spliterator().forEachRemaining(e -> ref.incrementAndGet());
+        assertEquals(2, ref.get());
+        ioBaseStream.spliterator().forEachRemaining(e -> ref.incrementAndGet());
+        assertEquals(4, ref.get());
+        ioBaseStreamPath.asBaseStream().spliterator().forEachRemaining(e -> ref.incrementAndGet());
+        assertEquals(6, ref.get());
+    }
+
+    @SuppressWarnings("resource")
+    @Test
+    public void testUnordered() throws IOException {
+        final AtomicInteger ref = new AtomicInteger();
+        baseStream.unordered().iterator().forEachRemaining(e -> ref.incrementAndGet());
+        assertEquals(2, ref.get());
+        ioBaseStream.unordered().iterator().forEachRemaining(e -> ref.incrementAndGet());
+        assertEquals(4, ref.get());
+        ioBaseStreamPath.asBaseStream().unordered().iterator().forEachRemaining(e -> ref.incrementAndGet());
+        assertEquals(6, ref.get());
+    }
+
+    @SuppressWarnings("resource")
+    @Test
+    public void testUnwrap() {
+        final AtomicInteger ref = new AtomicInteger();
+        baseStream.iterator().forEachRemaining(e -> ref.incrementAndGet());
+        assertEquals(2, ref.get());
+        ioBaseStream.unwrap().iterator().forEachRemaining(e -> ref.incrementAndGet());
+        assertEquals(4, ref.get());
+        ioBaseStreamPath.asBaseStream().iterator().forEachRemaining(e -> ref.incrementAndGet());
+        assertEquals(6, ref.get());
+    }
+
+    @Test
+    public void testWrap() {
+        final Stream<Path> stream = createStreamOfPaths();
+        @SuppressWarnings("resource")
+        final IOStream<Path> wrap = ioBaseStreamAdapter.wrap(stream);
+        assertNotNull(wrap);
+        assertEquals(stream, wrap.unwrap());
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/io/function/IOIntStream.java b/src/test/java/org/apache/commons/io/function/IOIntStream.java
new file mode 100644
index 00000000..961235e9
--- /dev/null
+++ b/src/test/java/org/apache/commons/io/function/IOIntStream.java
@@ -0,0 +1,27 @@
+/*
+ * 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.util.stream.IntStream;
+
+/**
+ * Placeholder for future possible development and makes sure we can extend IOBaseStream cleanly with proper generics.
+ */
+interface IOIntStream extends IOBaseStream<Integer, IOIntStream, IntStream> {
+    // Placeholder for future possible development.
+}
diff --git a/src/test/java/org/apache/commons/io/function/IOIntStreamAdapter.java b/src/test/java/org/apache/commons/io/function/IOIntStreamAdapter.java
new file mode 100644
index 00000000..7ab20d20
--- /dev/null
+++ b/src/test/java/org/apache/commons/io/function/IOIntStreamAdapter.java
@@ -0,0 +1,41 @@
+/*
+ * 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.util.stream.IntStream;
+
+/**
+ * Placeholder for future possible development and makes sure we can extend IOBaseStreamAdapter cleanly with proper
+ * generics.
+ */
+class IOIntStreamAdapter extends IOBaseStreamAdapter<Integer, IOIntStream, IntStream> implements IOIntStream {
+
+    static IOIntStream adapt(final IntStream stream) {
+        return new IOIntStreamAdapter(stream);
+    }
+
+    private IOIntStreamAdapter(final IntStream stream) {
+        super(stream);
+    }
+
+    @Override
+    public IOIntStream wrap(final IntStream delegate) {
+        return unwrap() == delegate ? this : IOIntStreamAdapter.adapt(delegate);
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/io/function/IOStreamTest.java b/src/test/java/org/apache/commons/io/function/IOStreamTest.java
new file mode 100644
index 00000000..6391e9b4
--- /dev/null
+++ b/src/test/java/org/apache/commons/io/function/IOStreamTest.java
@@ -0,0 +1,540 @@
+/*
+ * 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.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.stream.Collectors;
+import java.util.stream.DoubleStream;
+import java.util.stream.IntStream;
+import java.util.stream.LongStream;
+import java.util.stream.Stream;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests {@link IOStream}.
+ */
+public class IOStreamTest {
+
+    private void compareAndSetIO(final AtomicReference<String> ref, final String expected, final String update) throws IOException {
+        TestUtils.compareAndSetThrowsIO(ref, expected, update);
+    }
+
+    private void compareAndSetRE(final AtomicReference<String> ref, final String expected, final String update) {
+        TestUtils.compareAndSetThrowsRE(ref, expected, update);
+    }
+
+    private void ioExceptionOnNull(final Object test) throws IOException {
+        if (test == null) {
+            throw new IOException("Unexpected");
+        }
+    }
+
+    @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
+    @Test
+    public void testAdapt() {
+        assertEquals(0, IOStream.adapt((Stream<?>) null).count());
+        assertEquals(0, IOStream.adapt(Stream.empty()).count());
+        assertEquals(1, IOStream.adapt(Stream.of("A")).count());
+    }
+
+    @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
+    @Test
+    public void testAllMatch() throws IOException {
+        assertThrows(IOException.class, () -> IOStream.of("A", "B").allMatch(TestConstants.THROWING_IO_PREDICATE));
+        assertTrue(IOStream.of("A", "B").allMatch(IOPredicate.alwaysTrue()));
+        assertFalse(IOStream.of("A", "B").allMatch(IOPredicate.alwaysFalse()));
+    }
+
+    @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
+    @Test
+    public void testAnyMatch() throws IOException {
+        assertThrows(IOException.class, () -> IOStream.of("A", "B").anyMatch(TestConstants.THROWING_IO_PREDICATE));
+        assertTrue(IOStream.of("A", "B").anyMatch(IOPredicate.alwaysTrue()));
+        assertFalse(IOStream.of("A", "B").anyMatch(IOPredicate.alwaysFalse()));
+    }
+
+    @Test
+    public void testClose() {
+        IOStream.of("A", "B").close();
+    }
+
+    @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
+    @Test
+    public void testCollectCollectorOfQsuperTAR() {
+        // TODO IOCollector?
+        IOStream.of("A", "B").collect(Collectors.toList());
+    }
+
+    @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
+    @Test
+    public void testCollectSupplierOfRBiConsumerOfRQsuperTBiConsumerOfRR() throws IOException {
+        // TODO Need an IOCollector?
+        IOStream.of("A", "B").collect(() -> "A", (t, u) -> {}, (t, u) -> {});
+        assertEquals("AB", Stream.of("A", "B").collect(StringBuilder::new, StringBuilder::append, StringBuilder::append).toString());
+        assertEquals("AB", IOStream.of("A", "B").collect(StringBuilder::new, StringBuilder::append, StringBuilder::append).toString());
+        // Exceptions
+        assertThrows(IOException.class, () -> IOStream.of("A", "B").collect(TestUtils.throwingIOSupplier(), (t, u) -> {}, (t, u) -> {}));
+        assertThrows(IOException.class, () -> IOStream.of("A", "B").collect(() -> "A", TestUtils.throwingIOBiConsumer(), (t, u) -> {}));
+    }
+
+    @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
+    @Test
+    public void testCount() {
+        assertEquals(0, IOStream.of().count());
+        assertEquals(1, IOStream.of("A").count());
+        assertEquals(2, IOStream.of("A", "B").count());
+        assertEquals(3, IOStream.of("A", "B", "C").count());
+        assertEquals(3, IOStream.of("A", "A", "A").count());
+    }
+
+    @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
+    @Test
+    public void testDistinct() {
+        assertEquals(0, IOStream.of().distinct().count());
+        assertEquals(1, IOStream.of("A").distinct().count());
+        assertEquals(2, IOStream.of("A", "B").distinct().count());
+        assertEquals(3, IOStream.of("A", "B", "C").distinct().count());
+        assertEquals(1, IOStream.of("A", "A", "A").distinct().count());
+    }
+
+    @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
+    @Test
+    public void testEmpty() throws IOException {
+        assertEquals(0, Stream.empty().count());
+        assertEquals(0, IOStream.empty().count());
+        IOStream.empty().forEach(TestUtils.throwingIOConsumer());
+    }
+
+    @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
+    @Test
+    public void testFilter() throws IOException {
+        IOStream.of("A").filter(TestConstants.THROWING_IO_PREDICATE);
+        // compile vs type
+        assertThrows(IOException.class, () -> IOStream.of("A").filter(TestConstants.THROWING_IO_PREDICATE).count());
+        // compile vs inline lambda
+        assertThrows(IOException.class, () -> IOStream.of("A").filter(e -> {
+            throw new IOException("Failure");
+        }).count());
+    }
+
+    @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
+    @Test
+    public void testFindAny() throws IOException {
+        // compile vs type
+        assertThrows(IOException.class, () -> IOStream.of("A").filter(TestConstants.THROWING_IO_PREDICATE).findAny());
+        // compile vs inline lambda
+        assertThrows(IOException.class, () -> IOStream.of("A").filter(e -> {
+            throw new IOException("Failure");
+        }).findAny());
+
+        assertTrue(IOStream.of("A", "B").filter(IOPredicate.alwaysTrue()).findAny().isPresent());
+        assertFalse(IOStream.of("A", "B").filter(IOPredicate.alwaysFalse()).findAny().isPresent());
+    }
+
+    @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
+    @Test
+    public void testFindFirst() throws IOException {
+        // compile vs type
+        assertThrows(IOException.class, () -> IOStream.of("A").filter(TestConstants.THROWING_IO_PREDICATE).findFirst());
+        // compile vs inline lambda
+        assertThrows(IOException.class, () -> IOStream.of("A").filter(e -> {
+            throw new IOException("Failure");
+        }).findAny());
+
+        assertTrue(IOStream.of("A", "B").filter(IOPredicate.alwaysTrue()).findFirst().isPresent());
+        assertFalse(IOStream.of("A", "B").filter(IOPredicate.alwaysFalse()).findFirst().isPresent());
+    }
+
+    @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
+    @Test
+    public void testFlatMap() throws IOException {
+        assertEquals(Arrays.asList("A", "B", "C", "D"),
+            IOStream.of(IOStream.of("A", "B"), IOStream.of("C", "D")).flatMap(IOFunction.identity()).collect(Collectors.toList()));
+    }
+
+    @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
+    @Test
+    public void testFlatMapToDouble() throws IOException {
+        assertEquals('A' + 'B', IOStream.of("A", "B").flatMapToDouble(e -> DoubleStream.of(e.charAt(0))).sum());
+    }
+
+    @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
+    @Test
+    public void testFlatMapToInt() throws IOException {
+        assertEquals('A' + 'B', IOStream.of("A", "B").flatMapToInt(e -> IntStream.of(e.charAt(0))).sum());
+    }
+
+    @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
+    @Test
+    public void testFlatMapToLong() throws IOException {
+        assertEquals('A' + 'B', IOStream.of("A", "B").flatMapToLong(e -> LongStream.of(e.charAt(0))).sum());
+    }
+
+    @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
+    @Test
+    public void testForEachIOConsumerOfQsuperT() throws IOException {
+        // compile vs type
+        assertThrows(IOException.class, () -> IOStream.of("A").forEach(TestUtils.throwingIOConsumer()));
+        // compile vs inlnine
+        assertThrows(IOException.class, () -> IOStream.of("A").forEach(e -> {
+            throw new IOException("Failure");
+        }));
+        assertThrows(IOException.class, () -> IOStream.of("A", "B").forEach(TestUtils.throwingIOConsumer()));
+        final StringBuilder sb = new StringBuilder();
+        IOStream.of("A", "B").forEachOrdered(sb::append);
+        assertEquals("AB", sb.toString());
+    }
+
+    @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
+    @Test
+    public void testForaAllIOConsumer() throws IOException {
+        // compile vs type
+        assertThrows(IOException.class, () -> IOStream.of("A").forAll(TestUtils.throwingIOConsumer()));
+        // compile vs inlnine
+        assertThrows(IOException.class, () -> IOStream.of("A").forAll(e -> {
+            throw new IOException("Failure");
+        }));
+        assertThrows(IOException.class, () -> IOStream.of("A", "B").forAll(TestUtils.throwingIOConsumer()));
+        final StringBuilder sb = new StringBuilder();
+        IOStream.of("A", "B").forAll(sb::append);
+        assertEquals("AB", sb.toString());
+    }
+
+    @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
+    @Test
+    public void testForaAllIOConsumerBiFunction() throws IOException {
+        // compile vs type
+        assertThrows(IOException.class, () -> IOStream.of("A").forAll(TestUtils.throwingIOConsumer(), (i, e) -> e));
+        // compile vs inlnine
+        assertThrows(IOException.class, () -> IOStream.of("A").forAll(e -> {
+            throw new IOException("Failure");
+        }, (i, e) -> e));
+        assertThrows(IOException.class, () -> IOStream.of("A", "B").forAll(TestUtils.throwingIOConsumer(), (i, e) -> e));
+        final StringBuilder sb = new StringBuilder();
+        IOStream.of("A", "B").forAll(sb::append, (i, e) -> e);
+        assertEquals("AB", sb.toString());
+    }
+
+    @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
+    @Test
+    public void testForaAllIOConsumerBiFunctionNull() throws IOException {
+        // compile vs type
+        assertDoesNotThrow(() -> IOStream.of("A").forAll(TestUtils.throwingIOConsumer(), null));
+        // compile vs inlnine
+        assertDoesNotThrow(() -> IOStream.of("A").forAll(e -> {
+            throw new IOException("Failure");
+        }, null));
+        assertDoesNotThrow(() -> IOStream.of("A", "B").forAll(TestUtils.throwingIOConsumer(), null));
+        final StringBuilder sb = new StringBuilder();
+        IOStream.of("A", "B").forAll(sb::append, null);
+        assertEquals("AB", sb.toString());
+    }
+
+    @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
+    @Test
+    public void testForEachOrdered() throws IOException {
+        // compile vs type
+        assertThrows(IOException.class, () -> IOStream.of("A").forEach(TestUtils.throwingIOConsumer()));
+        // compile vs inlnine
+        assertThrows(IOException.class, () -> IOStream.of("A").forEach(e -> {
+            throw new IOException("Failure");
+        }));
+        assertThrows(IOException.class, () -> IOStream.of("A", "B").forEach(TestUtils.throwingIOConsumer()));
+        final StringBuilder sb = new StringBuilder();
+        IOStream.of("A", "B").forEachOrdered(sb::append);
+        assertEquals("AB", sb.toString());
+    }
+
+    @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
+    @Test
+    public void testIsParallel() {
+        assertFalse(IOStream.of("A", "B").isParallel());
+    }
+
+    @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
+    @Test
+    public void testIterateException() throws IOException {
+        final IOStream<Long> stream = IOStream.iterate(1L, TestUtils.throwingIOUnaryOperator());
+        final IOIterator<Long> iterator = stream.iterator();
+        assertEquals(1L, iterator.next());
+        assertThrows(IOException.class, () -> iterator.next());
+    }
+
+    @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
+    @Test
+    public void testIterateLong() throws IOException {
+        final IOStream<Long> stream = IOStream.iterate(1L, i -> i + 1);
+        final IOIterator<Long> iterator = stream.iterator();
+        assertEquals(1L, iterator.next());
+        assertEquals(2L, iterator.next());
+    }
+
+    @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
+    @Test
+    public void testIterator() throws IOException {
+        final AtomicInteger ref = new AtomicInteger();
+        IOStream.of("A", "B").iterator().forEachRemaining(e -> ref.incrementAndGet());
+        assertEquals(2, ref.get());
+    }
+
+    @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
+    @Test
+    public void testLimit() {
+        assertEquals(1, IOStream.of("A", "B").limit(1).count());
+    }
+
+    @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
+    @Test
+    public void testMap() throws IOException {
+        assertEquals(Arrays.asList("AC", "BC"), IOStream.of("A", "B").map(e -> e + "C").collect(Collectors.toList()));
+    }
+
+    @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
+    @Test
+    public void testMapToDouble() {
+        assertArrayEquals(new double[] {Double.parseDouble("1"), Double.parseDouble("2")}, IOStream.of("1", "2").mapToDouble(Double::parseDouble).toArray());
+    }
+
+    @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
+    @Test
+    public void testMapToInt() {
+        assertArrayEquals(new int[] {1, 2}, IOStream.of("1", "2").mapToInt(Integer::parseInt).toArray());
+    }
+
+    @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
+    @Test
+    public void testMapToLong() {
+        assertArrayEquals(new long[] {1L, 2L}, IOStream.of("1", "2").mapToLong(Long::parseLong).toArray());
+    }
+
+    @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
+    @Test
+    public void testMax() throws IOException {
+        assertEquals("B", IOStream.of("A", "B").max(String::compareTo).get());
+    }
+
+    @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
+    @Test
+    public void testMin() throws IOException {
+        assertEquals("A", IOStream.of("A", "B").min(String::compareTo).get());
+    }
+
+    @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
+    @Test
+    public void testNoneMatch() throws IOException {
+        assertThrows(IOException.class, () -> IOStream.of("A", "B").noneMatch(TestConstants.THROWING_IO_PREDICATE));
+        assertFalse(IOStream.of("A", "B").noneMatch(IOPredicate.alwaysTrue()));
+        assertTrue(IOStream.of("A", "B").noneMatch(IOPredicate.alwaysFalse()));
+    }
+
+    @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
+    @Test
+    public void testOfArray() {
+        assertEquals(0, IOStream.of((String[]) null).count());
+        assertEquals(0, IOStream.of().count());
+        assertEquals(2, IOStream.of("A", "B").count());
+    }
+
+    @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
+    @Test
+    public void testOfOne() {
+        assertEquals(1, IOStream.of("A").count());
+    }
+
+    @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
+    @Test
+    public void testOfIterable() {
+        assertEquals(0, IOStream.of((Iterable<?>) null).count());
+        assertEquals(0, IOStream.of(Collections.emptyList()).count());
+        assertEquals(0, IOStream.of(Collections.emptySet()).count());
+        assertEquals(0, IOStream.of(Collections.emptySortedSet()).count());
+        assertEquals(1, IOStream.of(Arrays.asList("a")).count());
+        assertEquals(2, IOStream.of(Arrays.asList("a", "b")).count());
+    }
+
+    @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
+    @Test
+    public void testOnClose() throws IOException {
+        assertThrows(IOException.class, () -> IOStream.of("A").onClose(TestConstants.THROWING_IO_RUNNABLE).close());
+        final AtomicReference<String> ref = new AtomicReference<>();
+        IOStream.of("A").onClose(() -> compareAndSetIO(ref, null, "new1")).close();
+        assertEquals("new1", ref.get());
+    }
+
+    @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
+    @Test
+    public void testOnCloseMultipleHandlers() throws IOException {
+        //
+        final AtomicReference<String> ref = new AtomicReference<>();
+        // Sanity check
+        ref.set(null);
+        final RuntimeException thrownRE = assertThrows(RuntimeException.class, () -> {
+            // @formatter:off
+            final Stream<String> stream = Stream.of("A")
+                .onClose(() -> compareAndSetRE(ref, null, "new1"))
+                .onClose(() -> TestConstants.throwRuntimeException("Failure 2"));
+            // @formatter:on
+            stream.close();
+        });
+        assertEquals("new1", ref.get());
+        assertEquals("Failure 2", thrownRE.getMessage());
+        assertEquals(0, thrownRE.getSuppressed().length);
+        // Test
+        ref.set(null);
+        final IOException thrownIO = assertThrows(IOException.class, () -> {
+            // @formatter:off
+            final IOStream<String> stream = IOStream.of("A")
+                .onClose(() -> compareAndSetIO(ref, null, "new1"))
+                .onClose(() -> TestConstants.throwIOException("Failure 2"));
+            // @formatter:on
+            stream.close();
+        });
+        assertEquals("new1", ref.get());
+        assertEquals("Failure 2", thrownIO.getMessage());
+        assertEquals(0, thrownIO.getSuppressed().length);
+        //
+        final IOException thrownB = assertThrows(IOException.class, () -> {
+            // @formatter:off
+            final IOStream<String> stream = IOStream.of("A")
+                .onClose(TestConstants.throwIOException("Failure 1"))
+                .onClose(TestConstants.throwIOException("Failure 2"));
+            // @formatter:on
+            stream.close();
+        });
+        assertEquals("Failure 1", thrownB.getMessage());
+        assertEquals(0, thrownB.getSuppressed().length);
+    }
+
+    @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
+    @Test
+    public void testParallel() {
+        assertEquals(2, IOStream.of("A", "B").parallel().count());
+    }
+
+    @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
+    @Test
+    public void testPeek() throws IOException {
+        final AtomicReference<String> ref = new AtomicReference<>();
+        assertEquals(1, IOStream.of("A").peek(e -> compareAndSetIO(ref, null, e)).count());
+        assertEquals("A", ref.get());
+    }
+
+    @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
+    @Test
+    public void testReduceBinaryOperatorOfT() throws IOException {
+        assertEquals("AB", IOStream.of("A", "B").reduce((t, u) -> t + u).get());
+        assertEquals(TestConstants.ABS_PATH_A.toRealPath(),
+            IOStream.of(TestConstants.ABS_PATH_A, TestConstants.ABS_PATH_B).reduce((t, u) -> t.toRealPath()).get());
+    }
+
+    @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
+    @Test
+    public void testReduceTBinaryOperatorOfT() throws IOException {
+        assertEquals("_AB", IOStream.of("A", "B").reduce("_", (t, u) -> t + u));
+        assertEquals(TestConstants.ABS_PATH_A.toRealPath(),
+            IOStream.of(TestConstants.ABS_PATH_A, TestConstants.ABS_PATH_B).reduce(TestConstants.ABS_PATH_A, (t, u) -> t.toRealPath()));
+    }
+
+    @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
+    @Test
+    public void testReduceUBiFunctionOfUQsuperTUBinaryOperatorOfU() throws IOException {
+        assertEquals("_AB", IOStream.of("A", "B").reduce("_", (t, u) -> t + u, (t, u) -> t + u));
+        assertEquals(TestConstants.ABS_PATH_A.toRealPath(), IOStream.of(TestConstants.ABS_PATH_A, TestConstants.ABS_PATH_B).reduce(TestConstants.ABS_PATH_A,
+            (t, u) -> t.toRealPath(), (t, u) -> u.toRealPath()));
+    }
+
+    @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
+    @Test
+    public void testSequential() {
+        assertEquals(2, IOStream.of("A", "B").sequential().count());
+    }
+
+    @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
+    @Test
+    public void testSkip() throws IOException {
+        final AtomicReference<String> ref = new AtomicReference<>();
+        assertEquals(1, IOStream.of("A", "B").skip(1).peek(e -> compareAndSetIO(ref, null, e)).count());
+        assertEquals("B", ref.get());
+    }
+
+    @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
+    @Test
+    public void testSorted() throws IOException {
+        assertEquals(Arrays.asList("A", "B", "C", "D"), IOStream.of("D", "A", "B", "C").sorted().collect(Collectors.toList()));
+        assertEquals(Arrays.asList("A", "B", "C", "D"), IOStream.of("D", "A", "B", "C").sorted().peek(this::ioExceptionOnNull).collect(Collectors.toList()));
+    }
+
+    @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
+    @Test
+    public void testSortedComparatorOfQsuperT() throws IOException {
+        assertEquals(Arrays.asList("A", "B", "C", "D"), IOStream.of("D", "A", "B", "C").sorted(String::compareTo).collect(Collectors.toList()));
+        assertEquals(Arrays.asList("A", "B", "C", "D"),
+            IOStream.of("D", "A", "B", "C").sorted(String::compareTo).peek(this::ioExceptionOnNull).collect(Collectors.toList()));
+    }
+
+    @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
+    @Test
+    public void testSpliterator() {
+        final AtomicInteger ref = new AtomicInteger();
+        IOStream.of("A", "B").spliterator().forEachRemaining(e -> ref.incrementAndGet());
+        assertEquals(2, ref.get());
+    }
+
+    @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
+    @Test
+    public void testToArray() {
+        assertArrayEquals(new String[] {"A", "B"}, IOStream.of("A", "B").toArray());
+    }
+
+    @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
+    @Test
+    public void testToArrayIntFunctionOfA() {
+        assertArrayEquals(new String[] {"A", "B"}, IOStream.of("A", "B").toArray(String[]::new));
+    }
+
+    @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
+    @Test
+    public void testUnordered() {
+        // Sanity check
+        assertArrayEquals(new String[] {"A", "B"}, Stream.of("A", "B").unordered().toArray());
+        // Test
+        assertArrayEquals(new String[] {"A", "B"}, IOStream.of("A", "B").unordered().toArray());
+    }
+
+    @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
+    @Test
+    public void testUnwrap() {
+        final Stream<String> unwrap = IOStream.of("A", "B").unwrap();
+        assertNotNull(unwrap);
+        assertEquals(2, unwrap.count());
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/io/function/IOSupplierTest.java b/src/test/java/org/apache/commons/io/function/IOSupplierTest.java
index 981a65bc..4b18f48f 100644
--- a/src/test/java/org/apache/commons/io/function/IOSupplierTest.java
+++ b/src/test/java/org/apache/commons/io/function/IOSupplierTest.java
@@ -51,7 +51,7 @@ public class IOSupplierTest {
     @Test
     public void testAsSupplier() {
         assertThrows(UncheckedIOException.class, () -> TestConstants.THROWING_IO_SUPPLIER.asSupplier().get());
-        assertEquals("new1", getThrowsNone(() -> TestUtils.compareAndSetThrows(ref1, "new1")));
+        assertEquals("new1", getThrowsNone(() -> TestUtils.compareAndSetThrowsIO(ref1, "new1")));
         assertEquals("new1", ref1.get());
         assertNotEquals(TestConstants.THROWING_IO_SUPPLIER.asSupplier(), TestConstants.THROWING_IO_SUPPLIER.asSupplier());
     }
@@ -62,7 +62,7 @@ public class IOSupplierTest {
         assertThrows(IOException.class, () -> {
             throw new IOException();
         });
-        assertEquals("new1", getThrows(() -> TestUtils.compareAndSetThrows(ref1, "new1")));
+        assertEquals("new1", getThrows(() -> TestUtils.compareAndSetThrowsIO(ref1, "new1")));
         assertEquals("new1", ref1.get());
     }
 
diff --git a/src/test/java/org/apache/commons/io/function/PathBaseStream.java b/src/test/java/org/apache/commons/io/function/PathBaseStream.java
new file mode 100644
index 00000000..174d1120
--- /dev/null
+++ b/src/test/java/org/apache/commons/io/function/PathBaseStream.java
@@ -0,0 +1,28 @@
+/*
+ * 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.nio.file.Path;
+import java.util.stream.BaseStream;
+
+/**
+ * Test fixture.
+ */
+interface PathBaseStream extends BaseStream<Path, PathBaseStream> {
+    // empty
+}
diff --git a/src/test/java/org/apache/commons/io/function/PathStream.java b/src/test/java/org/apache/commons/io/function/PathStream.java
new file mode 100644
index 00000000..ba2b7f33
--- /dev/null
+++ b/src/test/java/org/apache/commons/io/function/PathStream.java
@@ -0,0 +1,28 @@
+/*
+ * 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.nio.file.Path;
+import java.util.stream.Stream;
+
+/**
+ * Test fixture.
+ */
+interface PathStream extends Stream<Path> {
+    // empty
+}
diff --git a/src/test/java/org/apache/commons/io/function/TestConstants.java b/src/test/java/org/apache/commons/io/function/TestConstants.java
index 16c9c95b..5f0581c7 100644
--- a/src/test/java/org/apache/commons/io/function/TestConstants.java
+++ b/src/test/java/org/apache/commons/io/function/TestConstants.java
@@ -62,8 +62,16 @@ class TestConstants {
         throw new UncheckedIOException(new IOException("Failure"));
     };
 
-    private static <T> T throwIOException() throws IOException {
-        throw new IOException("Failure");
+    static <T> T throwIOException() throws IOException {
+        return throwIOException("Failure");
+    }
+
+    static <T> T throwIOException(final String message) throws IOException {
+        throw new IOException(message);
+    }
+
+    static <T> T throwRuntimeException(final String message) {
+        throw new RuntimeException(message);
     }
 
 }
diff --git a/src/test/java/org/apache/commons/io/function/TestUtils.java b/src/test/java/org/apache/commons/io/function/TestUtils.java
index db22fdd0..9b6f3aa7 100644
--- a/src/test/java/org/apache/commons/io/function/TestUtils.java
+++ b/src/test/java/org/apache/commons/io/function/TestUtils.java
@@ -22,27 +22,68 @@ import java.util.concurrent.atomic.AtomicReference;
 
 class TestUtils {
 
-    static <T> T compareAndSetThrows(final AtomicReference<T> ref, final T update) throws IOException {
-        return compareAndSetThrows(ref, null, update);
+    static <T> T compareAndSetThrowsIO(final AtomicReference<T> ref, final T update) throws IOException {
+        return compareAndSetThrowsIO(ref, null, update);
     }
 
-    static <T> T compareAndSetThrows(final AtomicReference<T> ref, final T expected, final T update) throws IOException {
+    static <T> T compareAndSetThrowsIO(final AtomicReference<T> ref, final T expected, final T update) throws IOException {
         if (!ref.compareAndSet(expected, update)) {
             throw new IOException("Unexpected");
         }
         return ref.get(); // same as update
     }
 
+    static <T> T compareAndSetThrowsRE(final AtomicReference<T> ref, final T expected, final T update) {
+        if (!ref.compareAndSet(expected, update)) {
+            throw new RuntimeException("Unexpected");
+        }
+        return ref.get(); // same as update
+    }
+
+    @SuppressWarnings("unchecked")
+    static <T, U> IOBiConsumer<T, U> throwingIOBiConsumer() {
+        return (IOBiConsumer<T, U>) TestConstants.THROWING_IO_BI_CONSUMER;
+    }
+
+    @SuppressWarnings("unchecked")
+    static <T, U, V> IOBiFunction<T, U, V> throwingIOBiFunction() {
+        return (IOBiFunction<T, U, V>) TestConstants.THROWING_IO_BI_FUNCTION;
+    }
+
     @SuppressWarnings("unchecked")
     static <T> IOBinaryOperator<T> throwingIOBinaryOperator() {
         return (IOBinaryOperator<T>) TestConstants.THROWING_IO_BINARY_OPERATOR;
     }
 
+    @SuppressWarnings("unchecked")
+    static <T> IOComparator<T> throwingIOComparator() {
+        return (IOComparator<T>) TestConstants.THROWING_IO_COMPARATOR;
+    }
+
     @SuppressWarnings("unchecked")
     static <T> IOConsumer<T> throwingIOConsumer() {
         return (IOConsumer<T>) TestConstants.THROWING_IO_CONSUMER;
     }
 
+    @SuppressWarnings("unchecked")
+    static <T, U> IOFunction<T, U> throwingIOFunction() {
+        return (IOFunction<T, U>) TestConstants.THROWING_IO_FUNCTION;
+    }
+
+    @SuppressWarnings("unchecked")
+    static <T> IOPredicate<T> throwingIOPredicate() {
+        return (IOPredicate<T>) TestConstants.THROWING_IO_PREDICATE;
+    }
+
+    static IORunnable throwingIORunnable() {
+        return TestConstants.THROWING_IO_RUNNABLE;
+    }
+
+    @SuppressWarnings("unchecked")
+    static <T> IOSupplier<T> throwingIOSupplier() {
+        return (IOSupplier<T>) TestConstants.THROWING_IO_SUPPLIER;
+    }
+
     @SuppressWarnings("unchecked")
     static <T> IOUnaryOperator<T> throwingIOUnaryOperator() {
         return (IOUnaryOperator<T>) TestConstants.THROWING_IO_UNARY_OPERATOR;
diff --git a/src/test/java/org/apache/commons/io/function/UncheckTest.java b/src/test/java/org/apache/commons/io/function/UncheckTest.java
index dbb73299..ee73abc6 100644
--- a/src/test/java/org/apache/commons/io/function/UncheckTest.java
+++ b/src/test/java/org/apache/commons/io/function/UncheckTest.java
@@ -54,8 +54,8 @@ public class UncheckTest {
         }, null, null));
         assertThrows(UncheckedIOException.class, () -> Uncheck.accept(TestConstants.THROWING_IO_BI_CONSUMER, null, null));
         Uncheck.accept((t, u) -> {
-            TestUtils.compareAndSetThrows(ref1, t);
-            TestUtils.compareAndSetThrows(ref2, u);
+            TestUtils.compareAndSetThrowsIO(ref1, t);
+            TestUtils.compareAndSetThrowsIO(ref2, u);
         }, "new1", "new2");
         assertEquals("new1", ref1.get());
         assertEquals("new2", ref2.get());
@@ -67,7 +67,7 @@ public class UncheckTest {
             throw new IOException();
         }, null));
         assertThrows(UncheckedIOException.class, () -> Uncheck.accept(TestUtils.throwingIOConsumer(), null));
-        Uncheck.accept(t -> TestUtils.compareAndSetThrows(ref1, t), "new1");
+        Uncheck.accept(t -> TestUtils.compareAndSetThrowsIO(ref1, t), "new1");
         assertEquals("new1", ref1.get());
     }
 
@@ -78,9 +78,9 @@ public class UncheckTest {
         }, null, null, null));
         assertThrows(UncheckedIOException.class, () -> Uncheck.accept(TestConstants.THROWING_IO_TRI_CONSUMER, null, null, null));
         Uncheck.accept((t, u, v) -> {
-            TestUtils.compareAndSetThrows(ref1, t);
-            TestUtils.compareAndSetThrows(ref2, u);
-            TestUtils.compareAndSetThrows(ref3, v);
+            TestUtils.compareAndSetThrowsIO(ref1, t);
+            TestUtils.compareAndSetThrowsIO(ref2, u);
+            TestUtils.compareAndSetThrowsIO(ref3, v);
         }, "new1", "new2", "new3");
         assertEquals("new1", ref1.get());
         assertEquals("new2", ref2.get());
@@ -94,8 +94,8 @@ public class UncheckTest {
         }, null, null));
         assertThrows(UncheckedIOException.class, () -> Uncheck.apply(TestConstants.THROWING_IO_BI_FUNCTION, null, null));
         assertEquals("new0", Uncheck.apply((t, u) -> {
-            TestUtils.compareAndSetThrows(ref1, t);
-            TestUtils.compareAndSetThrows(ref2, u);
+            TestUtils.compareAndSetThrowsIO(ref1, t);
+            TestUtils.compareAndSetThrowsIO(ref2, u);
             return "new0";
         }, "new1", "new2"));
         assertEquals("new1", ref1.get());
@@ -108,7 +108,7 @@ public class UncheckTest {
             throw new IOException();
         }, null));
         assertThrows(UncheckedIOException.class, () -> Uncheck.apply(TestConstants.THROWING_IO_FUNCTION, null));
-        Uncheck.apply(t -> TestUtils.compareAndSetThrows(ref1, t), "new1");
+        Uncheck.apply(t -> TestUtils.compareAndSetThrowsIO(ref1, t), "new1");
         assertEquals("new1", ref1.get());
     }
 
@@ -119,10 +119,10 @@ public class UncheckTest {
         }, null, null, null, null));
         assertThrows(UncheckedIOException.class, () -> Uncheck.apply(TestConstants.THROWING_IO_QUAD_FUNCTION, null, null, null, null));
         assertEquals("new0", Uncheck.apply((t, u, v, w) -> {
-            TestUtils.compareAndSetThrows(ref1, t);
-            TestUtils.compareAndSetThrows(ref2, u);
-            TestUtils.compareAndSetThrows(ref3, v);
-            TestUtils.compareAndSetThrows(ref4, w);
+            TestUtils.compareAndSetThrowsIO(ref1, t);
+            TestUtils.compareAndSetThrowsIO(ref2, u);
+            TestUtils.compareAndSetThrowsIO(ref3, v);
+            TestUtils.compareAndSetThrowsIO(ref4, w);
             return "new0";
         }, "new1", "new2", "new3", "new4"));
         assertEquals("new1", ref1.get());
@@ -138,9 +138,9 @@ public class UncheckTest {
         }, null, null, null));
         assertThrows(UncheckedIOException.class, () -> Uncheck.apply(TestConstants.THROWING_IO_TRI_FUNCTION, null, null, null));
         assertEquals("new0", Uncheck.apply((t, u, v) -> {
-            TestUtils.compareAndSetThrows(ref1, t);
-            TestUtils.compareAndSetThrows(ref2, u);
-            TestUtils.compareAndSetThrows(ref3, v);
+            TestUtils.compareAndSetThrowsIO(ref1, t);
+            TestUtils.compareAndSetThrowsIO(ref2, u);
+            TestUtils.compareAndSetThrowsIO(ref3, v);
             return "new0";
         }, "new1", "new2", "new3"));
         assertEquals("new1", ref1.get());
@@ -154,7 +154,7 @@ public class UncheckTest {
             throw new IOException();
         }));
         assertThrows(UncheckedIOException.class, () -> Uncheck.get(TestConstants.THROWING_IO_SUPPLIER));
-        assertEquals("new1", Uncheck.get(() -> TestUtils.compareAndSetThrows(ref1, "new1")));
+        assertEquals("new1", Uncheck.get(() -> TestUtils.compareAndSetThrowsIO(ref1, "new1")));
         assertEquals("new1", ref1.get());
     }
 
@@ -164,7 +164,7 @@ public class UncheckTest {
             throw new IOException();
         }));
         assertThrows(UncheckedIOException.class, () -> Uncheck.run(TestConstants.THROWING_IO_RUNNABLE));
-        Uncheck.run(() -> TestUtils.compareAndSetThrows(ref1, "new1"));
+        Uncheck.run(() -> TestUtils.compareAndSetThrowsIO(ref1, "new1"));
         assertEquals("new1", ref1.get());
     }
 
@@ -174,7 +174,7 @@ public class UncheckTest {
             throw new IOException();
         }, null));
         assertThrows(UncheckedIOException.class, () -> Uncheck.test(TestConstants.THROWING_IO_PREDICATE, null));
-        assertTrue(Uncheck.test(t -> TestUtils.compareAndSetThrows(ref1, t).equals(t), "new1"));
+        assertTrue(Uncheck.test(t -> TestUtils.compareAndSetThrowsIO(ref1, t).equals(t), "new1"));
         assertEquals("new1", ref1.get());
     }
 


[commons-io] 02/02: Use GitHub cache for CodeQL

Posted by gg...@apache.org.
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

commit d503c604482d27f1bb4c6d7d30d5d3280c665538
Author: Gary Gregory <ga...@gmail.com>
AuthorDate: Thu Sep 22 08:48:31 2022 -0400

    Use GitHub cache for CodeQL
---
 .github/workflows/codeql-analysis.yml | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index cdfd9c5a..29024680 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -43,6 +43,12 @@ jobs:
     steps:
     - name: Checkout repository
       uses: actions/checkout@v3.0.2
+    - uses: actions/cache@v3.0.8
+      with:
+        path: ~/.m2/repository
+        key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
+        restore-keys: |
+          ${{ runner.os }}-maven-
 
     # Initializes the CodeQL tools for scanning.
     - name: Initialize CodeQL