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 }