• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 The Guava Authors
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.google.common.graph;
18 
19 import static com.google.common.base.Preconditions.checkArgument;
20 import static com.google.common.base.Preconditions.checkNotNull;
21 import static com.google.common.base.Preconditions.checkState;
22 import static com.google.common.graph.GraphConstants.INNER_CAPACITY;
23 import static com.google.common.graph.GraphConstants.INNER_LOAD_FACTOR;
24 import static com.google.common.graph.Graphs.checkNonNegative;
25 import static com.google.common.graph.Graphs.checkPositive;
26 
27 import com.google.common.base.Function;
28 import com.google.common.collect.AbstractIterator;
29 import com.google.common.collect.ImmutableList;
30 import com.google.common.collect.Iterators;
31 import com.google.common.collect.UnmodifiableIterator;
32 import java.util.AbstractSet;
33 import java.util.ArrayList;
34 import java.util.Collections;
35 import java.util.HashMap;
36 import java.util.HashSet;
37 import java.util.Iterator;
38 import java.util.List;
39 import java.util.Map;
40 import java.util.Map.Entry;
41 import java.util.Set;
42 import java.util.concurrent.atomic.AtomicBoolean;
43 import javax.annotation.CheckForNull;
44 
45 /**
46  * An implementation of {@link GraphConnections} for directed graphs.
47  *
48  * @author James Sexton
49  * @author Jens Nyman
50  * @param <N> Node parameter type
51  * @param <V> Value parameter type
52  */
53 @ElementTypesAreNonnullByDefault
54 final class DirectedGraphConnections<N, V> implements GraphConnections<N, V> {
55   /**
56    * A wrapper class to indicate a node is both a predecessor and successor while still providing
57    * the successor value.
58    */
59   private static final class PredAndSucc {
60     private final Object successorValue;
61 
PredAndSucc(Object successorValue)62     PredAndSucc(Object successorValue) {
63       this.successorValue = successorValue;
64     }
65   }
66 
67   /**
68    * A value class representing single connection between the origin node and another node.
69    *
70    * <p>There can be two types of connections (predecessor and successor), which is represented by
71    * the two implementations.
72    */
73   private abstract static class NodeConnection<N> {
74     final N node;
75 
NodeConnection(N node)76     NodeConnection(N node) {
77       this.node = checkNotNull(node);
78     }
79 
80     static final class Pred<N> extends NodeConnection<N> {
Pred(N node)81       Pred(N node) {
82         super(node);
83       }
84 
85       @Override
equals(@heckForNull Object that)86       public boolean equals(@CheckForNull Object that) {
87         if (that instanceof Pred) {
88           return this.node.equals(((Pred<?>) that).node);
89         } else {
90           return false;
91         }
92       }
93 
94       @Override
hashCode()95       public int hashCode() {
96         // Adding the class hashCode to avoid a clash with Succ instances.
97         return Pred.class.hashCode() + node.hashCode();
98       }
99     }
100 
101     static final class Succ<N> extends NodeConnection<N> {
Succ(N node)102       Succ(N node) {
103         super(node);
104       }
105 
106       @Override
equals(@heckForNull Object that)107       public boolean equals(@CheckForNull Object that) {
108         if (that instanceof Succ) {
109           return this.node.equals(((Succ<?>) that).node);
110         } else {
111           return false;
112         }
113       }
114 
115       @Override
hashCode()116       public int hashCode() {
117         // Adding the class hashCode to avoid a clash with Pred instances.
118         return Succ.class.hashCode() + node.hashCode();
119       }
120     }
121   }
122 
123   private static final Object PRED = new Object();
124 
125   // Every value in this map must either be an instance of PredAndSucc with a successorValue of
126   // type V, PRED (representing predecessor), or an instance of type V (representing successor).
127   private final Map<N, Object> adjacentNodeValues;
128 
129   /**
130    * All node connections in this graph, in edge insertion order.
131    *
132    * <p>Note: This field and {@link #adjacentNodeValues} cannot be combined into a single
133    * LinkedHashMap because one target node may be mapped to both a predecessor and a successor. A
134    * LinkedHashMap combines two such edges into a single node-value pair, even though the edges may
135    * not have been inserted consecutively.
136    */
137   @CheckForNull private final List<NodeConnection<N>> orderedNodeConnections;
138 
139   private int predecessorCount;
140   private int successorCount;
141 
DirectedGraphConnections( Map<N, Object> adjacentNodeValues, @CheckForNull List<NodeConnection<N>> orderedNodeConnections, int predecessorCount, int successorCount)142   private DirectedGraphConnections(
143       Map<N, Object> adjacentNodeValues,
144       @CheckForNull List<NodeConnection<N>> orderedNodeConnections,
145       int predecessorCount,
146       int successorCount) {
147     this.adjacentNodeValues = checkNotNull(adjacentNodeValues);
148     this.orderedNodeConnections = orderedNodeConnections;
149     this.predecessorCount = checkNonNegative(predecessorCount);
150     this.successorCount = checkNonNegative(successorCount);
151     checkState(
152         predecessorCount <= adjacentNodeValues.size()
153             && successorCount <= adjacentNodeValues.size());
154   }
155 
of(ElementOrder<N> incidentEdgeOrder)156   static <N, V> DirectedGraphConnections<N, V> of(ElementOrder<N> incidentEdgeOrder) {
157     // We store predecessors and successors in the same map, so double the initial capacity.
158     int initialCapacity = INNER_CAPACITY * 2;
159 
160     List<NodeConnection<N>> orderedNodeConnections;
161     switch (incidentEdgeOrder.type()) {
162       case UNORDERED:
163         orderedNodeConnections = null;
164         break;
165       case STABLE:
166         orderedNodeConnections = new ArrayList<>();
167         break;
168       default:
169         throw new AssertionError(incidentEdgeOrder.type());
170     }
171 
172     return new DirectedGraphConnections<>(
173         /* adjacentNodeValues = */ new HashMap<N, Object>(initialCapacity, INNER_LOAD_FACTOR),
174         orderedNodeConnections,
175         /* predecessorCount = */ 0,
176         /* successorCount = */ 0);
177   }
178 
ofImmutable( N thisNode, Iterable<EndpointPair<N>> incidentEdges, Function<N, V> successorNodeToValueFn)179   static <N, V> DirectedGraphConnections<N, V> ofImmutable(
180       N thisNode, Iterable<EndpointPair<N>> incidentEdges, Function<N, V> successorNodeToValueFn) {
181     checkNotNull(thisNode);
182     checkNotNull(successorNodeToValueFn);
183 
184     Map<N, Object> adjacentNodeValues = new HashMap<>();
185     ImmutableList.Builder<NodeConnection<N>> orderedNodeConnectionsBuilder =
186         ImmutableList.builder();
187     int predecessorCount = 0;
188     int successorCount = 0;
189 
190     for (EndpointPair<N> incidentEdge : incidentEdges) {
191       if (incidentEdge.nodeU().equals(thisNode) && incidentEdge.nodeV().equals(thisNode)) {
192         // incidentEdge is a self-loop
193 
194         adjacentNodeValues.put(thisNode, new PredAndSucc(successorNodeToValueFn.apply(thisNode)));
195 
196         orderedNodeConnectionsBuilder.add(new NodeConnection.Pred<>(thisNode));
197         orderedNodeConnectionsBuilder.add(new NodeConnection.Succ<>(thisNode));
198         predecessorCount++;
199         successorCount++;
200       } else if (incidentEdge.nodeV().equals(thisNode)) { // incidentEdge is an inEdge
201         N predecessor = incidentEdge.nodeU();
202 
203         Object existingValue = adjacentNodeValues.put(predecessor, PRED);
204         if (existingValue != null) {
205           adjacentNodeValues.put(predecessor, new PredAndSucc(existingValue));
206         }
207 
208         orderedNodeConnectionsBuilder.add(new NodeConnection.Pred<>(predecessor));
209         predecessorCount++;
210       } else { // incidentEdge is an outEdge
211         checkArgument(incidentEdge.nodeU().equals(thisNode));
212 
213         N successor = incidentEdge.nodeV();
214         V value = successorNodeToValueFn.apply(successor);
215 
216         Object existingValue = adjacentNodeValues.put(successor, value);
217         if (existingValue != null) {
218           checkArgument(existingValue == PRED);
219           adjacentNodeValues.put(successor, new PredAndSucc(value));
220         }
221 
222         orderedNodeConnectionsBuilder.add(new NodeConnection.Succ<>(successor));
223         successorCount++;
224       }
225     }
226 
227     return new DirectedGraphConnections<>(
228         adjacentNodeValues,
229         orderedNodeConnectionsBuilder.build(),
230         predecessorCount,
231         successorCount);
232   }
233 
234   @Override
adjacentNodes()235   public Set<N> adjacentNodes() {
236     if (orderedNodeConnections == null) {
237       return Collections.unmodifiableSet(adjacentNodeValues.keySet());
238     } else {
239       return new AbstractSet<N>() {
240         @Override
241         public UnmodifiableIterator<N> iterator() {
242           Iterator<NodeConnection<N>> nodeConnections = orderedNodeConnections.iterator();
243           Set<N> seenNodes = new HashSet<>();
244           return new AbstractIterator<N>() {
245             @Override
246             @CheckForNull
247             protected N computeNext() {
248               while (nodeConnections.hasNext()) {
249                 NodeConnection<N> nodeConnection = nodeConnections.next();
250                 boolean added = seenNodes.add(nodeConnection.node);
251                 if (added) {
252                   return nodeConnection.node;
253                 }
254               }
255               return endOfData();
256             }
257           };
258         }
259 
260         @Override
261         public int size() {
262           return adjacentNodeValues.size();
263         }
264 
265         @Override
266         public boolean contains(@CheckForNull Object obj) {
267           return adjacentNodeValues.containsKey(obj);
268         }
269       };
270     }
271   }
272 
273   @Override
274   public Set<N> predecessors() {
275     return new AbstractSet<N>() {
276       @Override
277       public UnmodifiableIterator<N> iterator() {
278         if (orderedNodeConnections == null) {
279           Iterator<Entry<N, Object>> entries = adjacentNodeValues.entrySet().iterator();
280           return new AbstractIterator<N>() {
281             @Override
282             @CheckForNull
283             protected N computeNext() {
284               while (entries.hasNext()) {
285                 Entry<N, Object> entry = entries.next();
286                 if (isPredecessor(entry.getValue())) {
287                   return entry.getKey();
288                 }
289               }
290               return endOfData();
291             }
292           };
293         } else {
294           Iterator<NodeConnection<N>> nodeConnections = orderedNodeConnections.iterator();
295           return new AbstractIterator<N>() {
296             @Override
297             @CheckForNull
298             protected N computeNext() {
299               while (nodeConnections.hasNext()) {
300                 NodeConnection<N> nodeConnection = nodeConnections.next();
301                 if (nodeConnection instanceof NodeConnection.Pred) {
302                   return nodeConnection.node;
303                 }
304               }
305               return endOfData();
306             }
307           };
308         }
309       }
310 
311       @Override
312       public int size() {
313         return predecessorCount;
314       }
315 
316       @Override
317       public boolean contains(@CheckForNull Object obj) {
318         return isPredecessor(adjacentNodeValues.get(obj));
319       }
320     };
321   }
322 
323   @Override
324   public Set<N> successors() {
325     return new AbstractSet<N>() {
326       @Override
327       public UnmodifiableIterator<N> iterator() {
328         if (orderedNodeConnections == null) {
329           Iterator<Entry<N, Object>> entries = adjacentNodeValues.entrySet().iterator();
330           return new AbstractIterator<N>() {
331             @Override
332             @CheckForNull
333             protected N computeNext() {
334               while (entries.hasNext()) {
335                 Entry<N, Object> entry = entries.next();
336                 if (isSuccessor(entry.getValue())) {
337                   return entry.getKey();
338                 }
339               }
340               return endOfData();
341             }
342           };
343         } else {
344           Iterator<NodeConnection<N>> nodeConnections = orderedNodeConnections.iterator();
345           return new AbstractIterator<N>() {
346             @Override
347             @CheckForNull
348             protected N computeNext() {
349               while (nodeConnections.hasNext()) {
350                 NodeConnection<N> nodeConnection = nodeConnections.next();
351                 if (nodeConnection instanceof NodeConnection.Succ) {
352                   return nodeConnection.node;
353                 }
354               }
355               return endOfData();
356             }
357           };
358         }
359       }
360 
361       @Override
362       public int size() {
363         return successorCount;
364       }
365 
366       @Override
367       public boolean contains(@CheckForNull Object obj) {
368         return isSuccessor(adjacentNodeValues.get(obj));
369       }
370     };
371   }
372 
373   @Override
374   public Iterator<EndpointPair<N>> incidentEdgeIterator(N thisNode) {
375     checkNotNull(thisNode);
376 
377     Iterator<EndpointPair<N>> resultWithDoubleSelfLoop;
378     if (orderedNodeConnections == null) {
379       resultWithDoubleSelfLoop =
380           Iterators.concat(
381               Iterators.transform(
382                   predecessors().iterator(),
383                   (N predecessor) -> EndpointPair.ordered(predecessor, thisNode)),
384               Iterators.transform(
385                   successors().iterator(),
386                   (N successor) -> EndpointPair.ordered(thisNode, successor)));
387     } else {
388       resultWithDoubleSelfLoop =
389           Iterators.transform(
390               orderedNodeConnections.iterator(),
391               (NodeConnection<N> connection) -> {
392                 if (connection instanceof NodeConnection.Succ) {
393                   return EndpointPair.ordered(thisNode, connection.node);
394                 } else {
395                   return EndpointPair.ordered(connection.node, thisNode);
396                 }
397               });
398     }
399 
400     AtomicBoolean alreadySeenSelfLoop = new AtomicBoolean(false);
401     return new AbstractIterator<EndpointPair<N>>() {
402       @Override
403       @CheckForNull
404       protected EndpointPair<N> computeNext() {
405         while (resultWithDoubleSelfLoop.hasNext()) {
406           EndpointPair<N> edge = resultWithDoubleSelfLoop.next();
407           if (edge.nodeU().equals(edge.nodeV())) {
408             if (!alreadySeenSelfLoop.getAndSet(true)) {
409               return edge;
410             }
411           } else {
412             return edge;
413           }
414         }
415         return endOfData();
416       }
417     };
418   }
419 
420   @SuppressWarnings("unchecked")
421   @Override
422   @CheckForNull
423   public V value(N node) {
424     checkNotNull(node);
425     Object value = adjacentNodeValues.get(node);
426     if (value == PRED) {
427       return null;
428     }
429     if (value instanceof PredAndSucc) {
430       return (V) ((PredAndSucc) value).successorValue;
431     }
432     return (V) value;
433   }
434 
435   @SuppressWarnings("unchecked")
436   @Override
437   public void removePredecessor(N node) {
438     checkNotNull(node);
439 
440     Object previousValue = adjacentNodeValues.get(node);
441     boolean removedPredecessor;
442 
443     if (previousValue == PRED) {
444       adjacentNodeValues.remove(node);
445       removedPredecessor = true;
446     } else if (previousValue instanceof PredAndSucc) {
447       adjacentNodeValues.put((N) node, ((PredAndSucc) previousValue).successorValue);
448       removedPredecessor = true;
449     } else {
450       removedPredecessor = false;
451     }
452 
453     if (removedPredecessor) {
454       checkNonNegative(--predecessorCount);
455 
456       if (orderedNodeConnections != null) {
457         orderedNodeConnections.remove(new NodeConnection.Pred<>(node));
458       }
459     }
460   }
461 
462   @SuppressWarnings("unchecked")
463   @Override
464   @CheckForNull
465   public V removeSuccessor(Object node) {
466     checkNotNull(node);
467     Object previousValue = adjacentNodeValues.get(node);
468     Object removedValue;
469 
470     if (previousValue == null || previousValue == PRED) {
471       removedValue = null;
472     } else if (previousValue instanceof PredAndSucc) {
473       adjacentNodeValues.put((N) node, PRED);
474       removedValue = ((PredAndSucc) previousValue).successorValue;
475     } else { // successor
476       adjacentNodeValues.remove(node);
477       removedValue = previousValue;
478     }
479 
480     if (removedValue != null) {
481       checkNonNegative(--successorCount);
482 
483       if (orderedNodeConnections != null) {
484         orderedNodeConnections.remove(new NodeConnection.Succ<>((N) node));
485       }
486     }
487 
488     /*
489      * TODO(cpovirk): `return (V) removedValue` once our checker permits that.
490      *
491      * (We promoted a class of warnings into errors because sometimes they indicate real problems.
492      * But now we need to "undo" some instance of spurious errors, as discussed in
493      * https://github.com/jspecify/checker-framework/issues/8.)
494      */
495     return removedValue == null ? null : (V) removedValue;
496   }
497 
498   @Override
499   public void addPredecessor(N node, V unused) {
500     Object previousValue = adjacentNodeValues.put(node, PRED);
501     boolean addedPredecessor;
502 
503     if (previousValue == null) {
504       addedPredecessor = true;
505     } else if (previousValue instanceof PredAndSucc) {
506       // Restore previous PredAndSucc object.
507       adjacentNodeValues.put(node, previousValue);
508       addedPredecessor = false;
509     } else if (previousValue != PRED) { // successor
510       // Do NOT use method parameter value 'unused'. In directed graphs, successors store the value.
511       adjacentNodeValues.put(node, new PredAndSucc(previousValue));
512       addedPredecessor = true;
513     } else {
514       addedPredecessor = false;
515     }
516 
517     if (addedPredecessor) {
518       checkPositive(++predecessorCount);
519 
520       if (orderedNodeConnections != null) {
521         orderedNodeConnections.add(new NodeConnection.Pred<>(node));
522       }
523     }
524   }
525 
526   @SuppressWarnings("unchecked")
527   @Override
528   @CheckForNull
529   public V addSuccessor(N node, V value) {
530     Object previousValue = adjacentNodeValues.put(node, value);
531     Object previousSuccessor;
532 
533     if (previousValue == null) {
534       previousSuccessor = null;
535     } else if (previousValue instanceof PredAndSucc) {
536       adjacentNodeValues.put(node, new PredAndSucc(value));
537       previousSuccessor = ((PredAndSucc) previousValue).successorValue;
538     } else if (previousValue == PRED) {
539       adjacentNodeValues.put(node, new PredAndSucc(value));
540       previousSuccessor = null;
541     } else { // successor
542       previousSuccessor = previousValue;
543     }
544 
545     if (previousSuccessor == null) {
546       checkPositive(++successorCount);
547 
548       if (orderedNodeConnections != null) {
549         orderedNodeConnections.add(new NodeConnection.Succ<>(node));
550       }
551     }
552 
553     // See the comment on the similar cast in removeSuccessor.
554     return previousSuccessor == null ? null : (V) previousSuccessor;
555   }
556 
557   private static boolean isPredecessor(@CheckForNull Object value) {
558     return (value == PRED) || (value instanceof PredAndSucc);
559   }
560 
561   private static boolean isSuccessor(@CheckForNull Object value) {
562     return (value != PRED) && (value != null);
563   }
564 }
565