| // Copyright 2014 The Bazel Authors. All rights reserved. |
| // |
| // 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.devtools.build.lib.graph; |
| |
| import com.google.common.base.Preconditions; |
| import java.util.Collection; |
| |
| /** |
| * A generic directed-graph Node class. Type parameter T is the type of the node's label. |
| * |
| * <p>Each node is identified by a label, which is unique within the graph owning the node. |
| * |
| * <p>Nodes are immutable, that is, their labels cannot be changed. However, their |
| * predecessor/successor lists are mutable. |
| * |
| * <p>Nodes cannot be created directly by clients. |
| * |
| * <p>Clients should not confuse nodes belonging to two different graphs! (Use Digraph.checkNode() |
| * to catch such errors.) There is no way to find the graph to which a node belongs; it is |
| * intentionally not represented, to save space. |
| * |
| * <p>During adding or removing edge locks always hold in specific order: first=nodeFrom.succs then |
| * second=nodeTo.preds. That's why reordering deadlock never happens. |
| */ |
| public final class Node<T> { |
| |
| private final T label; |
| |
| /** A duplicate-free collection of edges from this node. May be null, indicating the empty set. */ |
| private final ConcurrentCollectionWrapper<Node<T>> succs = new ConcurrentCollectionWrapper<>(); |
| |
| /** A duplicate-free collection of edges to this node. May be null, indicating the empty set. */ |
| private final ConcurrentCollectionWrapper<Node<T>> preds = new ConcurrentCollectionWrapper<>(); |
| |
| /** |
| * Only Digraph.createNode() can call this! |
| */ |
| Node(T label) { |
| this.label = Preconditions.checkNotNull(label, "label"); |
| } |
| |
| /** |
| * Returns the label for this node. |
| */ |
| public T getLabel() { |
| return label; |
| } |
| |
| /** |
| * Returns a duplicate-free collection of the nodes that this node links to. |
| */ |
| public Collection<Node<T>> getSuccessors() { |
| return this.succs.get(); |
| } |
| |
| /** |
| * Remove all successors edges and return collection of its. Self edge removed but did not |
| * returned in result collection. |
| * |
| * @return all existed before successor nodes but this. |
| */ |
| Collection<Node<T>> removeAllSuccessors() { |
| this.removeEdge(this); // remove self edge |
| Collection<Node<T>> successors = this.succs.clear(); |
| for (Node<T> s : successors) { |
| if (!s.removePredecessor(this)) { |
| throw new IllegalStateException("inconsistent graph state"); |
| } |
| } |
| return successors; |
| } |
| |
| /** |
| * Equivalent to {@code !getSuccessors().isEmpty()} but possibly more |
| * efficient. |
| */ |
| public boolean hasSuccessors() { |
| return !this.succs.get().isEmpty(); |
| } |
| |
| /** |
| * Equivalent to {@code getSuccessors().size()} but possibly more efficient. |
| */ |
| public int numSuccessors() { |
| return this.succs.size(); |
| } |
| |
| /** |
| * Returns an (unordered, possibly immutable) set of the nodes that link to |
| * this node. |
| */ |
| public Collection<Node<T>> getPredecessors() { |
| return this.preds.get(); |
| } |
| |
| /** |
| * Remove all predecessors edges and return collection of its. Self edge removed but did not |
| * returned in result collection. |
| * |
| * @return all existed before predecessor nodes but this. |
| */ |
| Collection<Node<T>> removeAllPredecessors() { |
| this.removeEdge(this); // remove self edge |
| Collection<Node<T>> predecessors = this.preds.clear(); |
| for (Node<T> p : predecessors) { |
| if (!p.removeSuccessor(this)) { |
| throw new IllegalStateException("inconsistent graph state"); |
| } |
| } |
| return predecessors; |
| } |
| |
| /** |
| * Equivalent to {@code !getPredecessors().isEmpty()} but possibly more |
| * efficient. |
| */ |
| public boolean hasPredecessors() { |
| return !preds.get().isEmpty(); |
| } |
| |
| /** Equivalent to {@code getPredecessors().size()} but possibly more efficient. */ |
| public int numPredecessors() { |
| return this.preds.size(); |
| } |
| |
| /** |
| * Adds edge from this node to target |
| * |
| * <p>In this method one lock held inside another lock. But it can not be reason of reordering |
| * deadlock. Lock always holds in direction fromNode.succs -> toNode.preds. |
| * @see #removeEdge(Node) |
| * |
| * @return true if edge had been added. false - otherwise. |
| */ |
| boolean addEdge(Node<T> target) { |
| synchronized (succs) { |
| boolean isNewSuccessor = this.succs.add(target); |
| boolean isNewPredecessor = target.addPredecessor(this); |
| if (isNewPredecessor != isNewSuccessor) { |
| throw new IllegalStateException("inconsistent graph state"); |
| } |
| return isNewSuccessor; |
| } |
| } |
| |
| /** |
| * Adds edge from this node to target |
| * |
| * <p>In this method one lock held inside another lock. But it can not be reason of reordering |
| * deadlock. Lock always holds in direction fromNode.succs -> toNode.preds. |
| * @see #addEdge(Node) |
| * |
| * @return true if edge had been removed. false - otherwise. |
| */ |
| boolean removeEdge(Node<T> target) { |
| synchronized (succs) { |
| boolean isSuccessorRemoved = this.succs.remove(target); |
| if (isSuccessorRemoved) { |
| boolean isPredecessorRemoved = target.removePredecessor(this); |
| if (!isPredecessorRemoved) { |
| throw new IllegalStateException("inconsistent graph state"); |
| } |
| return true; |
| } |
| return false; |
| } |
| } |
| |
| /** |
| * Add 'from' as a predecessor of 'this' node. Returns true iff the graph changed. Private: breaks |
| * graph invariant! |
| */ |
| private boolean addPredecessor(Node<T> from) { |
| return preds.add(from); |
| } |
| |
| /** |
| * Remove edge: toNode.preds = {n | n in toNode.preds && n != fromNode} Private: breaks graph |
| * invariant! |
| */ |
| private boolean removePredecessor(Node<T> from) { |
| return preds.remove(from); |
| } |
| |
| private boolean removeSuccessor(Node<T> to) { |
| return succs.remove(to); |
| } |
| |
| @Override |
| public String toString() { |
| return "node:" + label; |
| } |
| |
| @Override |
| public int hashCode() { |
| return super.hashCode(); |
| } |
| |
| @Override |
| public boolean equals(Object that) { |
| return this == that; // Nodes are unique for a given label |
| } |
| } |