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} — 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}