/* * Copyright (C) 2012 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package interactivespaces.util.graph; import com.google.common.collect.Maps; import java.util.Map; /** * Walk a graph in a depth-first manner with an observer. * * <p> * The graph can be directed or undirected, and should be labeled as such. * * @param <I> * type of IDs in the graph * @param <T> * type of the data in the graph * * @author Keith M. Hughes */ public class DepthFirstGraphWalker<I, T> { /** * Map from node IDs to their data. */ private Map<I, WalkableGraphNode<I, T>> idToNode = Maps.newHashMap(); /** * Current time in the walker. */ private int time = 0; /** * {@code true} if the graph is directed. */ private boolean directed = true; /** * Is the graph directed or not? * * @return {@code true} if the graph is directed. */ public boolean isDirected() { return directed; } /** * Set whether or not the graph is directed. * * @param directed * {@code true} if the graph is directed */ public void setDirected(boolean directed) { this.directed = directed; } /** * Add a new node to the graph. * * @param id * the ID of the node * @param data * the data for the node * * @return the graph node */ public WalkableGraphNode<I, T> addNode(I id, T data) { WalkableGraphNode<I, T> node = getNode(id); node.setData(data); return node; } /** * Add a neighbor for a node. * * @param node * the node the neighbor will be added to * @param neighborNames * the IDs of neighbors */ public void addNodeNeighbor(WalkableGraphNode<I, T> node, I... neighborNames) { if (neighborNames != null) { for (I neighborName : neighborNames) { node.addNeighbor(getNode(neighborName)); } } } /** * Walk a particular node of the graph. * * @param node * the node to start down * @param observer * the observer watching the walk */ public void walkNode(WalkableGraphNode<I, T> node, GraphWalkerObserver<I, T> observer) { node.setDiscovered(true); ++time; node.setEntryTime(time); observer.observeGraphNodeBefore(node); for (WalkableGraphNode<I, T> neighbor : node.getNeighbors()) { if (!neighbor.isDiscovered()) { neighbor.setParent(node); observer.observeGraphEdge(node, neighbor, getEdgeClassification(node, neighbor)); walkNode(neighbor, observer); } else if (!neighbor.isProcessed() || directed) { observer.observeGraphEdge(node, neighbor, getEdgeClassification(node, neighbor)); } } observer.observeGraphNodeAfter(node); ++time; node.setExitTime(time); node.setProcessed(true); } /** * Get the node associated with a given data item. * * @param id * the ID of the node * * @return either an existing node or a brand new one if there was none */ public WalkableGraphNode<I, T> getNode(I id) { WalkableGraphNode<I, T> node = idToNode.get(id); if (node == null) { node = new WalkableGraphNode<I, T>(id); idToNode.put(id, node); } return node; } /** * Get a classification for an edge. * * @param nodeFrom * the node the walker is walking from * @param nodeTo * the node the walker is walking to * * @return the edge classification */ public GraphWalkerEdgeClassification getEdgeClassification(WalkableGraphNode<I, T> nodeFrom, WalkableGraphNode<I, T> nodeTo) { if (nodeFrom.equals(nodeTo.getParent())) { return GraphWalkerEdgeClassification.TREE; } else if (nodeTo.isDiscovered() && !nodeTo.isProcessed()) { return GraphWalkerEdgeClassification.BACK; } else if (nodeTo.isProcessed()) { if (nodeTo.getEntryTime() > nodeFrom.getEntryTime()) { return GraphWalkerEdgeClassification.FORWARD; } else { return GraphWalkerEdgeClassification.CROSS; } } return GraphWalkerEdgeClassification.UNCLASSIFIED; } }