All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.gradle.internal.graph.CachingDirectedGraphWalker Maven / Gradle / Ivy

There is a newer version: 8.6
Show newest version
/*
 * 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 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 values, Collection connectedNodes) {
            graph.getNodeValues(node, values, connectedNodes);
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy