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   * http://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  package org.apache.commons.compress.utils;
20  
21  import static java.nio.charset.StandardCharsets.UTF_8;
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.util.Arrays;
32  
33  import org.junit.jupiter.api.Disabled;
34  import org.junit.jupiter.api.Test;
35  
36  public class SeekableInMemoryByteChannelTest {
37  
38      private final byte[] testData = "Some data".getBytes(UTF_8);
39  
40      /*
41       * <q>If the stream is already closed then invoking this method has no effect.</q>
42       */
43      @Test
44      public void testCloseIsIdempotent() throws Exception {
45          try (SeekableByteChannel c = new SeekableInMemoryByteChannel()) {
46              c.close();
47              assertFalse(c.isOpen());
48              c.close();
49              assertFalse(c.isOpen());
50          }
51      }
52  
53      /*
54       * <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
55       * bytes at such a position will immediately return an end-of-file indication</q>
56       */
57      @Test
58      public void testReadingFromAPositionAfterEndReturnsEOF() throws Exception {
59          try (SeekableByteChannel c = new SeekableInMemoryByteChannel()) {
60              c.position(2);
61              assertEquals(2, c.position());
62              final ByteBuffer readBuffer = ByteBuffer.allocate(5);
63              assertEquals(-1, c.read(readBuffer));
64          }
65      }
66  
67      @Test
68      public void testShouldReadContentsProperly() throws IOException {
69          // given
70          try (SeekableInMemoryByteChannel c = new SeekableInMemoryByteChannel(testData)) {
71              final ByteBuffer readBuffer = ByteBuffer.allocate(testData.length);
72              // when
73              final int readCount = c.read(readBuffer);
74              // then
75              assertEquals(testData.length, readCount);
76              assertArrayEquals(testData, readBuffer.array());
77              assertEquals(testData.length, c.position());
78          }
79      }
80  
81      @Test
82      public void testShouldReadContentsWhenBiggerBufferSupplied() throws IOException {
83          // given
84          try (SeekableInMemoryByteChannel c = new SeekableInMemoryByteChannel(testData)) {
85              final ByteBuffer readBuffer = ByteBuffer.allocate(testData.length + 1);
86              // when
87              final int readCount = c.read(readBuffer);
88              // then
89              assertEquals(testData.length, readCount);
90              assertArrayEquals(testData, Arrays.copyOf(readBuffer.array(), testData.length));
91              assertEquals(testData.length, c.position());
92          }
93      }
94  
95      @Test
96      public void testShouldReadDataFromSetPosition() throws IOException {
97          // given
98          try (SeekableInMemoryByteChannel c = new SeekableInMemoryByteChannel(testData)) {
99              final ByteBuffer readBuffer = ByteBuffer.allocate(4);
100             // when
101             c.position(5L);
102             final int readCount = c.read(readBuffer);
103             // then
104             assertEquals(4L, readCount);
105             assertEquals("data", new String(readBuffer.array(), UTF_8));
106             assertEquals(testData.length, c.position());
107         }
108     }
109 
110     @Test
111     public void testShouldSetProperPosition() throws IOException {
112         // given
113         try (SeekableInMemoryByteChannel c = new SeekableInMemoryByteChannel(testData)) {
114             // when
115             final long posAtFour = c.position(4L).position();
116             final long posAtTheEnd = c.position(testData.length).position();
117             final long posPastTheEnd = c.position(testData.length + 1L).position();
118             // then
119             assertEquals(4L, posAtFour);
120             assertEquals(c.size(), posAtTheEnd);
121             assertEquals(testData.length + 1L, posPastTheEnd);
122         }
123     }
124 
125     @Test
126     public void testShouldSetProperPositionOnTruncate() throws IOException {
127         // given
128         try (SeekableInMemoryByteChannel c = new SeekableInMemoryByteChannel(testData)) {
129             // when
130             c.position(testData.length);
131             c.truncate(4L);
132             // then
133             assertEquals(4L, c.position());
134             assertEquals(4L, c.size());
135         }
136     }
137 
138     @Test
139     public void testShouldSignalEOFWhenPositionAtTheEnd() throws IOException {
140         // given
141         try (SeekableInMemoryByteChannel c = new SeekableInMemoryByteChannel(testData)) {
142             final ByteBuffer readBuffer = ByteBuffer.allocate(testData.length);
143             // when
144             c.position(testData.length + 1);
145             final int readCount = c.read(readBuffer);
146             // then
147             assertEquals(0L, readBuffer.position());
148             assertEquals(-1, readCount);
149             assertEquals(-1, c.read(readBuffer));
150         }
151     }
152 
153     @Test
154     public void testShouldThrowExceptionOnReadingClosedChannel() {
155         // given
156         final SeekableInMemoryByteChannel c = new SeekableInMemoryByteChannel();
157         // when
158         c.close();
159         assertThrows(ClosedChannelException.class, () -> c.read(ByteBuffer.allocate(1)));
160     }
161 
162     @Test
163     public void testShouldThrowExceptionOnWritingToClosedChannel() {
164         // given
165         final SeekableInMemoryByteChannel c = new SeekableInMemoryByteChannel();
166         // when
167         c.close();
168         assertThrows(ClosedChannelException.class, () -> c.write(ByteBuffer.allocate(1)));
169     }
170 
171     @Test
172     public void testShouldThrowExceptionWhenSettingIncorrectPosition() {
173         // given
174         try (SeekableInMemoryByteChannel c = new SeekableInMemoryByteChannel()) {
175             // when
176             assertThrows(IOException.class, () -> c.position(Integer.MAX_VALUE + 1L));
177         }
178     }
179 
180     @Test
181     public void testShouldThrowExceptionWhenTruncatingToIncorrectSize() {
182         // given
183         try (SeekableInMemoryByteChannel c = new SeekableInMemoryByteChannel()) {
184             // when
185             assertThrows(IllegalArgumentException.class, () -> c.truncate(Integer.MAX_VALUE + 1L));
186         }
187     }
188 
189     @Test
190     public void testShouldTruncateContentsProperly() {
191         // given
192         try (SeekableInMemoryByteChannel c = new SeekableInMemoryByteChannel(testData)) {
193             // when
194             c.truncate(4);
195             // then
196             final byte[] bytes = Arrays.copyOf(c.array(), (int) c.size());
197             assertEquals("Some", new String(bytes, UTF_8));
198         }
199     }
200 
201     // Contract Tests added in response to https://issues.apache.org/jira/browse/COMPRESS-499
202 
203     // https://docs.oracle.com/javase/8/docs/api/java/io/Closeable.html#close()
204 
205     @Test
206     public void testShouldWriteDataProperly() throws IOException {
207         // given
208         try (SeekableInMemoryByteChannel c = new SeekableInMemoryByteChannel()) {
209             final ByteBuffer inData = ByteBuffer.wrap(testData);
210             // when
211             final int writeCount = c.write(inData);
212             // then
213             assertEquals(testData.length, writeCount);
214             assertArrayEquals(testData, Arrays.copyOf(c.array(), (int) c.size()));
215             assertEquals(testData.length, c.position());
216         }
217     }
218 
219     // https://docs.oracle.com/javase/8/docs/api/java/nio/channels/SeekableByteChannel.html#position()
220 
221     @Test
222     public void testShouldWriteDataProperlyAfterPositionSet() throws IOException {
223         // given
224         try (SeekableInMemoryByteChannel c = new SeekableInMemoryByteChannel(testData)) {
225             final ByteBuffer inData = ByteBuffer.wrap(testData);
226             final ByteBuffer expectedData = ByteBuffer.allocate(testData.length + 5).put(testData, 0, 5).put(testData);
227             // when
228             c.position(5L);
229             final int writeCount = c.write(inData);
230 
231             // then
232             assertEquals(testData.length, writeCount);
233             assertArrayEquals(expectedData.array(), Arrays.copyOf(c.array(), (int) c.size()));
234             assertEquals(testData.length + 5, c.position());
235         }
236     }
237 
238     // https://docs.oracle.com/javase/8/docs/api/java/nio/channels/SeekableByteChannel.html#size()
239 
240     /*
241      * <q>ClosedChannelException - If this channel is closed</q>
242      */
243     @Test
244     public void testThrowsClosedChannelExceptionWhenPositionIsSetOnClosedChannel() throws Exception {
245         try (SeekableByteChannel c = new SeekableInMemoryByteChannel()) {
246             c.close();
247             assertThrows(ClosedChannelException.class, () -> c.position(0));
248         }
249     }
250 
251     // https://docs.oracle.com/javase/8/docs/api/java/nio/channels/SeekableByteChannel.html#position(long)
252 
253     /*
254      * <q>IllegalArgumentException - If the new position is negative</q>
255      */
256     @Test
257     public void testThrowsIllegalArgumentExceptionWhenTruncatingToANegativeSize() throws Exception {
258         try (SeekableByteChannel c = new SeekableInMemoryByteChannel()) {
259             assertThrows(IllegalArgumentException.class, () -> c.truncate(-1));
260         }
261     }
262 
263     /*
264      * <q>IOException - If the new position is negative</q>
265      */
266     @Test
267     public void testThrowsIOExceptionWhenPositionIsSetToANegativeValue() throws Exception {
268         try (SeekableByteChannel c = new SeekableInMemoryByteChannel()) {
269             assertThrows(IOException.class, () -> c.position(-1));
270         }
271     }
272 
273     /*
274      * <q> In either case, if the current position is greater than the given size then it is set to that size.</q>
275      */
276     @Test
277     public void testTruncateDoesntChangeSmallPosition() throws Exception {
278         try (SeekableByteChannel c = new SeekableInMemoryByteChannel(testData)) {
279             c.position(1);
280             c.truncate(testData.length - 1);
281             assertEquals(testData.length - 1, c.size());
282             assertEquals(1, c.position());
283         }
284     }
285 
286     /*
287      * <q> In either case, if the current position is greater than the given size then it is set to that size.</q>
288      */
289     @Test
290     public void testTruncateMovesPositionWhenNewSizeIsBiggerThanSizeAndPositionIsEvenBigger() throws Exception {
291         try (SeekableByteChannel c = new SeekableInMemoryByteChannel(testData)) {
292             c.position(2 * testData.length);
293             c.truncate(testData.length + 1);
294             assertEquals(testData.length, c.size());
295             assertEquals(testData.length + 1, c.position());
296         }
297     }
298 
299     // https://docs.oracle.com/javase/8/docs/api/java/nio/channels/SeekableByteChannel.html#truncate(long)
300 
301     /*
302      * <q> In either case, if the current position is greater than the given size then it is set to that size.</q>
303      */
304     @Test
305     public void testTruncateMovesPositionWhenNotResizingButPositionBiggerThanSize() throws Exception {
306         try (SeekableByteChannel c = new SeekableInMemoryByteChannel(testData)) {
307             c.position(2 * testData.length);
308             c.truncate(testData.length);
309             assertEquals(testData.length, c.size());
310             assertEquals(testData.length, c.position());
311         }
312     }
313 
314     /*
315      * <q> In either case, if the current position is greater than the given size then it is set to that size.</q>
316      */
317     @Test
318     public void testTruncateMovesPositionWhenShrinkingBeyondPosition() throws Exception {
319         try (SeekableByteChannel c = new SeekableInMemoryByteChannel(testData)) {
320             c.position(4);
321             c.truncate(3);
322             assertEquals(3, c.size());
323             assertEquals(3, c.position());
324         }
325     }
326 
327     /*
328      * <q>If the given size is greater than or equal to the current size then the entity is not modified.</q>
329      */
330     @Test
331     public void testTruncateToBiggerSizeDoesntChangeAnything() throws Exception {
332         try (SeekableByteChannel c = new SeekableInMemoryByteChannel(testData)) {
333             assertEquals(testData.length, c.size());
334             c.truncate(testData.length + 1);
335             assertEquals(testData.length, c.size());
336             final ByteBuffer readBuffer = ByteBuffer.allocate(testData.length);
337             assertEquals(testData.length, c.read(readBuffer));
338             assertArrayEquals(testData, Arrays.copyOf(readBuffer.array(), testData.length));
339         }
340     }
341 
342     /*
343      * <q>If the given size is greater than or equal to the current size then the entity is not modified.</q>
344      */
345     @Test
346     public void testTruncateToCurrentSizeDoesntChangeAnything() throws Exception {
347         try (SeekableByteChannel c = new SeekableInMemoryByteChannel(testData)) {
348             assertEquals(testData.length, c.size());
349             c.truncate(testData.length);
350             assertEquals(testData.length, c.size());
351             final ByteBuffer readBuffer = ByteBuffer.allocate(testData.length);
352             assertEquals(testData.length, c.read(readBuffer));
353             assertArrayEquals(testData, Arrays.copyOf(readBuffer.array(), testData.length));
354         }
355     }
356 
357     /*
358      * <q>ClosedChannelException - If this channel is closed</q>
359      */
360     @Test
361     @Disabled("we deliberately violate the spec")
362     public void throwsClosedChannelExceptionWhenPositionIsReadOnClosedChannel() throws Exception {
363         try (SeekableByteChannel c = new SeekableInMemoryByteChannel()) {
364             c.close();
365             c.position();
366         }
367     }
368 
369     /*
370      * <q>ClosedChannelException - If this channel is closed</q>
371      */
372     @Test
373     @Disabled("we deliberately violate the spec")
374     public void throwsClosedChannelExceptionWhenSizeIsReadOnClosedChannel() throws Exception {
375         try (SeekableByteChannel c = new SeekableInMemoryByteChannel()) {
376             c.close();
377             c.size();
378         }
379     }
380 
381     /*
382      * <q>ClosedChannelException - If this channel is closed</q>
383      */
384     @Test
385     @Disabled("we deliberately violate the spec")
386     public void throwsClosedChannelExceptionWhenTruncateIsCalledOnClosedChannel() throws Exception {
387         try (SeekableByteChannel c = new SeekableInMemoryByteChannel()) {
388             c.close();
389             c.truncate(0);
390         }
391     }
392 
393     /*
394      * <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
395      * 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
396      * newly-written bytes are unspecified.</q>
397      */
398     public void writingToAPositionAfterEndGrowsChannel() throws Exception {
399         try (SeekableByteChannel c = new SeekableInMemoryByteChannel()) {
400             c.position(2);
401             assertEquals(2, c.position());
402             final ByteBuffer inData = ByteBuffer.wrap(testData);
403             assertEquals(testData.length, c.write(inData));
404             assertEquals(testData.length + 2, c.size());
405 
406             c.position(2);
407             final ByteBuffer readBuffer = ByteBuffer.allocate(testData.length);
408             c.read(readBuffer);
409             assertArrayEquals(testData, Arrays.copyOf(readBuffer.array(), testData.length));
410         }
411     }
412 
413 }