Tree Display

Tree Display

The tree display utility renders hierarchical data as formatted text trees in the terminal. It supports multiple visual styles, depth limiting, and both a simple node-based API and a generic builder for traversing existing typed hierarchies.

Quick Start

import org.aesh.util.tree.Tree;
import org.aesh.util.tree.TreeNode;

TreeNode root = TreeNode.of("project")
        .child(TreeNode.of("src")
                .child("Main.java")
                .child("Util.java"))
        .child(TreeNode.of("test")
                .child("MainTest.java"));

String output = Tree.render(root);
invocation.println(output);

Output:

project
├── src
│   ├── Main.java
│   └── Util.java
└── test
    └── MainTest.java

TreeNode API

TreeNode provides a fluent API for building tree structures manually:

TreeNode root = TreeNode.of("root")          // factory method
        .child(TreeNode.of("branch")         // add subtree
                .child("leaf1")              // add leaf by label
                .child("leaf2"))
        .child("another-leaf");
MethodReturnsDescription
TreeNode.of(label)TreeNodeCreates a new node
child(TreeNode)thisAdds an existing node as child
child(String)thisCreates and adds a leaf node
label()StringReturns the node’s label
children()ListReturns unmodifiable children list

Static API

For quick rendering of TreeNode trees:

// Default UNICODE style
String output = Tree.render(root);

// Custom style
String output = Tree.render(root, TreeStyle.ASCII);

Builder API

For rendering existing typed hierarchies without converting to TreeNode:

import org.aesh.util.tree.Tree;
import org.aesh.util.tree.TreeStyle;

String output = Tree.<File>builder()
        .label(File::getName)
        .children(f -> f.isDirectory()
                ? Arrays.asList(f.listFiles())
                : Collections.emptyList())
        .style(TreeStyle.UNICODE)
        .maxDepth(3)
        .build()
        .render(rootDir);
MethodDefaultDescription
label(Function<T, String>)requiredExtracts display text from each node
children(Function<T, List<T>>)requiredExtracts children (null/empty = leaf)
style(TreeStyle)UNICODEVisual style for connectors
maxDepth(int)-1Max depth (-1 = unlimited, 0 = root’s children)

Calling build() throws IllegalStateException if label or children are not set.

Tree Styles

Four predefined styles are available via TreeStyle:

UNICODE (default):

root
├── child1
│   ├── grandchild1
│   └── grandchild2
└── child2

ASCII:

root
+-- child1
|   +-- grandchild1
|   \-- grandchild2
\-- child2

COMPACT:

root
├─ child1
│  ├─ grandchild1
│  └─ grandchild2
└─ child2

ROUNDED:

root
├── child1
│   ├── grandchild1
│   ╰── grandchild2
╰── child2

Max Depth

Use maxDepth to limit how deep the tree renders. When a node has children beyond the depth limit, ... is displayed:

String output = Tree.<TreeNode>builder()
        .label(TreeNode::label)
        .children(TreeNode::children)
        .maxDepth(1)
        .build()
        .render(root);
root
├── child1
│   ├── grandchild1
│   └── grandchild2
└── child2
    ...

With maxDepth(0), only the root’s direct children are shown:

root
├── child1
│   ...
└── child2
    ...

Using in Commands

Here is a complete command example that displays a directory tree:

@CommandDefinition(name = "tree", description = "Display directory tree")
public class TreeCommand implements Command<CommandInvocation> {

    @Argument(description = "Root directory")
    private Resource root;

    @Option(name = "depth", shortName = 'd', defaultValue = {"-1"},
            description = "Max depth (-1 for unlimited)")
    private int maxDepth;

    @Option(name = "style", shortName = 's', defaultValue = {"UNICODE"},
            description = "Tree style: ASCII, UNICODE, COMPACT, ROUNDED")
    private TreeStyle style;

    @Override
    public CommandResult execute(CommandInvocation invocation) {
        File dir = new File(root.getAbsolutePath());
        String output = Tree.<File>builder()
                .label(File::getName)
                .children(f -> f.isDirectory()
                        ? Arrays.asList(f.listFiles())
                        : Collections.emptyList())
                .style(style)
                .maxDepth(maxDepth)
                .build()
                .render(dir);

        invocation.println(output);
        return CommandResult.SUCCESS;
    }
}

Edge Cases

ScenarioBehavior
Root with no childrenOnly the root label is printed
children() returns nullTreated as a leaf node (no crash)
children() returns empty listTreated as a leaf node
maxDepth(-1)Unlimited depth (default)
maxDepth(0)Root’s children only, truncated below

API Reference

TreeNode

MethodReturnsDescription
of(String)TreeNodeFactory method
child(TreeNode)TreeNodeAdd subtree, returns this
child(String)TreeNodeAdd leaf, returns this
label()StringGet label
children()ListUnmodifiable children list

Tree (static)

MethodReturnsDescription
render(TreeNode)StringRender with UNICODE style
render(TreeNode, TreeStyle)StringRender with specified style
builder()BuilderCreate a generic builder

TreeStyle

ConstantBranchLastVerticalSpace
ASCII+--\--|
UNICODE├──└──
COMPACT├─└─
ROUNDED├──╰──

See Also