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 {
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 
CopyOptions()48         private CopyOptions() { }
49 
parse(CopyOption... options)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      */
convertMoveToCopyOptions(CopyOption... options)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      */
copyToForeignTarget(Path source, Path target, CopyOption... options)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      */
moveToForeignTarget(Path source, Path target, CopyOption... options)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 }
161