| // 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() { | |
| 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<>(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 | |
| } | |
| } |