Add and reuse IOConsumer.forEach(T[], IOConsumer<T>) and
forEachIndexed(Stream<T>, IOConsumer<T>)
MODIFY
src/changes/changes.xml
src/changes/changes.xml
@@ -287,6 +287,9 @@ The <action> type attribute can be add,update,fix,remove.
<action dev="ggregory" type="add" due-to="Gary Gregory">
Add IOBiConsumer.
</action>
+ <action dev="ggregory" type="add" due-to="Gary Gregory">
+ Add and reuse IOConsumer.forEach(T[], IOConsumer) and forEachIndexed(Stream, IOConsumer).
+ </action>
<!-- UPDATE -->
<action dev="ggregory" type="add" due-to="Gary Gregory">
Update FileEntry to use FileTime instead of long for file time stamps.

MODIFY
src/main/java/org/apache/commons/io/FileUtils.java
src/main/java/org/apache/commons/io/FileUtils.java
import org.apache.commons.io.filefilter.IOFileFilter;
import org.apache.commons.io.filefilter.SuffixFileFilter;
import org.apache.commons.io.filefilter.TrueFileFilter;
+import org.apache.commons.io.function.IOConsumer;
/**
* General file manipulation utilities.
@@ -340,17 +341,7 @@ public static long checksumCRC32(final File file) throws IOException {
* @see #forceDelete(File)
*/
public static void cleanDirectory(final File directory) throws IOException {
- final File[] files = listFiles(directory, null);
-
- final List<IOException> causeList = new ArrayList<>();
- for (final File file : files) {
- try {
- forceDelete(file);
- } catch (final IOException ioe) {
- causeList.add(ioe);
- }
- }
- IOExceptionList.checkEmpty(causeList, directory);
+ IOConsumer.forEach(listFiles(directory, null), file -> forceDelete(file));
}
/**
@@ -363,17 +354,7 @@ public static void cleanDirectory(final File directory) throws IOException {
* @see #forceDeleteOnExit(File)
*/
private static void cleanDirectoryOnExit(final File directory) throws IOException {
- final File[] files = listFiles(directory, null);
-
- final List<IOException> causeList = new ArrayList<>();
- for (final File file : files) {
- try {
- forceDeleteOnExit(file);
- } catch (final IOException ioe) {
- causeList.add(ioe);
- }
- }
- IOExceptionList.checkEmpty(causeList, directory);
+ IOConsumer.forEach(listFiles(directory, null), file -> forceDeleteOnExit(file));
}
/**

MODIFY
src/main/java/org/apache/commons/io/IOUtils.java
src/main/java/org/apache/commons/io/IOUtils.java
@@ -384,18 +384,14 @@ public static void close(final Closeable closeable) throws IOException {
}
/**
- * Closes the given {@link Closeable} as a null-safe operation.
+ * Closes the given {@link Closeable}s as null-safe operations.
*
* @param closeables The resource(s) to close, may be null.
* @throws IOException if an I/O error occurs.
* @since 2.8.0
*/
public static void close(final Closeable... closeables) throws IOException {
- if (closeables != null) {
- for (final Closeable closeable : closeables) {
- close(closeable);
- }
- }
+ IOConsumer.forEach(closeables, IOUtils::close);
}
/**

MODIFY
src/main/java/org/apache/commons/io/function/IOConsumer.java
src/main/java/org/apache/commons/io/function/IOConsumer.java
import java.io.IOException;
import java.util.Objects;
import java.util.function.Consumer;
+import java.util.stream.Stream;
+
+import org.apache.commons.io.IOExceptionList;
+import org.apache.commons.io.IOIndexedException;
/**
* Like {@link Consumer} but throws {@link IOException}.
*/
IOConsumer<?> NOOP_IO_CONSUMER = t -> {/* noop */};
+ /**
+ * Performs an action for each element of this stream.
+ *
+ * @param <T> The element type.
+ * @param array The input to stream.
+ * @param action The action to apply to each input element.
+ * @throws IOException if an I/O error occurs.
+ * @since 2.12.0
+ */
+ static <T> void forEach(final T[] array, final IOConsumer<T> action) throws IOException {
+ IOStreams.forEach(IOStreams.of(array), action);
+ }
+
+ /**
+ * Performs an action for each element of this stream.
+ *
+ * @param <T> The element type.
+ * @param stream The input to stream.
+ * @param action The action to apply to each input element.
+ * @throws IOExceptionList if an I/O error occurs.
+ * @since 2.12.0
+ */
+ static <T> void forEachIndexed(final Stream<T> stream, final IOConsumer<T> action) throws IOExceptionList {
+ IOStreams.forEachIndexed(stream, action, IOIndexedException::new);
+ }
+
/**
* Returns a constant NOOP consumer.
*
/**
* Returns a composed {@code IOConsumer} that performs, in sequence, this operation followed by the {@code after}
- * operation. If performing either operation throws an exception, it is relayed to the caller of the composed
- * operation. If performing this operation throws an exception, the {@code after} operation will not be performed.
+ * operation. If performing either operation throws an exception, it is relayed to the caller of the composed operation.
+ * If performing this operation throws an exception, the {@code after} operation will not be performed.
*
* @param after the operation to perform after this operation
- * @return a composed {@code Consumer} that performs in sequence this operation followed by the {@code after}
- * operation
+ * @return a composed {@code Consumer} that performs in sequence this operation followed by the {@code after} operation
* @throws NullPointerException if {@code after} is null
*/
default IOConsumer<T> andThen(final IOConsumer<? super T> after) {

ADD
src/main/java/org/apache/commons/io/function/IOStreams.java
+/*
+ * 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.List;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.BiFunction;
+import java.util.stream.Stream;
+
+import org.apache.commons.io.IOExceptionList;
+
+/**
+ * Keeps code package private for now.
+ */
+class IOStreams {
+
+ /**
+ * Null-safe version of {@link Stream#of(Object[])}.
+ *
+ * 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()}.
+ */
+ @SafeVarargs // Creating a stream from an array is safe
+ static <T> Stream<T> of(final T... values) {
+ return values == null ? Stream.empty() : Stream.of(values);
+ }
+
+ static <T> void forEach(final Stream<T> stream, final IOConsumer<T> action) throws IOException {
+ forEachIndexed(stream, action, (i, e) -> e);
+ }
+
+ static <T> void forEachIndexed(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();
+ stream.forEach(e -> {
+ try {
+ action.accept(e);
+ } catch (final IOException ioex) {
+ if (causeList.get() == null) {
+ causeList.set(new ArrayList<>());
+ }
+ causeList.get().add(exSupplier.apply(index.get(), ioex));
+ }
+ index.incrementAndGet();
+ });
+ IOExceptionList.checkEmpty(causeList.get(), "forEach");
+ }
+
+}

MODIFY
src/main/java/org/apache/commons/io/output/FilterCollectionWriter.java
src/main/java/org/apache/commons/io/output/FilterCollectionWriter.java
import java.io.FilterWriter;
import java.io.IOException;
import java.io.Writer;
-import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
-import java.util.List;
+import java.util.function.Predicate;
+import java.util.stream.Stream;
import org.apache.commons.io.IOExceptionList;
import org.apache.commons.io.IOIndexedException;
+import org.apache.commons.io.function.IOConsumer;
/**
* Abstract class for writing filtered character streams to a {@link Collection} of writers. This is in contrast to
*/
public class FilterCollectionWriter extends Writer {
+ @SuppressWarnings("rawtypes")
+ private static final Predicate NOT_NULL = e -> e != null;
+
+ @SuppressWarnings("unchecked")
+ private static <T> Predicate<T> notNull() {
+ return NOT_NULL;
+ }
+
/**
* Empty and immutable collection of writers.
*/
@@ -73,92 +82,27 @@ protected FilterCollectionWriter(final Writer... writers) {
this.writers = writers == null ? EMPTY_WRITERS : Arrays.asList(writers);
}
- /**
- * Adds an indexed exception to the list.
- *
- * @param causeList The target list.
- * @param i The index.
- * @param e The cause.
- * @return the given list or a new list on null input.
- */
- private List<IOException> add(List<IOException> causeList, final int i, final IOException e) {
- if (causeList == null) {
- causeList = new ArrayList<>();
- }
- causeList.add(new IOIndexedException(i, e));
- return causeList;
- }
-
@Override
public Writer append(final char c) throws IOException {
- List<IOException> causeList = null;
- int i = 0;
- for (final Writer w : writers) {
- if (w != null) {
- try {
- w.append(c);
- } catch (final IOException e) {
- causeList = add(causeList, i, e);
- }
- }
- i++;
- }
- IOExceptionList.checkEmpty(causeList, "append(char)");
+ IOConsumer.forEachIndexed(writers(), w -> w.append(c));
return this;
}
@Override
public Writer append(final CharSequence csq) throws IOException {
- List<IOException> causeList = null;
- int i = 0;
- for (final Writer w : writers) {
- if (w != null) {
- try {
- w.append(csq);
- } catch (final IOException e) {
- causeList = add(causeList, i, e);
- }
- }
- i++;
- }
- IOExceptionList.checkEmpty(causeList, "append(CharSequence)");
+ IOConsumer.forEachIndexed(writers(), w -> w.append(csq));
return this;
}
@Override
public Writer append(final CharSequence csq, final int start, final int end) throws IOException {
-
- List<IOException> causeList = null;
- int i = 0;
- for (final Writer w : writers) {
- if (w != null) {
- try {
- w.append(csq, start, end);
- } catch (final IOException e) {
- causeList = add(causeList, i, e);
- }
- }
- i++;
- }
- IOExceptionList.checkEmpty(causeList, "append(CharSequence, int, int)");
+ IOConsumer.forEachIndexed(writers(), w -> w.append(csq, start, end));
return this;
}
@Override
public void close() throws IOException {
- List<IOException> causeList = null;
- int i = 0;
- for (final Writer w : writers) {
- if (w != null) {
- try {
- w.close();
- } catch (final IOException e) {
- causeList = add(causeList, i, e);
- }
- }
- i++;
- }
- IOExceptionList.checkEmpty(causeList, "close()");
+ IOConsumer.forEachIndexed(writers(), Writer::close);
}
/**
@@ -168,36 +112,12 @@ public void close() throws IOException {
*/
@Override
public void flush() throws IOException {
- List<IOException> causeList = null;
- int i = 0;
- for (final Writer w : writers) {
- if (w != null) {
- try {
- w.flush();
- } catch (final IOException e) {
- causeList = add(causeList, i, e);
- }
- }
- i++;
- }
- IOExceptionList.checkEmpty(causeList, "flush()");
+ IOConsumer.forEachIndexed(writers(), Writer::flush);
}
@Override
public void write(final char[] cbuf) throws IOException {
- List<IOException> causeList = null;
- int i = 0;
- for (final Writer w : writers) {
- if (w != null) {
- try {
- w.write(cbuf);
- } catch (final IOException e) {
- causeList = add(causeList, i, e);
- }
- }
- i++;
- }
- IOExceptionList.checkEmpty(causeList, "write(char[])");
+ IOConsumer.forEachIndexed(writers(), w -> w.write(cbuf));
}
/**
@@ -211,19 +131,7 @@ public void write(final char[] cbuf) throws IOException {
*/
@Override
public void write(final char[] cbuf, final int off, final int len) throws IOException {
- List<IOException> causeList = null;
- int i = 0;
- for (final Writer w : writers) {
- if (w != null) {
- try {
- w.write(cbuf, off, len);
- } catch (final IOException e) {
- causeList = add(causeList, i, e);
- }
- }
- i++;
- }
- IOExceptionList.checkEmpty(causeList, "write(char[], int, int)");
+ IOConsumer.forEachIndexed(writers(), w -> w.write(cbuf, off, len));
}
/**
@@ -233,36 +141,12 @@ public void write(final char[] cbuf, final int off, final int len) throws IOExce
*/
@Override
public void write(final int c) throws IOException {
- List<IOException> causeList = null;
- int i = 0;
- for (final Writer w : writers) {
- if (w != null) {
- try {
- w.write(c);
- } catch (final IOException e) {
- causeList = add(causeList, i, e);
- }
- }
- i++;
- }
- IOExceptionList.checkEmpty(causeList, "write(int)");
+ IOConsumer.forEachIndexed(writers(), w -> w.write(c));
}
@Override
public void write(final String str) throws IOException {
- List<IOException> causeList = null;
- int i = 0;
- for (final Writer w : writers) {
- if (w != null) {
- try {
- w.write(str);
- } catch (final IOException e) {
- causeList = add(causeList, i, e);
- }
- }
- i++;
- }
- IOExceptionList.checkEmpty(causeList, "write(String)");
+ IOConsumer.forEachIndexed(writers(), w -> w.write(str));
}
/**
@@ -276,19 +160,11 @@ public void write(final String str) throws IOException {
*/
@Override
public void write(final String str, final int off, final int len) throws IOException {
- List<IOException> causeList = null;
- int i = 0;
- for (final Writer w : writers) {
- if (w != null) {
- try {
- w.write(str, off, len);
- } catch (final IOException e) {
- causeList = add(causeList, i, e);
- }
+ IOConsumer.forEachIndexed(writers(), w -> w.write(str, off, len));
}
- i++;
- }
- IOExceptionList.checkEmpty(causeList, "write(String, int, int)");
+
+ private Stream<Writer> writers() {
+ return writers.stream().filter(notNull());
}
}

Add and reuse IOConsumer.forEach(T[], IOConsumer<T>) and
forEachIndexed(Stream<T>, IOConsumer<T>)