/* * Copyright 2010 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.ArrayList; 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; public class FindSingleShortestPath { // private final Map<Node,Relationship> firstSet = // new HashMap<Node,Relationship>(); // private final Map<Node,Relationship> secondSet = // new HashMap<Node,Relationship>(); private OneDirection firstDirection; private OneDirection secondDirection; private final int maxDepth; private final Node startNode; private final Node endNode; private final Object[] relTypesAndDirections; private boolean doneCalculation; Node matchNode; public void reset() { initializeDirectionData(); doneCalculation = false; matchNode = null; } private void initializeDirectionData() { firstDirection = new OneDirection( startNode, maxDepth / 2 ); secondDirection = new OneDirection( endNode, firstDirection.depth + ( maxDepth % 2 ) ); } private static final Relationship NULL_REL = new FakeRelImpl(); private class OneDirection { private List<Node> nodeList = new ArrayList<Node>(); private int depth; private List<Node> nextNodeList = new ArrayList<Node>(); private Iterator<Node> iterator; private int currentDepth; private Map<Node, Relationship> path = new HashMap<Node, Relationship>(); OneDirection( Node node, int depth ) { this.depth = depth; this.nextNodeList.add( node ); this.path.put( node, NULL_REL ); switchToNext(); } void switchToNext() { this.nodeList = this.nextNodeList; this.iterator = this.nodeList.iterator(); this.nextNodeList = new ArrayList<Node>(); } void checkNextDepth() { if ( !iterator.hasNext() && currentDepth + 1 <= depth ) { currentDepth++; switchToNext(); } } } private boolean calculate() { if ( doneCalculation ) { return true; } if ( startNode.equals( endNode ) ) { matchNode = startNode; doneCalculation = true; return true; } boolean hasRecalculatedDepth = false; initializeDirectionData(); while ( firstDirection.iterator.hasNext() || secondDirection.iterator.hasNext() ) { if ( tryMatch( firstDirection, secondDirection ) ) { return true; } if ( tryMatch( secondDirection, firstDirection ) ) { return true; } firstDirection.checkNextDepth(); secondDirection.checkNextDepth(); if ( !hasRecalculatedDepth && firstDirection.currentDepth == 1 && this.maxDepth % 2 == 1 ) { // The one with the least relationships gets the greater depth boolean firstHasMore = firstDirection.nodeList.size() > secondDirection.nodeList.size(); if ( firstHasMore == firstDirection.depth > secondDirection.depth ) { // Switch 'em int tempDepth = firstDirection.depth; firstDirection.depth = secondDirection.depth; secondDirection.depth = tempDepth; } hasRecalculatedDepth = true; } } doneCalculation = true; return false; } private boolean tryMatch( OneDirection direction, OneDirection otherDirection ) { if ( !direction.iterator.hasNext() ) { return false; } Node node = direction.iterator.next(); if ( otherDirection.path.containsKey( node ) ) { matchNode = node; doneCalculation = true; return true; } if ( direction.currentDepth + 1 <= direction.depth ) { for ( int i = 0; i < relTypesAndDirections.length / 2; i++ ) { RelationshipType type = (RelationshipType) relTypesAndDirections[i*2]; Direction dir = (Direction) relTypesAndDirections[i*2+1]; for ( Relationship rel : node.getRelationships( type,dir ) ) { Node otherNode = rel.getOtherNode( node ); Relationship oldRel = direction.path.put( otherNode, rel ); if ( oldRel == null ) { direction.nextNodeList.add( otherNode ); } else { direction.path.put( otherNode, oldRel ); } } } } return false; } /** * @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 FindSingleShortestPath( 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 FindSingleShortestPath( 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; } return constructPath( true, true ); } private List<PropertyContainer> constructPath( boolean includeNodes, boolean includeRels ) { LinkedList<PropertyContainer> path = new LinkedList<PropertyContainer>(); Relationship rel = firstDirection.path.get( matchNode ); Node currentNode = matchNode; while ( rel != NULL_REL && rel != null ) { if ( includeRels ) { path.addFirst( rel ); } currentNode = rel.getOtherNode( currentNode ); if ( includeNodes && !currentNode.equals( matchNode ) && !currentNode.equals( startNode ) ) { path.addFirst( currentNode ); } rel = firstDirection.path.get( currentNode ); } if ( includeNodes ) { path.addFirst( startNode ); if ( !matchNode.equals( startNode ) ) { path.addLast( matchNode ); } } rel = secondDirection.path.get( matchNode ); currentNode = matchNode; while ( rel != NULL_REL && rel != null ) { if ( includeRels ) { path.addLast( rel ); } currentNode = rel.getOtherNode( currentNode ); if ( includeNodes && !currentNode.equals( endNode ) ) { path.addLast( currentNode ); } rel = secondDirection.path.get( currentNode ); } if ( includeNodes && !endNode.equals( matchNode ) ) { path.addLast( endNode ); } return path; } /** * @return One of the shortest paths found or null. */ public List<Node> getPathAsNodes() { calculate(); if ( matchNode == null ) { return null; } List<PropertyContainer> path = constructPath( true, false ); List<Node> converted = new ArrayList<Node>(); for ( PropertyContainer node : path ) { converted.add( (Node) node ); } return converted; } /** * @return One of the shortest paths found or null. */ public List<Relationship> getPathAsRelationships() { calculate(); if ( matchNode == null ) { return null; } List<PropertyContainer> path = constructPath( false, true ); List<Relationship> converted = new ArrayList<Relationship>(); for ( PropertyContainer rel : path ) { converted.add( (Relationship) rel ); } return converted; } }