org.gradle.internal.graph.CachingDirectedGraphWalker Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of gradle-api Show documentation
Show all versions of gradle-api Show documentation
Gradle 6.9.1 API redistribution.
/*
* Copyright 2010 the original author or 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 org.gradle.internal.graph;
import org.gradle.util.GUtil;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Deque;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* A graph walker which collects the values reachable from a given set of start nodes. Handles cycles in the graph. Can
* be reused to perform multiple searches, and reuses the results of previous searches.
*
* Uses a variation of Tarjan's algorithm: http://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm
*/
public class CachingDirectedGraphWalker {
private final DirectedGraphWithEdgeValues graph;
private List startNodes = new ArrayList();
private Set> strongComponents = new LinkedHashSet>();
private final Map> cachedNodeValues = new HashMap>();
public CachingDirectedGraphWalker(DirectedGraph graph) {
this.graph = new GraphWithEmptyEdges(graph);
}
public CachingDirectedGraphWalker(DirectedGraphWithEdgeValues graph) {
this.graph = graph;
}
/**
* Adds some start nodes.
*/
public CachingDirectedGraphWalker add(N... values) {
add(Arrays.asList(values));
return this;
}
/**
* Adds some start nodes.
*/
public CachingDirectedGraphWalker, ?> add(Iterable extends N> values) {
GUtil.addToCollection(startNodes, values);
return this;
}
/**
* Calculates the set of values of nodes reachable from the start nodes.
*/
public Set findValues() {
try {
return doSearch();
} finally {
startNodes.clear();
}
}
/**
* Returns the set of cycles seen in the graph.
*/
public List> findCycles() {
findValues();
List> result = new ArrayList>();
for (NodeDetails nodeDetails : strongComponents) {
Set componentMembers = new LinkedHashSet();
for (NodeDetails componentMember : nodeDetails.componentMembers) {
componentMembers.add(componentMember.node);
}
result.add(componentMembers);
}
return result;
}
private Set doSearch() {
int componentCount = 0;
Map> seenNodes = new HashMap>();
Map> components = new HashMap>();
Deque queue = new ArrayDeque(startNodes);
while (!queue.isEmpty()) {
N node = queue.getFirst();
NodeDetails details = seenNodes.get(node);
if (details == null) {
// Have not visited this node yet. Push its successors onto the queue in front of this node and visit
// them
details = new NodeDetails(node, componentCount++);
seenNodes.put(node, details);
components.put(details.component, details);
Set cacheValues = cachedNodeValues.get(node);
if (cacheValues != null) {
// Already visited this node
details.values = cacheValues;
details.finished = true;
queue.removeFirst();
continue;
}
graph.getNodeValues(node, details.values, details.successors);
for (N connectedNode : details.successors) {
NodeDetails connectedNodeDetails = seenNodes.get(connectedNode);
if (connectedNodeDetails == null) {
// Have not visited the successor node, so add to the queue for visiting
queue.addFirst(connectedNode);
} else if (!connectedNodeDetails.finished) {
// Currently visiting the successor node - we're in a cycle
details.stronglyConnected = true;
}
// Else, already visited
}
} else {
// Have visited all of this node's successors
queue.removeFirst();
if (cachedNodeValues.containsKey(node)) {
continue;
}
for (N connectedNode : details.successors) {
NodeDetails connectedNodeDetails = seenNodes.get(connectedNode);
if (!connectedNodeDetails.finished) {
// part of a cycle : use the 'minimum' component as the root of the cycle
int minSeen = Math.min(details.minSeen, connectedNodeDetails.minSeen);
details.minSeen = minSeen;
connectedNodeDetails.minSeen = minSeen;
details.stronglyConnected = true;
}
details.values.addAll(connectedNodeDetails.values);
graph.getEdgeValues(node, connectedNode, details.values);
}
if (details.minSeen != details.component) {
// Part of a strongly connected component (ie cycle) - move values to root of the component
// The root is the first node of the component we encountered
NodeDetails rootDetails = components.get(details.minSeen);
rootDetails.values.addAll(details.values);
details.values.clear();
rootDetails.componentMembers.addAll(details.componentMembers);
} else {
// Not part of a strongly connected component or the root of a strongly connected component
for (NodeDetails componentMember : details.componentMembers) {
cachedNodeValues.put(componentMember.node, details.values);
componentMember.finished = true;
components.remove(componentMember.component);
}
if (details.stronglyConnected) {
strongComponents.add(details);
}
}
}
}
Set values = new LinkedHashSet();
for (N startNode : startNodes) {
values.addAll(cachedNodeValues.get(startNode));
}
return values;
}
private static class NodeDetails {
private final int component;
private final N node;
private Set values = new LinkedHashSet();
private List successors = new ArrayList();
private Set> componentMembers = new LinkedHashSet>();
private int minSeen;
private boolean stronglyConnected;
private boolean finished;
public NodeDetails(N node, int component) {
this.node = node;
this.component = component;
minSeen = component;
componentMembers.add(this);
}
}
private static class GraphWithEmptyEdges implements DirectedGraphWithEdgeValues {
private final DirectedGraph graph;
public GraphWithEmptyEdges(DirectedGraph graph) {
this.graph = graph;
}
@Override
public void getEdgeValues(N from, N to, Collection values) {
}
@Override
public void getNodeValues(N node, Collection super T> values, Collection super N> connectedNodes) {
graph.getNodeValues(node, values, connectedNodes);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy