blob: 5079ed4ce49ec15b93249d783365244cdb8bfb49 [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.collect.Sets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
/**
* <p>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.
*/
public final class Node<T> {
private static final int ARRAYLIST_THRESHOLD = 6;
private static final int INITIAL_HASHSET_CAPACITY = 12;
// The succs and preds set representation changes depending on its size.
// It is implemented using the following collections:
// - null for size = 0.
// - Collections$SingletonList for size = 1.
// - ArrayList(6) for size = [2..6].
// - HashSet(12) for size > 6.
// These numbers were chosen based on profiling.
private final T label;
/**
* A duplicate-free collection of edges from this node. May be null,
* indicating the empty set.
*/
private Collection<Node<T>> succs = null;
/**
* A duplicate-free collection of edges to this node. May be null,
* indicating the empty set.
*/
private Collection<Node<T>> preds = null;
private final int hashCode;
/**
* Only Digraph.createNode() can call this!
*/
Node(T label, int hashCode) {
if (label == null) { throw new NullPointerException("label"); }
this.label = label;
this.hashCode = hashCode;
}
/**
* 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 succs == null ? Collections.emptyList() : Collections.unmodifiableCollection(succs);
}
/**
* Equivalent to {@code !getSuccessors().isEmpty()} but possibly more
* efficient.
*/
public boolean hasSuccessors() {
return succs != null;
}
/**
* Equivalent to {@code getSuccessors().size()} but possibly more efficient.
*/
public int numSuccessors() {
return succs == null ? 0 : succs.size();
}
/**
* Removes all edges to/from this node.
* Private: breaks graph invariant!
*/
void removeAllEdges() {
this.succs = null;
this.preds = null;
}
/**
* Returns an (unordered, possibly immutable) set of the nodes that link to
* this node.
*/
public Collection<Node<T>> getPredecessors() {
return preds == null ? Collections.emptyList() : Collections.unmodifiableCollection(preds);
}
/**
* Equivalent to {@code getPredecessors().size()} but possibly more
* efficient.
*/
public int numPredecessors() {
return preds == null ? 0 : preds.size();
}
/**
* Equivalent to {@code !getPredecessors().isEmpty()} but possibly more
* efficient.
*/
public boolean hasPredecessors() {
return preds != null;
}
/**
* Adds 'value' to either the predecessor or successor set, updating the
* appropriate field as necessary.
* @return {@code true} if the set was modified; {@code false} if the set
* was not modified
*/
private boolean add(boolean predecessorSet, Node<T> value) {
final Collection<Node<T>> set = predecessorSet ? preds : succs;
if (set == null) {
// null -> SingletonList
return updateField(predecessorSet, Collections.singletonList(value));
}
if (set.contains(value)) {
// already exists in this set
return false;
}
int previousSize = set.size();
if (previousSize == 1) {
// SingletonList -> ArrayList
Collection<Node<T>> newSet = new ArrayList<>(ARRAYLIST_THRESHOLD);
newSet.addAll(set);
newSet.add(value);
return updateField(predecessorSet, newSet);
} else if (previousSize < ARRAYLIST_THRESHOLD) {
// ArrayList
set.add(value);
return true;
} else if (previousSize == ARRAYLIST_THRESHOLD) {
// ArrayList -> HashSet
Collection<Node<T>> newSet = Sets.newHashSetWithExpectedSize(INITIAL_HASHSET_CAPACITY);
newSet.addAll(set);
newSet.add(value);
return updateField(predecessorSet, newSet);
} else {
// HashSet
set.add(value);
return true;
}
}
/**
* Removes 'value' from either 'preds' or 'succs', updating the appropriate
* field as necessary.
* @return {@code true} if the set was modified; {@code false} if the set
* was not modified
*/
private boolean remove(boolean predecessorSet, Node<T> value) {
final Collection<Node<T>> set = predecessorSet ? preds : succs;
if (set == null) {
// null
return false;
}
int previousSize = set.size();
if (previousSize == 1) {
if (set.contains(value)) {
// -> null
return updateField(predecessorSet, null);
} else {
return false;
}
}
// now remove the value
if (set.remove(value)) {
// may need to change representation
if (previousSize == 2) {
// -> SingletonList
List<Node<T>> list =
Collections.singletonList(set.iterator().next());
return updateField(predecessorSet, list);
} else if (previousSize == 1 + ARRAYLIST_THRESHOLD) {
// -> ArrayList
Collection<Node<T>> newSet = new ArrayList<>(ARRAYLIST_THRESHOLD);
newSet.addAll(set);
return updateField(predecessorSet, newSet);
}
return true;
}
return false;
}
/**
* Update either the {@link #preds} or {@link #succs} field to point to the
* new set.
* @return {@code true}, because the set must have been updated
*/
private boolean updateField(boolean predecessorSet,
Collection<Node<T>> newSet) {
if (predecessorSet) {
preds = newSet;
} else {
succs = newSet;
}
return true;
}
/**
* Add 'to' as a successor of 'this' node. Returns true iff
* the graph changed. Private: breaks graph invariant!
*/
boolean addSuccessor(Node<T> to) {
return add(false, to);
}
/**
* Add 'from' as a predecessor of 'this' node. Returns true iff
* the graph changed. Private: breaks graph invariant!
*/
boolean addPredecessor(Node<T> from) {
return add(true, from);
}
/**
* Remove edge: fromNode.succs = {n | n in fromNode.succs && n != toNode}
* Private: breaks graph invariant!
*/
boolean removeSuccessor(Node<T> to) {
return remove(false, to);
}
/**
* Remove edge: toNode.preds = {n | n in toNode.preds && n != fromNode}
* Private: breaks graph invariant!
*/
boolean removePredecessor(Node<T> from) {
return remove(true, from);
}
@Override
public String toString() {
return "node:" + label;
}
@Override
public int hashCode() {
return hashCode; // Fast, deterministic.
}
@Override
public boolean equals(Object that) {
return this == that; // Nodes are unique for a given label
}
}