1 /*
2  * Copyright (c) 2002, 2017, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.
8  *
9  * This code is distributed in the hope that it will be useful, but WITHOUT
10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12  * version 2 for more details (a copy is included in the LICENSE file that
13  * accompanied this code).
14  *
15  * You should have received a copy of the GNU General Public License version
16  * 2 along with this work; if not, write to the Free Software Foundation,
17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18  *
19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20  * or visit www.oracle.com if you need additional information or have any
21  * questions.
22  */
23 
24 /* @test
25  * @summary Comprehensive test for FileChannel.transfer{From,To}
26  * @bug 4708120
27  * @author Mark Reinhold
28  * @run main/timeout=300 Transfers
29  */
30 
31 import java.io.*;
32 import java.nio.*;
33 import java.nio.channels.*;
34 import java.util.*;
35 
36 
37 public class Transfers {
38 
39     static PrintStream out = System.out;
40 
41     private static class Failure
42         extends RuntimeException
43     {
44 
Failure(Exception x)45         Failure(Exception x) {
46             super(x);
47         }
48 
Failure(String s)49         Failure(String s) {
50             super(s);
51         }
52 
53     }
54 
55 
56     // -- Writing and reading random bytes --
57 
writeBytes(byte[] ba, FileChannel fc, int off, int len)58     private static void writeBytes(byte[] ba, FileChannel fc,
59                                    int off, int len)
60         throws IOException
61     {
62         fc.position(off);
63         if (fc.write(ByteBuffer.wrap(ba, 0, len)) != len)
64             throw new IOException("Incomplete write");
65         fc.position(0);
66     }
67 
writeRandomBytes(long seed, FileChannel fc, int off, int len)68     private static void writeRandomBytes(long seed,
69                                          FileChannel fc, int off, int len)
70         throws IOException
71     {
72         Random r = new Random(seed);
73         byte[] ba = new byte[len];
74         r.nextBytes(ba);
75         writeBytes(ba, fc, off, len);
76     }
77 
writeZeroBytes(FileChannel fc, int off, int len)78     private static void writeZeroBytes(FileChannel fc, int off, int len)
79         throws IOException
80     {
81         byte[] ba = new byte[len];
82         writeBytes(ba, fc, off, len);
83     }
84 
checkBytes(FileChannel fc, int off, int len, byte[] bytes)85     private static void checkBytes(FileChannel fc, int off, int len,
86                                    byte[] bytes)
87         throws IOException
88     {
89         ByteBuffer bb = ByteBuffer.allocate(len);
90         fc.position(off);
91         if (fc.read(bb) != len)
92             throw new IOException("Incomplete read");
93         bb.flip();
94         ByteBuffer bab = ByteBuffer.wrap(bytes, 0, len);
95         if (!bb.equals(bab))
96             throw new Failure("Wrong data written");
97     }
98 
checkRandomBytes(FileChannel fc, int off, int len, long seed)99     private static void checkRandomBytes(FileChannel fc, int off, int len,
100                                          long seed)
101         throws IOException
102     {
103         byte[] ba = new byte[len];
104         Random r = new Random(seed);
105         r.nextBytes(ba);
106         checkBytes(fc, off, len, ba);
107     }
108 
checkZeroBytes(FileChannel fc, int off, int len)109     private static void checkZeroBytes(FileChannel fc, int off, int len)
110         throws IOException
111     {
112         byte[] ba = new byte[len];
113         checkBytes(fc, off, len, ba);
114     }
115 
116     // For debugging
117     //
dump(FileChannel fc)118     private static void dump(FileChannel fc)
119         throws IOException
120     {
121         int sz = (int)fc.size();
122         ByteBuffer bb = ByteBuffer.allocate(sz);
123         fc.position(0);
124         if (fc.read(bb) != sz)
125             throw new IOException("Incomplete read");
126         bb.flip();
127         byte prev = -1;
128         int r = 0;                      // Repeats
129         int n = 0;
130         while (bb.hasRemaining() && (n < 32)) {
131             byte b = bb.get();
132             if (b == prev) {
133                 r++;
134                 continue;
135             }
136             if (r > 0) {
137                 int c = prev & 0xff;
138                 if (c < 0x10)
139                     out.print('0');
140                 out.print(Integer.toHexString(c));
141                 if (r > 1) {
142                     out.print("[");
143                     out.print(r);
144                     out.print("]");
145                 }
146                 n++;
147             }
148             prev = b;
149             r = 1;
150         }
151         if (r > 0) {
152             int c = prev & 0xff;
153             if (c < 0x10)
154                 out.print('0');
155             out.print(Integer.toHexString(c));
156             if (r > 1) {
157                 out.print("[");
158                 out.print(r);
159                 out.print("]");
160             }
161             n++;
162         }
163         if (bb.hasRemaining())
164             out.print("...");
165         out.println();
166     }
167 
168 
169 
170     static File sourceFile;
171     static File targetFile;
172 
173     // -- Self-verifying sources and targets --
174 
175     static abstract class Source {
176 
177         protected final int size;
178         protected final long seed;
179         private final String name;
180 
Source(int size, long seed, String name)181         Source(int size, long seed, String name) {
182             this.size = size;
183             this.seed = seed;
184             this.name = name;
185         }
186 
name()187         String name() {
188             return name;
189         }
190 
channel()191         abstract ReadableByteChannel channel();
192 
verify()193         abstract void verify() throws IOException;
194 
195     }
196 
197     static class FileSource
198         extends Source
199     {
200         private final File fn;
201         private final RandomAccessFile raf;
202         private final FileChannel fc;
203 
FileSource(int size, long seed)204         FileSource(int size, long seed) throws IOException {
205             super(size, seed, "FileChannel");
206             fn = sourceFile;
207             raf = new RandomAccessFile(fn, "rw");
208             fc = raf.getChannel();
209             fc.position(0);
210             writeRandomBytes(seed, fc, 0, size);
211         }
212 
channel()213         ReadableByteChannel channel() {
214             return fc;
215         }
216 
verify()217         void verify() throws IOException {
218             if (fc.position() != size)
219                 throw new Failure("Wrong position: "
220                                   + fc.position() + " (expected " + size +
221                                   ")");
222             checkRandomBytes(fc, 0, size, seed);
223             fc.close();
224             raf.close();                // Bug in 1.4.0
225         }
226 
227     }
228 
229     static class UserSource
230         extends Source
231     {
232         private ReadableByteChannel ch;
233         private final ByteBuffer src;
234 
UserSource(int size, long seed)235         UserSource(int size, long seed) {
236             super(size, seed, "UserChannel");
237 
238             final byte[] bytes = new byte[size + 1];
239             Random r = new Random(seed);
240             r.nextBytes(bytes);
241             src = ByteBuffer.wrap(bytes);
242 
243             ch = new ReadableByteChannel() {
244                     public int read(ByteBuffer dst) {
245                         if (!src.hasRemaining())
246                             return -1;
247                         int nr = Math.min(src.remaining(), dst.remaining());
248                         ByteBuffer s = src.duplicate();
249                         s.limit(s.position() + nr);
250                         dst.put(s);
251                         src.position(src.position() + nr);
252                         return nr;
253                     }
254                     public boolean isOpen() {
255                         return true;
256                     }
257                     public void close() { }
258                 };
259         }
260 
channel()261         ReadableByteChannel channel() {
262             return ch;
263         }
264 
verify()265         void verify() {
266             if (src.remaining() != 1)
267                 throw new Failure("Source has " + src.remaining()
268                                   + " bytes remaining (expected 1)");
269         }
270 
271     }
272 
273     static abstract class Target {
274 
275         protected final int size;
276         protected final long seed;
277         private final String name;
278 
Target(int size, long seed, String name)279         Target(int size, long seed, String name) {
280             this.size = size;
281             this.seed = seed;
282             this.name = name;
283         }
284 
name()285         String name() {
286             return name;
287         }
288 
channel()289         abstract WritableByteChannel channel();
290 
verify()291         abstract void verify() throws IOException;
292 
293     }
294 
295     static class FileTarget
296         extends Target
297     {
298         private final File fn;
299         private final RandomAccessFile raf;
300         private final FileChannel fc;
301 
FileTarget(int size, long seed)302         FileTarget(int size, long seed) throws IOException {
303             super(size, seed, "FileChannel");
304             fn = targetFile;
305             raf = new RandomAccessFile(fn, "rw");
306             fc = raf.getChannel();
307             fc.position(0);
308         }
309 
channel()310         WritableByteChannel channel() {
311             return fc;
312         }
313 
verify()314         void verify() throws IOException {
315             if (fc.position() != size)
316                 throw new Failure("Wrong position: "
317                                   + fc.position() + " (expected " + size + ")");
318             checkRandomBytes(fc, 0, size, seed);
319             fc.close();
320             raf.close();                // Bug in 1.4.0
321         }
322 
323     }
324 
325     static class UserTarget
326         extends Target
327     {
328         private WritableByteChannel ch;
329         private final ByteBuffer dst;
330 
UserTarget(int size, long seed)331         UserTarget(int size, long seed) {
332             super(size, seed, "UserChannel");
333             dst = ByteBuffer.wrap(new byte[size + 1]);
334 
335             ch = new WritableByteChannel() {
336                     public int write(ByteBuffer src) {
337                         int nr = Math.min(src.remaining(), dst.remaining());
338                         ByteBuffer s = src.duplicate();
339                         s.limit(s.position() + nr);
340                         dst.put(s);
341                         src.position(src.position() + nr);
342                         return nr;
343                     }
344                     public boolean isOpen() {
345                         return true;
346                     }
347                     public void close() { }
348                 };
349         }
350 
channel()351         WritableByteChannel channel() {
352             return ch;
353         }
354 
verify()355         void verify() {
356             if (dst.remaining() != 1)
357                 throw new Failure("Destination has " + dst.remaining()
358                                   + " bytes remaining (expected 1)");
359             byte[] ba = new byte[size];
360             Random r = new Random(seed);
361             r.nextBytes(ba);
362             dst.flip();
363             ByteBuffer rbb = ByteBuffer.wrap(ba, 0, size);
364             if (!dst.equals(rbb))
365                 throw new Failure("Wrong data written");
366         }
367 
368     }
369 
370 
371     // Generates a sequence of ints of the form 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
372     // 15, 16, 17, 31, 32, 33, ..., 2^i-1, 2^i, 2^i+1, ..., max.
373 
374     static class IntGenerator {
375 
376         private int max;
377         private int cur = -1;
378         private int p2 = 8;
379 
IntGenerator(int max)380         IntGenerator(int max) {
381             this.max = max;
382         }
383 
hasNext()384         boolean hasNext() {
385             return cur < max;
386         }
387 
next()388         int next() {
389             if (cur >= max)
390                 throw new IllegalStateException();
391             if (cur < 6) {
392                 cur++;
393                 return cur;
394             }
395             if (cur == p2 + 1) {
396                 p2 <<= 1;
397                 cur = p2 - 1;
398                 return cur;
399             }
400             cur++;
401             return cur;
402         }
403 
404     }
405 
406 
407     // -- Tests --
408 
409     private static final int MAX_XFER_SIZE = 1 << 14;
410     private static final int MAX_FILE_SIZE = MAX_XFER_SIZE << 1;
411 
412     private static boolean debug = false;
413     private static boolean verbose = false;
414 
show(String dir, String channelName, int off, int len)415     static void show(String dir, String channelName, int off, int len) {
416         if (!verbose)
417             return;
418         out.println(dir + " " + channelName +
419                     ": offset " + off + ", length " + len);
420     }
421 
testTo(long seed, FileChannel fc, int off, int len, Target tgt)422     static void testTo(long seed, FileChannel fc, int off, int len, Target tgt)
423         throws IOException
424     {
425         show("To", tgt.name(), off, len);
426 
427         // Clear source, then randomize just the source region
428         writeZeroBytes(fc, 0, MAX_FILE_SIZE);
429         writeRandomBytes(seed, fc, off, len);
430 
431         // Randomize position
432         int pos = (int)seed & 0xfff;
433         fc.position(pos);
434 
435         int n = (int)fc.transferTo(off, len, tgt.channel());
436         if (n != len)
437             throw new Failure("Incorrect transfer length: " + n
438                               + " (expected " + len + ")");
439 
440         // Check that source wasn't changed
441         if (fc.position() != pos)
442             throw new Failure("Position changed");
443         if (debug)
444             dump(fc);
445         checkRandomBytes(fc, off, len, seed);
446         writeZeroBytes(fc, off, len);
447         checkZeroBytes(fc, 0, MAX_FILE_SIZE);
448 
449         // Check that target was updated correctly
450         tgt.verify();
451     }
452 
testFrom(long seed, Source src, FileChannel fc, int off, int len)453     static void testFrom(long seed, Source src, FileChannel fc, int off, int len)
454         throws IOException
455     {
456         show("From", src.name(), off, len);
457 
458         // Clear target
459         writeZeroBytes(fc, 0, MAX_FILE_SIZE);
460 
461         // Randomize position
462         int pos = (int)seed & 0xfff;
463         fc.position(pos);
464 
465         int n = (int)fc.transferFrom(src.channel(), off, len);
466         if (n != len)
467             throw new Failure("Incorrect transfer length: " + n
468                               + " (expected " + len + ")");
469 
470         // Check that source didn't change, and was read correctly
471         src.verify();
472 
473         // Check that target was updated correctly
474         if (fc.position() != pos)
475             throw new Failure("Position changed");
476         if (debug)
477             dump(fc);
478         checkRandomBytes(fc, off, len, seed);
479         writeZeroBytes(fc, off, len);
480         checkZeroBytes(fc, 0, MAX_FILE_SIZE);
481     }
482 
main(String[] args)483     public static void main(String[] args)
484         throws Exception
485     {
486         if (args.length > 0) {
487             if (args[0].indexOf('v') >= 0)
488                 verbose = true;
489             if (args[0].indexOf('d') >= 0)
490                 debug = verbose = true;
491         }
492 
493         File testDir = new File(System.getProperty("test.dir", "."));
494 
495         sourceFile = File.createTempFile("xfer.src.", "", testDir);
496         sourceFile.deleteOnExit();
497         targetFile = File.createTempFile("xfer.tgt.", "", testDir);
498         targetFile.deleteOnExit();
499 
500         File fn = File.createTempFile("xfer.fch.", "", testDir);
501         fn.deleteOnExit();
502 
503         Random rnd = new Random();
504         int failures = 0;
505 
506         try (FileChannel fc = new RandomAccessFile(fn, "rw").getChannel()) {
507             for (boolean to = false;; to = true) {
508                 for (boolean user = false;; user = true) {
509                     if (!verbose)
510                         out.print((to ? "To " : "From ") +
511                                   (user ? "user channel" : "file channel")
512                                   + ":");
513                     IntGenerator offGen = new IntGenerator(MAX_XFER_SIZE + 2);
514                     while (offGen.hasNext()) {
515                         int off = offGen.next();
516                         if (!verbose) out.print(" " + off);
517                         IntGenerator lenGen = new IntGenerator(MAX_XFER_SIZE + 2);
518                         while (lenGen.hasNext()) {
519                             int len = lenGen.next();
520                             long s = rnd.nextLong();
521                             String chName = null;
522                             try {
523                                 if (to) {
524                                     Target tgt;
525                                     if (user)
526                                         tgt = new UserTarget(len, s);
527                                     else
528                                         tgt = new FileTarget(len, s);
529                                     chName = tgt.name();
530                                     testTo(s, fc, off, len, tgt);
531                                 }
532                                 else {
533                                     Source src;
534                                     if (user)
535                                         src = new UserSource(len, s);
536                                     else
537                                         src = new FileSource(len, s);
538                                     chName = src.name();
539                                     testFrom(s, src, fc, off, len);
540                                 }
541                             } catch (Failure x) {
542                                 out.println();
543                                 out.println("FAILURE: " + chName
544                                             + ", offset " + off
545                                             + ", length " + len);
546                                 x.printStackTrace(out);
547                                 failures++;
548                             }
549                         }
550                     }
551                     if (!verbose)
552                         out.println();
553                     if (user)
554                         break;
555                 }
556                 if (to)
557                     break;
558             }
559         }
560 
561         sourceFile.delete();
562         targetFile.delete();
563         fn.delete();
564 
565         if (failures > 0) {
566             out.println();
567             throw new RuntimeException("Some tests failed");
568         }
569 
570         out.println("Test succeeded.");
571     }
572 }
573