001/*
002 * Copyright (C) 2009-2011 Mathias Doenitz
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.parboiled.trees;
018
019import org.parboiled.common.Formatter;
020import org.parboiled.common.Preconditions;
021import org.parboiled.common.Predicate;
022import org.parboiled.common.Predicates;
023
024import java.util.Collection;
025import java.util.HashSet;
026
027/**
028 * General utility methods for operating on directed graphs (consisting of {@link GraphNode}s).
029 */
030public final class GraphUtils {
031
032    private GraphUtils() {}
033
034    /**
035     * Returns true if this node is not null and has at least one child node.
036     *
037     * @param node a node
038     * @return true if this node is not null and has at least one child node.
039     */
040    public static boolean hasChildren(GraphNode<?> node) {
041        return node != null && !node.getChildren().isEmpty();
042    }
043
044    /**
045     * Returns the first child node of the given node or null if node is null or does not have any children.
046     *
047     * @param node a node
048     * @return the first child node of the given node or null if node is null or does not have any children
049     */
050    public static <T extends GraphNode<T>> T getFirstChild(T node) {
051        return hasChildren(node) ? node.getChildren().get(0) : null;
052    }
053
054    /**
055     * Returns the last child node of the given node or null if node is null or does not have any children.
056     *
057     * @param node a node
058     * @return the last child node of the given node or null if node is null or does not have any children
059     */
060    public static <T extends GraphNode<T>> T getLastChild(T node) {
061        return hasChildren(node) ? node.getChildren().get(node.getChildren().size() - 1) : null;
062    }
063
064    /**
065     * Counts all distinct nodes in the graph reachable from the given node.
066     * This method can properly deal with cycles in the graph.
067     *
068     * @param node the root node
069     * @return the number of distinct nodes
070     */
071    public static <T extends GraphNode<T>> int countAllDistinct(T node) {
072        if (node == null) return 0;
073        return collectAllNodes(node, new HashSet<T>()).size();
074    }
075
076    /**
077     * Collects all nodes from the graph reachable from the given node in the given collection.
078     * This method can properly deal with cycles in the graph.
079     *
080     * @param node       the root node
081     * @param collection the collection to collect into
082     * @return the same collection passed as a parameter
083     */
084    public static <T extends GraphNode<T>, C extends Collection<T>> C collectAllNodes(T node, C collection) {
085        // we don't recurse if the collecion already contains the node
086        // this costs a bit of performance but prevents infinite recursion in the case of graph cycles
087        Preconditions.checkArgNotNull(collection, "collection");
088        if (node != null && !collection.contains(node)) {
089            collection.add(node);
090            for (T child : node.getChildren()) {
091                collectAllNodes(child, collection);
092            }
093        }
094        return collection;
095    }
096
097    /**
098     * Creates a string representation of the graph reachable from the given node using the given formatter.
099     *
100     * @param node      the root node
101     * @param formatter the node formatter
102     * @return a new string
103     */
104    public static <T extends GraphNode<T>> String printTree(T node, Formatter<T> formatter) {
105        Preconditions.checkArgNotNull(formatter, "formatter");
106        return printTree(node, formatter, Predicates.<T>alwaysTrue(), Predicates.<T>alwaysTrue());
107    }
108
109    /**
110     * Creates a string representation of the graph reachable from the given node using the given formatter.
111     * The given filter predicated determines whether a particular node (and its subtree respectively) is to be
112     * printed or not.
113     *
114     * @param node          the root node
115     * @param formatter     the node formatter
116     * @param nodeFilter    the predicate selecting the nodes to print
117     * @param subTreeFilter the predicate determining whether to descend into a given nodes subtree or not
118     * @return a new string
119     */
120    public static <T extends GraphNode<T>> String printTree(T node, Formatter<T> formatter,
121                                                            Predicate<T> nodeFilter,
122                                                            Predicate<T> subTreeFilter) {
123        Preconditions.checkArgNotNull(formatter, "formatter");
124        Preconditions.checkArgNotNull(nodeFilter, "nodeFilter");
125        Preconditions.checkArgNotNull(subTreeFilter, "subTreeFilter");
126        return node == null ? "" :
127                printTree(node, formatter, "", new StringBuilder(), nodeFilter, subTreeFilter).toString();
128    }
129
130    // private recursion helper
131
132    private static <T extends GraphNode<T>> StringBuilder printTree(T node, Formatter<T> formatter,
133                                                                    String indent, StringBuilder sb,
134                                                                    Predicate<T> nodeFilter,
135                                                                    Predicate<T> subTreeFilter) {
136        if (nodeFilter.apply(node)) {
137            String line = formatter.format(node);
138            if (line != null) {
139                sb.append(indent).append(line).append("\n");
140                indent += "  ";
141            }
142        }
143        if (subTreeFilter.apply(node)) {
144            for (T sub : node.getChildren()) {
145                printTree(sub, formatter, indent, sb, nodeFilter, subTreeFilter);
146            }
147        }
148        return sb;
149    }
150
151}