1 /*
   2  * Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
   3  *
   4  * Redistribution and use in source and binary forms, with or without
   5  * modification, are permitted provided that the following conditions
   6  * are met:
   7  *
   8  *   - Redistributions of source code must retain the above copyright
   9  *     notice, this list of conditions and the following disclaimer.
  10  *
  11  *   - Redistributions in binary form must reproduce the above copyright
  12  *     notice, this list of conditions and the following disclaimer in the
  13  *     documentation and/or other materials provided with the distribution.
  14  *
  15  *   - Neither the name of Oracle nor the names of its
  16  *     contributors may be used to endorse or promote products derived
  17  *     from this software without specific prior written permission.
  18  *
  19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
  20  * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
  21  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
  22  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  23  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  24  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  25  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  26  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  27  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  28  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  29  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  30  */
  31 
  32 import java.nio.file.*;
  33 import static java.nio.file.StandardCopyOption.*;
  34 import java.nio.file.attribute.*;
  35 import static java.nio.file.FileVisitResult.*;
  36 import java.io.IOException;
  37 import java.util.*;
  38 
  39 /**
  40  * Sample code that copies files in a similar manner to the cp(1) program.
  41  */
  42 
  43 public class Copy {
  44 
  45     /**
  46      * Returns {@code true} if okay to overwrite a  file ("cp -i")
  47      */
  48     static boolean okayToOverwrite(Path file) {
  49         String answer = System.console().readLine("overwrite %s (yes/no)? ", file);
  50         return (answer.equalsIgnoreCase("y") || answer.equalsIgnoreCase("yes"));
  51     }
  52 
  53     /**
  54      * Copy source file to target location. If {@code prompt} is true then
  55      * prompt user to overwrite target if it exists. The {@code preserve}
  56      * parameter determines if file attributes should be copied/preserved.
  57      */
  58     static void copyFile(Path source, Path target, boolean prompt, boolean preserve) {
  59         CopyOption[] options = (preserve) ?
  60             new CopyOption[] { COPY_ATTRIBUTES, REPLACE_EXISTING } :
  61             new CopyOption[] { REPLACE_EXISTING };
  62         if (!prompt || Files.notExists(target) || okayToOverwrite(target)) {
  63             try {
  64                 Files.copy(source, target, options);
  65             } catch (IOException x) {
  66                 System.err.format("Unable to copy: %s: %s%n", source, x);
  67             }
  68         }
  69     }
  70 
  71     /**
  72      * A {@code FileVisitor} that copies a file-tree ("cp -r")
  73      */
  74     static class TreeCopier implements FileVisitor<Path> {
  75         private final Path source;
  76         private final Path target;
  77         private final boolean prompt;
  78         private final boolean preserve;
  79 
  80         TreeCopier(Path source, Path target, boolean prompt, boolean preserve) {
  81             this.source = source;
  82             this.target = target;
  83             this.prompt = prompt;
  84             this.preserve = preserve;
  85         }
  86 
  87         @Override
  88         public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {
  89             // before visiting entries in a directory we copy the directory
  90             // (okay if directory already exists).
  91             CopyOption[] options = (preserve) ?
  92                 new CopyOption[] { COPY_ATTRIBUTES } : new CopyOption[0];
  93 
  94             Path newdir = target.resolve(source.relativize(dir));
  95             try {
  96                 Files.copy(dir, newdir, options);
  97             } catch (FileAlreadyExistsException x) {
  98                 // ignore
  99             } catch (IOException x) {
 100                 System.err.format("Unable to create: %s: %s%n", newdir, x);
 101                 return SKIP_SUBTREE;
 102             }
 103             return CONTINUE;
 104         }
 105 
 106         @Override
 107         public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
 108             copyFile(file, target.resolve(source.relativize(file)),
 109                      prompt, preserve);
 110             return CONTINUE;
 111         }
 112 
 113         @Override
 114         public FileVisitResult postVisitDirectory(Path dir, IOException exc) {
 115             // fix up modification time of directory when done
 116             if (exc == null && preserve) {
 117                 Path newdir = target.resolve(source.relativize(dir));
 118                 try {
 119                     FileTime time = Files.getLastModifiedTime(dir);
 120                     Files.setLastModifiedTime(newdir, time);
 121                 } catch (IOException x) {
 122                     System.err.format("Unable to copy all attributes to: %s: %s%n", newdir, x);
 123                 }
 124             }
 125             return CONTINUE;
 126         }
 127 
 128         @Override
 129         public FileVisitResult visitFileFailed(Path file, IOException exc) {
 130             if (exc instanceof FileSystemLoopException) {
 131                 System.err.println("cycle detected: " + file);
 132             } else {
 133                 System.err.format("Unable to copy: %s: %s%n", file, exc);
 134             }
 135             return CONTINUE;
 136         }
 137     }
 138 
 139     static void usage() {
 140         System.err.println("java Copy [-ip] source... target");
 141         System.err.println("java Copy -r [-ip] source-dir... target");
 142         System.exit(-1);
 143     }
 144 
 145     public static void main(String[] args) throws IOException {
 146         boolean recursive = false;
 147         boolean prompt = false;
 148         boolean preserve = false;
 149 
 150         // process options
 151         int argi = 0;
 152         while (argi < args.length) {
 153             String arg = args[argi];
 154             if (!arg.startsWith("-"))
 155                 break;
 156             if (arg.length() < 2)
 157                 usage();
 158             for (int i=1; i<arg.length(); i++) {
 159                 char c = arg.charAt(i);
 160                 switch (c) {
 161                     case 'r' : recursive = true; break;
 162                     case 'i' : prompt = true; break;
 163                     case 'p' : preserve = true; break;
 164                     default : usage();
 165                 }
 166             }
 167             argi++;
 168         }
 169 
 170         // remaining arguments are the source files(s) and the target location
 171         int remaining = args.length - argi;
 172         if (remaining < 2)
 173             usage();
 174         Path[] source = new Path[remaining-1];
 175         int i=0;
 176         while (remaining > 1) {
 177             source[i++] = Paths.get(args[argi++]);
 178             remaining--;
 179         }
 180         Path target = Paths.get(args[argi]);
 181 
 182         // check if target is a directory
 183         boolean isDir = Files.isDirectory(target);
 184 
 185         // copy each source file/directory to target
 186         for (i=0; i<source.length; i++) {
 187             Path dest = (isDir) ? target.resolve(source[i].getFileName()) : target;
 188 
 189             if (recursive) {
 190                 // follow links when copying files
 191                 EnumSet<FileVisitOption> opts = EnumSet.of(FileVisitOption.FOLLOW_LINKS);
 192                 TreeCopier tc = new TreeCopier(source[i], dest, prompt, preserve);
 193                 Files.walkFileTree(source[i], opts, Integer.MAX_VALUE, tc);
 194             } else {
 195                 // not recursive so source must not be a directory
 196                 if (Files.isDirectory(source[i])) {
 197                     System.err.format("%s: is a directory%n", source[i]);
 198                     continue;
 199                 }
 200                 copyFile(source[i], dest, prompt, preserve);
 201             }
 202         }
 203     }
 204 }