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