aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib/graph/Node.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/com/google/devtools/build/lib/graph/Node.java')
-rw-r--r--src/main/java/com/google/devtools/build/lib/graph/Node.java294
1 files changed, 294 insertions, 0 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/graph/Node.java b/src/main/java/com/google/devtools/build/lib/graph/Node.java
new file mode 100644
index 0000000000..9db2a4c3e7
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/graph/Node.java
@@ -0,0 +1,294 @@
+// Copyright 2014 Google Inc. 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 java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+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() {
+ if (succs == null) {
+ return Collections.emptyList();
+ } else {
+ return 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() {
+ if (preds == null) {
+ return Collections.emptyList();
+ } else {
+ return 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<Node<T>>(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 =
+ new HashSet<Node<T>>(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<Node<T>>(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
+ }
+}