// 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. * *

Each node is identified by a label, which is unique within the graph owning the node. * *

Nodes are immutable, that is, their labels cannot be changed. However, their * predecessor/successor lists are mutable. * *

Nodes cannot be created directly by clients. * *

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. * *

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 { private final T label; /** A duplicate-free collection of edges from this node. May be null, indicating the empty set. */ private final ConcurrentCollectionWrapper> succs = new ConcurrentCollectionWrapper<>(); /** A duplicate-free collection of edges to this node. May be null, indicating the empty set. */ private final ConcurrentCollectionWrapper> 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> 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> removeAllSuccessors() { this.removeEdge(this); // remove self edge Collection> successors = this.succs.clear(); for (Node 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> 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> removeAllPredecessors() { this.removeEdge(this); // remove self edge Collection> predecessors = this.preds.clear(); for (Node 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 * *

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 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 * *

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 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 from) { return preds.add(from); } /** * Remove edge: toNode.preds = {n | n in toNode.preds && n != fromNode} Private: breaks graph * invariant! */ private boolean removePredecessor(Node from) { return preds.remove(from); } private boolean removeSuccessor(Node 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 } }