/*
* 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;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
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;
/**
* This generates all simple paths, so calculations may be done on them later.
*/
public class AllSimplePaths
{
private final RelationshipType relationshipType;
private final Direction direction;
private final Node node1;
private final Node node2;
public AllSimplePaths( Node node1, Node node2, RelationshipType type,
Direction direction )
{
if ( node1 == null || node2 == null )
{
throw new IllegalArgumentException( "Null node" );
}
this.node1 = node1;
this.node2 = node2;
this.relationshipType = type;
this.direction = direction;
}
private class Path
{
private Node head;
private Relationship rel; // relationship from tail to head
private Path tail;
public Path( Node head, Relationship rel, Path tail )
{
super();
this.head = head;
this.rel = rel;
this.tail = tail;
}
}
private HashMap<Node, LinkedList<Path>> pathsFromOneDirection =
new HashMap<Node, LinkedList<Path>>();
private List<List<PropertyContainer>> foundPaths =
new ArrayList<List<PropertyContainer>>();
protected void addPathToNode( Node node, Path path )
{
LinkedList<Path> paths = pathsFromOneDirection.get( node );
if ( paths == null )
{
paths = new LinkedList<Path>();
pathsFromOneDirection.put( node, paths );
}
paths.add( path );
}
// Build all paths to all the nodes reachable
// note: currentPath contains currentNode
protected void traverser1( Node currentNode, int currentDepth,
Path currentPath )
{
// System.out.print( "Checking node "
// + currentNode.getProperty( "graphBuilderId" ) + " from " );
for ( Path p = currentPath.tail; p != null; p = p.tail )
{
// System.out.print( p.head.getProperty( "graphBuilderId" ) + " " );
// We have a cycle
if ( p.head.equals( currentNode ) )
{
// System.out.println( "Cycle (trav1)" );
return;
}
}
// System.out.println();
addPathToNode( currentNode, currentPath );
if ( currentDepth >= 0 )
{
for ( Relationship relationship :
currentNode.getRelationships( relationshipType, direction ) )
{
Node targetNode = relationship.getOtherNode( currentNode );
traverser1( targetNode, currentDepth - 1, new Path( targetNode,
relationship, currentPath ) );
}
}
}
// Try to find nodes the other traversion reached
// currentPathPredecessors is (node,relationship,...,node,relationship)
protected void traverser2( Node currentNode, int currentDepth,
LinkedList<PropertyContainer> currentPathPredecessors )
{
// Cycle?
if ( currentPathPredecessors.contains( currentNode ) )
{
return;
}
LinkedList<Path> paths = pathsFromOneDirection.get( currentNode );
if ( paths != null )
{
// For all paths that reached this node from the other direction...
for ( Path path : paths )
{
boolean simplePath = true;
// For all nodes in that path...
for ( Path p = path; p != null; p = p.tail )
{
// We must not have them in our current path
if ( currentPathPredecessors.contains( p.head ) )
{
simplePath = false;
// System.out.println( "Cycle (path)" );
break;
}
}
if ( simplePath && currentDepth > 0 )
{
// for all nodes we will continue to...
for ( Relationship relationship : currentNode.getRelationships(
relationshipType, direction ) )
{
Node targetNode = relationship.getOtherNode( currentNode );
// If we will find this exact path somewhere later, skip
// it
if ( path.tail != null
&& path.tail.head.equals( targetNode ) )
{
simplePath = false;
break;
}
}
}
if ( simplePath )
{
// A path is found!
LinkedList<PropertyContainer> newFoundPath = new LinkedList<PropertyContainer>(
currentPathPredecessors );
// For all nodes in that path...
for ( Path p = path; p != null; p = p.tail )
{
newFoundPath.add( p.head );
if ( p.rel != null )
{
newFoundPath.add( p.rel );
}
}
foundPaths.add( newFoundPath );
}
}
}
if ( currentDepth >= 0 )
{
for ( Relationship relationship : currentNode.getRelationships(
relationshipType, direction ) )
{
Node targetNode = relationship.getOtherNode( currentNode );
LinkedList<PropertyContainer> newPath = new LinkedList<PropertyContainer>(
currentPathPredecessors );
newPath.add( currentNode );
newPath.add( relationship );
traverser2( targetNode, currentDepth - 1, newPath );
}
}
}
protected void reset()
{
pathsFromOneDirection = new HashMap<Node, LinkedList<Path>>();
foundPaths = new ArrayList<List<PropertyContainer>>();
}
public List<List<PropertyContainer>> getPaths( int totalDepth )
{
reset();
// TODO The traverser only works if we go one step further
// than expected
totalDepth++;
// System.out.print( "Trav1... " );
traverser1( node2, totalDepth / 2, new Path( node2, null, null ) );
// System.out.print( "trav2... " );
traverser2( node1, totalDepth / 2 + totalDepth % 2,
new LinkedList<PropertyContainer>() );
// System.out.println( "end" );
return foundPaths;
}
/**
* Returns the found paths as lists of nodes.
* @return
*/
public List<List<Node>> getPathsAsNodes( int totalDepth )
{
LinkedList<List<Node>> paths = new LinkedList<List<Node>>();
List<List<PropertyContainer>> complexPaths = getPaths( totalDepth );
for ( List<PropertyContainer> complexPath : complexPaths )
{
List<Node> path = new LinkedList<Node>();
int flipflop = 0;
for ( PropertyContainer object : complexPath )
{
if ( flipflop == 0 )
{
path.add( (Node) object );
}
flipflop = 1 - flipflop;
}
paths.add( path );
}
return paths;
}
}