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.*;
  30 
  31 /**
  32  * Helper class to support copying of moving files when the source and target
  33  * are associated with different providers.
  34  */
  35 
  36 class CopyMoveHelper {
  37     private CopyMoveHelper() { }
  38 
  39     /**
  40      * Parses the arguments for a file copy operation.
  41      */
  42     private static class CopyOptions {
  43         boolean replaceExisting = false;
  44         boolean copyAttributes = false;
  45         boolean followLinks = true;
  46 
  47         private CopyOptions() { }
  48 
  49         static CopyOptions parse(CopyOption... options) {
  50             CopyOptions result = new CopyOptions();
  51             for (CopyOption option: options) {
  52                 if (option == StandardCopyOption.REPLACE_EXISTING) {
  53                     result.replaceExisting = true;
  54                     continue;
  55                 }
  56                 if (option == LinkOption.NOFOLLOW_LINKS) {
  57                     result.followLinks = false;
  58                     continue;
  59                 }
  60                 if (option == StandardCopyOption.COPY_ATTRIBUTES) {
  61                     result.copyAttributes = true;
  62                     continue;
  63                 }
  64                 if (option == null)
  65                     throw new NullPointerException();
  66                 throw new UnsupportedOperationException("'" + option +
  67                     "' is not a recognized copy option");
  68             }
  69             return result;
  70         }
  71     }
  72     
  73     /**
  74      * Converts the given array of options for moving a file to options suitable
  75      * for copying the file when a move is implemented as copy + delete.
  76      */
  77     private static CopyOption[] convertMoveToCopyOptions(CopyOption... options)
  78         throws AtomicMoveNotSupportedException
  79     {
  80         int len = options.length;
  81         CopyOption[] newOptions = new CopyOption[len+2];
  82         for (int i=0; i<len; i++) {
  83             CopyOption option = options[i];
  84             if (option == StandardCopyOption.ATOMIC_MOVE) {
  85                 throw new AtomicMoveNotSupportedException(null, null,
  86                     "Atomic move between providers is not supported");
  87             }
  88             newOptions[i] = option;
  89         }
  90         newOptions[len] = LinkOption.NOFOLLOW_LINKS;
  91         newOptions[len+1] = StandardCopyOption.COPY_ATTRIBUTES;
  92         return newOptions;
  93     }    
  94 
  95     /**
  96      * Simple copy for use when source and target are associated with different 
  97      * providers
  98      */
  99     static void copyToForeignTarget(Path source, Path target, 
 100                                     CopyOption... options)
 101         throws IOException
 102     {
 103         CopyOptions opts = CopyOptions.parse(options);
 104         LinkOption[] linkOptions = (opts.followLinks) ? new LinkOption[0] :
 105             new LinkOption[] { LinkOption.NOFOLLOW_LINKS };
 106 
 107         // attributes of source file
 108         BasicFileAttributes attrs = Files.readAttributes(source, 
 109                                                          BasicFileAttributes.class,
 110                                                          linkOptions);
 111         if (attrs.isSymbolicLink())
 112             throw new IOException("Copying of symbolic links not supported");
 113 
 114         // delete target if it exists and REPLACE_EXISTING is specified
 115         if (opts.replaceExisting) {
 116             Files.deleteIfExists(target);
 117         } else if (Files.exists(target))
 118             throw new FileAlreadyExistsException(target.toString());
 119 
 120         // create directory or copy file
 121         if (attrs.isDirectory()) {
 122             Files.createDirectory(target);
 123         } else {
 124             try (InputStream in = Files.newInputStream(source)) {
 125                 Files.copy(in, target);
 126             }
 127         }
 128 
 129         // copy basic attributes to target
 130         if (opts.copyAttributes) {
 131             BasicFileAttributeView view = 
 132                 Files.getFileAttributeView(target, BasicFileAttributeView.class, linkOptions);
 133             try {
 134                 view.setTimes(attrs.lastModifiedTime(),
 135                               attrs.lastAccessTime(),
 136                               attrs.creationTime());
 137             } catch (IOException x) {
 138                 // rollback
 139                 try {
 140                     Files.delete(target);
 141                 } catch (IOException ignore) { }
 142                 throw x;
 143             }
 144         }
 145     }
 146     
 147     /**
 148      * Simple move implements as copy+delete for use when source and target are 
 149      * associated with different providers
 150      */    
 151     static void moveToForeignTarget(Path source, Path target, 
 152                                     CopyOption... options) throws IOException
 153     {
 154         copyToForeignTarget(source, target, convertMoveToCopyOptions(options));
 155         Files.delete(source);
 156     }
 157 }