module Neo4j
module Core
module Traversal
# By using this class you can both specify traversals and create new relationships.
# This object is return from the Neo4j::Core::Traversal methods.
# @see Neo4j::Core::Traversal#outgoing
# @see Neo4j::Core::Traversal#incoming
class Traverser
include Enumerable
include ToJava
def initialize(from, dir=:both, type=nil)
@from = from
@depth = 1
if type.nil?
raise "Traversing all relationship in direction #{dir.inspect} not supported, only :both supported" unless dir == :both
@td = Java::OrgNeo4jKernelImplTraversal::TraversalDescriptionImpl.new.breadth_first()
else
@type = type_to_java(type)
@dir = dir_to_java(dir)
@td = Java::OrgNeo4jKernelImplTraversal::TraversalDescriptionImpl.new.breadth_first().relationships(@type, @dir)
end
end
# Sets traversing depth first.
#
# The pre_or_post parameter parameter can have two values: :pre or :post
# @param [:pre, :post] pre_or_post
# * :pre - Traversing depth first, visiting each node before visiting its child nodes (default)
# * :post - Traversing depth first, visiting each node after visiting its child nodes.
# @return self
#
def depth_first(pre_or_post = :pre)
case pre_or_post
when :pre then
@td = @td.order(Java::OrgNeo4jKernel::Traversal.preorderDepthFirst())
when :post then
@td = @td.order(Java::OrgNeo4jKernel::Traversal.postorderDepthFirst())
else
raise "Unknown type #{pre_or_post}, should be :pre or :post"
end
self
end
# Sets traversing breadth first (default).
#
# This is the default ordering if none is defined.
# The pre_or_post parameter parameter can have two values: :pre or :post
# * :pre - Traversing breadth first, visiting each node before visiting its child nodes (default)
# * :post - Traversing breadth first, visiting each node after visiting its child nodes.
#
# @param [:pre, :post] pre_or_post The traversal order
# @return self
#
# === Note
# Please note that breadth first traversals have a higher memory overhead than depth first traversals.
# BranchSelectors carries state and hence needs to be uniquely instantiated for each traversal.
# Therefore it is supplied to the TraversalDescription through a BranchOrderingPolicy interface, which is a factory of BranchSelector instances.
def breadth_first(pre_or_post = :pre)
case pre_or_post
when :pre then
@td = @td.order(Java::OrgNeo4jKernel::Traversal.preorderBreadthFirst())
when :post then
@td = @td.order(Java::OrgNeo4jKernel::Traversal.postorderBreadthFirst())
else
raise "Unknown type #{pre_or_post}, should be :pre or :post"
end
self
end
def eval_paths(&eval_path_block)
@td = @td.evaluator(Evaluator.new(&eval_path_block))
self
end
# Sets the rules for how positions can be revisited during a traversal as stated in Uniqueness.
# @param [:node_global, :node_path, :node_recent, :none, :rel_global, :rel_path, :rel_recent] u the uniqueness option
# @return self
# @see Neo4j::Core::Traverser#unique
def unique(u = :node_global)
case u
when :node_global then
# A node cannot be traversed more than once.
@td = @td.uniqueness(Java::OrgNeo4jKernel::Uniqueness::NODE_GLOBAL)
when :node_path then
# For each returned node there 's a unique path from the start node to it.
@td = @td.uniqueness(Java::OrgNeo4jKernel::Uniqueness::NODE_PATH)
when :node_recent then
# This is like NODE_GLOBAL, but only guarantees uniqueness among the most recent visited nodes, with a configurable count.
@td = @td.uniqueness(Java::OrgNeo4jKernel::Uniqueness::NODE_RECENT)
when :none then
# No restriction (the user will have to manage it).
@td = @td.uniqueness(Java::OrgNeo4jKernel::Uniqueness::NONE)
when :rel_global then
# A relationship cannot be traversed more than once, whereas nodes can.
@td = @td.uniqueness(Java::OrgNeo4jKernel::Uniqueness::RELATIONSHIP_GLOBAL)
when :rel_path then
# No restriction (the user will have to manage it).
@td = @td.uniqueness(Java::OrgNeo4jKernel::Uniqueness::RELATIONSHIP_PATH)
when :rel_recent then
# Same as for NODE_RECENT, but for relationships.
@td = @td.uniqueness(Java::OrgNeo4jKernel::Uniqueness::RELATIONSHIP_RECENT)
else
raise "Got option for unique '#{u}' allowed: :node_global, :node_path, :node_recent, :none, :rel_global, :rel_path, :rel_recent"
end
self
end
def to_s
"NodeTraverser [from: #{@from.neo_id} depth: #{@depth} type: #{@type} dir:#{@dir}"
end
# Creates a new relationship between given node and self
# @param [Neo4j::Node] other_node the node to which we want to create a relationship
# @return (see #new)
def <<(other_node)
new(other_node)
self
end
# Returns an real ruby array.
def to_ary
self.to_a
end
# Creates a new relationship between self and given node.
# @param [Hash] props properties of new relationship
# @return [Neo4j::Relationship] the created relationship
def new(other_node, props = {})
case @dir
when Java::OrgNeo4jGraphdb::Direction::OUTGOING
@from.create_relationship_to(other_node, @type).update(props)
when Java::OrgNeo4jGraphdb::Direction::INCOMING
other_node.create_relationship_to(@from, @type).update(props)
else
raise "Only allowed to create outgoing or incoming relationships (not #@dir)"
end
end
# @param (see Neo4j::Core::Traversal#both)
# @see Neo4j::Core::Traversal#both
def both(type)
@type = type_to_java(type) if type
@dir = dir_to_java(:both)
@td = @td.relationships(type_to_java(type), @dir)
self
end
# @param (see Neo4j::Core::Traversal#expand)
# @return self
# @see Neo4j::Core::Traversal#expand
def expander(&expander)
@td = @td.expand(RelExpander.create_pair(&expander))
self
end
# Adds one outgoing relationship type to the traversal
# @param (see Neo4j::Core::Traversal#outgoing)
# @return self
# @see Neo4j::Core::Traversal#outgoing
def outgoing(type)
@type = type_to_java(type) if type
@dir = dir_to_java(:outgoing)
@td = @td.relationships(type_to_java(type), @dir)
self
end
# Adds one incoming relationship type to the traversal
# @param (see Neo4j::Core::Traversal#incoming)
# @return self
# @see Neo4j::Core::Traversal#incoming
def incoming(type)
@type = type_to_java(type) if type
@dir = dir_to_java(:incoming)
@td = @td.relationships(type_to_java(type), @dir)
self
end
# Cuts of of parts of the traversal.
# @yield [path]
# @yieldparam [Java::OrgNeo4jGraphdb::Path] path the path which can be used to filter nodes
# @yieldreturn [true,false] only if true the path should be cut of, no traversal beyond this.
# @example
# a.outgoing(:friends).outgoing(:recommend).depth(:all).prune{|path| path.end_node[:name] == 'B'}
# @see http://components.neo4j.org/neo4j/milestone/apidocs/org/neo4j/graphdb/Path.html
def prune(&block)
@td = @td.prune(PruneEvaluator.new(block))
self
end
# Only include nodes in the traversal in which the provided block returns true.
# @yield [path]
# @yieldparam [Java::OrgNeo4jGraphdb::Path] path the path which can be used to filter nodes
# @yieldreturn [true,false] only if true the node will be included in the traversal result.
#
# @example Return nodes that are exact at depth 2 from me
# a_node.outgoing(:friends).depth(2).filter{|path| path.length == 2}
# @see http://components.neo4j.org/neo4j/milestone/apidocs/org/neo4j/graphdb/Path.html
def filter(&block)
# we keep a reference to filter predicate since only one filter is allowed and we might want to modify it
@filter_predicate ||= FilterPredicate.new
@filter_predicate.add(block)
@td = @td.filter(@filter_predicate)
self
end
# Sets depth, if :all then it will traverse any depth
# @param [Fixnum, :all] d the depth of traversal, or all
# @return self
def depth(d)
@depth = d
self
end
# By default the start node is not included in the traversal
# Specifies that the start node should be included
# @return self
def include_start_node
@include_start_node = true
self
end
# @param [Fixnum] index the n'th node that will be return from the traversal
def [](index)
each_with_index { |node, i| break node if index == i }
end
# @return [true,false]
def empty?
first == nil
end
# Required by the Ruby Enumerable Mixin
def each
@raw ? iterator.each { |i| yield i } : iterator.each { |i| yield i.wrapper }
end
# Same as #each but does not wrap each node in a Ruby class, yields the Java Neo4j Node instance instead.
def each_raw
iterator.each { |i| yield i }
end
# Returns an enumerable of relationships instead of nodes
# @return self
def rels
@traversal_result = :rels
self
end
# If this is called then it will not wrap the nodes but instead return the raw Java Neo4j::Node objects when traversing
# @return self
def raw
@raw = true
self
end
# Specifies that we should return an enumerable of paths instead of nodes.
# @return self
def paths
@traversal_result = :paths
@raw = true
self
end
# @return the java iterator
def iterator
unless @include_start_node
if @filter_predicate
@filter_predicate.include_start_node
else
@td = @td.filter(Java::OrgNeo4jKernel::Traversal.return_all_but_start_node)
end
end
@td = @td.prune(Java::OrgNeo4jKernel::Traversal.pruneAfterDepth(@depth)) unless @depth == :all
if @traversal_result == :rels
@td.traverse(@from._java_node).relationships
elsif @traversal_result == :paths
@td.traverse(@from._java_node).iterator
else
@td.traverse(@from._java_node).nodes
end
end
end
end
end
end