View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   https://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  
20  package org.apache.commons.io.channels;
21  
22  import static org.junit.jupiter.api.Assertions.assertArrayEquals;
23  import static org.junit.jupiter.api.Assertions.assertEquals;
24  import static org.junit.jupiter.api.Assertions.assertFalse;
25  import static org.junit.jupiter.api.Assertions.assertThrows;
26  
27  import java.io.IOException;
28  import java.nio.ByteBuffer;
29  import java.nio.channels.ClosedChannelException;
30  import java.nio.channels.SeekableByteChannel;
31  import java.nio.charset.StandardCharsets;
32  import java.util.Arrays;
33  
34  import org.junit.jupiter.api.AfterEach;
35  import org.junit.jupiter.api.Test;
36  import org.junit.jupiter.params.ParameterizedTest;
37  import org.junit.jupiter.params.provider.ValueSource;
38  
39  /**
40   * Tests {@link ByteArraySeekableByteChannel} in the same way Apache Commons Compress tests {@code SeekableInMemoryByteChannel}.
41   */
42  class ByteArraySeekableByteChannelCompressTest {
43  
44      private static final byte[] testData = "Some data".getBytes(StandardCharsets.UTF_8);
45  
46      @AfterEach
47      void afterEach() {
48          // Reading tests don't modify the data
49          assertArrayEquals("Some data".getBytes(StandardCharsets.UTF_8), testData);
50      }
51  
52      /*
53       * <q>If the stream is already closed then invoking this method has no effect.</q>
54       */
55      @Test
56      void testCloseIsIdempotent() throws Exception {
57          try (SeekableByteChannel c = new ByteArraySeekableByteChannel()) {
58              c.close();
59              assertFalse(c.isOpen());
60              c.close();
61              assertFalse(c.isOpen());
62          }
63      }
64  
65      /*
66       * <q>Setting the position to a value that is greater than the current size is legal but does not change the size of the entity. A later attempt to read
67       * bytes at such a position will immediately return an end-of-file indication</q>
68       */
69      @ParameterizedTest
70      @ValueSource(ints = { 0, 1, 2, 3, 4, 5, 6 })
71      void testReadingFromAPositionAfterEndReturnsEOF(final int size) throws Exception {
72          try (SeekableByteChannel c = ByteArraySeekableByteChannel.wrap(new byte[size])) {
73              final int position = 2;
74              c.position(position);
75              assertEquals(position, c.position());
76              final int readSize = 5;
77              final ByteBuffer readBuffer = ByteBuffer.allocate(readSize);
78              assertEquals(position >= size ? -1 : size - position, c.read(readBuffer));
79          }
80      }
81  
82      @Test
83      void testShouldReadContentsProperly() throws IOException {
84          try (ByteArraySeekableByteChannel c = ByteArraySeekableByteChannel.wrap(testData)) {
85              final ByteBuffer readBuffer = ByteBuffer.allocate(testData.length);
86              final int readCount = c.read(readBuffer);
87              assertEquals(testData.length, readCount);
88              assertArrayEquals(testData, readBuffer.array());
89              assertEquals(testData.length, c.position());
90          }
91      }
92  
93      @Test
94      void testShouldReadContentsWhenBiggerBufferSupplied() throws IOException {
95          try (ByteArraySeekableByteChannel c = ByteArraySeekableByteChannel.wrap(testData)) {
96              final ByteBuffer readBuffer = ByteBuffer.allocate(testData.length + 1);
97              final int readCount = c.read(readBuffer);
98              assertEquals(testData.length, readCount);
99              assertArrayEquals(testData, Arrays.copyOf(readBuffer.array(), testData.length));
100             assertEquals(testData.length, c.position());
101         }
102     }
103 
104     @Test
105     void testShouldReadDataFromSetPosition() throws IOException {
106         try (ByteArraySeekableByteChannel c = ByteArraySeekableByteChannel.wrap(testData)) {
107             final ByteBuffer readBuffer = ByteBuffer.allocate(4);
108             c.position(5L);
109             final int readCount = c.read(readBuffer);
110             assertEquals(4L, readCount);
111             assertEquals("data", new String(readBuffer.array(), StandardCharsets.UTF_8));
112             assertEquals(testData.length, c.position());
113         }
114     }
115 
116     @Test
117     void testShouldSetProperPosition() throws IOException {
118         try (ByteArraySeekableByteChannel c = ByteArraySeekableByteChannel.wrap(testData)) {
119             final long posAtFour = c.position(4L).position();
120             final long posAtTheEnd = c.position(testData.length).position();
121             final long posPastTheEnd = c.position(testData.length + 1L).position();
122             assertEquals(4L, posAtFour);
123             assertEquals(c.size(), posAtTheEnd);
124             assertEquals(testData.length + 1L, posPastTheEnd);
125         }
126     }
127 
128     @Test
129     void testShouldSetProperPositionOnTruncate() throws IOException {
130         try (ByteArraySeekableByteChannel c = ByteArraySeekableByteChannel.wrap(testData)) {
131             c.position(testData.length);
132             c.truncate(4L);
133             assertEquals(4L, c.position());
134             assertEquals(4L, c.size());
135         }
136     }
137 
138     @Test
139     void testShouldSignalEOFWhenPositionAtTheEnd() throws IOException {
140         try (ByteArraySeekableByteChannel c = ByteArraySeekableByteChannel.wrap(testData)) {
141             final ByteBuffer readBuffer = ByteBuffer.allocate(testData.length);
142             c.position(testData.length + 1);
143             final int readCount = c.read(readBuffer);
144             assertEquals(0L, readBuffer.position());
145             assertEquals(-1, readCount);
146             assertEquals(-1, c.read(readBuffer));
147         }
148     }
149 
150     @Test
151     void testShouldThrowExceptionOnReadingClosedChannel() {
152         final ByteArraySeekableByteChannel c = new ByteArraySeekableByteChannel();
153         c.close();
154         assertThrows(ClosedChannelException.class, () -> c.read(ByteBuffer.allocate(1)));
155     }
156 
157     @Test
158     void testShouldThrowExceptionOnWritingToClosedChannel() {
159         final ByteArraySeekableByteChannel c = new ByteArraySeekableByteChannel();
160         c.close();
161         assertThrows(ClosedChannelException.class, () -> c.write(ByteBuffer.allocate(1)));
162     }
163 
164     @Test
165     void testShouldThrowExceptionWhenSettingIncorrectPosition() {
166         try (ByteArraySeekableByteChannel c = new ByteArraySeekableByteChannel()) {
167             assertThrows(IllegalArgumentException.class, () -> c.position(Integer.MAX_VALUE + 1L));
168         }
169     }
170 
171     @Test
172     void testShouldThrowExceptionWhenTruncatingToIncorrectSize() {
173         try (ByteArraySeekableByteChannel c = new ByteArraySeekableByteChannel()) {
174             assertThrows(IllegalArgumentException.class, () -> c.truncate(Integer.MAX_VALUE + 1L));
175         }
176     }
177 
178     @Test
179     void testShouldTruncateContentsProperly() throws ClosedChannelException {
180         try (ByteArraySeekableByteChannel c = ByteArraySeekableByteChannel.wrap(testData)) {
181             c.truncate(4);
182             final byte[] bytes = Arrays.copyOf(c.array(), (int) c.size());
183             assertEquals("Some", new String(bytes, StandardCharsets.UTF_8));
184         }
185     }
186     // Contract Tests added in response to https://issues.apache.org/jira/browse/COMPRESS-499
187     // https://docs.oracle.com/javase/8/docs/api/java/io/Closeable.html#close()
188 
189     @Test
190     void testShouldWriteDataProperly() throws IOException {
191         try (ByteArraySeekableByteChannel c = new ByteArraySeekableByteChannel()) {
192             final ByteBuffer inData = ByteBuffer.wrap(testData);
193             final int writeCount = c.write(inData);
194             assertEquals(testData.length, writeCount);
195             assertEquals(testData.length, c.position());
196             assertArrayEquals(testData, Arrays.copyOf(c.array(), (int) c.position()));
197         }
198     }
199     // https://docs.oracle.com/javase/8/docs/api/java/nio/channels/SeekableByteChannel.html#position()
200 
201     @Test
202     void testShouldWriteDataProperlyAfterPositionSet() throws IOException {
203         try (ByteArraySeekableByteChannel c = ByteArraySeekableByteChannel.wrap(testData.clone())) {
204             final ByteBuffer inData = ByteBuffer.wrap(testData);
205             final ByteBuffer expectedData = ByteBuffer.allocate(testData.length + 5).put(testData, 0, 5).put(testData);
206             c.position(5L);
207             final int writeCount = c.write(inData);
208             assertEquals(testData.length, writeCount);
209             assertArrayEquals(expectedData.array(), Arrays.copyOf(c.array(), (int) c.size()));
210             assertEquals(testData.length + 5, c.position());
211         }
212     }
213     // https://docs.oracle.com/javase/8/docs/api/java/nio/channels/SeekableByteChannel.html#size()
214 
215     /*
216      * <q>ClosedChannelException - If this channel is closed</q>
217      */
218     @Test
219     void testThrowsClosedChannelExceptionWhenPositionIsSetOnClosedChannel() throws Exception {
220         try (SeekableByteChannel c = new ByteArraySeekableByteChannel()) {
221             c.close();
222             assertThrows(ClosedChannelException.class, () -> c.position(0));
223         }
224     }
225     // https://docs.oracle.com/javase/8/docs/api/java/nio/channels/SeekableByteChannel.html#position(long)
226 
227     /*
228      * <q>IllegalArgumentException - If the new position is negative</q>
229      */
230     @Test
231     void testThrowsIllegalArgumentExceptionWhenTruncatingToANegativeSize() throws Exception {
232         try (SeekableByteChannel c = new ByteArraySeekableByteChannel()) {
233             assertThrows(IllegalArgumentException.class, () -> c.truncate(-1));
234         }
235     }
236 
237     /*
238      * <q>IOException - If the new position is negative</q>
239      */
240     @Test
241     void testThrowsIOExceptionWhenPositionIsSetToANegativeValue() throws Exception {
242         try (SeekableByteChannel c = new ByteArraySeekableByteChannel()) {
243             assertThrows(IllegalArgumentException.class, () -> c.position(-1));
244         }
245     }
246 
247     /*
248      * <q> In either case, if the current position is greater than the given size then it is set to that size.</q>
249      */
250     @Test
251     void testTruncateDoesntChangeSmallPosition() throws Exception {
252         try (SeekableByteChannel c = ByteArraySeekableByteChannel.wrap(testData)) {
253             c.position(1);
254             c.truncate(testData.length - 1);
255             assertEquals(testData.length - 1, c.size());
256             assertEquals(1, c.position());
257         }
258     }
259 
260     /*
261      * <q> In either case, if the current position is greater than the given size then it is set to that size.</q>
262      */
263     @Test
264     void testTruncateMovesPositionWhenNewSizeIsBiggerThanSizeAndPositionIsEvenBigger() throws Exception {
265         try (SeekableByteChannel c = ByteArraySeekableByteChannel.wrap(testData)) {
266             c.position(2 * testData.length);
267             c.truncate(testData.length + 1);
268             assertEquals(testData.length, c.size());
269             assertEquals(testData.length + 1, c.position());
270         }
271     }
272     // https://docs.oracle.com/javase/8/docs/api/java/nio/channels/SeekableByteChannel.html#truncate(long)
273 
274     /*
275      * <q> In either case, if the current position is greater than the given size then it is set to that size.</q>
276      */
277     @Test
278     void testTruncateMovesPositionWhenNotResizingButPositionBiggerThanSize() throws Exception {
279         try (SeekableByteChannel c = ByteArraySeekableByteChannel.wrap(testData)) {
280             c.position(2 * testData.length);
281             c.truncate(testData.length);
282             assertEquals(testData.length, c.size());
283             assertEquals(testData.length, c.position());
284         }
285     }
286 
287     /*
288      * <q> In either case, if the current position is greater than the given size then it is set to that size.</q>
289      */
290     @Test
291     void testTruncateMovesPositionWhenShrinkingBeyondPosition() throws Exception {
292         try (SeekableByteChannel c = ByteArraySeekableByteChannel.wrap(testData)) {
293             c.position(4);
294             c.truncate(3);
295             assertEquals(3, c.size());
296             assertEquals(3, c.position());
297         }
298     }
299 
300     /*
301      * <q>If the given size is greater than or equal to the current size then the entity is not modified.</q>
302      */
303     @Test
304     void testTruncateToBiggerSizeDoesntChangeAnything() throws Exception {
305         try (SeekableByteChannel c = ByteArraySeekableByteChannel.wrap(testData)) {
306             assertEquals(testData.length, c.size());
307             c.truncate(testData.length + 1);
308             assertEquals(testData.length, c.size());
309             final ByteBuffer readBuffer = ByteBuffer.allocate(testData.length);
310             assertEquals(testData.length, c.read(readBuffer));
311             assertArrayEquals(testData, Arrays.copyOf(readBuffer.array(), testData.length));
312         }
313     }
314 
315     /*
316      * <q>If the given size is greater than or equal to the current size then the entity is not modified.</q>
317      */
318     @Test
319     void testTruncateToCurrentSizeDoesntChangeAnything() throws Exception {
320         try (SeekableByteChannel c = ByteArraySeekableByteChannel.wrap(testData)) {
321             assertEquals(testData.length, c.size());
322             c.truncate(testData.length);
323             assertEquals(testData.length, c.size());
324             final ByteBuffer readBuffer = ByteBuffer.allocate(testData.length);
325             assertEquals(testData.length, c.read(readBuffer));
326             assertArrayEquals(testData, Arrays.copyOf(readBuffer.array(), testData.length));
327         }
328     }
329 
330     /*
331      * <q>ClosedChannelException - If this channel is closed</q>
332      */
333     @Test
334     public void throwsClosedChannelExceptionWhenPositionIsReadOnClosedChannel() throws Exception {
335         try (SeekableByteChannel c = new ByteArraySeekableByteChannel()) {
336             c.close();
337             assertThrows(ClosedChannelException.class, c::position);
338         }
339     }
340 
341     /*
342      * <q>ClosedChannelException - If this channel is closed</q>
343      */
344     @Test
345     public void throwsClosedChannelExceptionWhenSizeIsReadOnClosedChannel() throws Exception {
346         try (SeekableByteChannel c = new ByteArraySeekableByteChannel()) {
347             c.close();
348             assertThrows(ClosedChannelException.class, c::size);
349         }
350     }
351 
352     /*
353      * <q>ClosedChannelException - If this channel is closed</q>
354      */
355     @Test
356     public void throwsClosedChannelExceptionWhenTruncateIsCalledOnClosedChannel() throws Exception {
357         try (SeekableByteChannel c = new ByteArraySeekableByteChannel()) {
358             c.close();
359             assertThrows(ClosedChannelException.class, () -> c.truncate(0));
360         }
361     }
362 
363     /*
364      * <q>Setting the position to a value that is greater than the current size is legal but does not change the size of the entity. A later attempt to write
365      * bytes at such a position will cause the entity to grow to accommodate the new bytes; the values of any bytes between the previous end-of-file and the
366      * newly-written bytes are unspecified.</q>
367      */
368     public void writingToAPositionAfterEndGrowsChannel() throws Exception {
369         try (SeekableByteChannel c = new ByteArraySeekableByteChannel()) {
370             c.position(2);
371             assertEquals(2, c.position());
372             final ByteBuffer inData = ByteBuffer.wrap(testData);
373             assertEquals(testData.length, c.write(inData));
374             assertEquals(testData.length + 2, c.size());
375             c.position(2);
376             final ByteBuffer readBuffer = ByteBuffer.allocate(testData.length);
377             c.read(readBuffer);
378             assertArrayEquals(testData, Arrays.copyOf(readBuffer.array(), testData.length));
379         }
380     }
381 }