Alfred
Alfred

Reputation: 21396

How to list directories only if there is no further subdirectory present using NIO in Java 8?

I am using Java 8 NIO and the below code to list directories;

String  rootDir = "root\\";
Files.walk(Paths.get(rootDir)).filter(Files::isDirectory).forEach(folder_path -> {
    System.out.println(folder_path.toString());
});

But this will give the output like below;

root
root\dir1
root\dir1\subdir1
root\dir1\subdir2
root\dir2
root\dir2\subdir1
root\dir2\subdir2
root\dir3
root\dir3\subdir1
root\dir3\subdir2

But, I would like to display the path, only if there is no further sub-directories. So, the expected output will be like;

root\dir1\subdir1
root\dir1\subdir2
root\dir2\subdir1
root\dir2\subdir2
root\dir3\subdir1
root\dir3\subdir2

Is this possible? If yes, how can I achieve this?

Upvotes: 1

Views: 106

Answers (3)

DuncG
DuncG

Reputation: 15136

Another simple version which uses streams:

LinkedHashSet<Path> dirs = new LinkedHashSet<>();
try(Stream<Path> stream = Files.find(dir, Integer.MAX_VALUE, (path,attr) -> attr.isDirectory())) {
    stream.filter(dirs::add).map(Path::getParent).forEach(dirs::remove);
}

Upvotes: 0

Holger
Holger

Reputation: 298153

You can implement a filter straight-forwardly, e.g.

Files.walk(Paths.get(rootDir))
    .filter(Files::isDirectory)
    .filter(dir -> {
       try(Stream<Path> sub=Files.list(dir)) { return sub.noneMatch(Files::isDirectory); }
       catch(IOException ex) { throw new UncheckedIOException(ex); }
    })
    .forEach(System.out::println);

which is simple but performs redundant operations on each encountered directory.

A more efficient alternative would be a classic loop solution:

Set<Path> dirs = new LinkedHashSet<>();
for(Iterator<Path> it = Files.walk(Paths.get(rootDir)).iterator(); it.hasNext(); ) {
    Path p = it.next();
    if(Files.isDirectory(p) && dirs.add(p)) dirs.remove(p.getParent());
}
dirs.forEach(System.out::println);

It simply removes parent directories when encountering another directory, so at the end of this single iteration, only directories not containing other directories are left.

Or with the good old Java 7 API:

Files.walkFileTree(root, new SimpleFileVisitor<Path>() {
    Path previous;

    @Override
    public FileVisitResult postVisitDirectory(Path dir, IOException exc) {
        if(previous == null || !previous.startsWith(dir))
            System.out.println(dir);
        previous = dir;
        return FileVisitResult.CONTINUE;
    }
});

By overriding postVisitDirectory (instead preVisitDirectory), we have visited a child directory of it right before, if there was one. So a simple test whether the previous path is a child of the current is sufficient.

Upvotes: 3

Vikas
Vikas

Reputation: 7165

You can create a method to check sub-directory and add it in filter as below,

private static Predicate<Path> checkSubDirectory() {
    return f1->{
        for (File file : f1.toFile().listFiles()) {
            if (file.isDirectory())
                return false;
        }
        return true;
    };
}

Files.walk(Paths.get(rootDir)).filter(Files::isDirectory)
       .filter(checkSubDirectory())                      
       .forEach(System.out::println);                                              

Upvotes: 0

Related Questions