org.grouplens.grapht.graph.DAGNode.java Source code

Java tutorial

Introduction

Here is the source code for org.grouplens.grapht.graph.DAGNode.java

Source

/*
 * 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);
        }
    }
}