1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.commons.io.channels;
19
20 import static java.nio.charset.StandardCharsets.US_ASCII;
21 import static org.junit.jupiter.api.Assertions.assertFalse;
22 import static org.junit.jupiter.api.Assertions.assertNotEquals;
23 import static org.junit.jupiter.api.Assertions.assertTrue;
24
25 import java.io.ByteArrayInputStream;
26 import java.io.File;
27 import java.io.FileInputStream;
28 import java.io.IOException;
29 import java.io.InputStream;
30 import java.io.SequenceInputStream;
31 import java.nio.channels.Channels;
32 import java.nio.channels.FileChannel;
33 import java.nio.channels.SeekableByteChannel;
34 import java.nio.file.Files;
35 import java.nio.file.Path;
36
37 import org.apache.commons.io.FileUtils;
38 import org.apache.commons.io.IOUtils;
39 import org.apache.commons.io.file.AbstractTempDirTest;
40 import org.apache.commons.lang3.ArrayUtils;
41 import org.apache.commons.lang3.StringUtils;
42 import org.junit.jupiter.params.ParameterizedTest;
43 import org.junit.jupiter.params.provider.ValueSource;
44 import org.junitpioneer.jupiter.cartesian.CartesianTest;
45 import org.junitpioneer.jupiter.cartesian.CartesianTest.Values;
46
47
48
49
50 class FileChannelsTest extends AbstractTempDirTest {
51
52 enum FileChannelType {
53 STOCK, PROXY, NON_BLOCKING, FIXED_READ_SIZE
54 }
55
56 private static final int LARGE_FILE_SIZE = Integer.getInteger(FileChannelsTest.class.getSimpleName(), 100_000);
57
58 private static final int SMALL_BUFFER_SIZE = 1024;
59 private static final String CONTENT = StringUtils.repeat("x", SMALL_BUFFER_SIZE);
60
61 @SuppressWarnings("resource")
62 private static FileChannel getChannel(final FileInputStream inNotEmpty, final FileChannelType fileChannelType, final int readSize) throws IOException {
63 return wrap(inNotEmpty.getChannel(), fileChannelType, readSize);
64 }
65
66 private static int half(final int bufferSize) {
67 return Math.max(1, bufferSize / 2);
68 }
69
70 private static boolean isEmpty(final File empty) {
71 return empty.length() == 0;
72 }
73
74 @SuppressWarnings("resource")
75 private static FileChannel open(final Path path, final FileChannelType fileChannelType, final int readSize) throws IOException {
76 return wrap(FileChannel.open(path), fileChannelType, readSize);
77 }
78
79 private static FileChannel reset(final FileChannel fc) throws IOException {
80 return fc.position(0);
81 }
82
83 private static byte reverse(final byte b) {
84 return (byte) (~b & 0xff);
85 }
86
87 private static FileChannel wrap(final FileChannel fc, final FileChannelType fileChannelType, final int readSize) throws IOException {
88 switch (fileChannelType) {
89 case NON_BLOCKING:
90 return new NonBlockingFileChannelProxy(fc);
91 case STOCK:
92 return fc;
93 case PROXY:
94 return new FileChannelProxy(fc);
95 case FIXED_READ_SIZE:
96 return new FixedReadSizeFileChannelProxy(fc, readSize);
97 default:
98 throw new UnsupportedOperationException("Unexpected FileChannelType " + fileChannelType);
99 }
100 }
101
102 private boolean contentEquals(final InputStream in1, final InputStream in2, final int bufferCapacity) throws IOException {
103 return FileChannels.contentEquals(Channels.newChannel(in1), Channels.newChannel(in2), bufferCapacity);
104 }
105
106 private void testContentEquals(final String content1, final String content2, final int bufferSize, final FileChannelType fileChannelType)
107 throws IOException {
108 assertTrue(FileChannels.contentEquals(null, null, bufferSize));
109
110
111 final File file1 = new File(tempDirFile, "test1.txt");
112 final File file2 = new File(tempDirFile, "test2.txt");
113 FileUtils.writeStringToFile(file1, content1, US_ASCII);
114 FileUtils.writeStringToFile(file2, content2, US_ASCII);
115
116 assertNotEquals(FileUtils.checksumCRC32(file1), FileUtils.checksumCRC32(file2));
117
118 try (FileInputStream in1 = new FileInputStream(file1);
119 FileInputStream in2 = new FileInputStream(file2);
120 FileChannel channel1 = getChannel(in1, fileChannelType, bufferSize);
121 FileChannel channel2 = getChannel(in2, fileChannelType, half(bufferSize))) {
122 assertFalse(FileChannels.contentEquals(channel1, channel2, bufferSize));
123 }
124
125 try (FileInputStream in1 = new FileInputStream(file1);
126 FileInputStream in2 = new FileInputStream(file2);
127 FileChannel channel1 = getChannel(in1, fileChannelType, bufferSize);
128 FileChannel channel2 = getChannel(in2, fileChannelType, half(bufferSize))) {
129 assertTrue(FileChannels.contentEquals(channel1, channel1, bufferSize));
130 assertTrue(FileChannels.contentEquals(channel2, channel2, bufferSize));
131 }
132 }
133
134 @CartesianTest()
135 void testContentEqualsDifferentPostfix(
136 @Values(ints = { 1, 2, IOUtils.DEFAULT_BUFFER_SIZE / 10, IOUtils.DEFAULT_BUFFER_SIZE, IOUtils.DEFAULT_BUFFER_SIZE * 10 }) final int bufferSize,
137 @CartesianTest.Enum final FileChannelType fileChannelType) throws IOException {
138 testContentEquals(CONTENT + "ABC", CONTENT + "XYZ", bufferSize, fileChannelType);
139 }
140
141 @CartesianTest()
142 void testContentEqualsDifferentPrefix(
143 @Values(ints = { 1, 2, IOUtils.DEFAULT_BUFFER_SIZE / 10, IOUtils.DEFAULT_BUFFER_SIZE, IOUtils.DEFAULT_BUFFER_SIZE * 10 }) final int bufferSize,
144 @CartesianTest.Enum final FileChannelType fileChannelType) throws IOException {
145 testContentEquals("ABC" + CONTENT, "XYZ" + CONTENT, bufferSize, fileChannelType);
146 }
147
148 @CartesianTest()
149 void testContentEqualsEmpty(
150 @Values(ints = { 1, 2, IOUtils.DEFAULT_BUFFER_SIZE / 10, IOUtils.DEFAULT_BUFFER_SIZE, IOUtils.DEFAULT_BUFFER_SIZE * 10 }) final int bufferSize,
151 @CartesianTest.Enum final FileChannelType fileChannelType) throws IOException {
152 assertTrue(FileChannels.contentEquals(null, null, bufferSize));
153
154 final File empty = new File(tempDirFile, "empty.txt");
155 final File notEmpty = new File(tempDirFile, "not-empty.txt");
156 FileUtils.writeStringToFile(empty, StringUtils.EMPTY, US_ASCII);
157 FileUtils.writeStringToFile(notEmpty, "X", US_ASCII);
158 assertTrue(isEmpty(empty));
159 assertFalse(isEmpty(notEmpty));
160
161 assertNotEquals(FileUtils.checksumCRC32(empty), FileUtils.checksumCRC32(notEmpty));
162 try (FileInputStream inEmpty = new FileInputStream(empty);
163 FileInputStream inNotEmpty = new FileInputStream(notEmpty);
164 FileChannel channelEmpty = getChannel(inEmpty, fileChannelType, bufferSize);
165 FileChannel channelNotEmpty = getChannel(inNotEmpty, fileChannelType, half(bufferSize))) {
166 assertFalse(FileChannels.contentEquals(channelEmpty, channelNotEmpty, bufferSize));
167 assertFalse(FileChannels.contentEquals(null, channelNotEmpty, bufferSize));
168 assertFalse(FileChannels.contentEquals(channelNotEmpty, null, bufferSize));
169 assertTrue(FileChannels.contentEquals(channelEmpty, channelEmpty, bufferSize));
170 assertTrue(FileChannels.contentEquals(null, channelEmpty, bufferSize));
171 assertTrue(FileChannels.contentEquals(channelEmpty, null, bufferSize));
172 assertTrue(FileChannels.contentEquals(channelNotEmpty, channelNotEmpty, bufferSize));
173 }
174 }
175
176 @CartesianTest
177 void testContentEqualsFileChannel(
178 @Values(ints = { 1, 2, IOUtils.DEFAULT_BUFFER_SIZE / 10, IOUtils.DEFAULT_BUFFER_SIZE, IOUtils.DEFAULT_BUFFER_SIZE * 10 }) final int bufferSize)
179 throws IOException {
180 final FileChannelType fileChannelType = FileChannelType.STOCK;
181 final Path bigFile1 = Files.createTempFile(getClass().getSimpleName(), "-1.bin");
182 final Path bigFile2 = Files.createTempFile(getClass().getSimpleName(), "-2.bin");
183 final Path bigFile3 = Files.createTempFile(getClass().getSimpleName(), "-3.bin");
184 try {
185
186 final int newLength = LARGE_FILE_SIZE;
187 final byte[] bytes1 = new byte[newLength];
188 final byte[] bytes2 = new byte[newLength];
189
190 ArrayUtils.shuffle(bytes1);
191 bytes1[0] = 1;
192 ArrayUtils.shuffle(bytes2);
193 bytes2[0] = 2;
194 Files.write(bigFile1, bytes1);
195 Files.write(bigFile2, bytes2);
196 try (FileChannel fc1 = open(bigFile1, fileChannelType, bufferSize); FileChannel fc2 = open(bigFile2, fileChannelType, half(bufferSize))) {
197 assertFalse(FileChannels.contentEquals(fc1, fc2, bufferSize));
198 assertFalse(FileChannels.contentEquals(reset(fc2), reset(fc1), bufferSize));
199 assertTrue(FileChannels.contentEquals(reset(fc1), reset(fc1), bufferSize));
200 }
201
202 byte[] bytes3 = bytes1.clone();
203 final int last = bytes3.length - 1;
204 bytes3[last] = reverse(bytes3[last]);
205 Files.write(bigFile3, bytes3);
206 try (FileChannel fc1 = open(bigFile1, fileChannelType, bufferSize); FileChannel fc3 = open(bigFile3, fileChannelType, half(bufferSize))) {
207 assertFalse(FileChannels.contentEquals(fc1, fc3, bufferSize));
208 assertFalse(FileChannels.contentEquals(reset(fc3), reset(fc1), bufferSize));
209
210 fc1.position(last);
211 fc3.position(last);
212 assertFalse(FileChannels.contentEquals(fc1, fc3, bufferSize));
213 }
214
215 bytes3 = bytes1.clone();
216 Files.write(bigFile3, bytes3);
217 try (FileChannel fc1 = open(bigFile1, fileChannelType, bufferSize); FileChannel fc3 = open(bigFile3, fileChannelType, half(bufferSize))) {
218
219 fc1.position(last);
220 fc3.position(last);
221 assertTrue(FileChannels.contentEquals(fc1, fc3, bufferSize));
222 }
223
224 bytes3 = bytes1.clone();
225 final int middle = bytes3.length / 2;
226 bytes3[middle] = reverse(bytes3[middle]);
227 Files.write(bigFile3, bytes3);
228 try (FileChannel fc1 = open(bigFile1, fileChannelType, bufferSize); FileChannel fc3 = open(bigFile3, fileChannelType, half(bufferSize))) {
229 assertFalse(FileChannels.contentEquals(fc1, fc3, bufferSize));
230 assertFalse(FileChannels.contentEquals(reset(fc3), reset(fc1), bufferSize));
231 }
232 } finally {
233
234 Files.deleteIfExists(bigFile1);
235 Files.deleteIfExists(bigFile2);
236 Files.deleteIfExists(bigFile3);
237 }
238 }
239
240 @CartesianTest()
241 void testContentEqualsSeekableByteChannel(
242 @Values(ints = { 1, 2, IOUtils.DEFAULT_BUFFER_SIZE / 10, IOUtils.DEFAULT_BUFFER_SIZE, IOUtils.DEFAULT_BUFFER_SIZE * 10 }) final int bufferSize,
243 @CartesianTest.Enum final FileChannelType fileChannelType1, @CartesianTest.Enum final FileChannelType fileChannelType2) throws IOException {
244 final Path bigFile1 = Files.createTempFile(getClass().getSimpleName(), "-1.bin");
245 final Path bigFile2 = Files.createTempFile(getClass().getSimpleName(), "-2.bin");
246 final Path bigFile3 = Files.createTempFile(getClass().getSimpleName(), "-3.bin");
247 try {
248
249 final int newLength = LARGE_FILE_SIZE;
250 final byte[] bytes1 = new byte[newLength];
251 final byte[] bytes2 = new byte[newLength];
252
253 ArrayUtils.shuffle(bytes1);
254 bytes1[0] = 1;
255 ArrayUtils.shuffle(bytes2);
256 bytes2[0] = 2;
257 Files.write(bigFile1, bytes1);
258 Files.write(bigFile2, bytes2);
259 try (FileChannel fc1 = open(bigFile1, fileChannelType1, bufferSize); FileChannel fc2 = open(bigFile2, fileChannelType2, half(bufferSize))) {
260 assertFalse(FileChannels.contentEquals((SeekableByteChannel) fc1, fc2, bufferSize));
261 assertFalse(FileChannels.contentEquals((SeekableByteChannel) reset(fc2), reset(fc1), bufferSize));
262 assertTrue(FileChannels.contentEquals((SeekableByteChannel) reset(fc1), reset(fc1), bufferSize));
263 }
264
265 byte[] bytes3 = bytes1.clone();
266 final int last = bytes3.length - 1;
267 bytes3[last] = reverse(bytes3[last]);
268 Files.write(bigFile3, bytes3);
269 try (FileChannel fc1 = open(bigFile1, fileChannelType1, bufferSize); FileChannel fc3 = open(bigFile3, fileChannelType2, half(bufferSize))) {
270 assertFalse(FileChannels.contentEquals((SeekableByteChannel) fc1, fc3, bufferSize));
271 assertFalse(FileChannels.contentEquals((SeekableByteChannel) reset(fc3), reset(fc1), bufferSize));
272
273 fc1.position(last);
274 fc3.position(last);
275 assertFalse(FileChannels.contentEquals((SeekableByteChannel) fc1, fc3, bufferSize));
276 }
277
278 bytes3 = bytes1.clone();
279 Files.write(bigFile3, bytes3);
280 try (FileChannel fc1 = open(bigFile1, fileChannelType1, bufferSize); FileChannel fc3 = open(bigFile3, fileChannelType2, half(bufferSize))) {
281
282 fc1.position(last);
283 fc3.position(last);
284 assertTrue(FileChannels.contentEquals((SeekableByteChannel) fc1, fc3, bufferSize));
285 }
286
287 bytes3 = bytes1.clone();
288 final int middle = bytes3.length / 2;
289 bytes3[middle] = reverse(bytes3[middle]);
290 Files.write(bigFile3, bytes3);
291 try (FileChannel fc1 = open(bigFile1, fileChannelType1, bufferSize); FileChannel fc3 = open(bigFile3, fileChannelType2, half(bufferSize))) {
292 assertFalse(FileChannels.contentEquals((SeekableByteChannel) fc1, fc3, bufferSize));
293 assertFalse(FileChannels.contentEquals((SeekableByteChannel) reset(fc3), reset(fc1), bufferSize));
294 }
295 } finally {
296
297 Files.deleteIfExists(bigFile1);
298 Files.deleteIfExists(bigFile2);
299 Files.deleteIfExists(bigFile3);
300 }
301 }
302
303 @ParameterizedTest
304 @ValueSource(ints = { 1, 2, 4, 8, 16, 1024, 4096, 8192 })
305 void testContentEqualsSequenceInputStream(final int bufferCapacity) throws Exception {
306
307
308 assertFalse(contentEquals(
309 new ByteArrayInputStream("ab".getBytes()),
310 new SequenceInputStream(
311 new ByteArrayInputStream("a".getBytes()),
312 new ByteArrayInputStream("b-".getBytes())), bufferCapacity));
313 assertFalse(contentEquals(
314 new ByteArrayInputStream("ab".getBytes()),
315 new SequenceInputStream(
316 new ByteArrayInputStream("a-".getBytes()),
317 new ByteArrayInputStream("b".getBytes())), bufferCapacity));
318 assertFalse(contentEquals(
319 new ByteArrayInputStream("ab-".getBytes()),
320 new SequenceInputStream(
321 new ByteArrayInputStream("a".getBytes()),
322 new ByteArrayInputStream("b".getBytes())), bufferCapacity));
323 assertFalse(contentEquals(
324 new ByteArrayInputStream("".getBytes()),
325 new SequenceInputStream(
326 new ByteArrayInputStream("a".getBytes()),
327 new ByteArrayInputStream("b".getBytes())), bufferCapacity));
328 assertFalse(contentEquals(
329 new ByteArrayInputStream("".getBytes()),
330 new SequenceInputStream(
331 new ByteArrayInputStream("".getBytes()),
332 new ByteArrayInputStream("b".getBytes())), bufferCapacity));
333 assertFalse(contentEquals(
334 new ByteArrayInputStream("ab".getBytes()),
335 new SequenceInputStream(
336 new ByteArrayInputStream("".getBytes()),
337 new ByteArrayInputStream("".getBytes())), bufferCapacity));
338
339 assertTrue(contentEquals(
340 new ByteArrayInputStream("".getBytes()),
341 new SequenceInputStream(
342 new ByteArrayInputStream("".getBytes()),
343 new ByteArrayInputStream("".getBytes())), bufferCapacity));
344 assertTrue(contentEquals(
345 new ByteArrayInputStream("ab".getBytes()),
346 new SequenceInputStream(
347 new ByteArrayInputStream("a".getBytes()),
348 new ByteArrayInputStream("b".getBytes())), bufferCapacity));
349 assertTrue(contentEquals(
350 new ByteArrayInputStream("ab".getBytes()),
351 new SequenceInputStream(
352 new ByteArrayInputStream("ab".getBytes()),
353 new ByteArrayInputStream("".getBytes())), bufferCapacity));
354 assertTrue(contentEquals(
355 new ByteArrayInputStream("ab".getBytes()),
356 new SequenceInputStream(
357 new ByteArrayInputStream("".getBytes()),
358 new ByteArrayInputStream("ab".getBytes())), bufferCapacity));
359
360 }
361 }