Skip to content

Commit b74b184

Browse files
New implementation: RECURSIVE (working)
1 parent 3e9a589 commit b74b184

30 files changed

+508
-97
lines changed

ROADMAP.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
- [x] Implement emojis for files
1212
- [x] Unify dir-in-a-row into a single entry option
1313
- [x] Max depth options
14+
- [x] Alternative implementation option, just for fun (using DirectoryStream instead of Files.walkFileTree's Visitor)
1415
- [ ] Cleaner code
1516
- [ ] Refactor unit tests (custom assert?)
1617
- [ ] Publish on Maven Central!
@@ -23,4 +24,3 @@
2324
- [ ] Follow symlink option
2425
- [ ] Print optional legend for symlink/other file types symbols (at the end of the tree)
2526
- [ ] File attributes LineRenderer (size, author, createAt, etc.)
26-
- [ ] Alternative implementation option, just for fun (using DirectoryStream instead of Files.walkFileTree)

src/example/java/io/github/computerdaddyguy/jfiletreeprettyprinter/example/FileTreeFormat.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
package io.github.computerdaddyguy.jfiletreeprettyprinter.example;
22

33
import io.github.computerdaddyguy.jfiletreeprettyprinter.FileTreePrettyPrinter;
4-
import io.github.computerdaddyguy.jfiletreeprettyprinter.visitor.RenderingOptions.TreeFormat;
4+
import io.github.computerdaddyguy.jfiletreeprettyprinter.options.RenderingOptions.TreeFormat;
55

66
public class FileTreeFormat {
77

88
public static void main(String[] args) {
99
var prettyPrinter = FileTreePrettyPrinter.builder()
10-
.customizeOptions(options -> options.withTreeFormat(TreeFormat.UNICODE_BOX_DRAWING))
10+
.customizeOptions(options -> options.withTreeFormat(TreeFormat.CLASSIC_ASCII))
1111
.build();
1212
var tree = prettyPrinter.prettyPrint("src/example/resources/tree_format");
1313
System.out.println(tree);

src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/FileTreePrettyPrinterBuilder.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package io.github.computerdaddyguy.jfiletreeprettyprinter;
22

3-
import io.github.computerdaddyguy.jfiletreeprettyprinter.visitor.VisitingFileTreePrettyPrinter;
3+
import io.github.computerdaddyguy.jfiletreeprettyprinter.impl.recursive.RecursiveFileTreePrettyPrinter;
4+
import io.github.computerdaddyguy.jfiletreeprettyprinter.impl.visitor.VisitingFileTreePrettyPrinter;
45
import java.util.Objects;
56
import java.util.function.Function;
67
import org.jspecify.annotations.NullMarked;
@@ -11,7 +12,10 @@ public class FileTreePrettyPrinterBuilder {
1112
private PrettyPrintOptions options = PrettyPrintOptions.createDefault();
1213

1314
public FileTreePrettyPrinter build() {
14-
return new VisitingFileTreePrettyPrinter(options);
15+
return switch (options.getImplementation()) {
16+
case VISITOR -> new VisitingFileTreePrettyPrinter(options);
17+
case RECURSIVE -> new RecursiveFileTreePrettyPrinter(options);
18+
};
1519
}
1620

1721
public FileTreePrettyPrinterBuilder withOptions(PrettyPrintOptions options) {

src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/PrettyPrintOptions.java

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,21 @@
11
package io.github.computerdaddyguy.jfiletreeprettyprinter;
22

3-
import io.github.computerdaddyguy.jfiletreeprettyprinter.visitor.RenderingOptions;
4-
import io.github.computerdaddyguy.jfiletreeprettyprinter.visitor.VisitingOptions;
3+
import io.github.computerdaddyguy.jfiletreeprettyprinter.options.ImplementationOptions;
4+
import io.github.computerdaddyguy.jfiletreeprettyprinter.options.RenderingOptions;
5+
import io.github.computerdaddyguy.jfiletreeprettyprinter.options.VisitingOptions;
56
import java.nio.file.Path;
67
import java.util.Objects;
78
import java.util.function.Function;
89
import org.jspecify.annotations.NullMarked;
910

1011
@NullMarked
11-
public class PrettyPrintOptions implements VisitingOptions, RenderingOptions {
12+
public class PrettyPrintOptions implements VisitingOptions, RenderingOptions, ImplementationOptions {
1213

1314
private Function<Path, Integer> childrenLimitFunction = p -> -1;
1415

16+
private Implementation impl = Implementation.RECURSIVE;
17+
// private Implementation impl = Implementation.VISITOR;
18+
1519
private TreeFormat treeFormat = TreeFormat.UNICODE_BOX_DRAWING;
1620
private boolean emojis = false;
1721
private boolean compactDirectories = false;
@@ -30,6 +34,23 @@ public static PrettyPrintOptions createDefault() {
3034

3135
// ----------------------------------------------
3236

37+
public enum Implementation {
38+
VISITOR,
39+
RECURSIVE;
40+
}
41+
42+
@Override
43+
public Implementation getImplementation() {
44+
return impl;
45+
}
46+
47+
public PrettyPrintOptions withImplementation(Implementation impl) {
48+
this.impl = Objects.requireNonNull(impl, "implementation is null");
49+
return this;
50+
}
51+
52+
// ----------------------------------------------
53+
3354
@Override
3455
public Function<Path, Integer> getChildrenLimitFunction() {
3556
return childrenLimitFunction;

src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/visitor/Depth.java renamed to src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/depth/Depth.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package io.github.computerdaddyguy.jfiletreeprettyprinter.visitor;
1+
package io.github.computerdaddyguy.jfiletreeprettyprinter.depth;
22

33
import java.util.ArrayList;
44
import java.util.List;
@@ -10,8 +10,8 @@ public class Depth {
1010

1111
private final List<DepthSymbol> symbols;
1212

13-
public Depth() {
14-
this(List.of());
13+
public static Depth createNewEmpty() {
14+
return new Depth(List.of());
1515
}
1616

1717
protected Depth(List<DepthSymbol> symbols) {

src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/visitor/DepthSymbol.java renamed to src/main/java/io/github/computerdaddyguy/jfiletreeprettyprinter/depth/DepthSymbol.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package io.github.computerdaddyguy.jfiletreeprettyprinter.visitor;
1+
package io.github.computerdaddyguy.jfiletreeprettyprinter.depth;
22

33
import org.jspecify.annotations.NullMarked;
44

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package io.github.computerdaddyguy.jfiletreeprettyprinter.impl.recursive;
2+
3+
import io.github.computerdaddyguy.jfiletreeprettyprinter.FileTreePrettyPrinter;
4+
import io.github.computerdaddyguy.jfiletreeprettyprinter.PrettyPrintOptions;
5+
import io.github.computerdaddyguy.jfiletreeprettyprinter.impl.recursive.handler.PathToTreeMapper;
6+
import io.github.computerdaddyguy.jfiletreeprettyprinter.renderer.LineRenderer;
7+
import java.nio.file.Path;
8+
import java.util.Objects;
9+
import org.jspecify.annotations.NullMarked;
10+
11+
@NullMarked
12+
public class RecursiveFileTreePrettyPrinter implements FileTreePrettyPrinter {
13+
14+
private final PrettyPrintOptions options;
15+
16+
public RecursiveFileTreePrettyPrinter(PrettyPrintOptions options) {
17+
this.options = Objects.requireNonNull(options, "options cannot be null");
18+
}
19+
20+
@Override
21+
public String prettyPrint(Path path) {
22+
var depth = 0;
23+
var pathHandler = PathToTreeMapper.create(options);
24+
var tree = pathHandler.handle(depth, path);
25+
26+
var lineRenderer = LineRenderer.create(options);
27+
var treeRenderer = new TreeEntryRenderer(options, lineRenderer);
28+
return treeRenderer.renderTree(tree);
29+
}
30+
31+
@Override
32+
public PrettyPrintOptions getOptions() {
33+
return options;
34+
}
35+
36+
}
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
package io.github.computerdaddyguy.jfiletreeprettyprinter.impl.recursive;
2+
3+
import java.io.IOException;
4+
import java.nio.file.Path;
5+
import java.nio.file.attribute.BasicFileAttributes;
6+
import java.util.Collection;
7+
import java.util.List;
8+
import java.util.Objects;
9+
import org.jspecify.annotations.NullMarked;
10+
11+
@NullMarked
12+
public sealed interface TreeEntry {
13+
14+
final class DirectoryEntry implements TreeEntry {
15+
16+
final Path dir;
17+
final List<TreeEntry> entries;
18+
19+
public DirectoryEntry(Path dir, List<TreeEntry> entries) {
20+
this.dir = Objects.requireNonNull(dir, "dir is null");
21+
this.entries = Objects.requireNonNull(entries, "entries is null");
22+
}
23+
24+
@Override
25+
public String toString() {
26+
return "DirectoryEntry[dir: " + dir.getFileName() + ", entries: " + entries + "]";
27+
}
28+
29+
}
30+
31+
final class DirectoryReadingAttributesExceptionEntry implements TreeEntry {
32+
33+
final Path dir;
34+
final IOException exception;
35+
36+
public DirectoryReadingAttributesExceptionEntry(Path dir, IOException exception) {
37+
this.dir = Objects.requireNonNull(dir, "dir is null");
38+
this.exception = Objects.requireNonNull(exception, "exception is null");
39+
}
40+
41+
@Override
42+
public String toString() {
43+
return "DirectoryReadingAttributesExceptionEntry[dir: " + dir.getFileName() + ", exception: " + exception + "]";
44+
}
45+
46+
}
47+
48+
final class FileEntry implements TreeEntry {
49+
50+
final Path file;
51+
final BasicFileAttributes attrs;
52+
53+
public FileEntry(Path file, BasicFileAttributes attrs) {
54+
this.file = Objects.requireNonNull(file, "file is null");
55+
this.attrs = Objects.requireNonNull(attrs, "attrs is null");
56+
}
57+
58+
@Override
59+
public String toString() {
60+
return "FileEntry[file: " + file.getFileName() + "]";
61+
}
62+
63+
}
64+
65+
final class FileReadingAttributesExceptionEntry implements TreeEntry {
66+
67+
final Path file;
68+
final IOException exception;
69+
70+
public FileReadingAttributesExceptionEntry(Path file, IOException exception) {
71+
this.file = Objects.requireNonNull(file, "file is null");
72+
this.exception = Objects.requireNonNull(exception, "exception is null");
73+
}
74+
75+
@Override
76+
public String toString() {
77+
return "FileReadingAttributesExceptionEntry[file: " + file.getFileName() + ", exception: " + exception + "]";
78+
}
79+
80+
}
81+
82+
final class SkippedChildrenEntry implements TreeEntry {
83+
84+
final Collection<Path> skippedChildren;
85+
86+
public SkippedChildrenEntry(List<Path> skippedChildren) {
87+
this.skippedChildren = Objects.requireNonNull(skippedChildren, "skippedChildren is null");
88+
}
89+
90+
@Override
91+
public String toString() {
92+
return "SkippedChildrenEntry[skippedChildren: " + skippedChildren.stream().map(Path::getFileName).toList() + "]";
93+
}
94+
95+
}
96+
97+
final class DirectoryListingExceptionEntry implements TreeEntry {
98+
99+
final Path dir;
100+
final IOException exception;
101+
102+
public DirectoryListingExceptionEntry(Path dir, IOException exception) {
103+
this.dir = Objects.requireNonNull(dir, "dir is null");
104+
this.exception = Objects.requireNonNull(exception, "exception is null");
105+
}
106+
107+
@Override
108+
public String toString() {
109+
return "DirectoryListingExceptionEntry[exception: " + exception + "]";
110+
}
111+
112+
}
113+
114+
final class MaxDepthReachEntry implements TreeEntry {
115+
116+
final int depth;
117+
118+
public MaxDepthReachEntry(int depth) {
119+
if (depth < 0) {
120+
throw new IllegalArgumentException("depth reached cannot be negative");
121+
}
122+
this.depth = depth;
123+
}
124+
125+
@Override
126+
public String toString() {
127+
return "MaxDepthReachEntry[depth: " + depth + "]";
128+
}
129+
130+
}
131+
132+
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
package io.github.computerdaddyguy.jfiletreeprettyprinter.impl.recursive;
2+
3+
import io.github.computerdaddyguy.jfiletreeprettyprinter.PrettyPrintOptions;
4+
import io.github.computerdaddyguy.jfiletreeprettyprinter.depth.Depth;
5+
import io.github.computerdaddyguy.jfiletreeprettyprinter.depth.DepthSymbol;
6+
import io.github.computerdaddyguy.jfiletreeprettyprinter.impl.recursive.TreeEntry.DirectoryEntry;
7+
import io.github.computerdaddyguy.jfiletreeprettyprinter.impl.recursive.TreeEntry.DirectoryListingExceptionEntry;
8+
import io.github.computerdaddyguy.jfiletreeprettyprinter.impl.recursive.TreeEntry.DirectoryReadingAttributesExceptionEntry;
9+
import io.github.computerdaddyguy.jfiletreeprettyprinter.impl.recursive.TreeEntry.FileEntry;
10+
import io.github.computerdaddyguy.jfiletreeprettyprinter.impl.recursive.TreeEntry.FileReadingAttributesExceptionEntry;
11+
import io.github.computerdaddyguy.jfiletreeprettyprinter.impl.recursive.TreeEntry.MaxDepthReachEntry;
12+
import io.github.computerdaddyguy.jfiletreeprettyprinter.impl.recursive.TreeEntry.SkippedChildrenEntry;
13+
import io.github.computerdaddyguy.jfiletreeprettyprinter.renderer.LineRenderer;
14+
import java.nio.file.Path;
15+
import java.util.ArrayList;
16+
import java.util.List;
17+
import java.util.Objects;
18+
import org.jspecify.annotations.NullMarked;
19+
20+
@NullMarked
21+
class TreeEntryRenderer {
22+
23+
private final PrettyPrintOptions options;
24+
private final LineRenderer lineRenderer;
25+
26+
public TreeEntryRenderer(PrettyPrintOptions options, LineRenderer lineRenderer) {
27+
this.options = Objects.requireNonNull(options, "options is null");
28+
this.lineRenderer = Objects.requireNonNull(lineRenderer, "lineRenderer is null");
29+
}
30+
31+
public String renderTree(TreeEntry entry) {
32+
var depth = Depth.createNewEmpty();
33+
var buff = new StringBuilder();
34+
renderTree(entry, depth, buff);
35+
return buff.toString();
36+
}
37+
38+
public void renderTree(TreeEntry entry, Depth depth, StringBuilder buff) {
39+
switch (entry) {
40+
case TreeEntry.DirectoryEntry dirEntry -> renderDirectory(dirEntry, depth, buff, List.of(dirEntry.dir));
41+
case TreeEntry.FileEntry fileEntry -> renderFile(fileEntry, depth, buff);
42+
case TreeEntry.SkippedChildrenEntry skippedChildrenEntry -> renderSkippedChildrenEntry(skippedChildrenEntry, depth, buff);
43+
case TreeEntry.MaxDepthReachEntry maxDepthReachEntry -> renderMaxDepthReachEntry(maxDepthReachEntry, depth, buff);
44+
case TreeEntry.DirectoryReadingAttributesExceptionEntry dirReadingAttrsException -> renderDirReadingAttributesExceptionEntry(dirReadingAttrsException, depth, buff);
45+
case TreeEntry.DirectoryListingExceptionEntry dirListingException -> renderDirectoryListingExceptionEntry(dirListingException, depth, buff);
46+
case TreeEntry.FileReadingAttributesExceptionEntry fileReadingAttrsException -> renderFileReadingAttributesExceptionEntry(fileReadingAttrsException, depth, buff);
47+
}
48+
}
49+
50+
private void renderDirectory(DirectoryEntry dirEntry, Depth depth, StringBuilder buff, List<Path> compactPaths) {
51+
52+
if (options.areCompactDirectoriesUsed()) {
53+
if (dirEntry.entries.size() == 1 && dirEntry.entries.get(0) instanceof DirectoryEntry childDirEntry) {
54+
var newCompactPaths = new ArrayList<>(compactPaths);
55+
newCompactPaths.add(childDirEntry.dir);
56+
renderDirectory(childDirEntry, depth, buff, newCompactPaths);
57+
return;
58+
}
59+
}
60+
61+
buff.append(lineRenderer.renderDirectoryBegin(depth, compactPaths));
62+
63+
var childIt = dirEntry.entries.iterator();
64+
65+
if (childIt.hasNext()) {
66+
buff.append('\n');
67+
}
68+
69+
while (childIt.hasNext()) {
70+
var childEntry = childIt.next();
71+
var childDepth = depth.append(childIt.hasNext() ? DepthSymbol.NON_LAST_FILE : DepthSymbol.LAST_FILE);
72+
renderTree(childEntry, childDepth, buff);
73+
if (childIt.hasNext()) {
74+
buff.append('\n');
75+
}
76+
}
77+
}
78+
79+
private void renderFile(FileEntry fileEntry, Depth depth, StringBuilder buff) {
80+
buff.append(lineRenderer.renderFile(depth, fileEntry.file, fileEntry.attrs));
81+
}
82+
83+
private void renderSkippedChildrenEntry(SkippedChildrenEntry skippedChildrenEntry, Depth depth, StringBuilder buff) {
84+
buff.append(lineRenderer.renderChildrenLimitReached(depth, skippedChildrenEntry.skippedChildren));
85+
}
86+
87+
private void renderMaxDepthReachEntry(MaxDepthReachEntry maxDepthReachEntry, Depth depth, StringBuilder buff) {
88+
buff.append(lineRenderer.renderMaxDepthReached(depth));
89+
}
90+
91+
private void renderDirReadingAttributesExceptionEntry(DirectoryReadingAttributesExceptionEntry dirReadingAttrsException, Depth depth, StringBuilder buff) {
92+
buff.append(lineRenderer.renderDirectoryException(depth, dirReadingAttrsException.dir, dirReadingAttrsException.exception));
93+
}
94+
95+
private void renderDirectoryListingExceptionEntry(DirectoryListingExceptionEntry dirListingException, Depth depth, StringBuilder buff) {
96+
buff.append(lineRenderer.renderDirectoryException(depth, dirListingException.dir, dirListingException.exception));
97+
}
98+
99+
private void renderFileReadingAttributesExceptionEntry(FileReadingAttributesExceptionEntry fileReadingAttrsException, Depth depth, StringBuilder buff) {
100+
buff.append(lineRenderer.renderFileException(depth, fileReadingAttrsException.file, fileReadingAttrsException.exception));
101+
}
102+
103+
}

0 commit comments

Comments
 (0)