View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.io.channels;
19  
20  import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
21  import static org.junit.jupiter.api.Assertions.assertEquals;
22  import static org.junit.jupiter.api.Assertions.assertFalse;
23  import static org.junit.jupiter.api.Assertions.assertInstanceOf;
24  import static org.junit.jupiter.api.Assertions.assertNotSame;
25  import static org.junit.jupiter.api.Assertions.assertSame;
26  import static org.junit.jupiter.api.Assertions.assertThrows;
27  import static org.junit.jupiter.api.Assertions.assertTrue;
28  import static org.mockito.Mockito.mock;
29  import static org.mockito.Mockito.never;
30  import static org.mockito.Mockito.times;
31  import static org.mockito.Mockito.verify;
32  import static org.mockito.Mockito.verifyNoInteractions;
33  import static org.mockito.Mockito.verifyNoMoreInteractions;
34  import static org.mockito.Mockito.when;
35  
36  import java.io.IOException;
37  import java.nio.channels.AsynchronousChannel;
38  import java.nio.channels.ByteChannel;
39  import java.nio.channels.Channel;
40  import java.nio.channels.ClosedChannelException;
41  import java.nio.channels.FileChannel;
42  import java.nio.channels.GatheringByteChannel;
43  import java.nio.channels.InterruptibleChannel;
44  import java.nio.channels.NetworkChannel;
45  import java.nio.channels.ReadableByteChannel;
46  import java.nio.channels.ScatteringByteChannel;
47  import java.nio.channels.SeekableByteChannel;
48  import java.nio.channels.WritableByteChannel;
49  import java.nio.file.Path;
50  import java.util.List;
51  import java.util.stream.Stream;
52  
53  import org.apache.commons.io.FileUtils;
54  import org.apache.commons.lang3.ClassUtils;
55  import org.junit.jupiter.api.Test;
56  import org.junit.jupiter.api.io.TempDir;
57  import org.junit.jupiter.params.ParameterizedTest;
58  import org.junit.jupiter.params.provider.MethodSource;
59  
60  /**
61   * Tests {@link CloseShieldChannel}.
62   */
63  class CloseShieldChannelTest {
64  
65      /**
66       * JRE {@link Channel} interfaces.
67       */
68      static Stream<Class<? extends Channel>> channelInterfaces() {
69          // @formatter:off
70          return Stream.of(
71                  AsynchronousChannel.class,
72                  ByteChannel.class,
73                  Channel.class,
74                  GatheringByteChannel.class,
75                  InterruptibleChannel.class,
76                  NetworkChannel.class,
77                  ReadableByteChannel.class,
78                  ScatteringByteChannel.class,
79                  SeekableByteChannel.class,
80                  WritableByteChannel.class);
81          // @formatter:on
82      }
83  
84      /**
85       * Gets all interfaces implemented by the class {@link FileChannel}.
86       */
87      static List<Class<?>> fileChannelInterfaces() {
88          return ClassUtils.getAllInterfaces(FileChannel.class);
89      }
90  
91      @ParameterizedTest
92      @MethodSource("channelInterfaces")
93      void testCloseDoesNotCloseDelegate(final Class<? extends Channel> channelClass) throws Exception {
94          final Channel channel = mock(channelClass);
95          final Channel shield = CloseShieldChannel.wrap(channel);
96          shield.close();
97          verify(channel, never()).close();
98      }
99  
100     @ParameterizedTest
101     @MethodSource("channelInterfaces")
102     void testCloseIsIdempotent(final Class<? extends Channel> channelClass) throws Exception {
103         final Channel channel = mock(channelClass);
104         final Channel shield = CloseShieldChannel.wrap(channel);
105         shield.close();
106         assertFalse(shield.isOpen());
107         shield.close();
108         assertFalse(shield.isOpen());
109         verifyNoInteractions(channel);
110     }
111 
112     @ParameterizedTest
113     @MethodSource("channelInterfaces")
114     void testCloseIsShielded(final Class<? extends Channel> channelInterface) throws Exception {
115         final Channel channel = mock(channelInterface);
116         when(channel.isOpen()).thenReturn(true, false, true, false);
117         final Channel shield = CloseShieldChannel.wrap(channel);
118         // Reflects delegate state initially
119         assertTrue(shield.isOpen(), "isOpen reflects delegate state");
120         assertFalse(shield.isOpen(), "isOpen reflects delegate state");
121         verify(channel, times(2)).isOpen();
122         shield.close();
123         // Reflects shield state after close
124         assertFalse(shield.isOpen(), "isOpen reflects shield state");
125         assertFalse(shield.isOpen(), "isOpen reflects shield state");
126         verify(channel, times(2)).isOpen();
127     }
128 
129     @Test
130     void testDoesNotDoubleWrap() {
131         final ByteChannel channel = mock(ByteChannel.class);
132         final ByteChannel shield1 = CloseShieldChannel.wrap(channel);
133         final ByteChannel shield2 = CloseShieldChannel.wrap(shield1);
134         assertSame(shield1, shield2);
135     }
136 
137     @ParameterizedTest
138     @MethodSource("channelInterfaces")
139     void testEquals(final Class<? extends Channel> channelClass) throws Exception {
140         final Channel channel = mock(channelClass);
141         final Channel shield = CloseShieldChannel.wrap(channel);
142         final Channel anotherShield = CloseShieldChannel.wrap(channel);
143         assertTrue(shield.equals(shield), "reflexive");
144         assertFalse(shield.equals(null), "null is not equal");
145         assertFalse(shield.equals(channel), "shield not equal to delegate");
146         assertTrue(shield.equals(anotherShield), "shields of same delegate are equal");
147     }
148 
149     @Test
150     void testGatheringByteChannelMethods() throws Exception {
151         final GatheringByteChannel channel = mock(GatheringByteChannel.class);
152         when(channel.isOpen()).thenReturn(true);
153         final GatheringByteChannel shield = CloseShieldChannel.wrap(channel);
154         // Before close write() should delegate
155         when(channel.write(null, 0, 0)).thenReturn(42L);
156         assertEquals(42, shield.write(null, 0, 0));
157         verify(channel).write(null, 0, 0);
158         // After close write() should throw ClosedChannelException
159         shield.close();
160         assertThrows(ClosedChannelException.class, () -> shield.write(null, 0, 0));
161         verifyNoMoreInteractions(channel);
162     }
163 
164     @ParameterizedTest
165     @MethodSource("channelInterfaces")
166     void testHashCode(final Class<? extends Channel> channelClass) throws Exception {
167         final Channel channel = mock(channelClass);
168         final Channel shield = CloseShieldChannel.wrap(channel);
169         final Channel anotherShield = CloseShieldChannel.wrap(channel);
170         assertEquals(shield.hashCode(), channel.hashCode(), "delegates hashCode");
171         assertEquals(shield.hashCode(), anotherShield.hashCode(), "shields of same delegate have same hashCode");
172     }
173 
174     @Test
175     void testNetworkChannelMethods() throws Exception {
176         final NetworkChannel channel = mock(NetworkChannel.class);
177         when(channel.isOpen()).thenReturn(true);
178         final NetworkChannel shield = CloseShieldChannel.wrap(channel);
179         // Before close getOption(), setOption(), getLocalAddress() and bind() should delegate
180         when(channel.getOption(null)).thenReturn("foo");
181         when(channel.setOption(null, null)).thenReturn(channel);
182         when(channel.getLocalAddress()).thenReturn(null);
183         when(channel.bind(null)).thenReturn(channel);
184         assertEquals("foo", shield.getOption(null));
185         assertEquals(shield, shield.setOption(null, null));
186         assertEquals(null, shield.getLocalAddress());
187         assertEquals(shield, shield.bind(null));
188         verify(channel).getOption(null);
189         verify(channel).setOption(null, null);
190         verify(channel).getLocalAddress();
191         verify(channel).bind(null);
192         // After close supportedOptions() should still work
193         shield.close();
194         assertDoesNotThrow(shield::supportedOptions);
195         verify(channel).supportedOptions();
196         // But the remaining methods should throw ClosedChannelException
197         assertThrows(ClosedChannelException.class, () -> shield.setOption(null, null));
198         assertThrows(ClosedChannelException.class, () -> shield.getOption(null));
199         assertThrows(ClosedChannelException.class, shield::getLocalAddress);
200         assertThrows(ClosedChannelException.class, () -> shield.bind(null));
201         verifyNoMoreInteractions(channel);
202     }
203 
204     @ParameterizedTest
205     @MethodSource("channelInterfaces")
206     void testPreservesInterfaces(final Class<? extends Channel> channelClass) {
207         final Channel channel = mock(channelClass);
208         final Channel shield = CloseShieldChannel.wrap(channel);
209         assertNotSame(channel, shield);
210         assertTrue(channelClass.isInstance(shield));
211     }
212 
213     @Test
214     void testReadableByteChannelMethods() throws Exception {
215         final ReadableByteChannel channel = mock(ReadableByteChannel.class);
216         when(channel.isOpen()).thenReturn(true);
217         final ReadableByteChannel shield = CloseShieldChannel.wrap(channel);
218         // Before close read() should delegate
219         when(channel.read(null)).thenReturn(42);
220         assertEquals(42, shield.read(null));
221         verify(channel).read(null);
222         // After close read() should throw ClosedChannelException
223         shield.close();
224         assertThrows(ClosedChannelException.class, () -> shield.read(null));
225         verifyNoMoreInteractions(channel);
226     }
227 
228     @Test
229     void testScatteringByteChannelMethods() throws Exception {
230         final ScatteringByteChannel channel = mock(ScatteringByteChannel.class);
231         when(channel.isOpen()).thenReturn(true);
232         final ScatteringByteChannel shield = CloseShieldChannel.wrap(channel);
233         // Before close read() should delegate
234         when(channel.read(null, 0, 0)).thenReturn(42L);
235         assertEquals(42, shield.read(null, 0, 0));
236         verify(channel).read(null, 0, 0);
237         // After close read() should throw ClosedChannelException
238         shield.close();
239         assertThrows(ClosedChannelException.class, () -> shield.read(null, 0, 0));
240         verifyNoMoreInteractions(channel);
241     }
242 
243     @Test
244     void testSeekableByteChannelMethods() throws Exception {
245         final SeekableByteChannel channel = mock(SeekableByteChannel.class);
246         when(channel.isOpen()).thenReturn(true);
247         final SeekableByteChannel shield = CloseShieldChannel.wrap(channel);
248         // Before close position() and size() should delegate
249         when(channel.position()).thenReturn(42L);
250         when(channel.size()).thenReturn(84L);
251         assertEquals(42, shield.position());
252         assertEquals(84, shield.size());
253         verify(channel).position();
254         verify(channel).size();
255         // Before close position(long) and truncate(long) should delegate
256         when(channel.position(21)).thenReturn(channel);
257         when(channel.truncate(21)).thenReturn(channel);
258         assertEquals(shield, shield.position(21));
259         assertEquals(shield, shield.truncate(21));
260         verify(channel).position(21);
261         verify(channel).truncate(21);
262         // After close position() should throw ClosedChannelException
263         shield.close();
264         assertThrows(ClosedChannelException.class, shield::position);
265         assertThrows(ClosedChannelException.class, () -> shield.position(0));
266         assertThrows(ClosedChannelException.class, shield::size);
267         assertThrows(ClosedChannelException.class, () -> shield.truncate(0));
268         verifyNoMoreInteractions(channel);
269     }
270 
271     @ParameterizedTest
272     @MethodSource("channelInterfaces")
273     void testToString(final Class<? extends Channel> channelClass) throws Exception {
274         final Channel channel = mock(channelClass);
275         when(channel.toString()).thenReturn("MyChannel");
276         final Channel shield = CloseShieldChannel.wrap(channel);
277         final String shieldString = shield.toString();
278         assertTrue(shieldString.contains("CloseShield"));
279         assertTrue(shieldString.contains("MyChannel"));
280     }
281 
282     @Test
283     void testWrapFileChannel(final @TempDir Path tempDir) throws IOException {
284         final Path testFile = tempDir.resolve("test.txt");
285         FileUtils.touch(testFile.toFile());
286         try (FileChannel channel = FileChannel.open(testFile); Channel shield = CloseShieldChannel.wrap(channel)) {
287             fileChannelInterfaces().forEach(iface -> assertInstanceOf(iface, shield));
288             // FileChannel is not an interface, so can not be implemented.
289             assertFalse(shield instanceof FileChannel, "not FileChannel");
290         }
291     }
292 
293     @Test
294     void testWritableByteChannelMethods() throws Exception {
295         final WritableByteChannel channel = mock(WritableByteChannel.class);
296         when(channel.isOpen()).thenReturn(true);
297         final WritableByteChannel shield = CloseShieldChannel.wrap(channel);
298         // Before close write() should delegate
299         when(channel.write(null)).thenReturn(42);
300         assertEquals(42, shield.write(null));
301         verify(channel).write(null);
302         // After close write() should throw ClosedChannelException
303         shield.close();
304         assertThrows(ClosedChannelException.class, () -> shield.write(null));
305         verifyNoMoreInteractions(channel);
306     }
307 }