aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib/graph/Node.java
blob: 2625b202a479b61154704e34b6cc4df623942924 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
// 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
  }
}