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
26package java.nio.file;
27
28import java.nio.file.attribute.*;
29import java.io.InputStream;
30import 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
37class 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}
161