org.elasticlib.console.display.TreePrinter.java Source code

Java tutorial

Introduction

Here is the source code for org.elasticlib.console.display.TreePrinter.java

Source

/* 
 * Copyright 2014 Guillaume Masclet <guillaume.masclet@yahoo.fr>.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.elasticlib.console.display;

import com.google.common.base.Splitter;
import com.google.common.collect.Sets;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
import org.elasticlib.common.hash.Hash;
import org.elasticlib.common.model.Revision;

/**
 * Draws a text-based representation of a revision tree.
 */
class TreePrinter {

    private static final String STRAIGHT_EDGE = "| ";
    private static final String MERGE_EDGE = "|\\ ";
    private static final String RIGHT_EDGE = "\\ ";
    private static final String LEFT_EDGE = "/";
    private static final String SPACE = " ";
    private static final String DOUBLE_SPACE = "  ";
    private static final String REVISION = "*";
    private static final String DASH = "-";
    private static final String DOT = ".";

    private final int padding;
    private final Function<Revision, String> formatter;
    private final StringBuilder builder = new StringBuilder();
    private final List<Hash> previousBranches = new ArrayList<>();
    private final List<Hash> nextBranches = new ArrayList<>();
    private Revision current;
    private int cursor;

    /**
     * Constructor.
     *
     * @param padding Padding between tree and revisions.
     * @param formatter Used to format revisions.
     */
    public TreePrinter(int padding, Function<Revision, String> formatter) {
        this.padding = padding;
        this.formatter = formatter;
    }

    /**
     * Adds a revision to drawed tree. Revisions are expected to be supplied in topological order.
     *
     * @param revision Next revision to draw.
     */
    public void add(Revision revision) {
        updateMapping(revision);
        printPreRevisionLines();
        printRevisionLines();
        cleanMapping();
        printPostRevisionLines();
    }

    /**
     * Prints tree.
     *
     * @return A text-based representation of all previously supplied revisions.
     */
    public String print() {
        return builder.toString();
    }

    private void updateMapping(Revision revision) {
        current = revision;
        previousBranches.clear();
        previousBranches.addAll(nextBranches);
        nextBranches.clear();
        previousBranches.forEach(rev -> {
            if (rev.equals(current.getRevision())) {
                if (current.getParents().isEmpty()) {
                    nextBranches.add(null);
                } else {
                    addCurrentParents();
                }
            } else {
                nextBranches.add(rev);
            }
        });
        if (!previousBranches.contains(current.getRevision())) {
            addCurrentParents();
        }
    }

    private void addCurrentParents() {
        Set<Hash> parents = current.getParents();
        Set<Hash> known = Sets.intersection(parents, new HashSet<>(nextBranches));
        Set<Hash> unknown = Sets.difference(parents, known);
        nextBranches.addAll(known);
        nextBranches.addAll(unknown);
    }

    private void printPreRevisionLines() {
        if (previousBranches.size() <= 1) {
            return;
        }
        int lines = 2 * (current.getParents().size() - 2);
        boolean currentPrinted = false;
        for (int i = 0; i < lines; i++) {
            for (Hash rev : previousBranches) {
                if (rev.equals(current.getRevision())) {
                    append(STRAIGHT_EDGE);
                    pad(SPACE, i);
                    currentPrinted = true;
                } else {
                    printEdge(currentPrinted);
                }
            }
            newLine();
        }
    }

    private void printRevisionLines() {
        int width = nextBranches.isEmpty() ? 1 + padding : nextBranches.size() * 2 - 1 + padding;
        int parents = current.getParents().size();
        Iterator<String> revision = formatRevision();

        printRevisionLine(width);
        if (revision.hasNext()) {
            append(revision.next());
        }
        newLine();
        if (parents >= 2) {
            printMergeLine(parents, width);
            if (revision.hasNext()) {
                append(revision.next());
            }
            newLine();
        }
        while (revision.hasNext()) {
            printPaddingLine(width);
            append(revision.next());
            newLine();
        }
    }

    private Iterator<String> formatRevision() {
        return Splitter.on(System.lineSeparator()).split(formatter.apply(current)).iterator();
    }

    private void printRevisionLine(int width) {
        boolean currentPrinted = false;
        for (Hash rev : previousBranches) {
            if (rev.equals(current.getRevision())) {
                printRevision();
                currentPrinted = true;
            } else {
                printEdge(currentPrinted && current.getParents().size() > 2);
            }
        }
        if (!currentPrinted) {
            printRevision();
        }
        pad(SPACE, width - cursor);
    }

    private void printMergeLine(int parents, int width) {
        boolean currentPrinted = false;
        for (Hash rev : previousBranches) {
            if (rev.equals(current.getRevision())) {
                printMergeEdges(parents);
                currentPrinted = true;
            } else {
                printEdge(currentPrinted);
            }
        }
        if (!currentPrinted) {
            printMergeEdges(parents);
        }
        pad(SPACE, width - cursor);
    }

    private void printPaddingLine(int width) {
        nextBranches.forEach(rev -> {
            append(rev != null ? STRAIGHT_EDGE : DOUBLE_SPACE);
        });
        pad(SPACE, width - cursor);
    }

    private void printRevision() {
        int parents = current.getParents().size();
        append(REVISION);
        if (parents > 2) {
            pad(DASH, 1 + 2 * (parents - 3));
            append(DOT);
        }
        append(SPACE);
    }

    private void printEdge(boolean isSkew) {
        if (isSkew) {
            append(RIGHT_EDGE);
        } else {
            append(STRAIGHT_EDGE);
        }
    }

    private void printMergeEdges(int parents) {
        append(MERGE_EDGE);
        pad(RIGHT_EDGE, parents - 2);
    }

    private void cleanMapping() {
        previousBranches.clear();
        previousBranches.addAll(nextBranches);
        nextBranches.clear();
        previousBranches.stream().filter(rev -> rev != null && !nextBranches.contains(rev))
                .forEach(nextBranches::add);
    }

    private void printPostRevisionLines() {
        int height = postRevisionLines();
        if (height <= 0) {
            return;
        }
        PrintGrid grid = new PrintGrid(previousBranches.size() * 2, height);
        printStraightEdges(grid);
        printSkewEdges(grid);

        grid.render().forEach(line -> {
            append(line);
            newLine();
        });
    }

    private int postRevisionLines() {
        int max = 0;
        for (int i = 0; i < previousBranches.size(); i++) {
            Hash rev = previousBranches.get(i);
            if (rev != null) {
                max = Math.max(max, i - nextBranches.indexOf(rev));
            }
        }
        return max * 2 - 1;
    }

    private void printStraightEdges(PrintGrid grid) {
        for (int i = 0; i < previousBranches.size(); i++) {
            Hash rev = previousBranches.get(i);
            if (rev == null || nextBranches.indexOf(rev) != i) {
                continue;
            }
            for (int y = 0; y < grid.getHeight(); y++) {
                grid.print(2 * i, y, STRAIGHT_EDGE);
            }
        }
    }

    private void printSkewEdges(PrintGrid grid) {
        for (int i = 0; i < previousBranches.size(); i++) {
            Hash rev = previousBranches.get(i);
            int nextIdx = nextBranches.indexOf(rev);
            if (rev == null || nextIdx == i) {
                continue;
            }
            int x = 2 * i - 1;
            int y = 0;
            while (y < grid.getHeight() && x > nextIdx) {
                grid.print(x, y, LEFT_EDGE);
                x--;
                y++;
            }
        }
    }

    private void pad(String pattern, int count) {
        for (int i = 0; i < count; i++) {
            append(pattern);
        }
    }

    private void append(String text) {
        builder.append(text);
        cursor += text.length();
    }

    private void newLine() {
        builder.append(System.lineSeparator());
        cursor = 0;
    }
}