001/*
002 * Copyright 2002-2020 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.util;
018
019import java.io.File;
020import java.io.IOException;
021import java.nio.file.FileVisitResult;
022import java.nio.file.Files;
023import java.nio.file.Path;
024import java.nio.file.SimpleFileVisitor;
025import java.nio.file.StandardCopyOption;
026import java.nio.file.attribute.BasicFileAttributes;
027import java.util.EnumSet;
028
029import org.springframework.lang.Nullable;
030
031import static java.nio.file.FileVisitOption.FOLLOW_LINKS;
032
033/**
034 * Utility methods for working with the file system.
035 *
036 * @author Rob Harrop
037 * @author Juergen Hoeller
038 * @since 2.5.3
039 * @see java.io.File
040 * @see java.nio.file.Path
041 * @see java.nio.file.Files
042 */
043public abstract class FileSystemUtils {
044
045        /**
046         * Delete the supplied {@link File} - for directories,
047         * recursively delete any nested directories or files as well.
048         * <p>Note: Like {@link File#delete()}, this method does not throw any
049         * exception but rather silently returns {@code false} in case of I/O
050         * errors. Consider using {@link #deleteRecursively(Path)} for NIO-style
051         * handling of I/O errors, clearly differentiating between non-existence
052         * and failure to delete an existing file.
053         * @param root the root {@code File} to delete
054         * @return {@code true} if the {@code File} was successfully deleted,
055         * otherwise {@code false}
056         */
057        public static boolean deleteRecursively(@Nullable File root) {
058                if (root == null) {
059                        return false;
060                }
061
062                try {
063                        return deleteRecursively(root.toPath());
064                }
065                catch (IOException ex) {
066                        return false;
067                }
068        }
069
070        /**
071         * Delete the supplied {@link File} &mdash; for directories,
072         * recursively delete any nested directories or files as well.
073         * @param root the root {@code File} to delete
074         * @return {@code true} if the {@code File} existed and was deleted,
075         * or {@code false} if it did not exist
076         * @throws IOException in the case of I/O errors
077         * @since 5.0
078         */
079        public static boolean deleteRecursively(@Nullable Path root) throws IOException {
080                if (root == null) {
081                        return false;
082                }
083                if (!Files.exists(root)) {
084                        return false;
085                }
086
087                Files.walkFileTree(root, new SimpleFileVisitor<Path>() {
088                        @Override
089                        public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
090                                Files.delete(file);
091                                return FileVisitResult.CONTINUE;
092                        }
093                        @Override
094                        public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
095                                Files.delete(dir);
096                                return FileVisitResult.CONTINUE;
097                        }
098                });
099                return true;
100        }
101
102        /**
103         * Recursively copy the contents of the {@code src} file/directory
104         * to the {@code dest} file/directory.
105         * @param src the source directory
106         * @param dest the destination directory
107         * @throws IOException in the case of I/O errors
108         */
109        public static void copyRecursively(File src, File dest) throws IOException {
110                Assert.notNull(src, "Source File must not be null");
111                Assert.notNull(dest, "Destination File must not be null");
112                copyRecursively(src.toPath(), dest.toPath());
113        }
114
115        /**
116         * Recursively copy the contents of the {@code src} file/directory
117         * to the {@code dest} file/directory.
118         * @param src the source directory
119         * @param dest the destination directory
120         * @throws IOException in the case of I/O errors
121         * @since 5.0
122         */
123        public static void copyRecursively(Path src, Path dest) throws IOException {
124                Assert.notNull(src, "Source Path must not be null");
125                Assert.notNull(dest, "Destination Path must not be null");
126                BasicFileAttributes srcAttr = Files.readAttributes(src, BasicFileAttributes.class);
127
128                if (srcAttr.isDirectory()) {
129                        Files.walkFileTree(src, EnumSet.of(FOLLOW_LINKS), Integer.MAX_VALUE, new SimpleFileVisitor<Path>() {
130                                @Override
131                                public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
132                                        Files.createDirectories(dest.resolve(src.relativize(dir)));
133                                        return FileVisitResult.CONTINUE;
134                                }
135                                @Override
136                                public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
137                                        Files.copy(file, dest.resolve(src.relativize(file)), StandardCopyOption.REPLACE_EXISTING);
138                                        return FileVisitResult.CONTINUE;
139                                }
140                        });
141                }
142                else if (srcAttr.isRegularFile()) {
143                        Files.copy(src, dest);
144                }
145                else {
146                        throw new IllegalArgumentException("Source File must denote a directory or file");
147                }
148        }
149
150}