NIOでフォルダを再帰処理する方法メモ


#環境
##OS
Windows7 64bit

##Java
1.7.0_51

#基本
```text:フォルダ階層
nio
│  file1.txt
│
└─dir1
    │  file2.txt
    │
    └─dir2
            file3.txt
```

```java:MyFileVisitor.java
package sample.nio;

import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.FileVisitor;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;

import org.apache.commons.lang3.text.StrBuilder;

public class MyFileVisitor implements FileVisitor<Path> {
    protected int indentSize;

    @Override
    public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
        print("preVisitDirectory : " + dir.getFileName());
        this.indentSize++;
        return FileVisitResult.CONTINUE;
    }

    @Override
    public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
        print("visitFile : " + file.getFileName());
        return FileVisitResult.CONTINUE;
    }

    @Override
    public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
        this.indentSize--;
        print("postVisitDirectory : " + dir.getFileName());
        return FileVisitResult.CONTINUE;
    }

    @Override
    public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
        String error = String.format(" [exception=%s, message=%s]", exc.getClass(), exc.getMessage());
        
        print("visitFileFailed : " + file.getFileName() + error);
        
        return FileVisitResult.CONTINUE;
    }
    
    protected void print(String message) {
        System.out.println(new StrBuilder().appendPadding(this.indentSize, ' ').append(message));
    }
}
```

```java:Main.java
package sample.nio;

import java.io.IOException;
import java.nio.file.FileVisitor;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

public class Main {
    public static void main(String[] args) throws IOException {
        Path start = Paths.get("nio");
        FileVisitor<Path> visitor = new MyFileVisitor();
        
        Files.walkFileTree(start, visitor);
    }
}
```

```text:実行結果
preVisitDirectory : nio
 preVisitDirectory : dir1
  preVisitDirectory : dir2
   visitFile : file3.txt
  postVisitDirectory : dir2
  visitFile : file2.txt
 postVisitDirectory : dir1
 visitFile : file1.txt
postVisitDirectory : nio
```

- `Files.walkFileTree(Path, FileVisitor)` で指定したフォルダ以下を再帰的に処理できる。
- 各フォルダの検索前後で `preVisitDirectory()` と `postVisitDirectory()` がコールバックされる。
- 各ファイルごとに `visitFile()` がコールバックされる。

#フォルダのリンクがあった場合の処理
```text:フォルダ階層
│  file1.txt
│
├─dir1
│  │  file2.txt
│  │
│  └─dir2
│          file3.txt
│
├─junction_dir2
│      file3.txt
│
└─symbolic_dir2
       file3.txt
```

```text:実行結果
preVisitDirectory : nio
 preVisitDirectory : dir1
  preVisitDirectory : dir2
   visitFile : file3.txt
  postVisitDirectory : dir2
  visitFile : file2.txt
 postVisitDirectory : dir1
 visitFile : file1.txt
 preVisitDirectory : junction_dir2
  visitFile : file3.txt
 postVisitDirectory : junction_dir2
 visitFile : symbolic_dir2
postVisitDirectory : nio
```

- `junction_dir2` が `dir1\dir2` のジャンクション、 `symbolic_dir2` がシンボリックリンクになっている。
- ジャンクションは再帰対象になるが、シンボリックリンクは対象にならない。

#シンボリックリンクのフォルダも再帰対象にする
```text:フォルダ階層
│  file1.txt
│
├─dir1
│  │  file2.txt
│  │
│  └─dir2
│          file3.txt
│
├─junction_dir2
│      file3.txt
│
└─symbolic_dir2
       file3.txt
```

```java:Main.java
package sample.nio;

import java.io.IOException;
import java.nio.file.FileVisitOption;
import java.nio.file.FileVisitor;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.EnumSet;
import java.util.Set;

public class Main {
    public static void main(String[] args) throws IOException {
        Path start = Paths.get("nio");
        FileVisitor<Path> visitor = new MyFileVisitor();
        
        Set<FileVisitOption> options = EnumSet.of(FileVisitOption.FOLLOW_LINKS);
        int maxDepth = Integer.MAX_VALUE;
        
        Files.walkFileTree(start, options, maxDepth, visitor);
    }
}
```

```text:実行結果
preVisitDirectory : nio
 preVisitDirectory : dir1
  preVisitDirectory : dir2
   visitFile : file3.txt
  postVisitDirectory : dir2
  visitFile : file2.txt
 postVisitDirectory : dir1
 visitFile : file1.txt
 preVisitDirectory : junction_dir2
  visitFile : file3.txt
 postVisitDirectory : junction_dir2
 preVisitDirectory : symbolic_dir2
  visitFile : file3.txt
 postVisitDirectory : symbolic_dir2
postVisitDirectory : nio
```

- `walkFileTree(Path, Set<FileVisitOption>, int, FileVisitor)` メソッドを使い、 `Set<FileVisitOption>` で `FileVisitOption.FOLLOW_LINKS` を渡すと、シンボリックリンクのフォルダも再帰処理の対象になる。


#再帰の深さを指定する
```text:フォルダ階層
└─dir1
    └─dir2
        └─dir3
```

```java:Main.java
package sample.nio;

import java.io.IOException;
import java.nio.file.FileVisitOption;
import java.nio.file.FileVisitor;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.EnumSet;
import java.util.Set;

public class Main {
    public static void main(String[] args) throws IOException {
        Path start = Paths.get("nio");
        Set<FileVisitOption> options = EnumSet.allOf(FileVisitOption.class);
        
        int maxDepth = 2; // 深さ 2 を指定
        FileVisitor<Path> visitor = new MyFileVisitor();
        
        Files.walkFileTree(start, options, maxDepth, visitor);
    }
}
```

```text:実行結果
preVisitDirectory : nio
 preVisitDirectory : dir1
  visitFile : dir2
 postVisitDirectory : dir1
postVisitDirectory : nio
```

- `maxDepth` で再帰の深さを指定できる。
- `maxDepth` で指定した深さにあるフォルダは、 `visitFile()` で処理される。

#リンクのフォルダを辿った結果、ループが検出された場合の処理
```text:フォルダ階層
│  file1.txt
│
└─dir1
    │  file2.txt
    │
    └─dir2
        │  file3.txt
        │
        └─link_dir1
```

```text:実行結果
preVisitDirectory : nio
 preVisitDirectory : dir1
  preVisitDirectory : dir2
   visitFile : file3.txt
   visitFileFailed : link_dir1 [exception=class java.nio.file.FileSystemLoopException, message=nio\dir1\dir2\link_dir1]
  postVisitDirectory : dir2
  visitFile : file2.txt
 postVisitDirectory : dir1
 visitFile : file1.txt
postVisitDirectory : nio
```

- `link_dir1` が、 `dir1` のジャンクションになっており、再帰処理をすると無限ループが発生するようになっている。
- 再帰処理でループが検出された場合、 `visitFileFailed()` がコールバックされる。
- このとき、 `FileSystemLoopException` が `visitFileFailed()` メソッドに渡される。


#任意のタイミングで再帰検索を中断する
```text:フォルダ階層
└─dir1
   ├─dir2
   └─terminate
```

```java:TerminateFileVisitor.java
package sample.nio;

import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;

public class TerminateFileVisitor extends MyFileVisitor {

    @Override
    public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
        print("preVisitDirectory : " + dir.getFileName());
        this.indentSize++;
        
        if ("terminate".equals(dir.getFileName().toString())) {
            System.out.println("TERMINATE!!");
            return FileVisitResult.TERMINATE;
        } else {
            return FileVisitResult.CONTINUE;
        }
    }
}
```

```java:Main.java
package sample.nio;

import java.io.IOException;
import java.nio.file.FileVisitor;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

public class Main {
    public static void main(String[] args) throws IOException {
        Path start = Paths.get("nio");
        FileVisitor<Path> visitor = new TerminateFileVisitor();
        
        Files.walkFileTree(start, visitor);
    }
}
```

```text:実行結果
preVisitDirectory : nio
 preVisitDirectory : dir1
  preVisitDirectory : dir2
  postVisitDirectory : dir2
  preVisitDirectory : terminate
TERMINATE!!
```

- `FileVisitor` のメソッドが `FileVisitResult.TERMINATE` を返すと、その時点で再帰処理は中断される。

#特定のフォルダだけ再帰処理をしないようにする
```text:フォルダ階層
├─dir1
│  └─dir2
└─skip
    └─dir3
```

```java:SkipSubtreeFileVisitor.java
package sample.nio;

import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;

public class SkipSubtreeFileVisitor extends MyFileVisitor {

    @Override
    public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
        print("preVisitDirectory : " + dir.getFileName());
        this.indentSize++;
        
        if ("skip".equals(dir.getFileName().toString())) {
            System.out.println("SKIP!!");
            this.indentSize--;
            return FileVisitResult.SKIP_SUBTREE;
        } else {
            return FileVisitResult.CONTINUE;
        }
    }
}
```

```java:Main.java
package sample.nio;

import java.io.IOException;
import java.nio.file.FileVisitor;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

public class Main {
    public static void main(String[] args) throws IOException {
        Path start = Paths.get("nio");
        FileVisitor<Path> visitor = new SkipSubtreeFileVisitor();
        
        Files.walkFileTree(start, visitor);
    }
}
```

```text:実行結果
preVisitDirectory : nio
 preVisitDirectory : dir1
  preVisitDirectory : dir2
  postVisitDirectory : dir2
 postVisitDirectory : dir1
 preVisitDirectory : skip
SKIP!!
postVisitDirectory : nio
```

- `preVisitDirectory()` メソッドが `FileVisitResult.SKIP_SUBTREE` を返した場合、そのフォルダはスキップされる（`postVisitDirectory()` メソッドも呼ばれない）。

#任意のタイミングで、特定のフォルダ以下の処理だけを中断させる
```text:フォルダ階層
├─dir1
│  │  bbb_skip.txt
│  │
│  ├─aaa
│  └─ccc
└─dir2
```

```java:SkipSiblingsFileVisitor.java
package sample.nio;

import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;

public class SkipSiblingsFileVisitor extends MyFileVisitor {
    
    @Override
    public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
        print("visitFile : " + file.getFileName());
        
        if (file.getFileName().toString().contains("skip")) {
            System.out.println("SKIP!!");
            return FileVisitResult.SKIP_SIBLINGS;
        } else {
            return FileVisitResult.CONTINUE;
        }
    }
}
```

```java:Main.java
package sample.nio;

import java.io.IOException;
import java.nio.file.FileVisitor;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

public class Main {
    public static void main(String[] args) throws IOException {
        Path start = Paths.get("nio");
        FileVisitor<Path> visitor = new SkipSiblingsFileVisitor();
        
        Files.walkFileTree(start, visitor);
    }
}
```

```text:実行結果
preVisitDirectory : nio
 preVisitDirectory : dir1
  preVisitDirectory : aaa
  postVisitDirectory : aaa
  visitFile : bbb_skip.txt
SKIP!!
 postVisitDirectory : dir1
 preVisitDirectory : dir2
 postVisitDirectory : dir2
postVisitDirectory : nio
```

- `FileVisitResult.SKIP_SIBLINGS` を返すと、現在処理中のフォルダに存在する未処理のファイルとフォルダは全て処理をスキップする。

#SimpleFileVisitor を使って必要な処理だけ実装する
`SimpleFileVisitor` は `FileVisitor` インターフェースの実装クラスで、 `FileVisitor` の各メソッドのデフォルト実装を提供している。

`FileVisitor` の全てのメソッドを実装する必要がない場合、 `SimpleFileVisitor` を継承して必要なメソッドだけオーバーライドする。

```java:
package sample.nio;

import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;

public class Main {
    public static void main(String[] args) throws IOException {
        Path start = Paths.get("nio");
        
        Files.walkFileTree(start, new SimpleFileVisitor<Path>() {
            @Override
            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                System.out.println(file.getFileName());
                return FileVisitResult.CONTINUE;
            }
        });
    }
}
```

#参考
- [Files (Java Platform SE 7) - walkFileTree](http://docs.oracle.com/javase/jp/7/api/java/nio/file/Files.html#walkFileTree%28java.nio.file.Path,%20java.util.Set,%20int,%20java.nio.file.FileVisitor%29)
- [FileVisitResult (Java Platform SE 7)](http://docs.oracle.com/javase/jp/7/api/java/nio/file/FileVisitResult.html)
- [FileVisitor (Java Platform SE 7)](http://docs.oracle.com/javase/jp/7/api/java/nio/file/FileVisitor.html)
- [SimpleFileVisitor (Java Platform SE 7)](http://docs.oracle.com/javase/jp/7/api/java/nio/file/SimpleFileVisitor.html)