/*
* Copyright (c) 2002-2009 "Neo Technology,"
* Network Engine for Objects in Lund AB [http://neotechnology.com]
*
* This file is part of Neo4j.
*
* Neo4j 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.kernel.impl.traversal;
import org.neo4j.graphdb.Direction;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.NotFoundException;
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 java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.NoSuchElementException;
/**
* This class provides a skeletal implementation of the {@link Traverser}
* interface to minimize the effort required to implement the
* {@link BreadthFirstTraverser} and {@link DepthFirstTraverser}. This is a
* package private class used only for implementation-reuse purposes. Any
* documentation interesting to a client programmer should reside in
* {@link Traverser}.
* <P>
* The AbstractTraverser contains the logic and functionality that is common to
* both traverser subtypes. In reality, this means ALL functionality except for
* the data structure used to store the traverser list (captured via the four
* abstract operations {@link #initializeList initializeList},
* {@link #addPositionToList addPositionToList},
* {@link #getNextPositionFromList getNextPositionFromList} and
* {@link #listIsEmpty listIsEmpty}) and whether children are processed in
* natural- or reverse order (captured via the abstract operation
* {@link #traverseChildrenInNaturalOrder traverserChildrenInNaturalOrder}).
* <P>
* In order to minimize overhead, the AbstractTraverser caches the result of
* {@link #hasNext} so that the subsequent implementation of {@link #next} or
* {@link #nextNode} won't have to redo the traversal.
*
* @see Traverser
* @see BreadthFirstTraverser
* @see DepthFirstTraverser
*/
abstract class AbstractTraverser implements Traverser, Iterator<Node>
{
private RelationshipType[] traversableRels = null;
private Direction[] traversableDirs = null;
private RelationshipType[] preservingRels = null;
private Direction[] preservingDirs = null;
private StopEvaluator stopEvaluator = null;
private ReturnableEvaluator returnableEvaluator = null;
private Set<Node> visitedNodes = new HashSet<Node>();
private Node cachedNode = null;
private int returnedNodesCount = 0;
private TraversalPositionImpl traversalPosition = null;
/**
* Creates an AbstractTraverser subclass, for information about the
* arguments please see the documentation of {@link InternalTraverserFactory}.
*/
AbstractTraverser( Node startNode, RelationshipType[] traversableRels,
Direction[] traversableDirs, RelationshipType[] preservingRels,
Direction[] preservingDirs, StopEvaluator stopEvaluator,
ReturnableEvaluator returnableEvaluator, RandomEvaluator randomEvaluator )
{
// Sanity check
if ( startNode == null || traversableRels == null
|| stopEvaluator == null || returnableEvaluator == null )
{
String s = "startNode = " + startNode + ", traversableRels = "
+ Arrays.toString( traversableRels ) + ", stopEvaluator = "
+ stopEvaluator + ", returnableEvaluator = "
+ returnableEvaluator;
throw new IllegalArgumentException( "null argument(s): " + s );
}
// Assign attributes
this.traversableRels = traversableRels;
this.traversableDirs = traversableDirs;
this.preservingRels = preservingRels;
this.preservingDirs = preservingDirs;
this.stopEvaluator = stopEvaluator;
this.returnableEvaluator = returnableEvaluator;
// Initialize the (subclass-specific) traverser list
this.initializeList();
// Add the first position to the list
TraversalPositionImpl firstPos = this.createPosition( startNode, null,
null, 0 );
this.addPositionToList( firstPos );
}
public Iterator<Node> iterator()
{
return this;
}
// javadoc: see java.util.Iterator or Traverser
public boolean hasNext()
{
// If we have one cached, then we're definitely go
if ( this.cachedNode != null )
{
return true;
}
// If not, check if we can find one
else
{
this.cachedNode = this.traverseToNextNode();
return this.cachedNode != null;
}
}
// javadoc: see java.util.Iterator or Traverser
public Node next()
{
return this.nextNode();
}
// javadoc: see java.util.Iterator or Traverser
public Node nextNode()
{
Node nodeToReturn = this.cachedNode;
// If no node is cached, then traverse to next
if ( nodeToReturn == null )
{
nodeToReturn = this.traverseToNextNode();
}
// If we couldn't find a node by traversing, report this by
// throwing a NoSuchElementException (as per java.util.Iterator)
if ( nodeToReturn == null )
{
throw new NoSuchElementException();
}
// Clear the cache
this.cachedNode = null;
return nodeToReturn;
}
// Traverses to the next node and returns it, or null if there are
// no more nodes in this traversal
private Node traverseToNextNode()
{
Node nodeToReturn = null;
while ( !this.listIsEmpty() && nodeToReturn == null )
{
// Get next node from the list
TraversalPositionImpl currentPos = this.getNextPositionFromList();
traversalPosition = currentPos;
Node currentNode = currentPos.currentNode();
// Make sure we haven't visited this node before: add() returns
// true if the set doesn't contain the node -- which means that
// we're fine.
if ( visitedNodes.add( currentNode ) )
{
// Update position with however many nodes we've returned
// from the traversal up until now, this may be used to
// determine whether we should stop and/or return currentPos
currentPos.setReturnedNodesCount( this.returnedNodesCount );
// If we're not stopping, then add related nodes to the list
// or current position not valid (last trav rel deleted)
if ( // !currentPos.isValid() ||
!this.stopEvaluator.isStopNode( currentPos ) )
{
// Add the nodes at the end of all traversable- and
// preserving relationships
try
{
this.addEndNodesToList( currentPos,
this.traversableRels, this.traversableDirs );
this.addEndNodesToList( currentPos,
this.preservingRels, this.preservingDirs );
}
catch ( NotFoundException e )
{
// currentNode deleted in other tx
// try next position from list
continue;
}
}
// Check if we should return currentPos
if ( // currentPos.isValid() &&
this.returnableEvaluator.isReturnableNode( currentPos ) )
{
this.returnedNodesCount++;
nodeToReturn = currentPos.currentNode();
}
}
}
return nodeToReturn;
}
// Adds the nodes at the end or start (depending on 'dirs') of all
// relationships of a type in 'relTypes' that are attached to the
// node in 'currentPos' to the list
private void addEndNodesToList( TraversalPositionImpl currentPos,
RelationshipType[] relTypes, Direction[] dirs )
{
if ( relTypes == null )
{
return;
}
// Get the node and compute new depth
Node currentNode = currentPos.currentNode();
int newDepth = currentPos.depth() + 1;
// For all relationship types...
for ( int i = 0; i < relTypes.length; i++ )
{
// ... get all rels of that type and direction from currentNode
Iterable<Relationship> rels = null;
try
{
if ( dirs == null || dirs[i] == Direction.BOTH
|| dirs[i] == null )
{
rels = currentNode.getRelationships( relTypes[i] );
}
else
{
rels = currentNode.getRelationships( relTypes[i], dirs[i] );
}
}
catch ( NotFoundException e )
{
// node deleted in other tx
}
// The order we process relationships is really irrelevant, but
// as long as we have a non-deterministic ordering in
// currentNode.getRelationship(), we'll have to resort to ugly
// hacks like this to be able to return the branches in the
// the order that people generally expect.
if ( rels != null )
{
for ( Relationship rel : rels )
{
this.processRel( currentNode, rel, newDepth );
}
}
}
}
private void processRel( Node currentNode, Relationship rel, int newDepth )
{
Node endNode = rel.getOtherNode( currentNode );
TraversalPositionImpl newPos = this.createPosition( endNode,
currentNode, rel, newDepth );
this.addPositionToList( newPos );
}
// Creates a traversal position populated with the specified data
private TraversalPositionImpl createPosition( Node currentNode,
Node previousNode, Relationship lastRelTraversed, int currentDepth )
{
return new TraversalPositionImpl( currentNode, previousNode,
lastRelTraversed, currentDepth );
}
// javadoc: see Traverser
public Collection<Node> getAllNodes()
{
// Temp storage
java.util.List<Node> tempList = new java.util.ArrayList<Node>();
// Traverse until the end, my beautiful friend
while ( this.hasNext() )
{
tempList.add( this.nextNode() );
}
// Return nodes
return tempList;
}
// javadoc: see java.util.Iterator
public void remove()
{
throw new UnsupportedOperationException();
}
// javadoc: see Traverser
public Traverser sort( NodeSortInfo<Node> nsi )
{
ArrayList<Node> tempList = new ArrayList<Node>();
// Traverse and get all remaining nodes
while ( this.hasNext() )
{
tempList.add( this.nextNode() );
}
Collections.sort( tempList, nsi );
return new SortedTraverser( tempList );
}
public TraversalPosition currentPosition()
{
return traversalPosition;
}
/**
* Instantiates the underlying container used to store future traversal
* positions. This operation is required to overcome, ehum, "limitations" in
* the way java resolves constructors and field initialization in complex
* inheritance hierarchies.
*/
abstract void initializeList();
/**
* Adds <CODE>position</CODE> to the end of the list.
*/
abstract void addPositionToList( TraversalPositionImpl position );
/**
* Returns the next position from the list.
*/
abstract TraversalPositionImpl getNextPositionFromList();
/**
* Returns <CODE>true</CODE> if there are no more nodes to traverse in the
* list, <CODE>false</CODE> otherwise.
*/
abstract boolean listIsEmpty();
/**
* Returns <CODE>true</CODE> if the traverser subtype wants children
* processed in natural order, <CODE>false</CODE> if it prefers them
* processed in reverse order. See implementation comments in
* <CODE>traverseToNextNode()</CODE> for more information.
*/
abstract boolean traverseChildrenInNaturalOrder();
}