StringBuilder generateTreeString(
            int depth, List<Boolean> lastChildren, StringBuilder builder) {
        if (depth > 0) {
            lastChildren.subList(0, lastChildren.size() - 1).forEach(isLast -> {
                String prefixFragment = isLast ? "   " : ":  ";
                builder.append(prefixFragment);
            });

            String branch = lastChildren.get(lastChildren.size() - 1) ? "+- " : ":- ";
            builder.append(branch);
        }

        builder.append(simpleString());
        builder.append("\n");

        List<BaseType> children = children();
        if (!children.isEmpty()) {
            children.subList(0, children.size() - 1).forEach(c -> {
                c.generateTreeString(
                        depth + 1,
                        concatToList(lastChildren, false),
                        builder);
            });
            children.get(children.size() - 1).generateTreeString(
                    depth + 1,
                    concatToList(lastChildren, false),
                    builder);
        }

        return builder;
    }
Appends the string represent of this node and its children to the given StringBuilder.

The `i`-th element in `lastChildren` indicates whether the ancestor of the current node at
depth `i + 1` is the last child of its own parent node.  The depth of the root node is 0, and
`lastChildren` for the root node should be empty.
indexr-query-opt/src/main/java/io/indexr/query/TreeNode.java