1

Trying to move files from a sub-directory along with the structure to a parent directory. And am not able to accomplish this using Files.move(). To illustrate the issue please see the below directory structure.

$ tree
.
└── b
    ├── c
    │   ├── cfile.gtxgt
    │   └── d
    │       ├── dfile.txt
    │       └── e
    └── x
        └── y
            └── z
                ├── 2.txt
                └── p
                    ├── file1.txt
                    └── q
                        ├── file
                        ├── file2.txt
                        └── r
                            └── 123.txt

I want to emulate the below move command via Java.

$ mv b/x/y/z/* b/c
b/x/y/z/2.txt -> b/c/2.txt
b/x/y/z/p -> b/c/p

And the output should be something similar to

$ tree
.
└── b
    ├── c
    │   ├── 2.txt
    │   ├── cfile.gtxgt
    │   ├── d
    │   │   ├── dfile.txt
    │   │   └── e
    │   └── p
    │       ├── file1.txt
    │       └── q
    │           ├── file
    │           ├── file2.txt
    │           └── r
    │               └── 123.txt
    └── x
        └── y
            └── z

In this move all the files and directories under directory z have been moved to c.

I have tried to do this:

public static void main(String[] args) throws IOException{
    String aPath = "/tmp/test/a/";
    String relativePathTomove = "b/x/y/z/";
    String relativePathToMoveTo = "b/c";

    Files.move(Paths.get(aPath, relativePathTomove), Paths.get(aPath, relativePathToMoveTo), StandardCopyOption.REPLACE_EXISTING);

}

However this causes this exception to the thrown java.nio.file.DirectoryNotEmptyException: /tmp/test/a/b/c and if the the REPLACE_EXISTING option is taken out the code throws a java.nio.file.FileAlreadyExistsException: /tmp/test/a/b/c.

This question has an answer that uses a recursive function to solve this problem. But in my case it will involve further complexity as I need to even re-created the sub-dir structure in the new location.

I have not tried the option of using the commons-io utility method org.apache.commons.io.FileUtils#moveDirectoryToDirectory as this code seems to be first copying files and then deleting them from the original location. And In my case the files are huge and hence this is not a preferred option.

How can I achieve the the move functionality in java without resorting to copying. Is individual file move my only option?

TLDR: How can I emulate the mv functionality in java for moving sub dir with files and structure to parent directory.

Yogesh_D
  • 17,656
  • 10
  • 41
  • 55

1 Answers1

0

I ended up doing this:

Create a FileVisitor Implementation like so:

package com.test.files;

import org.apache.log4j.Logger;

import javax.validation.constraints.NotNull;
import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.FileVisitor;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Objects;

import static java.nio.file.FileVisitResult.TERMINATE;


public class MoveFileVisitor implements FileVisitor<Path> {

    private static final Logger LOGGER = Logger.getLogger(MoveFileVisitor.class);
    private final Path target;
    private final Path source;

    public MoveFileVisitor(@NotNull Path source, @NotNull Path target) {
        this.target = Objects.requireNonNull(target);
        this.source = Objects.requireNonNull(source);
    }

    @Override
    public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
        Path relativePath = source.relativize(dir);
        Path finalPath = target.resolve(relativePath);
        Files.createDirectories(finalPath);
        return FileVisitResult.CONTINUE;
    }

    @Override
    public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
        Path relativePath = source.relativize(file);
        Path finalLocation = target.resolve(relativePath);
        Files.move(file, finalLocation);
        return FileVisitResult.CONTINUE;
    }

    @Override
    public FileVisitResult visitFileFailed(Path file, IOException exc) {
        LOGGER.error("Failed to visit file during move" + file.toAbsolutePath(), exc);
        return TERMINATE;
    }

    @Override
    public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
        Files.delete(dir);
        return FileVisitResult.CONTINUE;
    }


}

And then walking the path with this Visitor like so:

    String source = "/temp/test/a/b/x/y/z";
    String target = "/temp/test/a/b/c";

    MoveFileVisitor visitor = new MoveFileVisitor(Paths.get(source), Paths.get(target));
    Files.walkFileTree(Paths.get(source), visitor);
Yogesh_D
  • 17,656
  • 10
  • 41
  • 55
  • You don't need any of this. One single `File.rename()` with the correct arguments would have done the lot, in one line of code. – user207421 Mar 25 '19 at 08:52
  • 2
    @user207421 - I am assuming you mean the [```File.renameTo()```](https://docs.oracle.com/javase/7/docs/api/java/io/File.html#renameTo(java.io.File)) method. The documentation of this method recommends using ```Files.move()``` which throws the exceptions listed above. Also I dont think ```renameTo``` will take into consideration that the destination dir has some files and the files here need to merged into them. If all of that is possible can you give me the correct args. I would hate to write code for something that is already implemented. – Yogesh_D Mar 25 '19 at 09:08