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

pkarwasz pushed a commit to branch fix/close-shield-interfaces2
in repository https://gitbox.apache.org/repos/asf/commons-io.git


The following commit(s) were added to refs/heads/fix/close-shield-interfaces2 
by this push:
     new f4c52d3c1 Fixes interface discovery in `CloseShieldChannel`
f4c52d3c1 is described below

commit f4c52d3c10f8999088d79b27de011edc46983254
Author: Piotr P. Karwasz <[email protected]>
AuthorDate: Tue Oct 14 19:12:06 2025 +0200

    Fixes interface discovery in `CloseShieldChannel`
    
    This PR is split from #799.
    
    The `CloseShieldChannel` implementation only inspects interfaces 
**directly** implemented by the given channel’s class, ignoring those inherited 
from its superclasses.
    As a result, proxies for types such as `FileChannel` does not expose any of 
the interfaces declared on `FileChannel` itself.
---
 .../commons/io/channels/CloseShieldChannel.java    | 10 ++++++---
 .../io/channels/CloseShieldChannelTest.java        | 24 ++++++++++++++++++++++
 2 files changed, 31 insertions(+), 3 deletions(-)

diff --git 
a/src/main/java/org/apache/commons/io/channels/CloseShieldChannel.java 
b/src/main/java/org/apache/commons/io/channels/CloseShieldChannel.java
index a9a462da7..bde0feb25 100644
--- a/src/main/java/org/apache/commons/io/channels/CloseShieldChannel.java
+++ b/src/main/java/org/apache/commons/io/channels/CloseShieldChannel.java
@@ -40,11 +40,15 @@ public final class CloseShieldChannel {
     private static final Class<?>[] EMPTY = {};
 
     private static Set<Class<?>> collectChannelInterfaces(final Class<?> type, 
final Set<Class<?>> out) {
+        Class<?> currentType = type;
         // Visit interfaces
-        for (final Class<?> iface : type.getInterfaces()) {
-            if (Channel.class.isAssignableFrom(iface) && out.add(iface)) {
-                collectChannelInterfaces(iface, out);
+        while (currentType != null) {
+            for (final Class<?> iface : currentType.getInterfaces()) {
+                if (Channel.class.isAssignableFrom(iface) && out.add(iface)) {
+                    collectChannelInterfaces(iface, out);
+                }
             }
+            currentType = currentType.getSuperclass();
         }
         return out;
     }
diff --git 
a/src/test/java/org/apache/commons/io/channels/CloseShieldChannelTest.java 
b/src/test/java/org/apache/commons/io/channels/CloseShieldChannelTest.java
index ac7e85b7a..42a23f0af 100644
--- a/src/test/java/org/apache/commons/io/channels/CloseShieldChannelTest.java
+++ b/src/test/java/org/apache/commons/io/channels/CloseShieldChannelTest.java
@@ -20,6 +20,7 @@
 import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
 import static org.junit.jupiter.api.Assertions.assertNotSame;
 import static org.junit.jupiter.api.Assertions.assertSame;
 import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -32,11 +33,13 @@
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
+import java.io.IOException;
 import java.nio.channels.AsynchronousByteChannel;
 import java.nio.channels.AsynchronousChannel;
 import java.nio.channels.ByteChannel;
 import java.nio.channels.Channel;
 import java.nio.channels.ClosedChannelException;
+import java.nio.channels.FileChannel;
 import java.nio.channels.GatheringByteChannel;
 import java.nio.channels.InterruptibleChannel;
 import java.nio.channels.MulticastChannel;
@@ -45,9 +48,12 @@
 import java.nio.channels.ScatteringByteChannel;
 import java.nio.channels.SeekableByteChannel;
 import java.nio.channels.WritableByteChannel;
+import java.nio.file.Path;
 import java.util.stream.Stream;
 
+import org.apache.commons.io.FileUtils;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.MethodSource;
 
@@ -280,4 +286,22 @@ void testWritableByteChannelMethods() throws Exception {
         assertThrows(ClosedChannelException.class, () -> shield.write(null));
         verifyNoMoreInteractions(channel);
     }
+
+    @Test
+    void testCorrectlyDetectsInterfaces(@TempDir Path tempDir) throws 
IOException {
+        final Path testFile = tempDir.resolve("test.txt");
+        FileUtils.touch(testFile.toFile());
+        try (FileChannel channel = FileChannel.open(testFile); Channel shield 
= CloseShieldChannel.wrap(channel)) {
+            assertInstanceOf(SeekableByteChannel.class, shield);
+            assertInstanceOf(GatheringByteChannel.class, shield);
+            assertInstanceOf(WritableByteChannel.class, shield);
+            assertInstanceOf(ScatteringByteChannel.class, shield);
+            assertInstanceOf(ReadableByteChannel.class, shield);
+            assertInstanceOf(InterruptibleChannel.class, shield);
+            assertInstanceOf(ByteChannel.class, shield);
+            assertInstanceOf(Channel.class, shield);
+            // These are not interfaces, so can not be implemented
+            assertFalse(shield instanceof FileChannel, "not FileChannel");
+        }
+    }
 }

Reply via email to