きっかけ
Javaを使ってディレクトリの階層構造をツリー形式で表示する
by @ukiuki
を見て、FileVisitor でなんとかならんかなーと思ったのがきっかけ。
結論から言うと、なんとかなったけどあまり綺麗にならない…
綺麗にするのはあきらめたけどここで供養
(File/Directory に visitor パターンはしっくりこないことが多いのでそもそもトライしてません)
コード
jdk1.8.0 & pleiades neon で開発。
自前 walk 処理版
出力例
C:\Users\heignamerican\testroom\filewalk
├─dir 0\
│ ├─123.txt
│ ├─456.txt
│ └─dir01\
│ └─dir02\
├─dir1\
│ ├─dir1-1\
│ │ └─file1-1-1.txt
│ └─file1.txt
├─dir2\
│ ├─file 2.txt
│ ├─file1.txt
│ └─ほげほげ.txt
├─dir3\
├─dir4\
│ └─dir1-1 - ショートカット.lnk
└─file0.txt
C:\Users\heignamerican\testroom\filewalk\file0.txt
C:\Users\heignamerican\testroom\filewalk\dir4\dir1-1 - ショートカット.lnk
Exception in thread "main" java.lang.NullPointerException
at java.util.Objects.requireNonNull(Unknown Source)
at heignamerican.files.Walking$MyFileWalker.start(Walking.java:42)
at heignamerican.files.Walking.main(Walking.java:17)
Walking.java
package heignamerican.files;
import java.io.IOException;
import java.io.PrintStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
public class Walking {
public static void main(final String[] args) throws IOException {
new MyFileWalker(System.out).start(Paths.get("C:/Users/heignamerican/testroom/filewalk"));
new MyFileWalker(System.out).start(Paths.get("C:/Users/heignamerican/testroom/filewalk/file0.txt"));
new MyFileWalker(System.out).start(Paths.get("C:/Users/heignamerican/testroom/filewalk/dir4/dir1-1 - ショートカット.lnk"));
new MyFileWalker(System.out).start(null);
}
public static class MyFileWalker {
public MyFileWalker(final PrintStream out) {
this.out = out;
}
private final PrintStream out;
private StringBuilder indenter;
/**
* <p>
* 指定された root 以下を tree っぽく表示する。
*
* <p>
* コンストラクタで指定された {@code PrintStream} に出力する。<br>
* SymbolicLink はたどらない(通常のファイル扱い)。<br>
* スレッドセーフではない。<br>
*
* @param root
* null 禁止
* @throws IOException
*/
public void start(final Path root) throws IOException {
Objects.requireNonNull(root);
if (!Files.exists(root)) {
out.printf("'%s' not found.%n", root);
return;
}
out.println(root.toString());
if (Files.isDirectory(root)) {
indenter = new StringBuilder();
walk(root);
}
}
private static final String FILE_SEPARATOR = System.getProperty("file.separator");
private static final String PREFIX_FOR_MIDDLE = "├─";
private static final String PREFIX_FOR_LAST = "└─";
private static final String INDENT_FOR_MIDDLE = "│ ";
private static final String INDENT_FOR_LAST = " ";
private void walk(final Path path) throws IOException {
final List<Path> children = Files.list(path).collect(Collectors.toList());
final int lastIndex = children.size() - 1;
for (int index = 0; index <= lastIndex; index++) {
final Path child = children.get(index);
final boolean isDirectory = Files.isDirectory(child);
final boolean isMiddle = index != lastIndex;
out.println(indenter.toString() + (isMiddle ? PREFIX_FOR_MIDDLE : PREFIX_FOR_LAST) + child.getFileName() + (isDirectory ? FILE_SEPARATOR : ""));
if (isDirectory) {
final int oldLength = indenter.length();
indenter.append(isMiddle ? INDENT_FOR_MIDDLE : INDENT_FOR_LAST);
walk(child);
// if (notLast) { out.println(indenter); } // cmd の "tree /F" っぽくするなら入れたい
indenter.setLength(oldLength);
}
}
}
}
}
SimpleFileVisitor 版
出力例
C:\Users\heignamerican\testroom\filewalk
├─dir 0\
│ ├─123.txt
│ ├─456.txt
│ └─dir01\
│ └─dir02\
├─dir1\
│ ├─dir1-1\
│ │ └─file1-1-1.txt
│ └─file1.txt
├─dir2\
│ ├─file 2.txt
│ ├─file1.txt
│ └─ほげほげ.txt
├─dir3\
├─dir4\
│ └─dir1-1 - ショートカット.lnk
└─file0.txt
C:\Users\heignamerican\testroom\filewalk\file0.txt
C:\Users\heignamerican\testroom\filewalk\dir4\dir1-1 - ショートカット.lnk
Exception in thread "main" java.lang.NullPointerException
at java.util.Objects.requireNonNull(Unknown Source)
at heignamerican.files.Walking2$MyFileVisitor.start(Walking2.java:43)
at heignamerican.files.Walking2.main(Walking2.java:19)
Walking2.java
package heignamerican.files;
import java.io.IOException;
import java.io.PrintStream;
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;
import java.util.Objects;
import java.util.Stack;
public class Walking2 {
public static void main(final String[] args) throws IOException {
new MyFileVisitor(System.out).start(Paths.get("C:/Users/heignamerican/testroom/filewalk"));
new MyFileVisitor(System.out).start(Paths.get("C:/Users/heignamerican/testroom/filewalk/file0.txt"));
new MyFileVisitor(System.out).start(Paths.get("C:/Users/heignamerican/testroom/filewalk/dir4/dir1-1 - ショートカット.lnk"));
new MyFileVisitor(System.out).start(null);
}
public static class MyFileVisitor extends SimpleFileVisitor<Path> {
public MyFileVisitor(final PrintStream out) {
this.out = out;
}
private final PrintStream out;
/**
* <p>
* 指定された root 以下を tree っぽく表示する。
*
* <p>
* コンストラクタで指定された {@code PrintStream} に出力する。<br>
* SymbolicLink はたどらない(通常のファイル扱い)。<br>
* スレッドセーフではない。<br>
*
* @param root
* null 禁止
* @throws IOException
*/
public void start(final Path path) throws IOException {
Objects.requireNonNull(path);
if (!Files.exists(path)) {
out.printf("'%s' not found.%n", path);
return;
}
root = path;
childrenCounts = new Stack<>();
indenter = new StringBuilder();
Files.walkFileTree(root, this);
}
private static final String FILE_SEPARATOR = System.getProperty("file.separator");
private Path root;
private Stack<Long> childrenCounts;
private StringBuilder indenter;
@Override
public FileVisitResult visitFile(final Path path, final BasicFileAttributes attributes) throws IOException {
if (path == root) {
out.println(path);
} else {
out.println(indenter.toString() + getHereIndent() + path.getFileName());
countdownChildren();
}
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult preVisitDirectory(final Path path, final BasicFileAttributes attributes) throws IOException {
if (path == root) {
out.println(path);
listupChildrenCount(path);
} else {
out.println(indenter.toString() + getHereIndent() + path.getFileName() + FILE_SEPARATOR);
increseIndent();
listupChildrenCount(path);
}
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(final Path path, final IOException attributes) throws IOException {
if (path != root) {
decreseIndent();
countdownChildren();
}
return FileVisitResult.CONTINUE;
}
private void listupChildrenCount(final Path path) throws IOException {
final long newer = Files.list(path).count();
if (newer > 0) {
childrenCounts.push(newer);
}
}
private void countdownChildren() {
final long rest = childrenCounts.pop() - 1;
if (rest > 0) {
childrenCounts.push(rest);
}
}
private String getHereIndent() {
return (childrenCounts.peek() > 1L) ? "├─" : "└─"; // インデントの文字数を 2 で固定する
}
private void increseIndent() {
indenter.append((childrenCounts.peek() > 1L) ? "│ " : " "); // インデントの文字数を 2 で固定する
}
private void decreseIndent() {
indenter.setLength(indenter.length() - 2); // インデントの文字数を 2 で固定する
}
}
}
反省
- ちょっと最後の方力尽きてます…
- 「最後じゃない」っていう英語表現が分からず、最後まで分からない変数名