jinx 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) hide show
  1. data/.gitignore +14 -0
  2. data/.rspec +3 -0
  3. data/.yardopts +1 -0
  4. data/Gemfile +6 -0
  5. data/Gemfile.lock +27 -0
  6. data/History.md +6 -0
  7. data/LEGAL +5 -0
  8. data/LICENSE +22 -0
  9. data/README.md +44 -0
  10. data/Rakefile +41 -0
  11. data/examples/family/README.md +10 -0
  12. data/examples/family/ext/build.xml +35 -0
  13. data/examples/family/ext/src/family/Address.java +68 -0
  14. data/examples/family/ext/src/family/Child.java +24 -0
  15. data/examples/family/ext/src/family/DomainObject.java +26 -0
  16. data/examples/family/ext/src/family/Household.java +36 -0
  17. data/examples/family/ext/src/family/Parent.java +48 -0
  18. data/examples/family/ext/src/family/Person.java +42 -0
  19. data/examples/family/lib/family.rb +15 -0
  20. data/examples/family/lib/family/address.rb +6 -0
  21. data/examples/family/lib/family/domain_object.rb +6 -0
  22. data/examples/family/lib/family/household.rb +6 -0
  23. data/examples/family/lib/family/parent.rb +16 -0
  24. data/examples/family/lib/family/person.rb +6 -0
  25. data/examples/model/README.md +25 -0
  26. data/examples/model/ext/build.xml +35 -0
  27. data/examples/model/ext/src/domain/Child.java +192 -0
  28. data/examples/model/ext/src/domain/Dependent.java +29 -0
  29. data/examples/model/ext/src/domain/DomainObject.java +26 -0
  30. data/examples/model/ext/src/domain/Independent.java +83 -0
  31. data/examples/model/ext/src/domain/Parent.java +129 -0
  32. data/examples/model/ext/src/domain/Person.java +14 -0
  33. data/examples/model/lib/model.rb +13 -0
  34. data/examples/model/lib/model/child.rb +13 -0
  35. data/examples/model/lib/model/domain_object.rb +6 -0
  36. data/examples/model/lib/model/independent.rb +11 -0
  37. data/examples/model/lib/model/parent.rb +17 -0
  38. data/jinx.gemspec +22 -0
  39. data/lib/jinx.rb +3 -0
  40. data/lib/jinx/active_support/README.txt +2 -0
  41. data/lib/jinx/active_support/core_ext/string.rb +7 -0
  42. data/lib/jinx/active_support/core_ext/string/inflections.rb +167 -0
  43. data/lib/jinx/active_support/inflections.rb +55 -0
  44. data/lib/jinx/active_support/inflector.rb +398 -0
  45. data/lib/jinx/cli/application.rb +36 -0
  46. data/lib/jinx/cli/command.rb +214 -0
  47. data/lib/jinx/helpers/array.rb +108 -0
  48. data/lib/jinx/helpers/boolean.rb +42 -0
  49. data/lib/jinx/helpers/case_insensitive_hash.rb +39 -0
  50. data/lib/jinx/helpers/class.rb +149 -0
  51. data/lib/jinx/helpers/collection.rb +33 -0
  52. data/lib/jinx/helpers/collections.rb +11 -0
  53. data/lib/jinx/helpers/collector.rb +20 -0
  54. data/lib/jinx/helpers/conditional_enumerator.rb +21 -0
  55. data/lib/jinx/helpers/enumerable.rb +242 -0
  56. data/lib/jinx/helpers/enumerate.rb +35 -0
  57. data/lib/jinx/helpers/error.rb +15 -0
  58. data/lib/jinx/helpers/file_separator.rb +65 -0
  59. data/lib/jinx/helpers/filter.rb +52 -0
  60. data/lib/jinx/helpers/flattener.rb +38 -0
  61. data/lib/jinx/helpers/hash.rb +12 -0
  62. data/lib/jinx/helpers/hashable.rb +502 -0
  63. data/lib/jinx/helpers/inflector.rb +36 -0
  64. data/lib/jinx/helpers/key_transformer_hash.rb +43 -0
  65. data/lib/jinx/helpers/lazy_hash.rb +44 -0
  66. data/lib/jinx/helpers/log.rb +106 -0
  67. data/lib/jinx/helpers/math.rb +12 -0
  68. data/lib/jinx/helpers/merge.rb +60 -0
  69. data/lib/jinx/helpers/module.rb +18 -0
  70. data/lib/jinx/helpers/multi_enumerator.rb +31 -0
  71. data/lib/jinx/helpers/options.rb +92 -0
  72. data/lib/jinx/helpers/os.rb +19 -0
  73. data/lib/jinx/helpers/partial_order.rb +37 -0
  74. data/lib/jinx/helpers/pretty_print.rb +207 -0
  75. data/lib/jinx/helpers/set.rb +8 -0
  76. data/lib/jinx/helpers/stopwatch.rb +76 -0
  77. data/lib/jinx/helpers/transformer.rb +24 -0
  78. data/lib/jinx/helpers/transitive_closure.rb +55 -0
  79. data/lib/jinx/helpers/uniquifier.rb +50 -0
  80. data/lib/jinx/helpers/validation.rb +33 -0
  81. data/lib/jinx/helpers/visitor.rb +370 -0
  82. data/lib/jinx/import/class_path_modifier.rb +77 -0
  83. data/lib/jinx/import/java.rb +337 -0
  84. data/lib/jinx/importer.rb +240 -0
  85. data/lib/jinx/metadata.rb +155 -0
  86. data/lib/jinx/metadata/attribute_enumerator.rb +73 -0
  87. data/lib/jinx/metadata/dependency.rb +244 -0
  88. data/lib/jinx/metadata/id_alias.rb +23 -0
  89. data/lib/jinx/metadata/introspector.rb +179 -0
  90. data/lib/jinx/metadata/inverse.rb +170 -0
  91. data/lib/jinx/metadata/java_property.rb +169 -0
  92. data/lib/jinx/metadata/propertied.rb +500 -0
  93. data/lib/jinx/metadata/property.rb +401 -0
  94. data/lib/jinx/metadata/property_characteristics.rb +114 -0
  95. data/lib/jinx/resource.rb +862 -0
  96. data/lib/jinx/resource/copy_visitor.rb +36 -0
  97. data/lib/jinx/resource/inversible.rb +90 -0
  98. data/lib/jinx/resource/match_visitor.rb +180 -0
  99. data/lib/jinx/resource/matcher.rb +20 -0
  100. data/lib/jinx/resource/merge_visitor.rb +73 -0
  101. data/lib/jinx/resource/mergeable.rb +185 -0
  102. data/lib/jinx/resource/reference_enumerator.rb +49 -0
  103. data/lib/jinx/resource/reference_path_visitor.rb +38 -0
  104. data/lib/jinx/resource/reference_visitor.rb +55 -0
  105. data/lib/jinx/resource/unique.rb +35 -0
  106. data/lib/jinx/version.rb +3 -0
  107. data/spec/defaults_spec.rb +30 -0
  108. data/spec/definitions/model/alias/child.rb +5 -0
  109. data/spec/definitions/model/base/child.rb +5 -0
  110. data/spec/definitions/model/base/domain_object.rb +5 -0
  111. data/spec/definitions/model/base/independent.rb +5 -0
  112. data/spec/definitions/model/defaults/child.rb +5 -0
  113. data/spec/definitions/model/dependency/child.rb +5 -0
  114. data/spec/definitions/model/dependency/parent.rb +6 -0
  115. data/spec/definitions/model/inverse/child.rb +5 -0
  116. data/spec/definitions/model/inverse/independent.rb +5 -0
  117. data/spec/definitions/model/inverse/parent.rb +5 -0
  118. data/spec/definitions/model/mandatory/child.rb +6 -0
  119. data/spec/dependency_spec.rb +47 -0
  120. data/spec/family_spec.rb +64 -0
  121. data/spec/inverse_spec.rb +53 -0
  122. data/spec/mandatory_spec.rb +43 -0
  123. data/spec/metadata_spec.rb +68 -0
  124. data/spec/resource_spec.rb +30 -0
  125. data/spec/spec_helper.rb +3 -0
  126. data/spec/support/model.rb +19 -0
  127. data/test/fixtures/line_separator/cr_line_sep.txt +1 -0
  128. data/test/fixtures/line_separator/crlf_line_sep.txt +3 -0
  129. data/test/fixtures/line_separator/lf_line_sep.txt +3 -0
  130. data/test/fixtures/mixed/ext/build.xml +35 -0
  131. data/test/fixtures/mixed/ext/src/mixed/Case/Example.java +5 -0
  132. data/test/helper.rb +7 -0
  133. data/test/lib/jinx/command_test.rb +41 -0
  134. data/test/lib/jinx/helpers/boolean_test.rb +27 -0
  135. data/test/lib/jinx/helpers/class_test.rb +60 -0
  136. data/test/lib/jinx/helpers/collections_test.rb +402 -0
  137. data/test/lib/jinx/helpers/file_separator_test.rb +29 -0
  138. data/test/lib/jinx/helpers/inflector_test.rb +11 -0
  139. data/test/lib/jinx/helpers/lazy_hash_test.rb +32 -0
  140. data/test/lib/jinx/helpers/module_test.rb +24 -0
  141. data/test/lib/jinx/helpers/options_test.rb +66 -0
  142. data/test/lib/jinx/helpers/partial_order_test.rb +41 -0
  143. data/test/lib/jinx/helpers/pretty_print_test.rb +83 -0
  144. data/test/lib/jinx/helpers/stopwatch_test.rb +16 -0
  145. data/test/lib/jinx/helpers/transitive_closure_test.rb +80 -0
  146. data/test/lib/jinx/helpers/visitor_test.rb +288 -0
  147. data/test/lib/jinx/import/java_test.rb +78 -0
  148. data/test/lib/jinx/import/mixed_case_test.rb +16 -0
  149. metadata +272 -0
@@ -0,0 +1,33 @@
1
+ module Jinx
2
+ # Raised when an object fails a validation test.
3
+ class ValidationError < RuntimeError; end
4
+ end
5
+
6
+ class Object
7
+ # Returns whether this object is nil, false, empty, or a whitespace string.
8
+ # This method is borrowed from Rails ActiveSupport.
9
+ #
10
+ # @example
11
+ # ''.blank? => true
12
+ # nil.blank? => true
13
+ # false.blank? => true
14
+ # [].blank? => true
15
+ # [[]].blank? => false
16
+ # @return [Boolean] whether this object is nil, false, empty, or a whitespace string
17
+ # @see {#nil_or_empty?}
18
+ def blank?
19
+ respond_to?(:empty?) ? empty? : !self
20
+ end
21
+
22
+ # Returns whether this object is nil, empty, or a whitespace string.
23
+ # This method differs from {#blank?} in that +false+ is an allowed value.
24
+ #
25
+ # @example
26
+ # ''.nil_or_empty? => true
27
+ # nil.nil_or_empty? => true
28
+ # false.nil_or_empty? => false
29
+ # @return [Boolean] whether this object is nil, empty, or a whitespace string
30
+ def nil_or_empty?
31
+ blank? and self != false
32
+ end
33
+ end
@@ -0,0 +1,370 @@
1
+ require 'jinx/helpers/collections'
2
+
3
+ require 'jinx/helpers/options'
4
+
5
+ # Enumerator overwrites to_enum, so include it first
6
+ require 'enumerator'
7
+ require 'generator'
8
+
9
+ module Jinx
10
+ # Error raised on a visit failure.
11
+ class VisitError < RuntimeError; end
12
+
13
+ # Visitor traverses items and applies an operation, e.g.:
14
+ # class Node
15
+ # attr_accessor :children, :value
16
+ # def initialize(value, parent=nil)
17
+ # @value = value
18
+ # @children = []
19
+ # @parent = parent
20
+ # @parent.children << self if @parent
21
+ # end
22
+ # end
23
+ # parent = Node.new(1)
24
+ # child = Node.new(2, parent)
25
+ # multiplier = 2
26
+ # Jinx::Visitor.new { |node| node.children }.visit(parent) { |node| node.value *= multiplier } #=> 2
27
+ # parent.value #=> 2
28
+ # child.value #=> 4
29
+ #
30
+ # The visit result is the result of evaluating the operation block on the initial visited node.
31
+ # Visiting a collection returns an array of the result of visiting each member of the collection,
32
+ # e.g. augmenting the preceding example:
33
+ # parent2 = Node.new(3)
34
+ # child2 = Node.new(4, parent2)
35
+ # Jinx::Visitor.new { |node| node.children }.visit([parent, parent2]) { |node| node.value *= multiplier } #=> [2, 6]
36
+ # Each visit captures the visit result in the +visited+ hash, e.g.:
37
+ # parent = Node.new(1)
38
+ # child = Node.new(2, parent)
39
+ # visitor = Jinx::Visitor.new { |node| node.children }
40
+ # visitor.visit([parent]) { |node| node.value += 1 }
41
+ # parent.value #=> 2
42
+ # visitor.visited[parent] #=> 2
43
+ # child.value #=> 3
44
+ # visitor.visited[child] #=> 3
45
+ #
46
+ # A +return+ from the operation block terminates the visit and exits from the defining scope with the block return value,
47
+ # e.g. given the preceding example:
48
+ # def increment(parent, limit)
49
+ # Jinx::Visitor.new { |node| node.children }.visit(parent) { |node| node.value < limit ? node.value += 1 : return }
50
+ # end
51
+ # increment(parent, 2) #=> nil
52
+ # parent.value #=> 2
53
+ # child.value #=> 2
54
+ #
55
+ # The to_enum method allows navigator iteration, e.g.:
56
+ # Jinx::Visitor.new { |node| node.children }.to_enum(parent).detect { |node| node.value == 2 }
57
+ class Visitor
58
+
59
+ attr_reader :options, :visited, :lineage, :cycles
60
+
61
+ # Creates a new Visitor which traverses the child objects returned by the navigator block.
62
+ # The navigator block takes a parent node argument and returns an enumerator on the children
63
+ # to visit.
64
+ #
65
+ # The options is a _symbol_, _symbol_ => _value_ hash or nil. A _symbol_ argument is the same
66
+ # as +{+_symbol_ +=> true}+. Supported options include the following:
67
+ # * +:depth_first+: +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
69
+ # information.
70
+ #
71
+ # If the :operator option is set, then the visit operator block is called when a node is visited.
72
+ # The operator block argument is the visited node.
73
+ #
74
+ # @param [Symbol, {Symbol => Object}] opts the visit options. A symbol argument is the same
75
+ # as symbol => true
76
+ # @option opts [String] :depth_first depth-first traversal
77
+ # @option opts [Proc] :operator the operator applied to each visited node
78
+ # @option opts [String] :prune_cycle flag indicating whether to exclude cycles to the root in a visit
79
+ # @option opts [Boolean] :verbose print navigation log messages
80
+ # @yield [parent] returns an enumerator on the children to visit
81
+ # @yieldparam parent the current node
82
+ def initialize(opts=nil, &navigator)
83
+ raise ArgumentError.new('Visitor cannot be created without a navigator block') unless block_given?
84
+ @navigator = navigator
85
+ @options = Options.to_hash(opts)
86
+ @depth_first_flag = @options[:depth_first]
87
+ @prune_cycle_flag = @options[:prune_cycle]
88
+ @lineage = []
89
+ @cycles = []
90
+ @visited = {}
91
+ @verbose = Options.get(:verbose, opts, false)
92
+ @exclude = Set.new
93
+ end
94
+
95
+ # Navigates to node and the children returned by this Visitor's navigator block.
96
+ # Applies the optional operator block to each child node if the block is given to this method.
97
+ # Returns the result of the operator block if given, or the node itself otherwise.
98
+ #
99
+ # The nodes to visit from a parent node are determined in the following sequence:
100
+ # * Return if the parent node has already been visited.
101
+ # * If depth_first, then call the navigator block defined in the initializer on
102
+ # the parent node and visit each child node.
103
+ # * Visit the parent node.
104
+ # * If not depth-first, then call the navigator block defined in the initializer
105
+ # on the parent node and visit each child node.
106
+ # The :depth option value constrains child traversal to that number of levels.
107
+ #
108
+ # This method first clears the _visited_ hash, unless the :visited option was set in the initializer.
109
+ #
110
+ # @param node the root object to visit
111
+ # @yield [visited] an operator applied to each visited object
112
+ # @yieldparam visited the object currently being visited
113
+ # @return the result of the yield block on node, or node itself if no block is given
114
+ def visit(node, &operator)
115
+ visit_root(node, &operator)
116
+ end
117
+
118
+ # @param node the node to check
119
+ # @return [Boolean] whether the node was visited
120
+ def visited?(node)
121
+ @visited.has_key?(node)
122
+ end
123
+
124
+ # @return the top node visited
125
+ def root
126
+ @lineage.first
127
+ end
128
+
129
+ # @return the current node being visited
130
+ def current
131
+ @lineage.last
132
+ end
133
+
134
+ # @return the node most recently passed as an argument to this visitor's navigator block,
135
+ # or nil if visiting the first node
136
+ def from
137
+ @lineage[-2]
138
+ end
139
+
140
+ alias :parent :from
141
+
142
+ # @return [Enumerable] iterator over each visited node
143
+ def to_enum(node)
144
+ # JRuby could use Generator instead, but that results in dire behavior on any error
145
+ # by crashing with an elided Java lineage trace.
146
+ VisitorEnumerator.new(self, node)
147
+ end
148
+
149
+ # Returns a new visitor that traverses a collection of parent nodes in lock-step fashion using
150
+ # this visitor. The synced {#visit} method applies the visit operator block to an array of child
151
+ # nodes taken from each parent node, e.g.:
152
+ # parent1 = Node.new(1)
153
+ # child11 = Node.new(2, parent1)
154
+ # child12 = Node.new(3, parent1)
155
+ # parent2 = Node.new(1)
156
+ # child21 = Node.new(3, parent2)
157
+ # Jinx::Visitor.new { |node| node.children }.sync.to_enum.to_a #=> [
158
+ # [parent1, parent2],
159
+ # [child11, child21],
160
+ # [child12, nil]
161
+ # ]
162
+ #
163
+ # By default, the children are grouped in enumeration order. If a block is given to this method,
164
+ # then the block is called to match child nodes, e.g. using the above example:
165
+ # visitor = Jinx::Visitor.new { |node| node.children }
166
+ # synced = visitor.sync { |nodes, others| nodes.to_compact_hash { others.detect { |other| node.value == other.value } } }
167
+ # synced.to_enum.to_a #=> [
168
+ # [parent1, parent2],
169
+ # [child11, nil],
170
+ # [child12, child21]
171
+ # ]
172
+ #
173
+ # @yield [nodes, others] matches node in others (optional)
174
+ # @yieldparam [<Resource>] nodes the visited nodes to match
175
+ # @yieldparam [<Resource>] others the candidates for matching the node
176
+ def sync(&matcher)
177
+ SyncVisitor.new(self, &matcher)
178
+ end
179
+
180
+ # Returns a new Visitor which determines which nodes to visit by applying the given block
181
+ # to this visitor. The filter block arguments consist of a parent node and an array of
182
+ # children nodes for the parent. The block can return nil, a single node to visit or a
183
+ # collection of nodes to visit.
184
+ #
185
+ # @example
186
+ # visitor = Jinx::Visitor.new { |person| person.children }
187
+ # # Joe has age 55 and children aged 17 and 24, who have children aged [1] and [6, 3], resp.
188
+ # visitor.to_enum(joe) { |person| person.age } #=> [55, 20, 1, 24, 6, 3]
189
+ # # The filter navigates to the children sorted by age of parents 21 or older.
190
+ # filter = visitor.filter { |parent, children| children.sort { |c1, c2| c1.age <=> c2.age } if parent.age >= 21 }
191
+ # filter.to_enum(joe) { |person| person.age } #=> [55, 24, 3, 6]
192
+ #
193
+ # @return [Visitor] the filter visitor
194
+ # @yield [parent, children] the filter to select which of the children to visit next
195
+ # @yieldparam parent the currently visited node
196
+ # @yieldparam children the nodes slated by this Visitor to visit next
197
+ # @raise [ArgumentError] if a block is not given to this method
198
+ def filter
199
+ Jinx.fail(ArgumentError, "A filter block is not given to the visitor filter method") unless block_given?
200
+ Visitor.new(@options) { |node| yield(node, node_children(node)) }
201
+ end
202
+
203
+ protected
204
+
205
+ # Resets this visitor's state in preparation for a new visit.
206
+ def clear
207
+ # clear the lineage
208
+ @lineage.clear
209
+ # if the visited hash is not shared, then clear it
210
+ @visited.clear unless @options.has_key?(:visited)
211
+ # clear the cycles
212
+ @cycles.clear
213
+ end
214
+
215
+ # Returns the children to visit for the given node.
216
+ def node_children(node)
217
+ children = @navigator.call(node)
218
+ return Array::EMPTY_ARRAY if children.nil?
219
+ Enumerable === children ? children.to_a.compact : [children]
220
+ end
221
+
222
+ private
223
+
224
+ # @return [Boolean] whether the depth-first flag is set
225
+ def depth_first?
226
+ !!@depth_first_flag
227
+ end
228
+
229
+ # Visits the root node and all descendants.
230
+ def visit_root(node, &operator)
231
+ clear
232
+ prune_cycle_nodes(node) if @prune_cycle_flag
233
+ # visit the root node
234
+ visit_recursive(node, &operator)
235
+ end
236
+
237
+ # Excludes the internal nodes in cycles starting and ending at the given root.
238
+ def prune_cycle_nodes(root)
239
+ @exclude.clear
240
+ # visit the root, which will detect cycles, and remove the visited nodes afterwords
241
+ @prune_cycle_flag = false
242
+ to_enum(root).collect.each { |node| @visited.delete(node) }
243
+ @prune_cycle_flag = true
244
+ # add each cyclic internal node to the exclude list
245
+ @cycles.each { |cycle| cycle[1...-1].each { |node| @exclude << node } if cycle.first == root }
246
+ end
247
+
248
+ def visit_recursive(node, &operator)
249
+ # bail if no node or the node is specifically excluded
250
+ return if node.nil? or @exclude.include?(node)
251
+ # return the visited value if the node has already been visited
252
+ if @visited.has_key?(node) then
253
+ #capture a cycle
254
+ index = @lineage.index(node)
255
+ if index then
256
+ cycle = @lineage[index..-1] << node
257
+ @cycles << cycle
258
+ end
259
+ return @visited[node]
260
+ end
261
+ # return nil if the node has not been visited but has been navigated in a depth-first visit
262
+ return if @lineage.include?(node)
263
+ # all systems go: visit the node graph
264
+ visit_node_and_children(node, &operator)
265
+ end
266
+
267
+ # Visits the given node and its children. If this visitor is #{depth_first?}, then the
268
+ # operator is applied to the children before the given node. Otherwise, the operator is
269
+ # applied to the children after the given node. The default operator returns the visited
270
+ # node itself.
271
+ #
272
+ # @param node the node to visit
273
+ # @yield (see #visit)
274
+ # @yieldparam (see #visit)
275
+ def visit_node_and_children(node, &operator)
276
+ # set the current node
277
+ @lineage.push(node)
278
+ # if depth-first, then visit the children before the current node
279
+ visit_children(node, &operator) if depth_first?
280
+ # apply the operator to the current node, if given
281
+ result = @visited[node] = block_given? ? yield(node) : node
282
+ logger.debug { "#{self} visited #{node.qp} with result #{result.qp}" } if @verbose
283
+ # if not depth-first, then visit the children after the current node
284
+ visit_children(node, &operator) unless depth_first?
285
+ @lineage.pop
286
+ # return the visit result
287
+ result
288
+ end
289
+
290
+ def visit_children(parent, &operator)
291
+ @navigator.call(parent).enumerate { |child| visit_recursive(child, &operator) }
292
+ end
293
+
294
+ class VisitorEnumerator
295
+ include Enumerable
296
+
297
+ def initialize(visitor, node)
298
+ @visitor = visitor
299
+ @root = node
300
+ end
301
+
302
+ def each
303
+ @visitor.visit(@root) { |node| yield(node) }
304
+ end
305
+ end
306
+
307
+ class SyncVisitor < Visitor
308
+ # @param [Visitor] visitor the Visitor which will visit synchronized input
309
+ # @yield (see Visitor#sync)
310
+ def initialize(visitor, &matcher)
311
+ # the next node to visit is an array of child node pairs matched by the given matcher block
312
+ super() { |nodes| match_children(visitor, nodes, &matcher) }
313
+ end
314
+
315
+ # Visits the given pair of nodes.
316
+ #
317
+ # Raises ArgumentError if nodes does not consist of either two node arguments or one two-item Array
318
+ # argument.
319
+ def visit(*nodes)
320
+ if nodes.size == 1 then
321
+ nodes = nodes.first
322
+ Jinx.fail(ArgumentError, "Sync visitor requires a pair of entry nodes") unless nodes.size == 2
323
+ end
324
+ super(nodes)
325
+ end
326
+
327
+ # Returns an Enumerable which applies the given block to each matched node starting at the given nodes.
328
+ #
329
+ # Raises ArgumentError if nodes does not consist of either two node arguments or one two-item Array
330
+ # argument.
331
+ def to_enum(*nodes)
332
+ if nodes.size == 1 then
333
+ nodes = nodes.first
334
+ Jinx.fail(ArgumentError, "Sync visitor requires a pair of entry nodes") unless nodes.size == 2
335
+ end
336
+ super(nodes)
337
+ end
338
+
339
+ private
340
+
341
+ # Returns an array of arrays of matched children from the given parent nodes. The children are matched
342
+ # using the block given to this method, if supplied, or by index otherwise.
343
+ #
344
+ # @see #sync a usage example
345
+ # @yield (see Visitor#sync)
346
+ def match_children(visitor, nodes)
347
+ # the parent nodes
348
+ p1, p2 = nodes
349
+ # this visitor's children
350
+ c1 = visitor.node_children(p1)
351
+ c2 = p2 ? visitor.node_children(p2) : []
352
+
353
+ # Apply the matcher block on each of this visitor's children and the other children.
354
+ # If no block is given, then group the children by index, which is the transpose of the array of
355
+ # children arrays.
356
+ if block_given? then
357
+ # Match each item in the first children array to an item from the second children array using
358
+ # then given block.
359
+ matches = yield(c1, c2)
360
+ c1.map { |c| [c, matches[c]] }
361
+ else
362
+ # Ensure that both children arrays are the same size.
363
+ others = c2.size <= c1.size ? c2.fill(nil, c2.size...c1.size) : c2[0, c1.size]
364
+ # The children grouped by index is the transpose of the array of children arrays.
365
+ [c1, others].transpose
366
+ end
367
+ end
368
+ end
369
+ end
370
+ end
@@ -0,0 +1,77 @@
1
+ module Jinx
2
+ # Helper class which adds files to the Java class path.
3
+ class ClassPathModifier
4
+ # Adds the directories in the given path and all Java jar files contained in
5
+ # the directories to the Java classpath.
6
+ #
7
+ # @quirk Java The jar files found by this method are added to the classpath
8
+ # in sort order. Java applications usually add jars in sort order. For examle,
9
+ # the Apache Ant directory-based classpath tasks are in sort order, although
10
+ # this is not stipulated in the documentation. Well-behaved Java libraries are
11
+ # not dependent on the sort order of included jar files. For poorly-behaved
12
+ # Java libraries, ensure that the classpath is in the expected order. If the
13
+ # classpath must be in a non-sorted order, then call {#add_to_classpath}
14
+ # on each jar file instead.
15
+ #
16
+ # @param [String] path the colon or semi-colon separated directories
17
+ def expand_to_class_path(path)
18
+ # the path separator
19
+ sep = path[WINDOWS_PATH_SEP] ? WINDOWS_PATH_SEP : UNIX_PATH_SEP
20
+ # the path directories
21
+ dirs = path.split(sep).map { |dir| File.expand_path(dir) }
22
+ expanded = expand_jars(dirs)
23
+ expanded.each { |dir| add_to_classpath(dir) }
24
+ end
25
+
26
+ # Adds the given jar file or directory to the classpath.
27
+ #
28
+ # @param [String] file the jar file or directory to add
29
+ def add_to_classpath(file)
30
+ unless File.exist?(file) then
31
+ logger.warn("File to place on Java classpath does not exist: #{file}")
32
+ return
33
+ end
34
+ if File.extname(file) == '.jar' then
35
+ # require is preferred to classpath append for a jar file.
36
+ require file
37
+ else
38
+ # A directory must end in a slash since JRuby uses an URLClassLoader.
39
+ if File.directory?(file) then
40
+ last = file[-1, 1]
41
+ if last == "\\" then
42
+ file = file[0...-1] + '/'
43
+ elsif last != '/' then
44
+ file = file + '/'
45
+ end
46
+ end
47
+ # Append the file to the classpath.
48
+ $CLASSPATH << file
49
+ end
50
+ end
51
+
52
+ private
53
+
54
+ # The Windows semi-colon path separator.
55
+ WINDOWS_PATH_SEP = ';'
56
+
57
+ # The Unix colon path separator.
58
+ UNIX_PATH_SEP = ':'
59
+
60
+ # Expands the given directories to include the contained jar files.
61
+ # If a directory contains jar files, then the jar files are included in
62
+ # the resulting array. Otherwise, the directory itself is included in
63
+ # the resulting array.
64
+ #
65
+ # @param [<String>] directories the directories containing jars to add
66
+ # @return [<String>] each directory or its jars
67
+ def expand_jars(directories)
68
+ # If there are jar files, then the file list is the sorted jar files.
69
+ # Otherwise, the file list is a singleton directory array.
70
+ expanded = directories.map do |dir|
71
+ jars = Dir[File.join(dir , "**", "*.jar")].sort
72
+ jars.empty? ? [dir] : jars
73
+ end
74
+ expanded.flatten
75
+ end
76
+ end
77
+ end