1 /*
   2  * Copyright (c) 2011, 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.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package java.nio.file;
  27 
  28 import java.nio.file.attribute.*;
  29 import java.io.InputStream;
  30 import java.io.IOException;
  31 
  32 /**
  33  * Helper class to support copying or moving files when the source and target
  34  * are associated with different providers.
  35  */
  36 
  37 class CopyMoveHelper {
  38     private CopyMoveHelper() { }
  39 
  40     /**
  41      * Parses the arguments for a file copy operation.
  42      */
  43     private static class CopyOptions {
  44         boolean replaceExisting = false;
  45         boolean copyAttributes = false;
  46         boolean followLinks = true;
  47 
  48         private CopyOptions() { }
  49 
  50         static CopyOptions parse(CopyOption... options) {
  51             CopyOptions result = new CopyOptions();
  52             for (CopyOption option: options) {
  53                 if (option == StandardCopyOption.REPLACE_EXISTING) {
  54                     result.replaceExisting = true;
  55                     continue;
  56                 }
  57                 if (option == LinkOption.NOFOLLOW_LINKS) {
  58                     result.followLinks = false;
  59                     continue;
  60                 }
  61                 if (option == StandardCopyOption.COPY_ATTRIBUTES) {
  62                     result.copyAttributes = true;
  63                     continue;
  64                 }
  65                 if (option == null)
  66                     throw new NullPointerException();
  67                 throw new UnsupportedOperationException("'" + option +
  68                     "' is not a recognized copy option");
  69             }
  70             return result;
  71         }
  72     }
  73 
  74     /**
  75      * Converts the given array of options for moving a file to options suitable
  76      * for copying the file when a move is implemented as copy + delete.
  77      */
  78     private static CopyOption[] convertMoveToCopyOptions(CopyOption... options)
  79         throws AtomicMoveNotSupportedException
  80     {
  81         int len = options.length;
  82         CopyOption[] newOptions = new CopyOption[len+2];
  83         for (int i=0; i<len; i++) {
  84             CopyOption option = options[i];
  85             if (option == StandardCopyOption.ATOMIC_MOVE) {
  86                 throw new AtomicMoveNotSupportedException(null, null,
  87                     "Atomic move between providers is not supported");
  88             }
  89             newOptions[i] = option;
  90         }
  91         newOptions[len] = LinkOption.NOFOLLOW_LINKS;
  92         newOptions[len+1] = StandardCopyOption.COPY_ATTRIBUTES;
  93         return newOptions;
  94     }
  95 
  96     /**
  97      * Simple copy for use when source and target are associated with different
  98      * providers
  99      */
 100     static void copyToForeignTarget(Path source, Path target,
 101                                     CopyOption... options)
 102         throws IOException
 103     {
 104         CopyOptions opts = CopyOptions.parse(options);
 105         LinkOption[] linkOptions = (opts.followLinks) ? new LinkOption[0] :
 106             new LinkOption[] { LinkOption.NOFOLLOW_LINKS };
 107 
 108         // attributes of source file
 109         BasicFileAttributes attrs = Files.readAttributes(source,
 110                                                          BasicFileAttributes.class,
 111                                                          linkOptions);
 112         if (attrs.isSymbolicLink())
 113             throw new IOException("Copying of symbolic links not supported");
 114 
 115         // delete target if it exists and REPLACE_EXISTING is specified
 116         if (opts.replaceExisting) {
 117             Files.deleteIfExists(target);
 118         } else if (Files.exists(target))
 119             throw new FileAlreadyExistsException(target.toString());
 120 
 121         // create directory or copy file
 122         if (attrs.isDirectory()) {
 123             Files.createDirectory(target);
 124         } else {
 125             try (InputStream in = Files.newInputStream(source)) {
 126                 Files.copy(in, target);
 127             }
 128         }
 129 
 130         // copy basic attributes to target
 131         if (opts.copyAttributes) {
 132             BasicFileAttributeView view =
 133                 Files.getFileAttributeView(target, BasicFileAttributeView.class);
 134             try {
 135                 view.setTimes(attrs.lastModifiedTime(),
 136                               attrs.lastAccessTime(),
 137                               attrs.creationTime());
 138             } catch (Throwable x) {
 139                 // rollback
 140                 try {
 141                     Files.delete(target);
 142                 } catch (Throwable suppressed) {
 143                     x.addSuppressed(suppressed);
 144                 }
 145                 throw x;
 146             }
 147         }
 148     }
 149 
 150     /**
 151      * Simple move implements as copy+delete for use when source and target are
 152      * associated with different providers
 153      */
 154     static void moveToForeignTarget(Path source, Path target,
 155                                     CopyOption... options) throws IOException
 156     {
 157         copyToForeignTarget(source, target, convertMoveToCopyOptions(options));
 158         Files.delete(source);
 159     }
 160 }