212 lines
5.6 KiB
Java
212 lines
5.6 KiB
Java
/*******************************************************************************
|
|
* Copyright (c) 2002 - 2006 IBM Corporation.
|
|
* All rights reserved. This program and the accompanying materials
|
|
* are made available under the terms of the Eclipse Public License v1.0
|
|
* which accompanies this distribution, and is available at
|
|
* http://www.eclipse.org/legal/epl-v10.html
|
|
*
|
|
* Contributors:
|
|
* IBM Corporation - initial API and implementation
|
|
*******************************************************************************/
|
|
|
|
package com.ibm.wala.util.graph.traverse;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.Iterator;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Stack;
|
|
|
|
import com.ibm.wala.util.Predicate;
|
|
import com.ibm.wala.util.collections.HashMapFactory;
|
|
import com.ibm.wala.util.collections.NonNullSingletonIterator;
|
|
import com.ibm.wala.util.graph.Graph;
|
|
|
|
/**
|
|
* This class searches depth-first search for node that matches some criteria. If found, it reports a path to the first node found.
|
|
*
|
|
* This class follows the outNodes of the graph nodes to define the graph, but this behavior can be changed by overriding the
|
|
* getConnected method.
|
|
*/
|
|
public class DFSPathFinder<T> extends Stack<T> {
|
|
public static final long serialVersionUID = 9939900773328288L;
|
|
|
|
/**
|
|
* The graph to search
|
|
*/
|
|
protected final Graph<T> G;
|
|
|
|
/**
|
|
* The Filter which defines the target set of nodes to find
|
|
*/
|
|
final private Predicate<T> filter;
|
|
|
|
/**
|
|
* an enumeration of all nodes to search from
|
|
*/
|
|
final private Iterator<T> roots;
|
|
|
|
/**
|
|
* An iterator of child nodes for each node being searched
|
|
*/
|
|
final protected Map<Object, Iterator<? extends T>> pendingChildren = HashMapFactory.make(25);
|
|
|
|
/**
|
|
* Flag recording whether initialization has happened.
|
|
*/
|
|
private boolean initialized = false;
|
|
|
|
/**
|
|
* Construct a depth-first enumerator starting with a particular node in a directed graph.
|
|
*
|
|
* @param G the graph whose nodes to enumerate
|
|
* @throws IllegalArgumentException if G is null
|
|
*/
|
|
public DFSPathFinder(Graph<T> G, T N, Predicate<T> f) throws IllegalArgumentException {
|
|
if (G == null) {
|
|
throw new IllegalArgumentException("G is null");
|
|
}
|
|
if (!G.containsNode(N)) {
|
|
throw new IllegalArgumentException("source node not in graph: " + N);
|
|
}
|
|
this.G = G;
|
|
this.roots = new NonNullSingletonIterator<T>(N);
|
|
this.filter = f;
|
|
}
|
|
|
|
/**
|
|
* Construct a depth-first enumerator across the (possibly improper) subset of nodes reachable from the nodes in the given
|
|
* enumeration.
|
|
*
|
|
* @param nodes the set of nodes from which to start searching
|
|
*/
|
|
public DFSPathFinder(Graph<T> G, Iterator<T> nodes, Predicate<T> f) {
|
|
this.G = G;
|
|
this.roots = nodes;
|
|
this.filter = f;
|
|
if (G == null) {
|
|
throw new IllegalArgumentException("G is null");
|
|
}
|
|
if (roots == null) {
|
|
throw new IllegalArgumentException("roots is null");
|
|
}
|
|
if (filter == null) {
|
|
throw new IllegalArgumentException("filter is null");
|
|
}
|
|
}
|
|
|
|
private void init() {
|
|
initialized = true;
|
|
if (roots.hasNext()) {
|
|
T n = roots.next();
|
|
push(n);
|
|
setPendingChildren(n, getConnected(n));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return a List of nodes that specifies the first path found from a root to a node accepted by the filter. Returns null if no
|
|
* path found.
|
|
*/
|
|
public List<T> find() {
|
|
if (!initialized) {
|
|
init();
|
|
}
|
|
while (hasNext()) {
|
|
T n = peek();
|
|
if (filter.test(n)) {
|
|
List<T> path = currentPath();
|
|
advance();
|
|
return path;
|
|
}
|
|
advance();
|
|
}
|
|
return null;
|
|
}
|
|
|
|
protected List<T> currentPath() {
|
|
ArrayList<T> result = new ArrayList<T>();
|
|
for (Iterator<T> path = iterator(); path.hasNext();) {
|
|
result.add(0, path.next());
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Return whether there are any more nodes left to enumerate.
|
|
*
|
|
* @return true if there nodes left to enumerate.
|
|
*/
|
|
public boolean hasNext() {
|
|
return (!empty());
|
|
}
|
|
|
|
/**
|
|
* Method getPendingChildren.
|
|
*
|
|
* @return Object
|
|
*/
|
|
protected Iterator<? extends T> getPendingChildren(T n) {
|
|
return pendingChildren.get(n);
|
|
}
|
|
|
|
/**
|
|
* Method setPendingChildren.
|
|
*
|
|
* @param v
|
|
* @param iterator
|
|
*/
|
|
protected void setPendingChildren(T v, Iterator<? extends T> iterator) {
|
|
pendingChildren.put(v, iterator);
|
|
}
|
|
|
|
/**
|
|
* Advance to the next graph node in discover time order.
|
|
*/
|
|
private void advance() {
|
|
|
|
// we always return the top node on the stack.
|
|
T currentNode = peek();
|
|
|
|
// compute the next node to return.
|
|
assert getPendingChildren(currentNode) != null;
|
|
do {
|
|
T stackTop = peek();
|
|
for (Iterator<? extends T> it = getPendingChildren(stackTop); it.hasNext();) {
|
|
T child = it.next();
|
|
if (getPendingChildren(child) == null) {
|
|
// found a new child.
|
|
push(child);
|
|
setPendingChildren(child, getConnected(child));
|
|
return;
|
|
}
|
|
}
|
|
// didn't find any new children. pop the stack and try again.
|
|
pop();
|
|
|
|
} while (!empty());
|
|
|
|
// search for the next unvisited root.
|
|
while (roots.hasNext()) {
|
|
T nextRoot = roots.next();
|
|
if (getPendingChildren(nextRoot) == null) {
|
|
push(nextRoot);
|
|
setPendingChildren(nextRoot, getConnected(nextRoot));
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* get the out edges of a given node
|
|
*
|
|
* @param n the node of which to get the out edges
|
|
* @return the out edges
|
|
*
|
|
*/
|
|
protected Iterator<? extends T> getConnected(T n) {
|
|
return G.getSuccNodes(n);
|
|
}
|
|
}
|