1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package org.apache.commons.compress.utils;
21
22 import static java.nio.charset.StandardCharsets.UTF_8;
23 import static org.junit.jupiter.api.Assertions.assertArrayEquals;
24 import static org.junit.jupiter.api.Assertions.assertEquals;
25 import static org.junit.jupiter.api.Assertions.assertFalse;
26 import static org.junit.jupiter.api.Assertions.assertSame;
27 import static org.junit.jupiter.api.Assertions.assertThrows;
28 import static org.junit.jupiter.api.Assertions.assertTrue;
29
30 import java.io.File;
31 import java.io.IOException;
32 import java.nio.ByteBuffer;
33 import java.nio.channels.ClosedChannelException;
34 import java.nio.channels.NonWritableChannelException;
35 import java.nio.channels.SeekableByteChannel;
36 import java.util.ArrayList;
37 import java.util.Arrays;
38 import java.util.List;
39
40 import org.junit.jupiter.api.Disabled;
41 import org.junit.jupiter.api.Test;
42
43
44
45
46
47
48
49
50
51 class MultiReadOnlySeekableByteChannelTest {
52
53 private static final class ThrowingSeekableByteChannel implements SeekableByteChannel {
54 private boolean closed;
55
56 @Override
57 public void close() throws IOException {
58 closed = true;
59 throw new IOException("foo");
60 }
61
62 @Override
63 public boolean isOpen() {
64 return !closed;
65 }
66
67 @Override
68 public long position() {
69 return 0;
70 }
71
72 @Override
73 public SeekableByteChannel position(final long newPosition) {
74 return this;
75 }
76
77 @Override
78 public int read(final ByteBuffer dst) throws IOException {
79 return -1;
80 }
81
82 @Override
83 public long size() throws IOException {
84 return 0;
85 }
86
87 @Override
88 public SeekableByteChannel truncate(final long size) {
89 return this;
90 }
91
92 @Override
93 public int write(final ByteBuffer src) throws IOException {
94 return 0;
95 }
96 }
97
98 private void check(final byte[] expected) throws IOException {
99 for (int channelSize = 1; channelSize <= expected.length; channelSize++) {
100
101 try (SeekableByteChannel single = makeSingle(expected)) {
102 check(expected, single);
103 }
104
105 try (SeekableByteChannel multi = makeMulti(grouped(expected, channelSize))) {
106 check(expected, multi);
107 }
108 }
109 }
110
111 private void check(final byte[] expected, final SeekableByteChannel channel) throws IOException {
112 for (int readBufferSize = 1; readBufferSize <= expected.length + 5; readBufferSize++) {
113 check(expected, channel, readBufferSize);
114 }
115 }
116
117 private void check(final byte[] expected, final SeekableByteChannel channel, final int readBufferSize) throws IOException {
118 assertTrue(channel.isOpen(), "readBufferSize " + readBufferSize);
119 assertEquals(expected.length, channel.size(), "readBufferSize " + readBufferSize);
120 channel.position(0);
121 assertEquals(0, channel.position(), "readBufferSize " + readBufferSize);
122 assertEquals(0, channel.read(ByteBuffer.allocate(0)), "readBufferSize " + readBufferSize);
123
124
125 final ByteBuffer resultBuffer = ByteBuffer.allocate(expected.length + 100);
126
127
128 final ByteBuffer buf = ByteBuffer.allocate(readBufferSize);
129
130 int bytesRead = channel.read(buf);
131
132 while (bytesRead != -1) {
133 final int remaining = buf.remaining();
134
135 buf.flip();
136 resultBuffer.put(buf);
137 buf.clear();
138 bytesRead = channel.read(buf);
139
140
141
142 if (resultBuffer.position() < expected.length) {
143 assertEquals(0, remaining, "readBufferSize " + readBufferSize);
144 }
145
146 if (bytesRead == -1) {
147 assertEquals(0, buf.position(), "readBufferSize " + readBufferSize);
148 } else {
149 assertEquals(bytesRead, buf.position(), "readBufferSize " + readBufferSize);
150 }
151 }
152
153 resultBuffer.flip();
154 final byte[] arr = new byte[resultBuffer.remaining()];
155 resultBuffer.get(arr);
156 assertArrayEquals(expected, arr, "readBufferSize " + readBufferSize);
157 }
158
159 private void checkEmpty(final SeekableByteChannel channel) throws IOException {
160 final ByteBuffer buf = ByteBuffer.allocate(10);
161
162 assertTrue(channel.isOpen());
163 assertEquals(0, channel.size());
164 assertEquals(0, channel.position());
165 assertEquals(-1, channel.read(buf));
166
167 channel.position(5);
168 assertEquals(-1, channel.read(buf));
169
170 channel.close();
171 assertFalse(channel.isOpen());
172
173 assertThrows(ClosedChannelException.class, () -> channel.read(buf), "expected a ClosedChannelException");
174 assertThrows(ClosedChannelException.class, () -> channel.position(100), "expected a ClosedChannelException");
175 }
176
177 private byte[][] grouped(final byte[] input, final int chunkSize) {
178 final List<byte[]> groups = new ArrayList<>();
179 int idx = 0;
180 for (; idx + chunkSize <= input.length; idx += chunkSize) {
181 groups.add(Arrays.copyOfRange(input, idx, idx + chunkSize));
182 }
183 if (idx < input.length) {
184 groups.add(Arrays.copyOfRange(input, idx, input.length));
185 }
186 return groups.toArray(new byte[0][]);
187 }
188
189 private SeekableByteChannel makeEmpty() {
190 return makeSingle(ByteUtils.EMPTY_BYTE_ARRAY);
191 }
192
193 private SeekableByteChannel makeMulti(final byte[][] arr) {
194 final SeekableByteChannel[] s = new SeekableByteChannel[arr.length];
195 for (int i = 0; i < s.length; i++) {
196 s[i] = makeSingle(arr[i]);
197 }
198 return MultiReadOnlySeekableByteChannel.forSeekableByteChannels(s);
199 }
200
201 private SeekableByteChannel makeSingle(final byte[] arr) {
202 return new SeekableInMemoryByteChannel(arr);
203 }
204
205 @Test
206 void testCantPositionToANegativePosition() throws IOException {
207 try (SeekableByteChannel s = MultiReadOnlySeekableByteChannel.forSeekableByteChannels(makeEmpty(), makeEmpty())) {
208 assertThrows(IllegalArgumentException.class, () -> s.position(-1));
209 }
210 }
211
212 @Test
213 void testCantTruncate() throws IOException {
214 try (SeekableByteChannel s = MultiReadOnlySeekableByteChannel.forSeekableByteChannels(makeEmpty(), makeEmpty())) {
215 assertThrows(NonWritableChannelException.class, () -> s.truncate(1));
216 }
217 }
218
219 @Test
220 void testCantWrite() throws IOException {
221 try (SeekableByteChannel s = MultiReadOnlySeekableByteChannel.forSeekableByteChannels(makeEmpty(), makeEmpty())) {
222 assertThrows(NonWritableChannelException.class, () -> s.write(ByteBuffer.allocate(10)));
223 }
224 }
225
226 private SeekableByteChannel testChannel() {
227 return MultiReadOnlySeekableByteChannel.forSeekableByteChannels(makeEmpty(), makeEmpty());
228 }
229
230 @Test
231 void testCheckForSingleByte() throws IOException {
232 check(new byte[] { 0 });
233 }
234
235 @Test
236 void testCheckForString() throws IOException {
237 check("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".getBytes(UTF_8));
238 }
239
240
241
242
243 @Test
244 void testCloseIsIdempotent() throws Exception {
245 try (SeekableByteChannel c = testChannel()) {
246 c.close();
247 assertFalse(c.isOpen());
248 c.close();
249 assertFalse(c.isOpen());
250 }
251 }
252
253 @Test
254 void testClosesAllAndThrowsExceptionIfCloseThrows() {
255 final SeekableByteChannel[] ts = new ThrowingSeekableByteChannel[] { new ThrowingSeekableByteChannel(), new ThrowingSeekableByteChannel() };
256 final SeekableByteChannel s = MultiReadOnlySeekableByteChannel.forSeekableByteChannels(ts);
257 assertThrows(IOException.class, s::close, "IOException expected");
258 assertFalse(ts[0].isOpen());
259 assertFalse(ts[1].isOpen());
260 }
261
262 @Test
263 void testConstructorThrowsOnNullArg() {
264 assertThrows(NullPointerException.class, () -> new MultiReadOnlySeekableByteChannel(null));
265 }
266
267 @Test
268 void testForFilesThrowsOnNullArg() {
269 assertThrows(NullPointerException.class, () -> MultiReadOnlySeekableByteChannel.forFiles((File[]) null));
270 }
271
272 @Test
273 void testForSeekableByteChannelsReturnsIdentityForSingleElement() throws IOException {
274 try (SeekableByteChannel e = makeEmpty();
275 SeekableByteChannel m = MultiReadOnlySeekableByteChannel.forSeekableByteChannels(e)) {
276 assertSame(e, m);
277 }
278 }
279
280 @Test
281 void testForSeekableByteChannelsThrowsOnNullArg() {
282 assertThrows(NullPointerException.class, () -> MultiReadOnlySeekableByteChannel.forSeekableByteChannels((SeekableByteChannel[]) null));
283 }
284
285
286
287
288
289 @Test
290 void testReadingFromAPositionAfterEndReturnsEOF() throws Exception {
291 try (SeekableByteChannel c = testChannel()) {
292 c.position(2);
293 assertEquals(2, c.position());
294 final ByteBuffer readBuffer = ByteBuffer.allocate(5);
295 assertEquals(-1, c.read(readBuffer));
296 }
297 }
298
299
300
301 @Test
302 void testReferenceBehaviorForEmptyChannel() throws IOException {
303 checkEmpty(makeEmpty());
304 }
305
306
307
308
309
310
311 @Test
312 void testThrowsClosedChannelExceptionWhenPositionIsSetOnClosedChannel() throws Exception {
313 try (SeekableByteChannel c = testChannel()) {
314 c.close();
315 assertThrows(ClosedChannelException.class, () -> c.position(0));
316 }
317 }
318
319
320
321
322
323
324 @Test
325 void testThrowsClosedChannelExceptionWhenSizeIsReadOnClosedChannel() throws Exception {
326 try (SeekableByteChannel c = testChannel()) {
327 c.close();
328 assertThrows(ClosedChannelException.class, () -> c.size());
329 }
330 }
331
332
333
334
335
336
337 @Test
338 void testThrowsIOExceptionWhenPositionIsSetToANegativeValue() throws Exception {
339 try (SeekableByteChannel c = testChannel()) {
340 assertThrows(IllegalArgumentException.class, () -> c.position(-1));
341 }
342 }
343
344
345
346 @Test
347 void testTwoEmptyChannelsConcatenateAsEmptyChannel() throws IOException {
348 try (SeekableByteChannel channel = MultiReadOnlySeekableByteChannel.forSeekableByteChannels(makeEmpty(), makeEmpty())) {
349 checkEmpty(channel);
350 }
351 }
352
353 @Test
354 void testVerifyGrouped() {
355 assertArrayEquals(new byte[][] { new byte[] { 1, 2, 3, }, new byte[] { 4, 5, 6, }, new byte[] { 7, }, },
356 grouped(new byte[] { 1, 2, 3, 4, 5, 6, 7 }, 3));
357 assertArrayEquals(new byte[][] { new byte[] { 1, 2, 3, }, new byte[] { 4, 5, 6, }, }, grouped(new byte[] { 1, 2, 3, 4, 5, 6 }, 3));
358 assertArrayEquals(new byte[][] { new byte[] { 1, 2, 3, }, new byte[] { 4, 5, }, }, grouped(new byte[] { 1, 2, 3, 4, 5, }, 3));
359 }
360
361
362
363
364 @Test
365 @Disabled("we deliberately violate the spec")
366 public void throwsClosedChannelExceptionWhenPositionIsReadOnClosedChannel() throws Exception {
367 try (SeekableByteChannel c = testChannel()) {
368 c.close();
369 c.position();
370 }
371 }
372
373 }