Question

Say I have the following directory structure

/root/dir
/root/dir/file1.txt
/root/dir/subdir
/root/dir/subdir/file2.txt

And let's say I'll be using the following visitor:

class MyFileVisitor extends SimpleFileVisitor<Path> {

  @Override
  public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs)
  throws IOException {
    if(Files.isDirectory(file)) {
      throw new IllegalStateException("WAT!? Visiting directory: "+file.toAbsolutePath().toString());
    }
    System.out.println("Visiting file: "+file.toAbsolutePath().toString());
    return super.visitFile(file, attrs);
  }

If we use the simple overload of walkFileTree:

Files.walkFileTree(Paths.get("/root/dir"), new MyFileVisitor());  

Everything goes according to plan and we see the following output:

Visiting file: /root/dir/file1.txt
Visiting file: /root/dir/subdir/file2.txt

But when we try setting the max depth, things start to break down:

Files.walkFileTree(Paths.get("/root/dir"), EnumSet.noneOf(FileVisitOption.class), 1, new MyFileVisitor());

Output:

Visiting file: /root/dir/file1.txt

java.lang.IllegalStateException: WAT!? Visting directory: /root/dir/subdir

I'm pretty sure this is a bug, but I wanted to ask the community first: Is there something I'm missing and this is actually expected behavior? Thanks for confirming!

Details:

java -version
java version "1.7.0_45"
Java(TM) SE Runtime Environment (build 1.7.0_45-b18)
Java HotSpot(TM) 64-Bit Server VM (build 24.45-b08, mixed mode)
Was it helpful?

Solution

I played around a little bit with your code and added a couple of lines:

/*
 * (non-Javadoc)
 * 
 * @see java.nio.file.SimpleFileVisitor#preVisitDirectory(java.lang.Object,
 * java.nio.file.attribute.BasicFileAttributes)
 */
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)
        throws IOException {
    System.out.println(Thread.currentThread().getStackTrace()[1] + " "
            + dir);
    return super.preVisitDirectory(dir, attrs);
}

/*
 * (non-Javadoc)
 * 
 * @see java.nio.file.SimpleFileVisitor#postVisitDirectory(java.lang.Object,
 * java.io.IOException)
 */
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc)
        throws IOException {
    System.out.println(Thread.currentThread().getStackTrace()[1] + " "
            + dir);
    return super.postVisitDirectory(dir, exc);
}

/*
 * (non-Javadoc)
 * 
 * @see java.nio.file.SimpleFileVisitor#visitFileFailed(java.lang.Object,
 * java.io.IOException)
 */
@Override
public FileVisitResult visitFileFailed(Path file, IOException exc)
        throws IOException {
    System.out.println(Thread.currentThread().getStackTrace()[1] + " "
            + file);
    return super.visitFileFailed(file, exc);
}

just no-brainers to see what's going on.

What you call a bug happens here java.nio.file.FileTreeWalker:134:

    // at maximum depth or file is not a directory
    if (depth >= maxDepth || !attrs.isDirectory()) {
        return visitor.visitFile(file, attrs);
    }

Finally, what I think about it:

1.: subdir is part of depth 1
2.: Without the visitFile for deepest directories, you wouldn't even recognize they are there.

So I think that's regular behaviour.

By the way, you could use the attrs in your override of:

@Override
public FileVisitResult visitFile(final Path file,
        final BasicFileAttributes attrs) throws IOException {
    if (attrs.isDirectory()) {
        throw new IllegalStateException("WAT!? Visiting directory: "
                + file.toAbsolutePath().toString());
    }
    System.out
            .println("Visiting file: " + file.toAbsolutePath().toString());
    return super.visitFile(file, attrs);
}

OTHER TIPS

I honestly believe

java.nio.file.Files#walkFileTree(java.nio.file.Path, java.util.Set<java.nio.file.FileVisitOption>, int, java.nio.file.FileVisitor<? super java.nio.file.Path>)

breaks the contract of java.nio.file.FileVisitor interface.

In a nutshell: visitFile method is invoked on directories too IF they are exactly on the depth level that was provided.

Which really doesn't make sense if you read the documentation of the interface java.nio.file.FileVisitor#visitFile which clearly states:

Invoked for a file in a directory.


Example:

Given the following filesystem structure:

───start_dir
   │   file1.txt
   │
   ├───dir1
   │       file11.txt
   │
   └───dir2
       └───dir21
               file211.txt

Calling walkFileTree works as expected with large enough depth

Files.walkFileTree(Paths.get("start_dir"), 
                   EnumSet.noneOf(FileVisitOption.class),
                   1000, 
                   new MyFileVisitor());

produces the following invocations which are in line with the interface:

Previsit  dir: start_dir
Previsit  dir: start_dir\dir1
Visit    file: start_dir\dir1\file11.txt
Postvisit dir: start_dir\dir1
Previsit  dir: start_dir\dir2
Previsit  dir: start_dir\dir2\dir21
Visit    file: start_dir\dir2\dir21\file211.txt
Postvisit dir: start_dir\dir2\dir21
Postvisit dir: start_dir\dir2
Visit    file: start_dir\file1.txt
Postvisit dir: start_dir

Calling walkFileTree with depth 1

Files.walkFileTree(Paths.get("start_dir"),
                   EnumSet.noneOf(FileVisitOption.class),
                   1, 
                   new MyFileVisitor());

produces the following invocations in which visitFile is invoked on first level directories too in addition to the files:

Previsit  dir: start_dir
Visit    file: start_dir\dir1
Visit    file: start_dir\dir2
Visit    file: start_dir\file1.txt
Postvisit dir: start_dir

Calling walkFileTree with depth 2

Files.walkFileTree(Paths.get("start_dir"), 
                   EnumSet.noneOf(FileVisitOption.class),
                   2, 
                   new MyFileVisitor());

produces the following invocations in which visitFile is not invoked on first level directories but invoked on second level directories:

Previsit  dir: start_dir
Previsit  dir: start_dir\dir1
Visit    file: start_dir\dir1\file11.txt
Postvisit dir: start_dir\dir1
Previsit  dir: start_dir\dir2
Visit    file: start_dir\dir2\dir21
Postvisit dir: start_dir\dir2
Visit    file: start_dir\file1.txt
Postvisit dir: start_dir

This behavior is specified in the documentation of Files::walkFileTree, so it is intentional and not a bug:

The visitFile method is invoked for all files, including directories, encountered at maxDepth

In defense of visitFile being invoked on directories, directories are implicitly called files throughout the documentation of the java.nio.file classes. For example, the documentation of Files::walkFileTree itself says, "This method walks a file tree rooted at a given starting file," and that "starting file" may (of course) be a directory.

Also note that visitFileFailed may also be invoked on directories, as explained in the documentation:

Where the file is a directory, and the directory could not be opened, then the visitFileFailed method is invoked with the I/O exception

Franz Ebner showed the code where this behavior occurs and I agree with why he thinks it is intentional: "Without the visitFile for deepest directories, you wouldn't even recognize they are there." I also concur with checking the file type using the attrs parameter if you want only regular files (or symbolic links).

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top