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 testShouldTruncateContentsProperly() throws ClosedChannelException {
166         try (ByteArraySeekableByteChannel c = ByteArraySeekableByteChannel.wrap(testData)) {
167             c.truncate(4);
168             final byte[] bytes = Arrays.copyOf(c.array(), (int) c.size());
169             assertEquals("Some", new String(bytes, StandardCharsets.UTF_8));
170         }
171     }
172     // Contract Tests added in response to https://issues.apache.org/jira/browse/COMPRESS-499
173     // https://docs.oracle.com/javase/8/docs/api/java/io/Closeable.html#close()
174 
175     @Test
176     void testShouldWriteDataProperly() throws IOException {
177         try (ByteArraySeekableByteChannel c = new ByteArraySeekableByteChannel()) {
178             final ByteBuffer inData = ByteBuffer.wrap(testData);
179             final int writeCount = c.write(inData);
180             assertEquals(testData.length, writeCount);
181             assertEquals(testData.length, c.position());
182             assertArrayEquals(testData, Arrays.copyOf(c.array(), (int) c.position()));
183         }
184     }
185     // https://docs.oracle.com/javase/8/docs/api/java/nio/channels/SeekableByteChannel.html#position()
186 
187     @Test
188     void testShouldWriteDataProperlyAfterPositionSet() throws IOException {
189         try (ByteArraySeekableByteChannel c = ByteArraySeekableByteChannel.wrap(testData.clone())) {
190             final ByteBuffer inData = ByteBuffer.wrap(testData);
191             final ByteBuffer expectedData = ByteBuffer.allocate(testData.length + 5).put(testData, 0, 5).put(testData);
192             c.position(5L);
193             final int writeCount = c.write(inData);
194             assertEquals(testData.length, writeCount);
195             assertArrayEquals(expectedData.array(), Arrays.copyOf(c.array(), (int) c.size()));
196             assertEquals(testData.length + 5, c.position());
197         }
198     }
199     // https://docs.oracle.com/javase/8/docs/api/java/nio/channels/SeekableByteChannel.html#size()
200 
201     /*
202      * <q>ClosedChannelException - If this channel is closed</q>
203      */
204     @Test
205     void testThrowsClosedChannelExceptionWhenPositionIsSetOnClosedChannel() throws Exception {
206         try (SeekableByteChannel c = new ByteArraySeekableByteChannel()) {
207             c.close();
208             assertThrows(ClosedChannelException.class, () -> c.position(0));
209         }
210     }
211     // https://docs.oracle.com/javase/8/docs/api/java/nio/channels/SeekableByteChannel.html#position(long)
212 
213     /*
214      * <q>IllegalArgumentException - If the new position is negative</q>
215      */
216     @Test
217     void testThrowsIllegalArgumentExceptionWhenTruncatingToANegativeSize() throws Exception {
218         try (SeekableByteChannel c = new ByteArraySeekableByteChannel()) {
219             assertThrows(IllegalArgumentException.class, () -> c.truncate(-1));
220         }
221     }
222 
223     /*
224      * <q>IOException - If the new position is negative</q>
225      */
226     @Test
227     void testThrowsIOExceptionWhenPositionIsSetToANegativeValue() throws Exception {
228         try (SeekableByteChannel c = new ByteArraySeekableByteChannel()) {
229             assertThrows(IllegalArgumentException.class, () -> c.position(-1));
230         }
231     }
232 
233     @Test
234     void testThrowWhenSettingIncorrectPosition() throws IOException {
235         try (ByteArraySeekableByteChannel c = new ByteArraySeekableByteChannel()) {
236             final ByteBuffer buffer = ByteBuffer.allocate(1);
237             // write
238             c.write(buffer);
239             assertEquals(1, c.position());
240             // bad pos A
241             c.position(c.size() + 1);
242             assertEquals(c.size() + 1, c.position());
243             assertEquals(-1, c.read(buffer));
244             // bad pos B
245             c.position(Integer.MAX_VALUE + 1L);
246             assertEquals(Integer.MAX_VALUE + 1L, c.position());
247             assertEquals(-1, c.read(buffer));
248             assertThrows(IOException.class, () -> c.write(buffer));
249             // negative input is the only illegal input
250             assertThrows(IllegalArgumentException.class, () -> c.position(-1));
251             assertThrows(IllegalArgumentException.class, () -> c.position(Integer.MIN_VALUE));
252             assertThrows(IllegalArgumentException.class, () -> c.position(Long.MIN_VALUE));
253         }
254     }
255 
256     @Test
257     void testThrowWhenTruncatingToIncorrectSize() throws IOException {
258         try (ByteArraySeekableByteChannel c = new ByteArraySeekableByteChannel()) {
259             final ByteBuffer buffer = ByteBuffer.allocate(1);
260             c.truncate(c.size() + 1);
261             assertEquals(-1, c.read(buffer));
262             c.truncate(Integer.MAX_VALUE + 1L);
263             assertEquals(-1, c.read(buffer));
264             assertThrows(IllegalArgumentException.class, () -> c.truncate(-1));
265             assertThrows(IllegalArgumentException.class, () -> c.truncate(Integer.MIN_VALUE));
266             assertThrows(IllegalArgumentException.class, () -> c.truncate(Long.MIN_VALUE));
267         }
268     }
269 
270     /*
271      * <q> In either case, if the current position is greater than the given size then it is set to that size.</q>
272      */
273     @Test
274     void testTruncateDoesntChangeSmallPosition() throws Exception {
275         try (SeekableByteChannel c = ByteArraySeekableByteChannel.wrap(testData)) {
276             c.position(1);
277             c.truncate(testData.length - 1);
278             assertEquals(testData.length - 1, c.size());
279             assertEquals(1, c.position());
280         }
281     }
282 
283     /*
284      * <q> In either case, if the current position is greater than the given size then it is set to that size.</q>
285      */
286     @Test
287     void testTruncateMovesPositionWhenNewSizeIsBiggerThanSizeAndPositionIsEvenBigger() throws Exception {
288         try (SeekableByteChannel c = ByteArraySeekableByteChannel.wrap(testData)) {
289             c.position(2 * testData.length);
290             c.truncate(testData.length + 1);
291             assertEquals(testData.length, c.size());
292             assertEquals(testData.length + 1, c.position());
293         }
294     }
295     // https://docs.oracle.com/javase/8/docs/api/java/nio/channels/SeekableByteChannel.html#truncate(long)
296 
297     /*
298      * <q> In either case, if the current position is greater than the given size then it is set to that size.</q>
299      */
300     @Test
301     void testTruncateMovesPositionWhenNotResizingButPositionBiggerThanSize() throws Exception {
302         try (SeekableByteChannel c = ByteArraySeekableByteChannel.wrap(testData)) {
303             c.position(2 * testData.length);
304             c.truncate(testData.length);
305             assertEquals(testData.length, c.size());
306             assertEquals(testData.length, c.position());
307         }
308     }
309 
310     /*
311      * <q> In either case, if the current position is greater than the given size then it is set to that size.</q>
312      */
313     @Test
314     void testTruncateMovesPositionWhenShrinkingBeyondPosition() throws Exception {
315         try (SeekableByteChannel c = ByteArraySeekableByteChannel.wrap(testData)) {
316             c.position(4);
317             c.truncate(3);
318             assertEquals(3, c.size());
319             assertEquals(3, c.position());
320         }
321     }
322 
323     /*
324      * <q>If the given size is greater than or equal to the current size then the entity is not modified.</q>
325      */
326     @Test
327     void testTruncateToBiggerSizeDoesntChangeAnything() throws Exception {
328         try (SeekableByteChannel c = ByteArraySeekableByteChannel.wrap(testData)) {
329             assertEquals(testData.length, c.size());
330             c.truncate(testData.length + 1);
331             assertEquals(testData.length, c.size());
332             final ByteBuffer readBuffer = ByteBuffer.allocate(testData.length);
333             assertEquals(testData.length, c.read(readBuffer));
334             assertArrayEquals(testData, Arrays.copyOf(readBuffer.array(), testData.length));
335         }
336     }
337 
338     /*
339      * <q>If the given size is greater than or equal to the current size then the entity is not modified.</q>
340      */
341     @Test
342     void testTruncateToCurrentSizeDoesntChangeAnything() throws Exception {
343         try (SeekableByteChannel c = ByteArraySeekableByteChannel.wrap(testData)) {
344             assertEquals(testData.length, c.size());
345             c.truncate(testData.length);
346             assertEquals(testData.length, c.size());
347             final ByteBuffer readBuffer = ByteBuffer.allocate(testData.length);
348             assertEquals(testData.length, c.read(readBuffer));
349             assertArrayEquals(testData, Arrays.copyOf(readBuffer.array(), testData.length));
350         }
351     }
352 
353     /*
354      * <q>ClosedChannelException - If this channel is closed</q>
355      */
356     @Test
357     public void throwsClosedChannelExceptionWhenPositionIsReadOnClosedChannel() throws Exception {
358         try (SeekableByteChannel c = new ByteArraySeekableByteChannel()) {
359             c.close();
360             assertThrows(ClosedChannelException.class, c::position);
361         }
362     }
363 
364     /*
365      * <q>ClosedChannelException - If this channel is closed</q>
366      */
367     @Test
368     public void throwsClosedChannelExceptionWhenSizeIsReadOnClosedChannel() throws Exception {
369         try (SeekableByteChannel c = new ByteArraySeekableByteChannel()) {
370             c.close();
371             assertThrows(ClosedChannelException.class, c::size);
372         }
373     }
374 
375     /*
376      * <q>ClosedChannelException - If this channel is closed</q>
377      */
378     @Test
379     public void throwsClosedChannelExceptionWhenTruncateIsCalledOnClosedChannel() throws Exception {
380         try (SeekableByteChannel c = new ByteArraySeekableByteChannel()) {
381             c.close();
382             assertThrows(ClosedChannelException.class, () -> c.truncate(0));
383         }
384     }
385 
386     /*
387      * <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
388      * 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
389      * newly-written bytes are unspecified.</q>
390      */
391     public void writingToAPositionAfterEndGrowsChannel() throws Exception {
392         try (SeekableByteChannel c = new ByteArraySeekableByteChannel()) {
393             c.position(2);
394             assertEquals(2, c.position());
395             final ByteBuffer inData = ByteBuffer.wrap(testData);
396             assertEquals(testData.length, c.write(inData));
397             assertEquals(testData.length + 2, c.size());
398             c.position(2);
399             final ByteBuffer readBuffer = ByteBuffer.allocate(testData.length);
400             c.read(readBuffer);
401             assertArrayEquals(testData, Arrays.copyOf(readBuffer.array(), testData.length));
402         }
403     }
404 }