javafilerecursiondirectory

Recursively copy directories and files into destination


We have the following task in Java:

Given a list of directories and files followed by the destination copy all these directories(recursively) and files into the destination. Note: all the subdirectories and files inside the given directories must be kept in the same nested structure in destination folder.

We take array of files as an input as well as the destination folder. The output must produce something like this:

Started: resources\docs\books...
Started: resources\docs\books\american...
Started: resources\docs\books\american\London...
Started: resources\docs\books\american\London\BEFORE ADAM.txt...
Finished FILE: resources\docs\books\american\London\BEFORE ADAM.txt
Total 229KB were copied!
Started: resources\docs\books\american\London\THE IRON HEEL.txt...
Finished FILE: resources\docs\books\american\London\THE IRON HEEL.txt
Total 528KB were copied!
Started: resources\docs\books\american\London\The People of the Abyss.txt...
Finished FILE: resources\docs\books\american\London\The People of the Abyss.txt
Total 370KB were copied!
Finished FOLDER: resources\docs\books\american\London
Finished FOLDER: resources\docs\books\american

Does anyone have any ideas?


Solution

  • This is a perfect example of a task that can be handled with an implementation of the FileVisitor interface. In this approach, the code can be broken down into three parts:

    public class FileCopier implements FileVisitor<Path> {
    
        private final Path sourcePath;
        private final Path targetPath;
    
        //Map holding the size of each directory
        private final Map<String, Long> mapSize;
    
        public FileCopier(Path sourcePath, Path targetPath) throws IOException {
            //Check whether the destination directory exists or not
            if (!Files.exists(targetPath)) {
                Files.createDirectories(targetPath);
            }
    
            this.sourcePath = sourcePath.toRealPath();
            this.targetPath = targetPath.toRealPath();
            this.mapSize = new HashMap<>();
        }
    
        @Override
        public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
            //Copying the directory first, and then its content (copying a directory does not copy its content)
    
            //Retrieving the last bit of the folder's path relatively from the source path ("subtracts" the source path)
            Path lastBitPath = sourcePath.relativize(dir.toAbsolutePath().normalize());
    
            //Adding the last bit to the target path
            Path targetCopyPath = targetPath.resolve(lastBitPath).toAbsolutePath().normalize();
    
            //Copying the folder is this doesn't exist yet
            if (!Files.exists(targetCopyPath)) {
                Files.copy(dir, targetCopyPath, StandardCopyOption.COPY_ATTRIBUTES);
            }
    
            //Adding a new key-value pair, where the key is the folder's path, while the value is folder's size in bytes
            mapSize.put(targetCopyPath.toString(), 0L);
            System.out.println("Started: " + targetCopyPath);
            return FileVisitResult.CONTINUE;
        }
    
        @Override
        public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
            //Building the new file path by removing the source path from the file, and then adding the result on top of the target path
            Path targetCopyPath = targetPath.resolve(sourcePath.relativize(file.toAbsolutePath().normalize())).toAbsolutePath().normalize();
            Files.copy(file, targetCopyPath, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.COPY_ATTRIBUTES);
    
            //Adding the file's size to the pair's value identified by the folder's path
            mapSize.put(targetCopyPath.getParent().toString(), mapSize.get(targetCopyPath.getParent().toString()) + Files.size(file));
            System.out.println("Finished FILE: " + targetCopyPath);
            return FileVisitResult.CONTINUE;
        }
    
        @Override
        public FileVisitResult visitFileFailed(Path file, IOException exc) {
            return FileVisitResult.TERMINATE;
        }
    
        @Override
        public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
            //Printing the folder's size after finishing its copy (visit)
    
            //Retrieving the last bit of the folder's path by removing the source location
            Path lastBitPath = sourcePath.relativize(dir.toAbsolutePath().normalize());
    
            //Adding the last bit of the folder's path on top of the target path
            Path targetCopyPath = targetPath.resolve(lastBitPath).toAbsolutePath().normalize();
    
            //Showing the number of bytes corresponded to the copied directory
            System.out.println("Total " + this.mapSize.get(targetCopyPath.toString()) + " bytes were copied into " + targetCopyPath);
            return FileVisitResult.CONTINUE;
        }
    
        public static void main(String[] args) throws IOException {
            Path sourcePath = Paths.get("./test1");
            Path targetPath = Paths.get("./test2");
            FileCopier fc = new FileCopier(sourcePath, targetPath);
            Files.walkFileTree(sourcePath, fc);
        }
    }