From f4c52d3c10f8999088d79b27de011edc46983254 Mon Sep 17 00:00:00 2001 From: "Piotr P. Karwasz" Date: Tue, 14 Oct 2025 19:12:06 +0200 Subject: [PATCH] Fixes interface discovery in `CloseShieldChannel` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- .../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 a9a462da73b..bde0feb25cc 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> collectChannelInterfaces(final Class type, final Set> 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 ac7e85b7a89..42a23f0af1c 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"); + } + } }