caruby-core 1.4.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (86) hide show
  1. data/History.txt +4 -0
  2. data/LEGAL +5 -0
  3. data/LICENSE +22 -0
  4. data/README.md +51 -0
  5. data/doc/website/css/site.css +1 -5
  6. data/doc/website/images/avatar.png +0 -0
  7. data/doc/website/images/favicon.ico +0 -0
  8. data/doc/website/images/logo.png +0 -0
  9. data/doc/website/index.html +82 -0
  10. data/doc/website/install.html +87 -0
  11. data/doc/website/quick_start.html +87 -0
  12. data/doc/website/tissue.html +85 -0
  13. data/doc/website/uom.html +10 -0
  14. data/lib/caruby.rb +3 -0
  15. data/lib/caruby/active_support/README.txt +2 -0
  16. data/lib/caruby/active_support/core_ext/string.rb +7 -0
  17. data/lib/caruby/active_support/core_ext/string/inflections.rb +167 -0
  18. data/lib/caruby/active_support/inflections.rb +55 -0
  19. data/lib/caruby/active_support/inflector.rb +398 -0
  20. data/lib/caruby/cli/application.rb +36 -0
  21. data/lib/caruby/cli/command.rb +169 -0
  22. data/lib/caruby/csv/csv_mapper.rb +157 -0
  23. data/lib/caruby/csv/csvio.rb +185 -0
  24. data/lib/caruby/database.rb +252 -0
  25. data/lib/caruby/database/fetched_matcher.rb +66 -0
  26. data/lib/caruby/database/persistable.rb +432 -0
  27. data/lib/caruby/database/persistence_service.rb +162 -0
  28. data/lib/caruby/database/reader.rb +599 -0
  29. data/lib/caruby/database/saved_merger.rb +131 -0
  30. data/lib/caruby/database/search_template_builder.rb +59 -0
  31. data/lib/caruby/database/sql_executor.rb +75 -0
  32. data/lib/caruby/database/store_template_builder.rb +200 -0
  33. data/lib/caruby/database/writer.rb +469 -0
  34. data/lib/caruby/domain/annotatable.rb +25 -0
  35. data/lib/caruby/domain/annotation.rb +23 -0
  36. data/lib/caruby/domain/attribute_metadata.rb +447 -0
  37. data/lib/caruby/domain/java_attribute_metadata.rb +160 -0
  38. data/lib/caruby/domain/merge.rb +91 -0
  39. data/lib/caruby/domain/properties.rb +95 -0
  40. data/lib/caruby/domain/reference_visitor.rb +289 -0
  41. data/lib/caruby/domain/resource_attributes.rb +528 -0
  42. data/lib/caruby/domain/resource_dependency.rb +205 -0
  43. data/lib/caruby/domain/resource_introspection.rb +159 -0
  44. data/lib/caruby/domain/resource_metadata.rb +117 -0
  45. data/lib/caruby/domain/resource_module.rb +285 -0
  46. data/lib/caruby/domain/uniquify.rb +38 -0
  47. data/lib/caruby/import/annotatable_class.rb +28 -0
  48. data/lib/caruby/import/annotation_class.rb +27 -0
  49. data/lib/caruby/import/annotation_module.rb +67 -0
  50. data/lib/caruby/import/java.rb +338 -0
  51. data/lib/caruby/migration/migratable.rb +167 -0
  52. data/lib/caruby/migration/migrator.rb +533 -0
  53. data/lib/caruby/migration/resource.rb +8 -0
  54. data/lib/caruby/migration/resource_module.rb +11 -0
  55. data/lib/caruby/migration/uniquify.rb +20 -0
  56. data/lib/caruby/resource.rb +969 -0
  57. data/lib/caruby/util/attribute_path.rb +46 -0
  58. data/lib/caruby/util/cache.rb +53 -0
  59. data/lib/caruby/util/class.rb +99 -0
  60. data/lib/caruby/util/collection.rb +1053 -0
  61. data/lib/caruby/util/controlled_value.rb +35 -0
  62. data/lib/caruby/util/coordinate.rb +75 -0
  63. data/lib/caruby/util/domain_extent.rb +49 -0
  64. data/lib/caruby/util/file_separator.rb +65 -0
  65. data/lib/caruby/util/inflector.rb +20 -0
  66. data/lib/caruby/util/log.rb +95 -0
  67. data/lib/caruby/util/math.rb +12 -0
  68. data/lib/caruby/util/merge.rb +59 -0
  69. data/lib/caruby/util/module.rb +34 -0
  70. data/lib/caruby/util/options.rb +92 -0
  71. data/lib/caruby/util/partial_order.rb +36 -0
  72. data/lib/caruby/util/person.rb +119 -0
  73. data/lib/caruby/util/pretty_print.rb +184 -0
  74. data/lib/caruby/util/properties.rb +112 -0
  75. data/lib/caruby/util/stopwatch.rb +66 -0
  76. data/lib/caruby/util/topological_sync_enumerator.rb +53 -0
  77. data/lib/caruby/util/transitive_closure.rb +45 -0
  78. data/lib/caruby/util/tree.rb +48 -0
  79. data/lib/caruby/util/trie.rb +37 -0
  80. data/lib/caruby/util/uniquifier.rb +30 -0
  81. data/lib/caruby/util/validation.rb +48 -0
  82. data/lib/caruby/util/version.rb +56 -0
  83. data/lib/caruby/util/visitor.rb +351 -0
  84. data/lib/caruby/util/weak_hash.rb +36 -0
  85. data/lib/caruby/version.rb +3 -0
  86. metadata +186 -0
@@ -0,0 +1,66 @@
1
+ require 'benchmark'
2
+
3
+ # Stopwatch is a simple execution time accumulator.
4
+ class Stopwatch
5
+ # Time accumulates elapsed real time and total CPU time.
6
+ class Time
7
+ # The Benchmark::Tms wrapped by this Time.
8
+ attr_reader :tms
9
+
10
+ def initialize(tms=nil)
11
+ @tms = tms || Benchmark::Tms.new
12
+ end
13
+
14
+ # Returns the cumulative elapsed real clock time.
15
+ def elapsed
16
+ @tms.real
17
+ end
18
+
19
+ # Returns the cumulative CPU total time.
20
+ def cpu
21
+ @tms.total
22
+ end
23
+
24
+ # Adds the time to execute the given block to this time. Returns the split execution Time.
25
+ def split(&block)
26
+ stms = Benchmark.measure(&block)
27
+ @tms += stms
28
+ Time.new(stms)
29
+ end
30
+
31
+ def reset
32
+ @tms = Benchmark::Tms.new
33
+ end
34
+ end
35
+
36
+ # Executes the given block. Returns the execution Time.
37
+ def self.measure(&block)
38
+ new.run(&block)
39
+ end
40
+
41
+ # Creates a new idle Stopwatch.
42
+ def initialize
43
+ @time = Time.new
44
+ end
45
+
46
+ # Executes the given block. Accumulates the execution time in this Stopwatch.
47
+ # Returns the execution Time.
48
+ def run(&block)
49
+ @time.split(&block)
50
+ end
51
+
52
+ # Returns the cumulative elapsed real clock time spent in {#run} executions.
53
+ def elapsed
54
+ @time.elapsed
55
+ end
56
+
57
+ # Returns the cumulative CPU total time spent in {#run} executions for the current process and its children.
58
+ def cpu
59
+ @time.cpu
60
+ end
61
+
62
+ # Resets this Stopwatch's cumulative time to zero.
63
+ def reset
64
+ @time.reset
65
+ end
66
+ end
@@ -0,0 +1,53 @@
1
+ require 'caruby/util/collection'
2
+
3
+ class TopologicalSyncEnumerator
4
+ include Enumerable
5
+
6
+ def initialize(targets, sources, symbol, &matcher)
7
+ @tgts = targets
8
+ @srcs = sources
9
+ @mthd = symbol
10
+ @matcher = matcher || lambda { |tgt, srcs| srcs.first }
11
+ end
12
+
13
+ # Calls the given block on each matching target and source.
14
+ # Returns the matching target => source hash.
15
+ def each # :yields: target, source
16
+ # the parent hashes for targets and sources
17
+ pt = @tgts.to_compact_hash { |tgt| tgt.send(@mthd) }
18
+ ps = @srcs.to_compact_hash { |src| src.send(@mthd) }
19
+
20
+ # the child hashes
21
+ ct = LazyHash.new { Array.new }
22
+ cs = LazyHash.new { Array.new }
23
+
24
+ # collect the chidren and roots
25
+ rt = @tgts.reject { |tgt| p = pt[tgt]; ct[p] << tgt if p }
26
+ rs = @srcs.reject { |src| p = ps[src]; cs[p] << src if p }
27
+
28
+ # the match hash
29
+ matches = {}
30
+ # match recursively
31
+ each_match_recursive(rt, rs, ct, cs) do |tgt, src|
32
+ yield(tgt, src)
33
+ matches[tgt] = src
34
+ end
35
+
36
+ matches
37
+ end
38
+
39
+ private
40
+
41
+ def each_match_recursive(targets, sources, ct, cs, &block)
42
+ # copy the sources
43
+ srcs = sources.dup
44
+ # match each target, removing the matched source for the
45
+ # next iteration
46
+ targets.each do |tgt|
47
+ src = @matcher.call(tgt, srcs) || next
48
+ yield(tgt, src)
49
+ srcs.delete(src)
50
+ each_match_recursive(ct[tgt], cs[src], ct, cs, &block)
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,45 @@
1
+ require 'caruby/util/visitor'
2
+
3
+ class Object
4
+ # Returns the transitive closure over a method or block, e.g.:
5
+ # class Node
6
+ # attr_reader :parent, :children
7
+ # def initialize(name, parent=nil)
8
+ # super()
9
+ # @name = name
10
+ # @parent = parent
11
+ # @children = []
12
+ # parent.children << self if parent
13
+ # end
14
+ #
15
+ # def to_s;
16
+ # end
17
+ # a = Node.new('a'); b = Node.new('b', a), c = Node.new('c', a); d = Node.new('d', c)
18
+ # a.transitive_closure { |node| node.children }.to_a.join(", ") #=> a, b, c, d
19
+ # a.transitive_closure(:children).to_a.join(", ") #=> a, b, c, d
20
+ # This method returns an array partially ordered by the closure method, i.e.
21
+ # each node occurs before all other nodes referenced directly or indirectly by the closure method.
22
+ #
23
+ # If a method symbol or name is provided, then that method is called. Otherwise, the block is called.
24
+ # In either case, the call is expected to return an object or Enumerable of objects which also respond
25
+ # to the method or block.
26
+ def transitive_closure(method=nil)
27
+ raise ArgumentError.new("Missing both a method argument and a block") if method.nil? and not block_given?
28
+ return transitive_closure() { |node| node.send(method) } if method
29
+ Visitor.new(:depth_first) { |node| yield node }.to_enum(self).to_a.reverse
30
+ end
31
+ end
32
+
33
+ module Enumerable
34
+ # Returns the transitive closure over all items in this Enumerable.
35
+ #
36
+ # @see Object#transitive_closure
37
+ def transitive_closure(method=nil, &navigator)
38
+ # delegate to Object if there is a method argument
39
+ return super(method, &navigator) if method
40
+ # this Enumerable's children are this Enumerable's contents
41
+ closure = super() { |node| node.equal?(self) ? self : yield(node) }
42
+ # remove this collection from the closure
43
+ closure[1..-1]
44
+ end
45
+ end
@@ -0,0 +1,48 @@
1
+ # A Tree consists of a root node and subtree children.
2
+ # A Tree can be decorated with an optional value.
3
+ class Tree
4
+ attr_reader :root, :children
5
+
6
+ attr_accessor :value
7
+
8
+ # Creates a Trie with the given root node.
9
+ def initialize(root=nil)
10
+ @root = root
11
+ @children = []
12
+ end
13
+
14
+ # Adds a subtree rooted at the given node as a child of this tree.
15
+ def <<(node)
16
+ @children << self.class.new(node)
17
+ self
18
+ end
19
+
20
+ # Returns the subtree at the given node path.
21
+ def subtree(*path)
22
+ return self if path.empty?
23
+ first = path.shift
24
+ tree = @children.detect { |child| child.root == first }
25
+ tree.subtree(*path) if tree
26
+ end
27
+
28
+ alias :[] :subtree
29
+
30
+ # Creates the given node path if it does not yet exist.
31
+ # Returns the subtree at the path.
32
+ def fill(*path)
33
+ return self if path.empty?
34
+ first = path.shift
35
+ tree = subtree(first)
36
+ if tree.nil? then
37
+ self << first
38
+ tree = @children.last
39
+ end
40
+ tree.fill(*path)
41
+ end
42
+
43
+ def to_s
44
+ root_s = @root.nil? || Symbol === root ? root.inspect : root.to_s
45
+ return "[#{root_s}]" if @children.empty?
46
+ "[#{root_s} -> #{@children.join(', ')}]"
47
+ end
48
+ end
@@ -0,0 +1,37 @@
1
+ require File.join(File.dirname(__FILE__), 'tree')
2
+
3
+ # A Trie[http://en.wikipedia.org/wiki/Trie] is an associative access tree structure.
4
+ #
5
+ # @example
6
+ # trie = Trie.new
7
+ # trie[:a, :b] = 1
8
+ # trie[:a, :b] #=> 1
9
+ # trie[:a, :c] #=> nil
10
+ class Trie
11
+ # Creates a new empty Trie.
12
+ def initialize
13
+ @tree = Tree.new
14
+ end
15
+
16
+ # @return the value at the given trie path
17
+ def [](*path)
18
+ tree = @tree[nil, *path]
19
+ tree.value if tree
20
+ end
21
+
22
+ # @return the top_level Tree for this trie
23
+ def to_tree
24
+ @tree
25
+ end
26
+
27
+ # Sets the value for a node path.
28
+ #
29
+ # @example
30
+ # trie = Trie.new
31
+ # trie[:a, :b] = 1
32
+ # trie[:a, :b] #=> 1
33
+ def []=(*path_and_value)
34
+ value = path_and_value.pop
35
+ @tree.fill(nil, *path_and_value).value = value
36
+ end
37
+ end
@@ -0,0 +1,30 @@
1
+ # A test utility class to generate id qualifiers.
2
+ class Uniquifier
3
+ # Returns a relatively unique integral qualifier. Successive calls to this method
4
+ # within the same time zone spaced more than a millisecond apart return different
5
+ # integers. Each generated qualifier is greater than the previous by an unspecified
6
+ # amount.
7
+ def self.qualifier
8
+ # the first date that this method could be called
9
+ @first ||= Date.new(2000, 01, 01)
10
+ # days as integer + milliseconds as fraction since the first date
11
+ diff = DateTime.now - @first
12
+ # shift a tenth of a milli up into the integer portion
13
+ decimillis = diff * 24 * 60 * 60 * 10000
14
+ # truncate the fraction
15
+ decimillis.truncate
16
+ end
17
+ end
18
+
19
+ class String
20
+ # Returns a relatively unique value obtained from the specified base value.
21
+ # Spaces are removed, e.g.:
22
+ # Uniquifier.uniquify('Test Name')
23
+ # might produce:
24
+ # Test_Name_3309388006
25
+ #
26
+ # The suffix is generated by {Uniquifier.qualifier}.
27
+ def uniquify
28
+ gsub(' ', '_') + "_#{Uniquifier.qualifier}"
29
+ end
30
+ end
@@ -0,0 +1,48 @@
1
+ # Raised when an object fails a validation test.
2
+ class ValidationError < RuntimeError; end
3
+
4
+ class Object
5
+ # Returns whether this object is nil, false, empty, or a whitespace string.
6
+ # For example, "", " ", +nil+, [], and {} are blank.
7
+ #
8
+ # This method is borrowed from Rails ActiveSupport.
9
+ def blank?
10
+ respond_to?(:empty?) ? empty? : !self
11
+ end
12
+
13
+ # Returns whether this object is nil, empty, or a whitespace string.
14
+ # For example, "", " ", +nil+, [], and {} return +true+.
15
+ #
16
+ # This method differs from blank? in that +false+ is an allowed value.
17
+ def nil_or_empty?
18
+ nil? or (respond_to?(:empty?) and empty?)
19
+ end
20
+ end
21
+
22
+ module Validation
23
+ # A Validator is a procedure which responds to the +validate(value)+ method.
24
+ class Validator < Proc
25
+ alias :validate :call
26
+ end
27
+
28
+ # Validates that each key value in the value_type_assns value => type hash is an instance of the associated class.
29
+ #
30
+ # Raises ValidationError if the value is missing.
31
+ # Raises TypeError if the value is not the specified type.
32
+ def validate_type(value_type_assns)
33
+ TYPE_VALIDATOR.validate(value_type_assns)
34
+ end
35
+
36
+ private
37
+
38
+ def self.create_type_validator
39
+ Validator.new do |value_type_assns|
40
+ value_type_assns.each do |value, type|
41
+ raise ArgumentError.new("Missing #{type.name} argument") if value.nil?
42
+ raise TypeError.new("Unsupported argument type; expected: #{type.name} found: #{value.class.name}") unless type === value
43
+ end
44
+ end
45
+ end
46
+
47
+ TYPE_VALIDATOR = create_type_validator
48
+ end
@@ -0,0 +1,56 @@
1
+ # A Version is an Array of version major and minor components that is comparable to
2
+ # another version identifier based on a precedence relationship.
3
+ class Version < Array
4
+ include Comparable
5
+
6
+ attr_reader :predecessor
7
+
8
+ # Creates a new Version from the given version components and optional predecessor.
9
+ #
10
+ # @example
11
+ # alpha = Version.new(1, '1alpha')
12
+ # Version.new(1, 1, alpha) > alpha #=> true
13
+ def initialize(*params)
14
+ @predecessor = params.pop if self.class === params.last
15
+ super(params)
16
+ end
17
+
18
+ # Returns the comparison of this version identifier to the other version identifier as follows:
19
+ # * if this version can be compared to other via the predecessor graph, then return that comparison result
20
+ # * otherwise, return a component-wise comparison
21
+ #
22
+ # @example
23
+ # beta = Version.new(1, '1beta')
24
+ # Version.new(1) < beta > #=> true
25
+ # Version.new(1, 1) < beta #=> true
26
+ # Version.new(1, 1, beta) > beta #=> true
27
+ def <=>(other)
28
+ return 0 if equal?(other)
29
+ raise ArgumentError.new("Comparand is not a #{self.class}: #{other}") unless self.class === other
30
+ return -1 if other.predecessor == self
31
+ return 1 unless predecessor.nil? or predecessor < other
32
+ each_with_index do |component, index|
33
+ return 1 unless index < other.length
34
+ other_component = other[index]
35
+ if String === other_component then
36
+ component = component.to_s
37
+ elsif String === component
38
+ other_component = other_component.to_s
39
+ end
40
+ cmp = (component <=> other_component)
41
+ return cmp unless cmp.zero?
42
+ end
43
+ length < other.length ? -1 : 0
44
+ end
45
+ end
46
+
47
+ class String
48
+ # Returns this String as a Version.
49
+ #
50
+ # @example
51
+ # "1.2.1alpha".to_version #=> [1, 2, "1alpha"]
52
+ def to_version
53
+ components = split('.').map { |component| component =~ /[\D]/ ? component : component.to_i }
54
+ Version.new(*components)
55
+ end
56
+ end
@@ -0,0 +1,351 @@
1
+ require 'caruby/util/collection'
2
+ require 'caruby/util/options'
3
+
4
+ # Enumerator overwrites to_enum, so include it first
5
+ require 'enumerator'
6
+ require 'generator'
7
+
8
+ # Error raised on a visit failure.
9
+ class VisitError < RuntimeError; end
10
+
11
+ # Visitor traverses items and applies an operation, e.g.:
12
+ # class Node
13
+ # attr_accessor :children, :value
14
+ # def initialize(value, parent=nil)
15
+ # @value = value
16
+ # @children = []
17
+ # @parent = parent
18
+ # @parent.children << self if @parent
19
+ # end
20
+ # end
21
+ # parent = Node.new(1)
22
+ # child = Node.new(2, parent)
23
+ # multiplier = 2
24
+ # Visitor.new { |node| node.children }.visit(parent) { |node| node.value *= multiplier } #=> 2
25
+ # parent.value #=> 2
26
+ # child.value #=> 4
27
+ #
28
+ # The visit result is the result of evaluating the operation block on the initial visited node.
29
+ # Visiting a collection returns an array of the result of visiting each member of the collection,
30
+ # e.g. augmenting the preceding example:
31
+ # parent2 = Node.new(3)
32
+ # child2 = Node.new(4, parent2)
33
+ # Visitor.new { |node| node.children }.visit([parent, parent2]) { |node| node.value *= multiplier } #=> [2, 6]
34
+ # Each visit captures the visit result in the +visited+ hash, e.g.:
35
+ # parent = Node.new(1)
36
+ # child = Node.new(2, parent)
37
+ # visitor = Visitor.new { |node| node.children }
38
+ # visitor.visit([parent]) { |node| node.value += 1 }
39
+ # parent.value #=> 2
40
+ # visitor.visited[parent] #=> 2
41
+ # child.value #=> 3
42
+ # visitor.visited[child] #=> 3
43
+ #
44
+ # A +return+ from the operation block terminates the visit and exits from the defining scope with the block return value,
45
+ # e.g. given the preceding example:
46
+ # def increment(parent, limit)
47
+ # Visitor.new { |node| node.children }.visit(parent) { |node| node.value < limit ? node.value += 1 : return }
48
+ # end
49
+ # increment(parent, 2) #=> nil
50
+ # parent.value #=> 2
51
+ # child.value #=> 2
52
+ #
53
+ # The to_enum method allows navigator iteration, e.g.:
54
+ # Visitor.new { |node| node.children }.to_enum(parent).detect { |node| node.value == 2 }
55
+ class Visitor
56
+
57
+ attr_reader :options, :visited, :lineage, :cycles
58
+
59
+ # Creates a new Visitor which traverses the child objects returned by the navigator block.
60
+ # The navigator block takes a parent argument and returns the children to visit. If the block
61
+ # return value is not nil and not a collection, then the returned object is visited. A nil or
62
+ # empty child is not visited.
63
+ #
64
+ # options is a symbol => value hash. A Symbol argument _symbol_ is the same as +{+_symbol_+=>true}+.
65
+ # Supported options include the follwing:
66
+ #
67
+ # The value of :depth_first can be +true+, +false+ or a Proc. If the value is a Proc, then
68
+ # value determines whether a child is visited depth-first. See the {#visit} method for more information.
69
+ #
70
+ # If the the :visited option is set, then the visited nodes are recorded in the :visited option hash.
71
+ # In that case, the {#visit} call does not clear the visited hash.
72
+ #
73
+ # If the :operator option is set, then the visit operator block is called when a node is visited.
74
+ # The operator block argument is the visited node.
75
+ #
76
+ # @param [Symbol, {Symbol => Object}] options the visit options. A symbol argument is the same
77
+ # as symbol => true
78
+ # @option options [String] :depth_first depth-first traversal
79
+ # @option options [Hash] :visited the hash to use when recording visited node => value associations
80
+ # @option options [Proc] :operator the visit operator block
81
+ # @option options [String] :prune_cycle flag indicating whether to exclude cycles to the root in a visit
82
+ # @yield [parent] the parent being visited
83
+ def initialize(options=nil, &navigator)
84
+ @navigator = navigator
85
+ @options = Options.to_hash(options)
86
+ @depth_first_flag = @options[:depth_first]
87
+ @visited = @options[:visited] || {}
88
+ @prune_cycle_flag = @options[:prune_cycle]
89
+ @lineage = []
90
+ @cycles = []
91
+ @exclude = Set.new
92
+ end
93
+
94
+ # Navigates to node and the children returned by this Visitor's navigator block.
95
+ # Applies the optional operator block to each child node if the block is given to this method.
96
+ # Returns the result of the operator block if given, or the node itself otherwise.
97
+ #
98
+ # The nodes to visit from a parent node are determined in the following sequence:
99
+ # * Return if the parent node has already been visited.
100
+ # * If depth_first, then call the navigator block defined in the initializer on
101
+ # the parent node and visit each child node.
102
+ # * Visit the parent node.
103
+ # * If not depth-first, then call the navigator block defined in the initializer
104
+ # on the parent node and visit each child node.
105
+ # The :depth option value constrains child traversal to that number of levels.
106
+ #
107
+ # This method first clears the _visited_ hash, unless the :visited option was set in the initializer.
108
+ #
109
+ # @param node the root object to visit
110
+ # @yield [visited] an operator applied to each visited object
111
+ # @yieldparam visited the object currently being visited
112
+ # @return the result of the yield block on node, or node itself if no block is given
113
+ def visit(node, &operator)
114
+ visit_root(node, &operator)
115
+ end
116
+
117
+ # @param node the node to check
118
+ # @return whether the node was visited
119
+ def visited?(node)
120
+ @visited.has_key?(node)
121
+ end
122
+
123
+ # @return the top node visited
124
+ def root
125
+ @lineage.first
126
+ end
127
+
128
+ # @return the current node being visited
129
+ def current
130
+ @lineage.last
131
+ end
132
+
133
+ # @return the node most recently passed as an argument to this visitor's navigator block, or nil if
134
+ # visiting the first node
135
+ def parent
136
+ @lineage[-2]
137
+ end
138
+
139
+ # @return [Enumerable] iterator over each visited node
140
+ def to_enum(node)
141
+ # could use Generator, but that results in dire behavior on any error by crashing with an elided Java lineage trace
142
+ VisitorEnumerator.new(self, node)
143
+ end
144
+
145
+ # Returns a new visitor that traverses a collection of parent nodes in lock-step fashion using this visitor.
146
+ # The synced {#visit} method applies the visit operator block to an array of child nodes taken
147
+ # from each parent node, e.g. given the class documentation example:
148
+ # parent1 = Node.new(1)
149
+ # child11 = Node.new(2, parent1)
150
+ # child12 = Node.new(3, parent1)
151
+ # parent2 = Node.new(1)
152
+ # child21 = Node.new(3, parent2)
153
+ # Visitor.new { |node| node.children }.sync.enum.to_a #=> [
154
+ # [parent1, parent2],
155
+ # [child11, child21],
156
+ # [child12, nil]
157
+ # ]
158
+ #
159
+ # By default, the children are grouped in enumeration order. If a block is given to this
160
+ # method, then the block is called to match child nodes, e.g. using the above example:
161
+ # visitor = Visitor.new { |node| node.children }
162
+ # synced = visitor.sync { |node, others| others.detect { |other| node.value == other.value }
163
+ # synced.enum.to_a #=> [
164
+ # [parent1, parent2],
165
+ # [child11, nil],
166
+ # [child12, child21]
167
+ # ]
168
+ #
169
+ # @yield [node, others] matches node in others (optional)
170
+ # @yieldparam [Resource] node the visited node to match
171
+ # @yieldparam [<Resource>] the candidates for matching the node
172
+ def sync(&matcher) # :yields: node, others
173
+ SyncVisitor.new(self, &matcher)
174
+ end
175
+
176
+ # Returns a new Visitor which determines which nodes to visit by applying the given block
177
+ # to this visitor, e.g.:
178
+ # Visitor.new { |node| node.children }.filter { |parent, children| children.first if parent.age >= 18 }
179
+ # navigates to the first child of parents 18 or older.
180
+ #
181
+ # The filter block arguments consist of a parent node and an array of children nodes for the parent.
182
+ # The block can return nil, a single node to visit or a collection of nodes to visit.
183
+ #
184
+ # @return [Visitor] the filter visitor
185
+ # @yield [parent, children] the filter to select which of the children to visit next
186
+ # @yieldparam parent the currently visited node
187
+ # @yieldparam children the nodes slated by this Visitor to visit next
188
+ # @raise [ArgumentError] if a block is not given to this method
189
+ def filter
190
+ raise ArgumentError.new("Filter block not given to visitor filter method") unless block_given?
191
+ Visitor.new(@options) { |node| yield(node, node_children(node)) }
192
+ end
193
+
194
+ protected
195
+
196
+ # Resets this visitor's state in preparation for a new visit.
197
+ def clear
198
+ # clear the lineage
199
+ @lineage.clear
200
+ # if the visited hash is not shared, then clear it
201
+ @visited.clear unless @options.has_key?(:visited)
202
+ # clear the cycles
203
+ @cycles.clear
204
+ end
205
+
206
+ # Sets the visited hash.
207
+ def visited=(hash)
208
+ @visited = hash ||= {}
209
+ end
210
+
211
+ # Visits the given node using the block given to this method.
212
+ # The default block returns node.
213
+ def visit_node(node)
214
+ @visited[node] = block_given? ? yield(node) : node
215
+ end
216
+
217
+ # Returns the children to visit for the given node.
218
+ def node_children(node)
219
+ children = @navigator.call(node)
220
+ return Array::EMPTY_ARRAY if children.nil?
221
+ Enumerable === children ? children.to_a.compact : [children]
222
+ end
223
+
224
+ private
225
+
226
+ # Visits the root node and all descendants.
227
+ def visit_root(node, &operator)
228
+ clear
229
+ prune_cycle_nodes(node) if @prune_cycle_flag
230
+ # visit the root node
231
+ visit_recursive(node, &operator)
232
+ end
233
+
234
+ # Excludes the internal nodes in cycles starting and ending at the given root.
235
+ def prune_cycle_nodes(root)
236
+ @exclude.clear
237
+ # visit the root, which will detect cycles, and remove the visited nodes afterwords
238
+ @prune_cycle_flag = false
239
+ to_enum(root).collect.each { |node| @visited.delete(node) }
240
+ @prune_cycle_flag = true
241
+ # add each cyclic internal node to the exclude list
242
+ @cycles.each { |cycle| cycle[1...-1].each { |node| @exclude << node } if cycle.first == root }
243
+ end
244
+
245
+ def visit_recursive(node, &operator)
246
+ return if node.nil? or @exclude.include?(node)
247
+ # return the visited value if the node has already been visited
248
+ if @visited.has_key?(node) then
249
+ #capture a cycle
250
+ index = @lineage.index(node)
251
+ if index then
252
+ cycle = @lineage[index..-1] << node
253
+ @cycles << cycle
254
+ end
255
+ return @visited[node]
256
+ end
257
+ # return nil if the node has not been visited but has been navigated in a depth-first visit
258
+ return if @lineage.include?(node)
259
+ visit_node_and_children(node, &operator)
260
+ end
261
+
262
+ def visit_node_and_children(node, &operator)
263
+ # set the current node
264
+ @lineage.push(node)
265
+ # if depth-first, then visit the children before the current node
266
+ visit_children(node, &operator) if @depth_first_flag
267
+ # visit the current node
268
+ result = visit_node(node, &operator)
269
+ # if not depth-first, then visit the children after the current node
270
+ visit_children(node, &operator) unless @depth_first_flag
271
+ @lineage.pop
272
+ # return the visit result
273
+ result
274
+ end
275
+
276
+ def visit_children(node, &operator)
277
+ children = node_children(node)
278
+ children.each { |child| visit_recursive(child, &operator) }
279
+ end
280
+
281
+ class VisitorEnumerator
282
+ include Enumerable
283
+
284
+ def initialize(visitor, node)
285
+ @visitor = visitor
286
+ @root = node
287
+ end
288
+
289
+ def each
290
+ @visitor.visit(@root) { |node| yield(node) }
291
+ end
292
+ end
293
+
294
+ class SyncVisitor < Visitor
295
+ # @param [Visitor] visitor the Visitor which will visit synchronized input
296
+ # @yield (see Visitor#sync)
297
+ def initialize(visitor, &matcher)
298
+ # the next node to visit is an array of child node pairs matched by the given matcher block
299
+ super() { |nodes| match_children(visitor, nodes, &matcher) }
300
+ end
301
+
302
+ # Visits the given pair of nodes.
303
+ #
304
+ # Raises ArgumentError if nodes does not consist of either two node arguments or one two-item Array
305
+ # argument.
306
+ def visit(*nodes)
307
+ if nodes.size == 1 then
308
+ nodes = nodes.first
309
+ raise ArgumentError.new("Sync visitor requires a pair of entry nodes.") unless nodes.size == 2
310
+ end
311
+ super(nodes)
312
+ end
313
+
314
+ # Returns an Enumerable which applies the given block to each matched node starting at the given nodes.
315
+ #
316
+ # Raises ArgumentError if nodes does not consist of either two node arguments or one two-item Array
317
+ # argument.
318
+ def to_enum(*nodes)
319
+ if nodes.size == 1 then
320
+ nodes = nodes.first
321
+ raise ArgumentError.new("Sync visitor requires a pair of entry nodes.") unless nodes.size == 2
322
+ end
323
+ super(nodes)
324
+ end
325
+
326
+ private
327
+
328
+ # Returns an array of arrays of matched children from the given parent nodes. The children are matched
329
+ # using the block given to this method, if supplied, or by index otherwise.
330
+ #
331
+ # @see #sync a usage example
332
+ def match_children(visitor, nodes) # :yields: child, others
333
+ # the parent nodes
334
+ p1, p2 = nodes
335
+ # this visitor's children
336
+ c1 = visitor.node_children(p1)
337
+ c2 = p2 ? visitor.node_children(p2) : []
338
+
339
+ # apply the matcher block on each of this visitor's children and the other children.
340
+ # if no block, then group the children by index, which is the transpose of the array of children arrays.
341
+ if block_given? then
342
+ c1.map { |c| [c, yield(c, c2)] }
343
+ else
344
+ # ensure that both children arrays are the same size
345
+ others = c2.size <= c1.size ? c2.fill(nil, c2.size...c1.size) : c2[0, c1.size]
346
+ # the children grouped by index is the transpose of the array of children arrays
347
+ [c1, others].transpose
348
+ end
349
+ end
350
+ end
351
+ end