View Javadoc

1   /*
2    * Grapht, an open source dependency injector.
3    * Copyright 2014-2015 various contributors (see CONTRIBUTORS.txt)
4    * Copyright 2010-2014 Regents of the University of Minnesota
5    *
6    * This program is free software; you can redistribute it and/or modify
7    * it under the terms of the GNU Lesser General Public License as
8    * published by the Free Software Foundation; either version 2.1 of the
9    * License, or (at your option) any later version.
10   *
11   * This program is distributed in the hope that it will be useful, but WITHOUT
12   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13   * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
14   * details.
15   *
16   * You should have received a copy of the GNU General Public License along with
17   * this program; if not, write to the Free Software Foundation, Inc., 51
18   * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19   */
20  package org.grouplens.grapht.graph;
21  
22  import com.google.common.base.*;
23  import com.google.common.collect.*;
24  import org.apache.commons.lang3.tuple.Pair;
25  
26  import javax.annotation.Nonnull;
27  import javax.annotation.Nullable;
28  import javax.annotation.concurrent.Immutable;
29  import java.io.IOException;
30  import java.io.ObjectInputStream;
31  import java.io.Serializable;
32  import java.util.*;
33  
34  /**
35   * A node in a (rooted) DAG.  Since DAGs are rooted, a full graph is just represented by its root
36   * node.  Nodes are compared using reference equality, so distinct nodes do not compare equal even
37   * if they have identical labels and edge sets.
38   *
39   * <p>Nodes and edges may not have null labels.  There <em>may</em> be multiple edges from one
40   * node to another, so long as those edges have distinct labels.
41   *
42   * <p>Nodes know about all nodes reachable from them, and the edges connecting those nodes.
43   *
44   * <p>DAGs and their nodes are immutable.  You can build them using a {@linkplain DAGNodeBuilder builder},
45   * obtained from {@link #newBuilder(Object)}.
46   *
47   * @param <V> The type of node (vertex) labels.
48   * @param <E> The type of edge labels.
49   * @since 0.7.0
50   * @author <a href="http://www.grouplens.org">GroupLens Research</a>
51   */
52  @Immutable
53  public class DAGNode<V,E> implements Serializable {
54      private static final long serialVersionUID = 1L;
55  
56      @Nonnull
57      @SuppressWarnings("squid:S1948") // serializable warning; node is serializable iff its label type is
58      private final V label;
59      @Nonnull
60      private final ImmutableSet<DAGEdge<V,E>> outgoingEdges;
61  
62      private transient Supplier<SetMultimap<DAGNode<V,E>,DAGEdge<V,E>>> reverseEdgeCache;
63      private transient Supplier<Set<DAGNode<V,E>>> reachableNodeCache;
64      private transient Supplier<List<DAGNode<V,E>>> topologicalSortCache;
65  
66      /**
67       * Create a new DAG node with no outgoing edges.
68       * @param label The node label.
69       * @param <V> The type of node labels.
70       * @param <E> The type of edge labels.
71       */
72      public static <V,E> DAGNode<V,E> singleton(V label) {
73          Preconditions.checkNotNull(label, "node label");
74          return new DAGNode<V,E>(label, ImmutableSet.<Pair<DAGNode<V, E>, E>>of());
75      }
76  
77      /**
78       * Construct a new DAG node builder.
79       * @param <V> The type of node labels.
80       * @param <E> The type of edge labels.
81       * @return The DAG node builder.
82       */
83      public static <V,E> DAGNodeBuilder<V,E> newBuilder() {
84          return new DAGNodeBuilder<V, E>();
85      }
86  
87      /**
88       * Construct a new DAG node builder.
89       * @param label The node label.
90       * @param <V> The type of node labels.
91       * @param <E> The type of edge labels.
92       * @return The DAG node builder.
93       */
94      public static <V,E> DAGNodeBuilder<V,E> newBuilder(V label) {
95          return new DAGNodeBuilder<V, E>(label);
96      }
97  
98      /**
99       * Create a new builder initialized to build a copy of the specified node.
100      * @param node The node to copy.
101      * @param <V> The type of node labels.
102      * @param <E> The type of edge labels.
103      * @return A new builder initialized with the labels and edges of {@code node}.
104      */
105     public static <V,E> DAGNodeBuilder<V,E> copyBuilder(DAGNode<V,E> node) {
106         DAGNodeBuilder<V,E> bld = newBuilder(node.getLabel());
107         for (DAGEdge<V,E> edge: node.getOutgoingEdges()) {
108             bld.addEdge(edge.getTail(), edge.getLabel());
109         }
110         return bld;
111     }
112 
113     /**
114      * Construct a new DAG node.
115      * @param lbl The label.
116      * @param edges The edges.  This takes pairs, not actual edge objects, because the edge objects
117      *              need to be constructed within the constructor in order to create the circular
118      *              references back to the head nodes properly.
119      */
120     DAGNode(@Nonnull V lbl, Iterable<Pair<DAGNode<V,E>,E>> edges) {
121         label = lbl;
122         ImmutableSet.Builder<DAGEdge<V,E>> bld = ImmutableSet.builder();
123         for (Pair<DAGNode<V,E>,E> pair: edges) {
124             DAGEdge<V,E> edge = new DAGEdge<V, E>(this, pair.getLeft(), pair.getRight());
125             bld.add(edge);
126         }
127         outgoingEdges = bld.build();
128         initializeCaches();
129     }
130 
131     /**
132      * Initialize caches for traversing this node.
133      */
134     private void initializeCaches() {
135         reverseEdgeCache = Suppliers.memoize(new EdgeMapSupplier());
136         reachableNodeCache = Suppliers.memoize(new NodeSetSupplier());
137         topologicalSortCache = Suppliers.memoize(new TopologicalSortSupplier());
138     }
139 
140     /**
141      * Override the object reading protocol to make sure caches are instantiated.  This just calls
142      * {@link #initializeCaches()} after doing the default object-reading.
143      *
144      * @param stream The stream to read from.
145      * @throws IOException If an I/O exception occurs deserializing the object.
146      * @throws ClassNotFoundException If there is a missing class deserializing the object.
147      */
148     private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
149         stream.defaultReadObject();
150         initializeCaches();
151     }
152 
153     /**
154      * Get the label for this node.
155      * @return The node's label.
156      */
157     @Nonnull
158     public V getLabel() {
159         return label;
160     }
161 
162     /**
163      * Get the outgoing edges of this node.
164      * @return The outgoing edges of the node.
165      */
166     @Nonnull
167     public Set<DAGEdge<V,E>> getOutgoingEdges() {
168         return outgoingEdges;
169     }
170 
171     /**
172      * Get the outgoing edge with the specified target and label, if it exists.
173      * @param target The target node.
174      * @param label The label.
175      * @return The edge from this node to {@code target} with label {@code label}, if it exists, or
176      * {@code null} if no such edge exists.
177      */
178     public DAGEdge<V,E> getOutgoingEdge(DAGNode<V,E> target, E label) {
179         for (DAGEdge<V,E> edge: outgoingEdges) {
180             if (edge.getTail().equals(target) && edge.getLabel().equals(label)) {
181                 return edge;
182             }
183         }
184         return null;
185     }
186 
187     /**
188      * Get an outgoing edge from this node with the specified label, if it exists.
189      *
190      * @param label The label.
191      * @return An outgoing edge with the specified label, or {@code null} if no such edge exists.
192      * If multiple edges have this label, an arbitrary one is returned.
193      */
194     public DAGEdge<V,E> getOutgoingEdgeWithLabel(E label) {
195         return getOutgoingEdgeWithLabel(Predicates.equalTo(label));
196     }
197 
198     /**
199      * Search for an outgoing edge by a predicate.
200      *
201      * @param predicate A predicate over labels.
202      * @return An outgoing edge matching the predicate, or {@code null} if no such edge exists.  If
203      *         multiple edges have labels matching the predicate, it is undefined which one will be
204      *         added.
205      */
206     public DAGEdge<V, E> getOutgoingEdgeWithLabel(Predicate<? super E> predicate) {
207         Predicate<DAGEdge<?, E>> edgePred = DAGEdge.labelMatches(predicate);
208         return Iterables.find(outgoingEdges, edgePred, null);
209     }
210 
211     /**
212      * Get the nodes that are adjacent to this node (only considering outgoing edges).
213      * @return The set of adjacent nodes.
214      */
215     public Set<DAGNode<V,E>> getAdjacentNodes() {
216         return FluentIterable.from(outgoingEdges)
217                              .transform(DAGEdge.<V, E>extractTail())
218                              .toSet();
219     }
220 
221     /**
222      * Get a multimap of incoming edges.  For each node reachable from this node, the map will
223      * contain each of its incoming edges (also reachable from this graph).
224      *
225      * @return The reverse edge map.
226      */
227     @Nonnull
228     private SetMultimap<DAGNode<V,E>,DAGEdge<V,E>> getIncomingEdgeMap() {
229         return reverseEdgeCache.get();
230     }
231 
232     @Nonnull
233     public Set<DAGNode<V,E>> getReachableNodes() {
234         return reachableNodeCache.get();
235     }
236 
237     /**
238      * Topographical sort all nodes reachable from the given root node. Nodes
239      * that are farther away, or more connected, are at the beginning of the
240      * list.
241      * <p>
242      * Nodes in the graph that are not connected to the root will not appear in
243      * the returned list.
244      * @return The sorted list of reachable nodes.
245      */
246     @Nonnull
247     public List<DAGNode<V,E>> getSortedNodes() {
248         return topologicalSortCache.get();
249     }
250 
251     /**
252      * Helper mode for {@link #getSortedNodes()}, via {@link TopologicalSortSupplier}.  This method
253      * does a depth-first traversal of the nodes, adding each to the {@code visited} set when it is
254      * left.  This results in {@code visited} being a topological sort.
255      *
256      * @param visited The set of nodes seen so far.
257      */
258     private void sortVisit(LinkedHashSet<DAGNode<V,E>> visited) {
259         if (!visited.contains(this)) {
260             for (DAGEdge<V,E> nbr: outgoingEdges) {
261                 nbr.getTail().sortVisit(visited);
262             }
263             // neighbors won't have added this, or we have an impossible cycle
264             assert !visited.contains(this);
265             visited.add(this);
266         }
267     }
268 
269     /**
270      * Get the incoming edges to a node reachable from this node.
271      * @return The set of incoming edges, or an empty set if the node is not reachable.
272      */
273     @Nonnull
274     public Set<DAGEdge<V,E>> getIncomingEdges(DAGNode<V,E> node) {
275         return getIncomingEdgeMap().get(node);
276     }
277 
278     /**
279      * Replace one node with another in this graph.  All edges referencing {@code node} are replaced
280      * with edges referencing {@code replacement}.
281      *
282      * @param node The node to replace.
283      * @param replacement The replacement node.
284      * @param memory A table to remember node replacements.  It maintains a mapping of every node
285      *               that has to be replaced with the node that replaces it.  This map should
286      *               usually be empty on the initial call to this method.  In particular, it should
287      *               not contain any reachable nodes on the initial call, or unexpected behavior
288      *               may arise.  Recursive calls of this method to itself do contain such nodes.
289      * @return The graph with the replaced node.
290      */
291     public DAGNode<V,E> replaceNode(DAGNode<V,E> node, DAGNode<V,E> replacement,
292                                     Map<DAGNode<V,E>,DAGNode<V,E>> memory) {
293         if (this.equals(node)) {
294             memory.put(node, replacement);
295             return replacement;
296         } else if (memory.containsKey(this)) {
297             // we have already been replaced, reuse the replacement
298             return memory.get(this);
299         } else if (getReachableNodes().contains(node)) {
300             DAGNodeBuilder<V,E> bld = newBuilder(label);
301             for (DAGEdge<V,E> edge: outgoingEdges) {
302                 DAGNode<V,E> newTail = edge.getTail().replaceNode(node, replacement, memory);
303                 bld.addEdge(newTail, edge.getLabel());
304             }
305             DAGNode<V,E> repl = bld.build();
306             memory.put(this, repl);
307             return repl;
308         } else {
309             return this;
310         }
311     }
312 
313     /**
314      * Do a breadth-first search for a node.
315      *
316      * @param pred The predicate for matching nodes.
317      * @return The first node matching {@code pred} in a breadth-first search, or {@code null} if no
318      *         such node is found.
319      */
320     public DAGNode<V, E> findNodeBFS(@Nonnull Predicate<? super DAGNode<V, E>> pred) {
321         if (pred.apply(this)) {
322             return this;
323         }
324 
325         Queue<DAGNode<V, E>> work = Lists.newLinkedList();
326         Set<DAGNode<V, E>> seen = Sets.newHashSet();
327         work.add(this);
328         seen.add(this);
329         while (!work.isEmpty()) {
330             DAGNode<V, E> node = work.remove();
331             for (DAGEdge<V, E> e : node.getOutgoingEdges()) {
332                 // is this the node we are looking for?
333                 DAGNode<V, E> nbr = e.getTail();
334                 if (!seen.contains(nbr)) {
335                     if (pred.apply(nbr)) {
336                         return nbr;
337                     } else {
338                         seen.add(nbr);
339                         work.add(nbr);
340                     }
341                 }
342             }
343         }
344 
345         // no node found
346         return null;
347     }
348 
349     /**
350      * Do a breadth-first search for an edge.
351      *
352      * @param pred The predicate for matching nodes.
353      * @return The first node matching {@code pred} in a breadth-first search, or {@code null} if no
354      *         such node is found.
355      */
356     public DAGEdge<V, E> findEdgeBFS(@Nonnull Predicate<? super DAGEdge<V, E>> pred) {
357         Queue<DAGNode<V, E>> work = Lists.newLinkedList();
358         Set<DAGNode<V, E>> seen = Sets.newHashSet();
359         work.add(this);
360         seen.add(this);
361         while (!work.isEmpty()) {
362             DAGNode<V, E> node = work.remove();
363             for (DAGEdge<V, E> e : node.getOutgoingEdges()) {
364                 // is this the edge we are looking for?
365                 if (pred.apply(e)) {
366                     return e;
367                 } else if (!seen.contains(e.getTail())) {
368                     seen.add(e.getTail());
369                     work.add(e.getTail());
370                 }
371             }
372         }
373 
374         // no node found
375         return null;
376     }
377 
378     /**
379      * Transform the edges in this graph.  Edges in parent nodes are passed <em>after</em> their
380      * target nodes are rewritten, if necessary.
381      *
382      * @param function The edge transformation function.  Any edge returned by this function must
383      *                 have the same head node as the function it was passed.  The transform
384      *                 function may need to replace the head node on a returned edge; the label and
385      *                 tail will be preserved.  If the function returns {@code null}, that is the
386      *                 same as returning its input unmodified.
387      * @return The rewritten graph.
388      */
389     public DAGNode<V,E> transformEdges(Function<? super DAGEdge<V,E>, ? extends DAGEdge<V,E>> function) {
390         // builder for new node
391         DAGNodeBuilder<V,E> builder = null;
392         // intact edges (unmodified edges)
393         List<DAGEdge<V,E>> intact = Lists.newArrayListWithCapacity(outgoingEdges.size());
394         for (DAGEdge<V,E> edge: outgoingEdges) {
395             DAGNode<V,E> tail = edge.getTail();
396             DAGNode<V,E> transformedTail = tail.transformEdges(function);
397             DAGEdge<V,E> toQuery = edge;
398             if (transformedTail != tail) {
399                 // the node changed, query with the updated edge
400                 toQuery = DAGEdge.create(this, transformedTail, edge.getLabel());
401             }
402             DAGEdge<V,E> transformedEdge = function.apply(toQuery);
403             if (transformedEdge == null) {
404                 transformedEdge = toQuery;
405             }
406             if (edge.equals(transformedEdge)) {
407                 // edge unmodified
408                 if (builder == null) {
409                     intact.add(transformedEdge);
410                 } else {
411                     builder.addEdge(transformedEdge.getTail(), transformedEdge.getLabel());
412                 }
413             } else {
414                 // modified, need to transform this node
415                 if (builder == null) {
416                     builder = newBuilder(label);
417                     for (DAGEdge<V,E> done: intact) {
418                         builder.addEdge(done.getTail(), done.getLabel());
419                     }
420                 }
421                 builder.addEdge(transformedEdge.getTail(), transformedEdge.getLabel());
422             }
423         }
424 
425         if (builder != null) {
426             return builder.build();
427         } else {
428             return this;
429         }
430     }
431 
432     @Override
433     public String toString() {
434         StringBuilder sb = new StringBuilder();
435         sb.append("node ")
436           .append(label)
437           .append(" with ")
438           .append(getReachableNodes().size())
439           .append(" nodes and ")
440           .append(outgoingEdges.size())
441           .append(" edges");
442         return sb.toString();
443     }
444 
445     public static <V> Predicate<DAGNode<V,?>> labelMatches(final Predicate<? super V> pred) {
446         return new Predicate<DAGNode<V, ?>>() {
447             @Override
448             public boolean apply(@Nullable DAGNode<V, ?> input) {
449                 V lbl = input == null ? null : input.getLabel();
450                 return pred.apply(lbl);
451             }
452         };
453     }
454 
455     /**
456      * Supplier to compute the map of incoming edges.  Used to implement {@link #getIncomingEdgeMap()}.
457      */
458     private class EdgeMapSupplier implements Supplier<SetMultimap<DAGNode<V, E>, DAGEdge<V, E>>> {
459         @Override
460         public SetMultimap<DAGNode<V, E>, DAGEdge<V, E>> get() {
461             ImmutableSetMultimap.Builder<DAGNode<V,E>,DAGEdge<V,E>> bld = ImmutableSetMultimap.builder();
462             for (DAGEdge<V,E> nbr: outgoingEdges) {
463                 bld.put(nbr.getTail(), nbr);
464                 bld.putAll(nbr.getTail().getIncomingEdgeMap());
465             }
466             return bld.build();
467         }
468     }
469 
470     /**
471      * Supplier to compute the set of reachable nodes.
472      */
473     private class NodeSetSupplier implements Supplier<Set<DAGNode<V, E>>> {
474         @Override
475         public ImmutableSet<DAGNode<V, E>> get() {
476             return ImmutableSet.copyOf(getSortedNodes());
477         }
478     }
479 
480     private class TopologicalSortSupplier implements Supplier<List<DAGNode<V,E>>> {
481         @Override
482         public List<DAGNode<V, E>> get() {
483             LinkedHashSet<DAGNode<V,E>> visited = Sets.newLinkedHashSet();
484             sortVisit(visited);
485             return ImmutableList.copyOf(visited);
486         }
487     }
488 }