com.facebook.buck.graph.AcyclicDepthFirstPostOrderTraversal.java Source code

Java tutorial

Introduction

Here is the source code for com.facebook.buck.graph.AcyclicDepthFirstPostOrderTraversal.java

Source

/*
 * Copyright 2012-present Facebook, Inc.
 *
 * 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.facebook.buck.graph;

import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;

import java.util.Deque;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Set;

/**
 * Performs a depth-first, post-order traversal over a DAG.
 * <p>
 * If a cycle is encountered, a {@link CycleException} is thrown by {@link #traverse(Iterable)}.
 * @param <T> the type of node in the graph
 */
public class AcyclicDepthFirstPostOrderTraversal<T> {

    private final GraphTraversable<T> traversable;

    public AcyclicDepthFirstPostOrderTraversal(GraphTraversable<T> traversable) {
        this.traversable = traversable;
    }

    /**
     * Performs a depth-first, post-order traversal over a DAG.
     * @param initialNodes The nodes from which to perform the traversal. Not allowed to contain
     *     {@code null}.
     * @throws CycleException if a cycle is found while performing the traversal.
     */
    @SuppressWarnings("PMD.PrematureDeclaration")
    public Iterable<T> traverse(Iterable<? extends T> initialNodes) throws CycleException {
        // This corresponds to the current chain of nodes being explored. Enforcing this invariant makes
        // this data structure useful for debugging.
        Deque<Explorable> toExplore = Lists.newLinkedList();
        for (T node : initialNodes) {
            toExplore.add(new Explorable(node));
        }

        Set<T> inProgress = Sets.newHashSet();
        LinkedHashSet<T> explored = Sets.newLinkedHashSet();

        while (!toExplore.isEmpty()) {
            Explorable explorable = toExplore.peek();
            T node = explorable.node;

            // This could happen if one of the initial nodes is a dependency of the other, for example.
            if (explored.contains(node)) {
                toExplore.removeFirst();
                continue;
            }

            inProgress.add(node);

            // Find children that need to be explored to add to the stack.
            int stackSize = toExplore.size();
            for (Iterator<T> iter = explorable.children; iter.hasNext();) {
                T child = iter.next();
                if (inProgress.contains(child)) {
                    throw createCycleException(child, toExplore);
                } else if (!explored.contains(child)) {
                    toExplore.addFirst(new Explorable(child));

                    // Without this break statement:
                    // (1) Children will be explored in reverse order instead of the specified order.
                    // (2) CycleException may contain extra nodes.
                    // Comment out the break statement and run the unit test to verify this for yourself.
                    break;
                }
            }

            if (stackSize == toExplore.size()) {
                // Nothing was added to toExplore, so the current node can be popped off the stack and
                // marked as explored.
                toExplore.removeFirst();
                inProgress.remove(node);
                explored.add(node);
            }
        }

        Preconditions.checkState(inProgress.isEmpty(), "No more nodes should be in progress.");

        return Iterables.unmodifiableIterable(explored);
    }

    /**
     * A node that needs to be explored, paired with a (possibly paused) iteration of its children.
     */
    private class Explorable {
        private final T node;
        private final Iterator<T> children;

        Explorable(T node) {
            this.node = node;
            this.children = traversable.findChildren(node);
        }
    }

    private CycleException createCycleException(T collisionNode, Iterable<Explorable> currentExploration) {
        Deque<T> chain = Lists.newLinkedList();
        chain.add(collisionNode);

        boolean foundStartOfCycle = false;
        for (Explorable explorable : currentExploration) {
            T node = explorable.node;
            chain.addFirst(node);
            if (collisionNode.equals(node)) {
                // The start of the cycle has been reached!
                foundStartOfCycle = true;
                break;
            }
        }

        Preconditions.checkState(foundStartOfCycle, "Start of cycle %s should appear in traversal history %s.",
                collisionNode, chain);

        return new CycleException(chain);
    }

    @SuppressWarnings("serial")
    public static final class CycleException extends Exception {

        private final ImmutableList<?> nodes;

        private CycleException(Iterable<?> nodes) {
            super("Cycle found: " + Joiner.on(" -> ").join(nodes));
            this.nodes = ImmutableList.copyOf(nodes);
        }

        public ImmutableList<?> getCycle() {
            return nodes;
        }
    }
}