package org.neo4j.kernel; import java.util.Iterator; import org.neo4j.graphdb.Direction; import org.neo4j.graphdb.Expander; import org.neo4j.graphdb.Node; import org.neo4j.graphdb.Path; import org.neo4j.graphdb.Relationship; import org.neo4j.graphdb.RelationshipExpander; import org.neo4j.graphdb.RelationshipType; import org.neo4j.graphdb.traversal.ExpansionSource; import org.neo4j.graphdb.traversal.Position; import org.neo4j.graphdb.traversal.PruneEvaluator; import org.neo4j.graphdb.traversal.SourceSelector; import org.neo4j.graphdb.traversal.SourceSelectorFactory; import org.neo4j.graphdb.traversal.TraversalDescription; import org.neo4j.kernel.impl.traversal.FinalExpansionSource; import org.neo4j.kernel.impl.traversal.TraversalDescriptionImpl; /** * A factory for objects regarding traversal of the graph. F.ex. it has a * method {@link #createTraversalDescription()} for creating a new * {@link TraversalDescription}, methods for creating new * {@link ExpansionSource} instances and more. */ public class TraversalFactory { private static final SourceSelectorFactory PREORDER_DEPTH_FIRST_SELECTOR = new SourceSelectorFactory() { public SourceSelector create( ExpansionSource startSource ) { return new PreorderDepthFirstSelector( startSource ); } }; private static final SourceSelectorFactory POSTORDER_DEPTH_FIRST_SELECTOR = new SourceSelectorFactory() { public SourceSelector create( ExpansionSource startSource ) { return new PostorderDepthFirstSelector( startSource ); } }; private static final SourceSelectorFactory PREORDER_BREADTH_FIRST_SELECTOR = new SourceSelectorFactory() { public SourceSelector create( ExpansionSource startSource ) { return new PreorderBreadthFirstSelector( startSource ); } }; private static final SourceSelectorFactory POSTORDER_BREADTH_FIRST_SELECTOR = new SourceSelectorFactory() { public SourceSelector create( ExpansionSource startSource ) { return new PostorderBreadthFirstSelector( startSource ); } }; /** * Creates a new {@link TraversalDescription} with default value for * everything so that it's OK to call * {@link TraversalDescription#traverse(org.neo4j.graphdb.Node)} without * modification. But it isn't a very useful traversal, instead you should * add rules and behaviours to it before traversing. * * @return a new {@link TraversalDescription} with default values. */ public static TraversalDescription createTraversalDescription() { return new TraversalDescriptionImpl(); } /** * Creates a new {@link RelationshipExpander} which is set to expand * relationships with {@code type} and {@code direction}. * * @param type the {@link RelationshipType} to expand. * @param dir the {@link Direction} to expand. * @return a new {@link RelationshipExpander}. */ public static Expander expanderForTypes( RelationshipType type, Direction dir ) { return StandardExpander.create( type, dir ); } public static Expander emptyExpander() { return StandardExpander.DEFAULT; // TODO: should this be a PROPER empty? } /** * Creates a new {@link RelationshipExpander} which is set to expand * relationships with two different types and directions. * * @param type1 a {@link RelationshipType} to expand. * @param dir1 a {@link Direction} to expand. * @param type2 another {@link RelationshipType} to expand. * @param dir2 another {@link Direction} to expand. * @return a new {@link RelationshipExpander}. */ public static Expander expanderForTypes( RelationshipType type1, Direction dir1, RelationshipType type2, Direction dir2 ) { return StandardExpander.create( type1, dir1, type2, dir2 ); } /** * Creates a new {@link RelationshipExpander} which is set to expand * relationships with multiple types and directions. * * @param type1 a {@link RelationshipType} to expand. * @param dir1 a {@link Direction} to expand. * @param type2 another {@link RelationshipType} to expand. * @param dir2 another {@link Direction} to expand. * @param more additional pairs or type/direction to expand. * @return a new {@link RelationshipExpander}. */ public static Expander expanderForTypes( RelationshipType type1, Direction dir1, RelationshipType type2, Direction dir2, Object... more ) { return StandardExpander.create( type1, dir1, type2, dir2, more ); } /** * Returns a {@link RelationshipExpander} which expands relationships * of all types and directions. * @return a relationship expander which expands all relationships. */ public static Expander expanderForAllTypes() { return expanderForAllTypes( Direction.BOTH ); } public static Expander expanderForAllTypes( Direction direction ) { return StandardExpander.create( direction ); } public static Expander expander( RelationshipExpander expander ) { if ( expander instanceof Expander ) { return (Expander) expander; } return StandardExpander.wrap( expander ); } /** * Combines two {@link ExpansionSource}s with a common * {@link ExpansionSource#node() head node} in order to obtain an * {@link ExpansionSource} representing a path from the start node of the * <code>source</code> {@link ExpansionSource} to the start node of the * <code>target</code> {@link ExpansionSource}. The resulting * {@link ExpansionSource} will not {@link ExpansionSource#next() expand * further}, and does not provide a {@link ExpansionSource#parent() parent} * {@link ExpansionSource}. * * @param source the {@link ExpansionSource} where the resulting path starts * @param target the {@link ExpansionSource} where the resulting path ends * @throws IllegalArgumentException if the {@link ExpansionSource#node() * head nodes} of the supplied {@link ExpansionSource}s does not * match * @return an {@link ExpansionSource} that represents the path from the * start node of the <code>source</code> {@link ExpansionSource} to * the start node of the <code>target</code> {@link ExpansionSource} */ public static ExpansionSource combineSourcePaths( ExpansionSource source, ExpansionSource target ) { if ( !source.node().equals( target.node() ) ) { throw new IllegalArgumentException( "The nodes of the head and tail must match" ); } Path headPath = source.position().path(), tailPath = target.position().path(); Relationship[] relationships = new Relationship[headPath.length() + tailPath.length()]; Iterator<Relationship> iter = headPath.relationships().iterator(); for ( int i = 0; iter.hasNext(); i++ ) { relationships[i] = iter.next(); } iter = tailPath.relationships().iterator(); for ( int i = relationships.length - 1; iter.hasNext(); i-- ) { relationships[i] = iter.next(); } return new FinalExpansionSource( tailPath.getStartNode(), relationships ); } /** * A {@link PruneEvaluator} which prunes everything beyond {@code depth}. * @param depth the depth to prune beyond (after). * @return a {@link PruneEvaluator} which prunes everything after * {@code depth}. */ public static PruneEvaluator pruneAfterDepth( final int depth ) { return new PruneEvaluator() { public boolean pruneAfter( Position position ) { return position.depth() >= depth; } }; } /** * Returns a "preorder depth first" selector factory . A depth first selector * always tries to select positions (from the current position) which are * deeper than the current position. * @return a {@link SourceSelectorFactory} for a preorder depth first * selector. */ public static SourceSelectorFactory preorderDepthFirstSelector() { return PREORDER_DEPTH_FIRST_SELECTOR; } /** * Returns a "postorder depth first" selector factory. A depth first * selector always tries to select positions (from the current position) * which are deeper than the current position. A postorder depth first * selector selects deeper position before the shallower ones. * @return a {@link SourceSelectorFactory} for a postorder depth first * selector. */ public static SourceSelectorFactory postorderDepthFirstSelector() { return POSTORDER_DEPTH_FIRST_SELECTOR; } /** * Returns a "preorder breadth first" selector factory. A breadth first * selector always selects all positions on the current depth before * advancing to the next depth. * @return a {@link SourceSelectorFactory} for a preorder breadth first * selector. */ public static SourceSelectorFactory preorderBreadthFirstSelector() { return PREORDER_BREADTH_FIRST_SELECTOR; } /** * Returns a "postorder breadth first" selector factory. A breadth first * selector always selects all positions on the current depth before * advancing to the next depth. A postorder breadth first selector selects * the levels in the reversed order, starting with the deepest. * @return a {@link SourceSelectorFactory} for a postorder breadth first * selector. */ public static SourceSelectorFactory postorderBreadthFirstSelector() { return POSTORDER_BREADTH_FIRST_SELECTOR; } /** * Provides hooks to help build a string representation of a {@link Path}. * @param <T> the type of {@link Path}. */ public static interface PathDescriptor<T extends Path> { /** * Returns a string representation of a {@link Node}. * @param path the {@link Path} we're building a string representation * from. * @param node the {@link Node} to return a string representation of. * @return a string representation of a {@link Node}. */ String nodeRepresentation( T path, Node node ); /** * Returns a string representation of a {@link Relationship}. * @param path the {@link Path} we're building a string representation * from. * @param from the previous {@link Node} in the path. * @param relationship the {@link Relationship} to return a string * representation of. * @return a string representation of a {@link Relationship}. */ String relationshipRepresentation( T path, Node from, Relationship relationship ); } /** * The default {@link PathDescriptor} used in common toString() * representations in classes implementing {@link Path}. * @param <T> the type of {@link Path}. */ public static class DefaultPathDescriptor<T extends Path> implements PathDescriptor<T> { public String nodeRepresentation( Path path, Node node ) { return "(" + node.getId() + ")"; } public String relationshipRepresentation( Path path, Node from, Relationship relationship ) { String prefix = "--", suffix = "--"; if ( from.equals( relationship.getEndNode() ) ) { prefix = "<--"; } else { suffix = "-->"; } return prefix + "<" + relationship.getType().name() + "," + relationship.getId() + "]" + suffix; } } /** * Method for building a string representation of a {@link Path}, using * the given {@code builder}. * @param <T> the type of {@link Path}. * @param path the {@link Path} to build a string representation of. * @param builder the {@link PathDescriptor} to get * {@link Node} and {@link Relationship} representations from. * @return a string representation of a {@link Path}. */ public static <T extends Path> String pathToString( T path, PathDescriptor<T> builder ) { Node current = path.getStartNode(); StringBuilder result = new StringBuilder(); for ( Relationship rel : path.relationships() ) { result.append( builder.nodeRepresentation( path, current ) ); result.append( builder.relationshipRepresentation( path, current, rel ) ); current = rel.getOtherNode( current ); } result.append( builder.nodeRepresentation( path, current ) ); return result.toString(); } /** * Returns the default string representation of a {@link Path}. It uses * the {@link DefaultPathDescriptor} to get representations. * @param path the {@link Path} to build a string representation of. * @return the default string representation of a {@link Path}. */ public static String defaultPathToString( Path path ) { return pathToString( path, new DefaultPathDescriptor<Path>() ); } /** * Returns a quite simple string representation of a {@link Path}. It * doesn't print relationship types or ids, just directions. * @param path the {@link Path} to build a string representation of. * @return a quite simple representation of a {@link Path}. */ public static String simplePathToString( Path path ) { return pathToString( path, new DefaultPathDescriptor<Path>() { @Override public String relationshipRepresentation( Path path, Node from, Relationship relationship ) { return relationship.getStartNode().equals( from ) ? "-->" : "<--"; } } ); } /** * Returns a quite simple string representation of a {@link Path}. It * doesn't print relationship types or ids, just directions. it uses the * {@code nodePropertyKey} to try to display that property value as in the * node representation instead of the node id. If that property doesn't * exist, the id is used. * @param path the {@link Path} to build a string representation of. * @return a quite simple representation of a {@link Path}. */ public static String simplePathToString( Path path, final String nodePropertyKey ) { return pathToString( path, new DefaultPathDescriptor<Path>() { @Override public String nodeRepresentation( Path path, Node node ) { return "(" + node.getProperty( nodePropertyKey, node.getId() ) + ")"; } @Override public String relationshipRepresentation( Path path, Node from, Relationship relationship ) { return relationship.getStartNode().equals( from ) ? "-->" : "<--"; } } ); } }