com.google.javascript.jscomp.graph.FixedPointGraphTraversal Maven / Gradle / Ivy
/*
* Copyright 2008 The Closure Compiler Authors.
*
* 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 com.google.javascript.jscomp.graph;
import static com.google.common.base.Preconditions.checkState;
import static java.lang.Math.max;
import com.google.javascript.jscomp.graph.DiGraph.DiGraphEdge;
import com.google.javascript.jscomp.graph.DiGraph.DiGraphNode;
import java.util.LinkedHashSet;
import java.util.Set;
/**
* A utility class for doing fixed-point computations. We traverse
* the edges over the given directed graph until the graph reaches
* a steady state.
*
*
* @param Value type that the graph node stores.
* @param Value type that the graph edge stores.
*/
public final class FixedPointGraphTraversal {
// TODO(nicksantos): Generalize the algorithm for undirected graphs, if we
// need it.
private final EdgeCallback callback;
private enum TraversalDirection {
INWARDS, // from a node to its incoming edges
OUTWARDS // from a node to its outgoing edges
}
private final TraversalDirection traversalDirection;
public static final String NON_HALTING_ERROR_MSG =
"Fixed point computation not halting";
/**
* Create a new traversal.
*
* @param callback A callback for updating the state of the graph each time an edge is traversed.
*/
private FixedPointGraphTraversal(
EdgeCallback callback, TraversalDirection traversalDirection) {
this.callback = callback;
this.traversalDirection = traversalDirection;
}
/** Helper method for creating new traversals that traverse from parent to child. */
public static FixedPointGraphTraversal newTraversal(
EdgeCallback callback) {
return new FixedPointGraphTraversal<>(callback, TraversalDirection.OUTWARDS);
}
/** Helper method for creating new traversals that traverse from child to parent. */
public static FixedPointGraphTraversal newReverseTraversal(
EdgeCallback callback) {
return new FixedPointGraphTraversal<>(callback, TraversalDirection.INWARDS);
}
/**
* Compute a fixed point for the given graph.
*
* @param graph The graph to traverse.
*/
public void computeFixedPoint(DiGraph graph) {
Set nodes = new LinkedHashSet<>();
for (DiGraphNode node : graph.getNodes()) {
nodes.add(node.getValue());
}
computeFixedPoint(graph, nodes);
}
/**
* Compute a fixed point for the given graph, entering from the given node.
* @param graph The graph to traverse.
* @param entry The node to begin traversing from.
*/
public void computeFixedPoint(DiGraph graph, N entry) {
Set entrySet = new LinkedHashSet<>();
entrySet.add(entry);
computeFixedPoint(graph, entrySet);
}
/**
* Compute a fixed point for the given graph, entering from the given nodes.
* @param graph The graph to traverse.
* @param entrySet The nodes to begin traversing from.
*/
public void computeFixedPoint(DiGraph graph, Set entrySet) {
int cycleCount = 0;
long nodeCount = graph.getNodeCount();
// Choose a bail-out heuristically in case the computation
// doesn't converge.
long maxIterations = max(nodeCount * nodeCount * nodeCount, 100);
// Use a LinkedHashSet, so that the traversal is deterministic.
LinkedHashSet> workSet = new LinkedHashSet<>();
for (N n : entrySet) {
workSet.add(graph.getNode(n));
}
for (; !workSet.isEmpty() && cycleCount < maxIterations; cycleCount++) {
visitNode(workSet.iterator().next(), workSet);
}
checkState(cycleCount != maxIterations, NON_HALTING_ERROR_MSG);
}
private void visitNode(DiGraphNode node, LinkedHashSet> workSet) {
// For every out edge in the workSet, traverse that edge. If that
// edge updates the state of the graph, then add the destination
// node to the resultSet, so that we can update all of its out edges
// on the next iteration.
workSet.remove(node);
switch (traversalDirection) {
case OUTWARDS:
N sourceValue = node.getValue();
for (DiGraphEdge edge : node.getOutEdges()) {
N destValue = edge.getDestination().getValue();
if (callback.traverseEdge(sourceValue, edge.getValue(), destValue)) {
workSet.add(edge.getDestination());
}
}
return;
case INWARDS:
N revSourceValue = node.getValue();
for (DiGraphEdge edge : node.getInEdges()) {
N revDestValue = edge.getSource().getValue();
if (callback.traverseEdge(revSourceValue, edge.getValue(), revDestValue)) {
workSet.add(edge.getSource());
}
}
return;
}
throw new AssertionError("Unrecognized direction " + traversalDirection);
}
/** Edge callback */
public interface EdgeCallback {
/**
* Update the state of the destination node when the given edge is traversed.
*
* Recall that depending on the direction of the traversal, {@code source} and {@code
* destination} may be swapped compared to the orientation of the edge in the graph. In either
* case, only the {@code destination} parameter may be mutated.
*
* @param source The start node.
* @param e The edge.
* @param destination The end node.
* @return Whether the state of the destination node changed.
*/
boolean traverseEdge(Node source, Edge e, Node destination);
}
}