blob: 2625b202a479b61154704e34b6cc4df623942924 [file] [log] [blame]
// 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
}
}