/*
* Copyright 2008 Network Engine for Objects in Lund AB [neotechnology.com]
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.neo4j.graphalgo.shortestpath;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.neo4j.graphdb.Direction;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.PropertyContainer;
import org.neo4j.graphdb.Relationship;
import org.neo4j.graphdb.RelationshipType;
import org.neo4j.graphdb.ReturnableEvaluator;
import org.neo4j.graphdb.StopEvaluator;
import org.neo4j.graphdb.TraversalPosition;
import org.neo4j.graphdb.Traverser;
import org.neo4j.graphdb.Traverser.Order;
/**
* This class can be used to find one of the shortest paths in an unweighted
* network. Only supports one relationship type and direction is always BOTH.
* Based on code from an early IMDB example application.
* @see FindPath
* @author Patrik Larsson
*/
public class FindSinglePath
{
private HashMap<Node,List<Relationship>> predecessors1;
private HashMap<Node,List<Relationship>> predecessors2;
private final int maxDepth;
private final Node startNode;
private final Node endNode;
private final Object[] relTypesAndDirections;
private boolean doneCalculation;
Node matchNode;
/**
* Resets the result data to force the computation to be run again when some
* result is asked for.
*/
public void reset()
{
predecessors1 = new HashMap<Node,List<Relationship>>();
predecessors2 = new HashMap<Node,List<Relationship>>();
doneCalculation = false;
matchNode = null;
}
/**
* Makes the main calculation. If some limit is set, the shortest path(s)
* that could be found within those limits will be calculated.
* @return True if a path was found.
*/
public boolean calculate()
{
// Do this first as a general error check since this is supposed to be
// called whenever a result is asked for.
if ( startNode == null || endNode == null )
{
throw new RuntimeException( "Start or end node undefined." );
}
// Don't do it more than once
if ( doneCalculation )
{
return true;
}
doneCalculation = true;
// Special case when path length is zero
if ( startNode.equals( endNode ) )
{
matchNode = startNode;
return true;
}
PathStopEval stopEval1 = new PathStopEval( maxDepth / 2 );
PathStopEval stopEval2 = new PathStopEval( maxDepth / 2 + maxDepth % 2 );
PathReturnEval returnEval1 = new PathReturnEval( predecessors1,
predecessors2 );
PathReturnEval returnEval2 = new PathReturnEval( predecessors2,
predecessors1 );
Traverser trav1 = startNode.traverse( Order.BREADTH_FIRST, stopEval1,
returnEval1, relTypesAndDirections ); // relationshipTypes, directions );
Traverser trav2 = endNode.traverse( Order.BREADTH_FIRST, stopEval2,
returnEval2, relTypesAndDirections ); // relationshipTypes, directions );
Iterator<Node> itr1 = trav1.iterator();
Iterator<Node> itr2 = trav2.iterator();
while ( itr1.hasNext() || itr2.hasNext() )
{
if ( itr1.hasNext() )
{
itr1.next();
}
if ( returnEval1.getMatchNode() != null )
{
matchNode = returnEval1.getMatchNode();
return true;
}
if ( itr2.hasNext() )
{
itr2.next();
}
if ( returnEval2.getMatchNode() != null )
{
matchNode = returnEval2.getMatchNode();
return true;
}
}
return false;
}
private static class PathStopEval implements StopEvaluator
{
private int maximumDepth;
public PathStopEval( int maximumDepth )
{
super();
this.maximumDepth = maximumDepth;
}
public boolean isStopNode( TraversalPosition currentPos )
{
return currentPos.depth() >= maximumDepth;
}
}
private static class PathReturnEval implements ReturnableEvaluator
{
final Map<Node,List<Relationship>> myNodes;
final Map<Node,List<Relationship>> otherNodes;
private Node matchNode = null;
public PathReturnEval( final Map<Node,List<Relationship>> myNodes,
final Map<Node,List<Relationship>> otherNodes )
{
super();
this.myNodes = myNodes;
this.otherNodes = otherNodes;
}
public boolean isReturnableNode( TraversalPosition currentPos )
{
Node currentNode = currentPos.currentNode();
Relationship relationshipToCurrent = currentPos
.lastRelationshipTraversed();
if ( relationshipToCurrent != null )
{
LinkedList<Relationship> predList = new LinkedList<Relationship>();
predList.add( relationshipToCurrent );
myNodes.put( currentNode, predList );
}
else
{
myNodes.put( currentNode, null );
}
if ( otherNodes.containsKey( currentNode ) )
{
// match
matchNode = currentNode;
}
return true;
}
public Node getMatchNode()
{
return matchNode;
}
}
/**
* @param startNode
* The node in which the path should start.
* @param endNode
* The node in which the path should end.
* @param relationshipType
* The type of relationship to follow.
* @param maxDepth
* A maximum search length.
*/
public FindSinglePath( Node startNode, Node endNode,
RelationshipType relationshipType, int maxDepth )
{
super();
reset();
this.startNode = startNode;
this.endNode = endNode;
this.relTypesAndDirections = new Object[2];
this.relTypesAndDirections[0] = relationshipType;
this.relTypesAndDirections[1] = Direction.BOTH;
this.maxDepth = maxDepth;
}
public FindSinglePath( Node startNode, Node endNode,
RelationshipType[] relationshipTypes, Direction[] directions, int maxDepth )
{
super();
reset();
this.startNode = startNode;
this.endNode = endNode;
relTypesAndDirections = new Object[ relationshipTypes.length * 2 ];
for ( int i = 0; i < relationshipTypes.length; i++ )
{
relTypesAndDirections[i*2] = relationshipTypes[i];
relTypesAndDirections[i*2 + 1] = directions[i];
}
this.maxDepth = maxDepth;
}
/**
* @return One of the shortest paths found or null.
*/
public List<PropertyContainer> getPath()
{
calculate();
if ( matchNode == null )
{
return null;
}
LinkedList<PropertyContainer> path = new LinkedList<PropertyContainer>();
path.addAll( Util.constructSinglePathToNode( matchNode, predecessors1,
true, false ) );
path.addAll( Util.constructSinglePathToNode( matchNode, predecessors2,
false, true ) );
return path;
}
/**
* @return One of the shortest paths found or null.
*/
public List<Node> getPathAsNodes()
{
calculate();
if ( matchNode == null )
{
return null;
}
LinkedList<Node> pathNodes = new LinkedList<Node>();
pathNodes.addAll( Util.constructSinglePathToNodeAsNodes( matchNode,
predecessors1, true, false ) );
pathNodes.addAll( Util.constructSinglePathToNodeAsNodes( matchNode,
predecessors2, false, true ) );
return pathNodes;
}
/**
* @return One of the shortest paths found or null.
*/
public List<Relationship> getPathAsRelationships()
{
calculate();
if ( matchNode == null )
{
return null;
}
List<Relationship> path = new LinkedList<Relationship>();
path.addAll( Util.constructSinglePathToNodeAsRelationships( matchNode,
predecessors1, false ) );
path.addAll( Util.constructSinglePathToNodeAsRelationships( matchNode,
predecessors2, true ) );
return path;
}
}