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}