Java tutorial
/* * Grapht, an open source dependency injector. * Copyright 2014-2015 various contributors (see CONTRIBUTORS.txt) * Copyright 2010-2014 Regents of the University of Minnesota * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License along with * this program; if not, write to the Free Software Foundation, Inc., 51 * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package org.grouplens.grapht.graph; import com.google.common.base.*; import com.google.common.collect.*; import org.apache.commons.lang3.tuple.Pair; import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; import java.io.IOException; import java.io.ObjectInputStream; import java.io.Serializable; import java.util.*; /** * A node in a (rooted) DAG. Since DAGs are rooted, a full graph is just represented by its root * node. Nodes are compared using reference equality, so distinct nodes do not compare equal even * if they have identical labels and edge sets. * * <p>Nodes and edges may not have null labels. There <em>may</em> be multiple edges from one * node to another, so long as those edges have distinct labels. * * <p>Nodes know about all nodes reachable from them, and the edges connecting those nodes. * * <p>DAGs and their nodes are immutable. You can build them using a {@linkplain DAGNodeBuilder builder}, * obtained from {@link #newBuilder(Object)}. * * @param <V> The type of node (vertex) labels. * @param <E> The type of edge labels. * @since 0.7.0 * @author <a href="http://www.grouplens.org">GroupLens Research</a> */ @Immutable public class DAGNode<V, E> implements Serializable { private static final long serialVersionUID = 1L; @Nonnull @SuppressWarnings("squid:S1948") // serializable warning; node is serializable iff its label type is private final V label; @Nonnull private final ImmutableSet<DAGEdge<V, E>> outgoingEdges; private transient Supplier<SetMultimap<DAGNode<V, E>, DAGEdge<V, E>>> reverseEdgeCache; private transient Supplier<Set<DAGNode<V, E>>> reachableNodeCache; private transient Supplier<List<DAGNode<V, E>>> topologicalSortCache; /** * Create a new DAG node with no outgoing edges. * @param label The node label. * @param <V> The type of node labels. * @param <E> The type of edge labels. */ public static <V, E> DAGNode<V, E> singleton(V label) { Preconditions.checkNotNull(label, "node label"); return new DAGNode<V, E>(label, ImmutableSet.<Pair<DAGNode<V, E>, E>>of()); } /** * Construct a new DAG node builder. * @param <V> The type of node labels. * @param <E> The type of edge labels. * @return The DAG node builder. */ public static <V, E> DAGNodeBuilder<V, E> newBuilder() { return new DAGNodeBuilder<V, E>(); } /** * Construct a new DAG node builder. * @param label The node label. * @param <V> The type of node labels. * @param <E> The type of edge labels. * @return The DAG node builder. */ public static <V, E> DAGNodeBuilder<V, E> newBuilder(V label) { return new DAGNodeBuilder<V, E>(label); } /** * Create a new builder initialized to build a copy of the specified node. * @param node The node to copy. * @param <V> The type of node labels. * @param <E> The type of edge labels. * @return A new builder initialized with the labels and edges of {@code node}. */ public static <V, E> DAGNodeBuilder<V, E> copyBuilder(DAGNode<V, E> node) { DAGNodeBuilder<V, E> bld = newBuilder(node.getLabel()); for (DAGEdge<V, E> edge : node.getOutgoingEdges()) { bld.addEdge(edge.getTail(), edge.getLabel()); } return bld; } /** * Construct a new DAG node. * @param lbl The label. * @param edges The edges. This takes pairs, not actual edge objects, because the edge objects * need to be constructed within the constructor in order to create the circular * references back to the head nodes properly. */ DAGNode(@Nonnull V lbl, Iterable<Pair<DAGNode<V, E>, E>> edges) { label = lbl; ImmutableSet.Builder<DAGEdge<V, E>> bld = ImmutableSet.builder(); for (Pair<DAGNode<V, E>, E> pair : edges) { DAGEdge<V, E> edge = new DAGEdge<V, E>(this, pair.getLeft(), pair.getRight()); bld.add(edge); } outgoingEdges = bld.build(); initializeCaches(); } /** * Initialize caches for traversing this node. */ private void initializeCaches() { reverseEdgeCache = Suppliers.memoize(new EdgeMapSupplier()); reachableNodeCache = Suppliers.memoize(new NodeSetSupplier()); topologicalSortCache = Suppliers.memoize(new TopologicalSortSupplier()); } /** * Override the object reading protocol to make sure caches are instantiated. This just calls * {@link #initializeCaches()} after doing the default object-reading. * * @param stream The stream to read from. * @throws IOException If an I/O exception occurs deserializing the object. * @throws ClassNotFoundException If there is a missing class deserializing the object. */ private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { stream.defaultReadObject(); initializeCaches(); } /** * Get the label for this node. * @return The node's label. */ @Nonnull public V getLabel() { return label; } /** * Get the outgoing edges of this node. * @return The outgoing edges of the node. */ @Nonnull public Set<DAGEdge<V, E>> getOutgoingEdges() { return outgoingEdges; } /** * Get the outgoing edge with the specified target and label, if it exists. * @param target The target node. * @param label The label. * @return The edge from this node to {@code target} with label {@code label}, if it exists, or * {@code null} if no such edge exists. */ public DAGEdge<V, E> getOutgoingEdge(DAGNode<V, E> target, E label) { for (DAGEdge<V, E> edge : outgoingEdges) { if (edge.getTail().equals(target) && edge.getLabel().equals(label)) { return edge; } } return null; } /** * Get an outgoing edge from this node with the specified label, if it exists. * * @param label The label. * @return An outgoing edge with the specified label, or {@code null} if no such edge exists. * If multiple edges have this label, an arbitrary one is returned. */ public DAGEdge<V, E> getOutgoingEdgeWithLabel(E label) { return getOutgoingEdgeWithLabel(Predicates.equalTo(label)); } /** * Search for an outgoing edge by a predicate. * * @param predicate A predicate over labels. * @return An outgoing edge matching the predicate, or {@code null} if no such edge exists. If * multiple edges have labels matching the predicate, it is undefined which one will be * added. */ public DAGEdge<V, E> getOutgoingEdgeWithLabel(Predicate<? super E> predicate) { Predicate<DAGEdge<?, E>> edgePred = DAGEdge.labelMatches(predicate); return Iterables.find(outgoingEdges, edgePred, null); } /** * Get the nodes that are adjacent to this node (only considering outgoing edges). * @return The set of adjacent nodes. */ public Set<DAGNode<V, E>> getAdjacentNodes() { return FluentIterable.from(outgoingEdges).transform(DAGEdge.<V, E>extractTail()).toSet(); } /** * Get a multimap of incoming edges. For each node reachable from this node, the map will * contain each of its incoming edges (also reachable from this graph). * * @return The reverse edge map. */ @Nonnull private SetMultimap<DAGNode<V, E>, DAGEdge<V, E>> getIncomingEdgeMap() { return reverseEdgeCache.get(); } @Nonnull public Set<DAGNode<V, E>> getReachableNodes() { return reachableNodeCache.get(); } /** * Topographical sort all nodes reachable from the given root node. Nodes * that are farther away, or more connected, are at the beginning of the * list. * <p> * Nodes in the graph that are not connected to the root will not appear in * the returned list. * @return The sorted list of reachable nodes. */ @Nonnull public List<DAGNode<V, E>> getSortedNodes() { return topologicalSortCache.get(); } /** * Helper mode for {@link #getSortedNodes()}, via {@link TopologicalSortSupplier}. This method * does a depth-first traversal of the nodes, adding each to the {@code visited} set when it is * left. This results in {@code visited} being a topological sort. * * @param visited The set of nodes seen so far. */ private void sortVisit(LinkedHashSet<DAGNode<V, E>> visited) { if (!visited.contains(this)) { for (DAGEdge<V, E> nbr : outgoingEdges) { nbr.getTail().sortVisit(visited); } // neighbors won't have added this, or we have an impossible cycle assert !visited.contains(this); visited.add(this); } } /** * Get the incoming edges to a node reachable from this node. * @return The set of incoming edges, or an empty set if the node is not reachable. */ @Nonnull public Set<DAGEdge<V, E>> getIncomingEdges(DAGNode<V, E> node) { return getIncomingEdgeMap().get(node); } /** * Replace one node with another in this graph. All edges referencing {@code node} are replaced * with edges referencing {@code replacement}. * * @param node The node to replace. * @param replacement The replacement node. * @param memory A table to remember node replacements. It maintains a mapping of every node * that has to be replaced with the node that replaces it. This map should * usually be empty on the initial call to this method. In particular, it should * not contain any reachable nodes on the initial call, or unexpected behavior * may arise. Recursive calls of this method to itself do contain such nodes. * @return The graph with the replaced node. */ public DAGNode<V, E> replaceNode(DAGNode<V, E> node, DAGNode<V, E> replacement, Map<DAGNode<V, E>, DAGNode<V, E>> memory) { if (this.equals(node)) { memory.put(node, replacement); return replacement; } else if (memory.containsKey(this)) { // we have already been replaced, reuse the replacement return memory.get(this); } else if (getReachableNodes().contains(node)) { DAGNodeBuilder<V, E> bld = newBuilder(label); for (DAGEdge<V, E> edge : outgoingEdges) { DAGNode<V, E> newTail = edge.getTail().replaceNode(node, replacement, memory); bld.addEdge(newTail, edge.getLabel()); } DAGNode<V, E> repl = bld.build(); memory.put(this, repl); return repl; } else { return this; } } /** * Do a breadth-first search for a node. * * @param pred The predicate for matching nodes. * @return The first node matching {@code pred} in a breadth-first search, or {@code null} if no * such node is found. */ public DAGNode<V, E> findNodeBFS(@Nonnull Predicate<? super DAGNode<V, E>> pred) { if (pred.apply(this)) { return this; } Queue<DAGNode<V, E>> work = Lists.newLinkedList(); Set<DAGNode<V, E>> seen = Sets.newHashSet(); work.add(this); seen.add(this); while (!work.isEmpty()) { DAGNode<V, E> node = work.remove(); for (DAGEdge<V, E> e : node.getOutgoingEdges()) { // is this the node we are looking for? DAGNode<V, E> nbr = e.getTail(); if (!seen.contains(nbr)) { if (pred.apply(nbr)) { return nbr; } else { seen.add(nbr); work.add(nbr); } } } } // no node found return null; } /** * Do a breadth-first search for an edge. * * @param pred The predicate for matching nodes. * @return The first node matching {@code pred} in a breadth-first search, or {@code null} if no * such node is found. */ public DAGEdge<V, E> findEdgeBFS(@Nonnull Predicate<? super DAGEdge<V, E>> pred) { Queue<DAGNode<V, E>> work = Lists.newLinkedList(); Set<DAGNode<V, E>> seen = Sets.newHashSet(); work.add(this); seen.add(this); while (!work.isEmpty()) { DAGNode<V, E> node = work.remove(); for (DAGEdge<V, E> e : node.getOutgoingEdges()) { // is this the edge we are looking for? if (pred.apply(e)) { return e; } else if (!seen.contains(e.getTail())) { seen.add(e.getTail()); work.add(e.getTail()); } } } // no node found return null; } /** * Transform the edges in this graph. Edges in parent nodes are passed <em>after</em> their * target nodes are rewritten, if necessary. * * @param function The edge transformation function. Any edge returned by this function must * have the same head node as the function it was passed. The transform * function may need to replace the head node on a returned edge; the label and * tail will be preserved. If the function returns {@code null}, that is the * same as returning its input unmodified. * @return The rewritten graph. */ public DAGNode<V, E> transformEdges(Function<? super DAGEdge<V, E>, ? extends DAGEdge<V, E>> function) { // builder for new node DAGNodeBuilder<V, E> builder = null; // intact edges (unmodified edges) List<DAGEdge<V, E>> intact = Lists.newArrayListWithCapacity(outgoingEdges.size()); for (DAGEdge<V, E> edge : outgoingEdges) { DAGNode<V, E> tail = edge.getTail(); DAGNode<V, E> transformedTail = tail.transformEdges(function); DAGEdge<V, E> toQuery = edge; if (transformedTail != tail) { // the node changed, query with the updated edge toQuery = DAGEdge.create(this, transformedTail, edge.getLabel()); } DAGEdge<V, E> transformedEdge = function.apply(toQuery); if (transformedEdge == null) { transformedEdge = toQuery; } if (edge.equals(transformedEdge)) { // edge unmodified if (builder == null) { intact.add(transformedEdge); } else { builder.addEdge(transformedEdge.getTail(), transformedEdge.getLabel()); } } else { // modified, need to transform this node if (builder == null) { builder = newBuilder(label); for (DAGEdge<V, E> done : intact) { builder.addEdge(done.getTail(), done.getLabel()); } } builder.addEdge(transformedEdge.getTail(), transformedEdge.getLabel()); } } if (builder != null) { return builder.build(); } else { return this; } } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("node ").append(label).append(" with ").append(getReachableNodes().size()).append(" nodes and ") .append(outgoingEdges.size()).append(" edges"); return sb.toString(); } public static <V> Predicate<DAGNode<V, ?>> labelMatches(final Predicate<? super V> pred) { return new Predicate<DAGNode<V, ?>>() { @Override public boolean apply(@Nullable DAGNode<V, ?> input) { V lbl = input == null ? null : input.getLabel(); return pred.apply(lbl); } }; } /** * Supplier to compute the map of incoming edges. Used to implement {@link #getIncomingEdgeMap()}. */ private class EdgeMapSupplier implements Supplier<SetMultimap<DAGNode<V, E>, DAGEdge<V, E>>> { @Override public SetMultimap<DAGNode<V, E>, DAGEdge<V, E>> get() { ImmutableSetMultimap.Builder<DAGNode<V, E>, DAGEdge<V, E>> bld = ImmutableSetMultimap.builder(); for (DAGEdge<V, E> nbr : outgoingEdges) { bld.put(nbr.getTail(), nbr); bld.putAll(nbr.getTail().getIncomingEdgeMap()); } return bld.build(); } } /** * Supplier to compute the set of reachable nodes. */ private class NodeSetSupplier implements Supplier<Set<DAGNode<V, E>>> { @Override public ImmutableSet<DAGNode<V, E>> get() { return ImmutableSet.copyOf(getSortedNodes()); } } private class TopologicalSortSupplier implements Supplier<List<DAGNode<V, E>>> { @Override public List<DAGNode<V, E>> get() { LinkedHashSet<DAGNode<V, E>> visited = Sets.newLinkedHashSet(); sortVisit(visited); return ImmutableList.copyOf(visited); } } }