1 /*
   2  * Copyright 2008-2009 Sun Microsystems, Inc.  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 Sun Microsystems 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(FileRef 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 || target.notExists() || okayToOverwrite(target)) {
  63             try {
  64                 source.copyTo(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) {
  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                 dir.copyTo(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 preVisitDirectoryFailed(Path dir, IOException exc) {
 108             System.err.format("Unable to copy: %s: %s%n", dir, exc);
 109             return CONTINUE;
 110         }
 111 
 112         @Override
 113         public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
 114             if (attrs.isDirectory()) {
 115                 System.err.println("cycle detected: " + file);
 116             } else {
 117                 copyFile(file, target.resolve(source.relativize(file)),
 118                          prompt, preserve);
 119             }
 120             return CONTINUE;
 121         }
 122 
 123         @Override
 124         public FileVisitResult postVisitDirectory(Path dir, IOException exc) {
 125             // fix up modification time of directory when done
 126             if (exc == null && preserve) {
 127                 Path newdir = target.resolve(source.relativize(dir));
 128                 try {
 129                     BasicFileAttributes attrs = Attributes.readBasicFileAttributes(dir);
 130                     Attributes.setLastModifiedTime(newdir,
 131                         attrs.lastModifiedTime(), attrs.resolution());
 132                 } catch (IOException x) {
 133                     System.err.format("Unable to copy all attributes to: %s: %s%n", newdir, x);
 134                 }
 135             }
 136             return CONTINUE;
 137         }
 138 
 139         @Override
 140         public FileVisitResult visitFileFailed(Path file, IOException exc) {
 141             System.err.format("Unable to copy: %s: %s%n", file, exc);
 142             return CONTINUE;
 143         }
 144     }
 145 
 146     static void usage() {
 147         System.err.println("java Copy [-ip] source... target");
 148         System.err.println("java Copy -r [-ip] source-dir... target");
 149         System.exit(-1);
 150     }
 151 
 152     public static void main(String[] args) throws IOException {
 153         boolean recursive = false;
 154         boolean prompt = false;
 155         boolean preserve = false;
 156 
 157         // process options
 158         int argi = 0;
 159         while (argi < args.length) {
 160             String arg = args[argi];
 161             if (!arg.startsWith("-"))
 162                 break;
 163             if (arg.length() < 2)
 164                 usage();
 165             for (int i=1; i<arg.length(); i++) {
 166                 char c = arg.charAt(i);
 167                 switch (c) {
 168                     case 'r' : recursive = true; break;
 169                     case 'i' : prompt = true; break;
 170                     case 'p' : preserve = true; break;
 171                     default : usage();
 172                 }
 173             }
 174             argi++;
 175         }
 176 
 177         // remaining arguments are the source files(s) and the target location
 178         int remaining = args.length - argi;
 179         if (remaining < 2)
 180             usage();
 181         Path[] source = new Path[remaining-1];
 182         int i=0;
 183         while (remaining > 1) {
 184             source[i++] = Paths.get(args[argi++]);
 185             remaining--;
 186         }
 187         Path target = Paths.get(args[argi]);
 188 
 189         // check if target is a directory
 190         boolean isDir = false;
 191         try {
 192             isDir = Attributes.readBasicFileAttributes(target).isDirectory();
 193         } catch (IOException x) {
 194             // ignore (probably target does not exist)
 195         }
 196 
 197         // copy each source file/directory to target
 198         for (i=0; i<source.length; i++) {
 199             Path dest = (isDir) ? target.resolve(source[i].getName()) : target;
 200 
 201             if (recursive) {
 202                 // follow links when copying files
 203                 EnumSet<FileVisitOption> opts = EnumSet.of(FileVisitOption.FOLLOW_LINKS);
 204                 TreeCopier tc = new TreeCopier(source[i], dest, prompt, preserve);
 205                 Files.walkFileTree(source[i], opts, Integer.MAX_VALUE, tc);
 206             } else {
 207                 // not recursive so source must not be a directory
 208                 try {
 209                     if (Attributes.readBasicFileAttributes(source[i]).isDirectory()) {
 210                         System.err.format("%s: is a directory%n", source[i]);
 211                         continue;
 212                     }
 213                 } catch (IOException x) {
 214                     // assume not directory
 215                 }
 216                 copyFile(source[i], dest, prompt, preserve);
 217             }
 218         }
 219     }
 220 }