// Copyright (c) 2001 Dustin Sallings <dustin@spy.net>
package net.spy.util;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
/**
* This is a utility class for finding the least costly paths from each node
* from a collection to all nodes to which they link.
*
* <p>
* The basic process is to create some {@link SPNode}s and link them
* together arbitrarily with {@link SPVertex} instances. Once the graph is
* complete, obtain an instance of ShortestPathFinder and have it calculate
* the paths for any (or all) nodes. calculatePaths may be called as many
* times as you need, it will reset the paths and build new ones.
* </p>
*
* <p>
* For example, consider the graph to the right. The following will be
* true (this is actually my test case):
* <table>
* <tr width="100%">
* <td valign="top">
* <table border="1">
* <tr><th>From</th><th>To</th><th>Next Hop</th><th>Cost</th></tr>
*
* <tr><td>A</td><td>A</td><td><i>n/a</i></td><td><i>n/a</i></td></tr>
* <tr><td>A</td><td>B</td><td>B</td><td>10</td></tr>
* <tr><td>A</td><td>C</td><td>C</td><td>15</td></tr>
* <tr><td>A</td><td>D</td><td>C</td><td>25</td></tr>
* <tr><td>A</td><td>E</td><td>C</td><td>25</td></tr>
* <tr><td>A</td><td>F</td><td>C</td><td>25</td></tr>
* <tr><td>A</td><td>G</td><td>C</td><td>35</td></tr>
*
* <tr><td>B</td><td>A</td><td><i>n/a</i></td><td><i>n/a</i></td></tr>
* <tr><td>B</td><td>B</td><td>C</td><td>220</td></tr>
* <tr><td>B</td><td>C</td><td>C</td><td>10</td></tr>
* <tr><td>B</td><td>D</td><td>C</td><td>20</td></tr>
* <tr><td>B</td><td>E</td><td>C</td><td>20</td></tr>
* <tr><td>B</td><td>F</td><td>C</td><td>20</td></tr>
* <tr><td>B</td><td>G</td><td>C</td><td>30</td></tr>
*
* <tr><td>C</td><td>A</td><td><i>n/a</i></td><td><i>n/a</i></td></tr>
* <tr><td>C</td><td>B</td><td>F</td><td>210</td></tr>
* <tr><td>C</td><td>C</td><td>D</td><td>110</td></tr>
* <tr><td>C</td><td>D</td><td>D</td><td>10</td></tr>
* <tr><td>C</td><td>E</td><td>E</td><td>10</td></tr>
* <tr><td>C</td><td>F</td><td>F</td><td>10</td></tr>
* <tr><td>C</td><td>G</td><td>F</td><td>20</td></tr>
*
* <tr><td>D</td><td>A</td><td><i>n/a</i></td><td><i>n/a</i></td></tr>
* <tr><td>D</td><td>B</td><td>C</td><td>310</td></tr>
* <tr><td>D</td><td>C</td><td>C</td><td>100</td></tr>
* <tr><td>D</td><td>D</td><td>C</td><td>110</td></tr>
* <tr><td>D</td><td>E</td><td>C</td><td>110</td></tr>
* <tr><td>D</td><td>F</td><td>C</td><td>110</td></tr>
* <tr><td>D</td><td>G</td><td>C</td><td>120</td></tr>
* </table>
* </td>
*
* <td valign="top">
* <table border="1">
* <tr><th>From</th><th>To</th><th>Next Hop</th><th>Cost</th></tr>
* <tr><td>E</td><td>A</td><td><i>n/a</i></td><td><i>n/a</i></td></tr>
* <tr><td>E</td><td>B</td><td><i>n/a</i></td><td><i>n/a</i></td></tr>
* <tr><td>E</td><td>C</td><td><i>n/a</i></td><td><i>n/a</i></td></tr>
* <tr><td>E</td><td>D</td><td><i>n/a</i></td><td><i>n/a</i></td></tr>
* <tr><td>E</td><td>E</td><td>E</td><td>10</td></tr>
* <tr><td>E</td><td>F</td><td><i>n/a</i></td><td><i>n/a</i></td></tr>
* <tr><td>E</td><td>G</td><td><i>n/a</i></td><td><i>n/a</i></td></tr>
*
* <tr><td>F</td><td>A</td><td><i>n/a</i></td><td><i>n/a</i></td></tr>
* <tr><td>F</td><td>B</td><td>B</td><td>200</td></tr>
* <tr><td>F</td><td>C</td><td>B</td><td>210</td></tr>
* <tr><td>F</td><td>D</td><td>B</td><td>220</td></tr>
* <tr><td>F</td><td>E</td><td>B</td><td>220</td></tr>
* <tr><td>F</td><td>F</td><td>B</td><td>220</td></tr>
* <tr><td>F</td><td>G</td><td>G</td><td>10</td></tr>
*
* <tr><td>G</td><td>A</td><td><i>n/a</i></td><td><i>n/a</i></td></tr>
* <tr><td>G</td><td>B</td><td><i>n/a</i></td><td><i>n/a</i></td></tr>
* <tr><td>G</td><td>C</td><td><i>n/a</i></td><td><i>n/a</i></td></tr>
* <tr><td>G</td><td>D</td><td><i>n/a</i></td><td><i>n/a</i></td></tr>
* <tr><td>G</td><td>E</td><td><i>n/a</i></td><td><i>n/a</i></td></tr>
* <tr><td>G</td><td>F</td><td><i>n/a</i></td><td><i>n/a</i></td></tr>
* <tr><td>G</td><td>G</td><td><i>n/a</i></td><td><i>n/a</i></td></tr>
*
* </table>
* </td>
* <td valign="top">
* <img src="../../../images/graphtest.png">
* </td>
* </tr>
* </table>
* </p>
*
*/
public class ShortestPathFinder extends Object {
/**
* Get an instance of ShortestPathFinder.
*/
public ShortestPathFinder() {
super();
}
/**
* Calculate all the paths for all the nodes in the given collection.
*
* @param nodes the nodes to calculate
*/
public void calculatePaths(Collection<? extends SPNode<?>> nodes) {
for(SPNode<?> node : nodes) {
calculatePaths(node);
}
}
/**
* Calculate all of the paths for a single node.
*
* @param node the node from which to calculate paths
*/
public void calculatePaths(SPNode<?> node) {
// Clear the current list
node.clearNextHops();
for(SPVertex<?> spv : node.getConnections()) {
recordLink(node, spv.getCost(), spv.getTo(), spv.getTo(),
new HashSet<SPNode<?>>());
}
}
// Add a hop if the path doesn't exist, or the new path will be less costly
// than the existing path
@SuppressWarnings("unchecked")
private void addHopFrom(SPNode node, SPNode dest, SPNode next, int cost) {
SPVertex currentHop=node.getNextHop(dest);
if(currentHop == null) {
node.addNextHop(dest, new SPVertex(next, cost));
} else {
if(cost < currentHop.getCost()) {
node.addNextHop(dest, new SPVertex(next, cost));
}
}
}
// Calculate the links in node ``node'' at the given cost, routed over
// the given next hop, starting at node ``other'' and maintaining seen
// links in s
private void recordLink(SPNode<?> node, int cost, SPNode<?> nextHop,
SPNode<?> other, Set<SPNode<?>> s) {
// Make sure we're not looping over a path we've already seen.
if(!s.contains(other)) {
s.add(other);
// Add the next hop if there's not already one with an equal or
// greater cost
addHopFrom(node, other, nextHop, cost);
// Flip through the connections to other nodes and recurse
for(SPVertex<?> spv : other.getConnections()) {
// The cost to this link is the sum of the costs to this
// link and the cost of this link.
int nextCost=cost+spv.getCost();
// This is the node we've found in this loop
SPNode<?> thisNode=spv.getTo();
// recurse
recordLink(node, nextCost, nextHop, thisNode, s);
}
}
}
}