diff --git a/src/main/java/org/apache/commons/io/input/CloseShieldInputStream.java b/src/main/java/org/apache/commons/io/input/CloseShieldInputStream.java
index 64592afc611..b58c6a891ac 100644
--- a/src/main/java/org/apache/commons/io/input/CloseShieldInputStream.java
+++ b/src/main/java/org/apache/commons/io/input/CloseShieldInputStream.java
@@ -16,8 +16,11 @@
*/
package org.apache.commons.io.input;
+import java.io.IOException;
import java.io.InputStream;
+import org.apache.commons.io.function.IOUnaryOperator;
+
/**
* Proxy stream that prevents the underlying input stream from being closed.
*
@@ -30,6 +33,50 @@
*/
public class CloseShieldInputStream extends ProxyInputStream {
+ /**
+ * Constructs a new builder for {@link CloseShieldInputStream}.
+ *
+ * @since 2.22.0
+ */
+ public static class Builder extends AbstractBuilder {
+
+ private IOUnaryOperator onClose = is -> ClosedInputStream.INSTANCE;
+
+ /**
+ * Constructs a new instance.
+ */
+ public Builder() {
+ // empty
+ }
+
+ @Override
+ public CloseShieldInputStream get() throws IOException {
+ return new CloseShieldInputStream(this);
+ }
+
+ /**
+ * Sets the {@code onClose} function. By default, replaces the underlying input stream when {@link #close()} is called.
+ *
+ * @param onClose the onClose function.
+ * @return {@code this} instance.
+ */
+ public Builder setOnClose(final IOUnaryOperator onClose) {
+ this.onClose = onClose;
+ return asThis();
+ }
+
+ }
+
+ /**
+ * Constructs a new builder for {@link CloseShieldInputStream}.
+ *
+ * @return the new builder.
+ * @since 2.22.0
+ */
+ public static Builder builder() {
+ return new Builder();
+ }
+
/**
* Constructs a proxy that only shields {@link System#in} from closing.
*
@@ -52,6 +99,13 @@ public static CloseShieldInputStream wrap(final InputStream inputStream) {
return new CloseShieldInputStream(inputStream);
}
+ private final IOUnaryOperator onClose;
+
+ private CloseShieldInputStream(final Builder builder) throws IOException {
+ super(builder.getInputStream());
+ this.onClose = builder.onClose;
+ }
+
/**
* Constructs a proxy that shields the given input stream from being closed.
*
@@ -63,16 +117,21 @@ public static CloseShieldInputStream wrap(final InputStream inputStream) {
@Deprecated
public CloseShieldInputStream(final InputStream inputStream) {
super(inputStream);
+ this.onClose = builder().onClose;
}
/**
- * Replaces the underlying input stream with a {@link ClosedInputStream}
- * sentinel. The original input stream will remain open, but this proxy will
- * appear closed.
+ * Applies the {@code onClose} function to the underlying input stream, replacing it with the result.
+ *
+ * By default, replaces the underlying input stream with a {@link ClosedInputStream} sentinel. The original input stream will remain open, but this proxy
+ * will appear closed.
+ *
+ *
+ * @throws IOException Thrown by the {@code onClose} function.
*/
@Override
- public void close() {
- in = ClosedInputStream.INSTANCE;
+ public void close() throws IOException {
+ in = onClose.apply(in);
}
}
diff --git a/src/test/java/org/apache/commons/io/input/CloseShieldInputStreamTest.java b/src/test/java/org/apache/commons/io/input/CloseShieldInputStreamTest.java
index 54bb0810911..ceb52ed993d 100644
--- a/src/test/java/org/apache/commons/io/input/CloseShieldInputStreamTest.java
+++ b/src/test/java/org/apache/commons/io/input/CloseShieldInputStreamTest.java
@@ -18,6 +18,7 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrowsExactly;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.ByteArrayInputStream;
@@ -79,6 +80,32 @@ void testClose() throws IOException {
assertEquals(data[0], byteArrayInputStream.read(), "read()");
}
+ @Test
+ void testOnClose() throws Exception {
+ try (InputStream in = CloseShieldInputStream.builder().setInputStream(byteArrayInputStream).setOnClose(is -> {
+ assertFalse(closed);
+ closed = true;
+ return ClosedInputStream.INSTANCE;
+ }).get()) {
+ assertEquals(3, in.available());
+ }
+ assertTrue(closed);
+ }
+
+ @Test
+ void testOnCloseException() throws Exception {
+ final String message = "test";
+ assertEquals("test", assertThrowsExactly(IOException.class, () -> {
+ try (InputStream in = CloseShieldInputStream.builder().setInputStream(byteArrayInputStream).setOnClose(is -> {
+ assertFalse(closed);
+ throw new IOException(message);
+ }).get()) {
+ assertEquals("test", assertThrowsExactly(IOException.class, in::close).getMessage());
+ }
+ }).getMessage());
+ assertFalse(closed);
+ }
+
@Test
void testReadAfterCose() throws Exception {
final InputStream shadow;