jinx 2.1.1 → 2.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. data/History.md +4 -0
  2. data/lib/jinx.rb +0 -1
  3. data/lib/jinx/cli/application.rb +3 -3
  4. data/lib/jinx/cli/command.rb +1 -1
  5. data/lib/jinx/helpers/array.rb +3 -3
  6. data/lib/jinx/helpers/associative.rb +41 -0
  7. data/lib/jinx/helpers/collections.rb +0 -1
  8. data/lib/jinx/helpers/enumerable.rb +5 -2
  9. data/lib/jinx/helpers/hash.rb +1 -1
  10. data/lib/jinx/helpers/hashable.rb +1 -7
  11. data/lib/jinx/helpers/inflector.rb +1 -1
  12. data/lib/jinx/helpers/log.rb +63 -11
  13. data/lib/jinx/helpers/math.rb +20 -11
  14. data/lib/jinx/helpers/options.rb +4 -4
  15. data/lib/jinx/helpers/pretty_print.rb +0 -1
  16. data/lib/jinx/helpers/transitive_closure.rb +1 -1
  17. data/lib/jinx/helpers/uniquifier.rb +50 -17
  18. data/lib/jinx/helpers/visitor.rb +3 -3
  19. data/lib/jinx/import/java.rb +1 -1
  20. data/lib/jinx/importer.rb +3 -2
  21. data/lib/jinx/metadata/attribute_enumerator.rb +1 -1
  22. data/lib/jinx/metadata/dependency.rb +3 -3
  23. data/lib/jinx/metadata/introspector.rb +46 -42
  24. data/lib/jinx/metadata/inverse.rb +17 -1
  25. data/lib/jinx/metadata/java_property.rb +10 -10
  26. data/lib/jinx/metadata/propertied.rb +19 -16
  27. data/lib/jinx/metadata/property.rb +11 -11
  28. data/lib/jinx/resource.rb +86 -14
  29. data/lib/jinx/resource/match_visitor.rb +7 -5
  30. data/lib/jinx/resource/merge_visitor.rb +3 -10
  31. data/lib/jinx/resource/mergeable.rb +16 -16
  32. data/lib/jinx/resource/reference_enumerator.rb +0 -1
  33. data/lib/jinx/resource/reference_path_visitor.rb +1 -1
  34. data/lib/jinx/resource/reference_visitor.rb +5 -6
  35. data/lib/jinx/resource/unique.rb +1 -1
  36. data/lib/jinx/version.rb +1 -1
  37. data/test/lib/jinx/helpers/associative_test.rb +26 -0
  38. data/test/lib/jinx/helpers/collections_test.rb +14 -2
  39. data/test/lib/jinx/helpers/uniquifier_test.rb +11 -0
  40. metadata +9 -8
  41. data/Gemfile.lock +0 -27
  42. data/lib/jinx/helpers/error.rb +0 -15
  43. data/lib/jinx/helpers/key_transformer_hash.rb +0 -43
@@ -85,7 +85,7 @@ module Jinx
85
85
  # @return [Resource] this domain object
86
86
  # @raise (see #validate_local)
87
87
  def validate
88
- if not @validated then
88
+ unless @validated then
89
89
  validate_local
90
90
  @validated = true
91
91
  end
@@ -104,7 +104,7 @@ module Jinx
104
104
  if attributes.empty? then
105
105
  attributes = self.class.nondomain_attributes
106
106
  elsif Enumerable === attributes.first then
107
- Jinx.fail(ArgumentError, "#{qp} copy attributes argument is not a Symbol: #{attributes.first}") unless attributes.size == 1
107
+ raise ArgumentError.new("#{qp} copy attributes argument is not a Symbol: #{attributes.first}") unless attributes.size == 1
108
108
  attributes = attributes.first
109
109
  end
110
110
  self.class.new.merge_attributes(self, attributes)
@@ -136,10 +136,13 @@ module Jinx
136
136
  #
137
137
  # @see Mergeable#merge_attribute
138
138
  def set_property_value(attribute, value)
139
- # bail out if the value argument is the current value
140
- return value if value.equal?(send(attribute))
141
- clear_attribute(attribute)
142
- merge_attribute(attribute, value)
139
+ prop = self.class.property(attribute)
140
+ if prop.domain? and prop.collection? then
141
+ clear_attribute(attribute)
142
+ merge_attribute(attribute, value)
143
+ else
144
+ set_typed_property_value(prop, value)
145
+ end
143
146
  end
144
147
 
145
148
  # Returns the first non-nil {#key_value} for the primary, secondary
@@ -208,7 +211,7 @@ module Jinx
208
211
  # @raise [NoMethodError] if this Resource's class does not have exactly one owner attribute
209
212
  def owner=(owner)
210
213
  pa = self.class.owner_attribute
211
- if pa.nil? then Jinx.fail(NoMethodError, "#{self.class.qp} does not have a unique owner attribute") end
214
+ if pa.nil? then raise NoMethodError.new("#{self.class.qp} does not have a unique owner attribute") end
212
215
  set_property_value(pa, owner)
213
216
  end
214
217
 
@@ -324,6 +327,26 @@ module Jinx
324
327
  matches_key_attributes?(other, self.class.alternate_key_attributes)
325
328
  end
326
329
 
330
+ # Matches this domain object with the other domain object. The match
331
+ # succeeds if and only if the object classes match and for each
332
+ # attribute, at least one of the following conditions hold:
333
+ # * this object's attribute value is nil or empty
334
+ # * the other object's attribute value is nil or empty
335
+ # * if the attribute is a nondomain attribute, then the respective values
336
+ # are equal
337
+ # * if the attribute value is a Resource, then the value recursively
338
+ # matches the other value
339
+ # * if the attribute value is a Resource collection, then every
340
+ # item in the collection matches some item in the other collection
341
+ #
342
+ # @param [Resource] the domain object to match
343
+ # @return [Boolean] whether this object matches the other object on class
344
+ # and content
345
+ def content_matches?(other)
346
+ logger.debug { "Matching #{self} content against #{other}..." }
347
+ content_matches_recursive?(other)
348
+ end
349
+
327
350
  # Matches this dependent domain object with the others on type and key attributes
328
351
  # in the scope of a parent object.
329
352
  # Returns the object in others which matches this domain object, or nil if none.
@@ -448,7 +471,7 @@ module Jinx
448
471
  #
449
472
  # @yield [dep] operation on the visited domain object
450
473
  # @yieldparam [Resource] dep the domain object to visit
451
- def visit_dependents(&operator) # :yields: dependent
474
+ def visit_dependents(&operator)
452
475
  DEPENDENT_VISITOR.visit(self, &operator)
453
476
  end
454
477
 
@@ -472,9 +495,6 @@ module Jinx
472
495
  # Prints this domain object's content and recursively prints the referenced content.
473
496
  # The optional selector block determines the attributes to print. The default is the
474
497
  # {Propertied#java_attributes}.
475
- #
476
- #
477
- # TODO caRuby override to do_without_lazy_loader
478
498
  #
479
499
  # @yield [owner] the owner attribute selector
480
500
  # @yieldparam [Resource] owner the domain object to print
@@ -569,6 +589,46 @@ module Jinx
569
589
  # Recurse to the dependents.
570
590
  each_defaultable_reference { |ref| ref.add_defaults_recursive }
571
591
  end
592
+
593
+ # @param [Resource] (see #content_matches?)
594
+ # @param [<Resource>] visited the matched references
595
+ # @return (see #content_matches?)
596
+ def content_matches_recursive?(other, visited=Set.new)
597
+ return false unless self.class == other.class
598
+ return false unless self.class.nondomain_attributes.all? do |pa|
599
+ v = send(pa)
600
+ ov = other.send(pa)
601
+ if v.nil? || ov.nil? or Resource.value_equal?(v, ov) then
602
+ true
603
+ else
604
+ logger.debug { "#{self} does not match #{other} on property #{pa}: #{v.qp} vs #{ov.qp}" }
605
+ false
606
+ end
607
+ end
608
+ self.class.domain_attributes.all? do |pa|
609
+ v = send(pa)
610
+ ov = other.send(pa)
611
+ if v.nil_or_empty? or ov.nil_or_empty? or visited.include?(v) then
612
+ true
613
+ else
614
+ logger.debug { "Matching #{self} #{pa} value #{v.qp} against #{other} #{pa} value #{ov.qp}..." }
615
+ if Enumerable === v then
616
+ v.all? do |ref|
617
+ oref = ref.match_in(ov)
618
+ if oref.nil? then
619
+ logger.debug { "#{self} does not match #{other} on property #{pa}: #{v.pp_s} vs #{ov.pp_s}" }
620
+ false
621
+ else
622
+ ref.content_matches_recursive?(oref, visited)
623
+ end
624
+ end
625
+ else
626
+ visited << v
627
+ v.content_matches_recursive?(ov, visited)
628
+ end
629
+ end
630
+ end
631
+ end
572
632
 
573
633
  private
574
634
 
@@ -614,7 +674,7 @@ module Jinx
614
674
  invalid = missing_mandatory_attributes
615
675
  unless invalid.empty? then
616
676
  logger.error("Validation of #{qp} unsuccessful - missing #{invalid.join(', ')}:\n#{dump}")
617
- Jinx.fail(ValidationError, "Required attribute value missing for #{self}: #{invalid.join(', ')}")
677
+ raise ValidationError.new("Required attribute value missing for #{self}: #{invalid.join(', ')}")
618
678
  end
619
679
  validate_owner
620
680
  end
@@ -632,12 +692,24 @@ module Jinx
632
692
  if self.class.owner_attributes.size > 1 then
633
693
  vh = value_hash(self.class.owner_attributes)
634
694
  if vh.size > 1 then
635
- Jinx.fail(ValidationError, "Dependent #{self} references multiple owners #{vh.pp_s}:\n#{dump}")
695
+ raise ValidationError.new("Dependent #{self} references multiple owners #{vh.pp_s}:\n#{dump}")
636
696
  end
637
697
  end
638
698
  # If there is an owner reference attribute, then there must be an owner.
639
699
  if self.class.bidirectional_dependent? then
640
- Jinx.fail(ValidationError, "Dependent #{self} does not reference an owner")
700
+ raise ValidationError.new("Dependent #{self} does not reference an owner")
701
+ end
702
+ end
703
+
704
+ # @param [Property] property the property to set
705
+ # @param value the new value
706
+ # @raise [TypeError] if the value is incompatible with the property
707
+ def set_typed_property_value(property, value)
708
+ begin
709
+ send(property.writer, value)
710
+ rescue TypeError
711
+ # Add the attribute to the error message.
712
+ raise TypeError.new("Cannot set #{self.class.qp} #{property} to #{value.qp} - " + $!)
641
713
  end
642
714
  end
643
715
 
@@ -20,7 +20,7 @@ module Jinx
20
20
  # @yield (see ReferenceVisitor#initialize)
21
21
  # @yieldparam [Resource] source the matched source object
22
22
  def initialize(opts=nil)
23
- Jinx.fail(ArgumentError, "Reference visitor missing domain reference selector") unless block_given?
23
+ raise ArgumentError.new("Reference visitor missing domain reference selector") unless block_given?
24
24
  opts = Options.to_hash(opts)
25
25
  @matcher = opts.delete(:matcher) || DEF_MATCHER
26
26
  @matchable = opts.delete(:matchable)
@@ -35,9 +35,11 @@ module Jinx
35
35
  # only a matched reference is visited.
36
36
  super do |src|
37
37
  tgt = @matches[src]
38
- attrs = yield(src)
39
- match_references(src, tgt, attrs)
40
- attrs
38
+ # the attributes to match on
39
+ mas = yield(src)
40
+ # match the attribute references
41
+ match_references(src, tgt, mas)
42
+ mas
41
43
  end
42
44
  end
43
45
 
@@ -98,7 +100,7 @@ module Jinx
98
100
  # @raise [ValidationError] if there is no match
99
101
  def match_for_visited(source)
100
102
  target = @matches[source]
101
- if target.nil? then Jinx.fail(ValidationError, "Match visitor target not found for #{source}") end
103
+ if target.nil? then raise ValidationError.new("Match visitor target not found for #{source}") end
102
104
  target
103
105
  end
104
106
 
@@ -54,20 +54,13 @@ module Jinx
54
54
  return target if source.equal?(target)
55
55
  # the domain attributes to merge
56
56
  mas = @mergeable.call(source)
57
- logger.debug { format_merge_log_message(source, target, mas) }
57
+ unless mas.empty? then
58
+ logger.debug { "Merging #{source.qp} #{mas.to_series} into #{target.qp}..." }
59
+ end
58
60
  # merge the non-domain attributes
59
61
  target.merge_attributes(source)
60
62
  # merge the source domain attributes into the target
61
63
  target.merge(source, mas, @matches)
62
64
  end
63
-
64
- # @param source (see #merge)
65
- # @param target (see #merge)
66
- # @param attributes (see Mergeable#merge)
67
- # @return [String] the log message
68
- def format_merge_log_message(source, target, attributes)
69
- attr_clause = " including domain attributes #{attributes.to_series}" unless attributes.empty?
70
- "Merging #{source.qp} into #{target.qp}#{attr_clause}..."
71
- end
72
65
  end
73
66
  end
@@ -87,19 +87,19 @@ module Jinx
87
87
  # Discriminate between a domain and non-domain attribute.
88
88
  prop = self.class.property(attribute)
89
89
  if prop.domain? then
90
- merge_domain_attribute_value(prop, oldval, newval, matches)
90
+ merge_domain_property_value(prop, oldval, newval, matches)
91
91
  else
92
- merge_nondomain_attribute_value(prop, oldval, newval)
92
+ merge_nondomain_property_value(prop, oldval, newval)
93
93
  end
94
94
  end
95
95
 
96
96
  private
97
97
 
98
98
  # @see #merge_attribute
99
- def merge_nondomain_attribute_value(prop, oldval, newval)
99
+ def merge_nondomain_property_value(property, oldval, newval)
100
100
  if oldval.nil? then
101
- send(prop.writer, newval)
102
- elsif prop.collection? then
101
+ set_typed_property_value(property, newval)
102
+ elsif property.collection? then
103
103
  oldval.merge(newval)
104
104
  else
105
105
  oldval
@@ -107,12 +107,12 @@ module Jinx
107
107
  end
108
108
 
109
109
  # @see #merge_attribute
110
- def merge_domain_attribute_value(prop, oldval, newval, matches)
110
+ def merge_domain_property_value(property, oldval, newval, matches)
111
111
  # the dependent owner writer method, if any
112
- if prop.dependent? then
113
- val = prop.collection? ? newval.first : newval
112
+ if property.dependent? then
113
+ val = property.collection? ? newval.first : newval
114
114
  klass = val.class if val
115
- inv_prop = self.class.inverse_property(prop, klass)
115
+ inv_prop = self.class.inverse_property(property, klass)
116
116
  if inv_prop and not inv_prop.collection? then
117
117
  owtr = inv_prop.writer
118
118
  end
@@ -122,14 +122,14 @@ module Jinx
122
122
  # collection value and add each unmatched source to the collection.
123
123
  # Otherwise, if the attribute is not yet set and there is a new value, then set it
124
124
  # to the new value match or the new value itself if unmatched.
125
- if prop.collection? then
125
+ if property.collection? then
126
126
  # TODO - refactor into method
127
127
  if oldval.nil? then
128
- Jinx.fail(ValidationError, "Merge into #{qp} #{prop} with nil collection value is not supported")
128
+ raise ValidationError.new("Merge into #{qp} #{property} with nil collection value is not supported")
129
129
  end
130
130
  # the references to add
131
131
  adds = []
132
- logger.debug { "Merging #{newval.qp} into #{qp} #{prop} #{oldval.qp}..." } unless newval.nil_or_empty?
132
+ logger.debug { "Merging #{newval.qp} into #{qp} #{property} #{oldval.qp}..." } unless newval.nil_or_empty?
133
133
  newval.enumerate do |src|
134
134
  # If the match target is in the current collection, then update the matched
135
135
  # target from the source.
@@ -150,11 +150,11 @@ module Jinx
150
150
  end
151
151
  end
152
152
  # add the unmatched sources
153
- logger.debug { "Adding #{qp} #{prop} unmatched #{adds.qp}..." } unless adds.empty?
153
+ logger.debug { "Adding #{qp} #{property} unmatched #{adds.qp}..." } unless adds.empty?
154
154
  adds.each do |ref|
155
155
  # If there is an owner writer attribute, then add the ref to the attribute collection by
156
156
  # delegating to the owner writer. Otherwise, add the ref to the attribute collection directly.
157
- owtr ? delegate_to_inverse_setter(prop, ref, owtr) : oldval << ref
157
+ owtr ? delegate_to_inverse_setter(property, ref, owtr) : oldval << ref
158
158
  end
159
159
  oldval
160
160
  elsif newval.nil? then
@@ -167,10 +167,10 @@ module Jinx
167
167
  # No target; set the attribute to the source.
168
168
  # The target is either a source match or the source itself.
169
169
  ref = (matches[newval] if matches) || newval
170
- logger.debug { "Setting #{qp} #{prop} reference #{ref.qp}..." }
170
+ logger.debug { "Setting #{qp} #{property} reference #{ref.qp}..." }
171
171
  # If the target is a dependent, then set the dependent owner, which will in turn
172
172
  # set the attribute to the dependent. Otherwise, set the attribute to the target.
173
- owtr ? delegate_to_inverse_setter(prop, ref, owtr) : send(prop.writer, ref)
173
+ owtr ? delegate_to_inverse_setter(property, ref, owtr) : set_typed_property_value(property, ref)
174
174
  end
175
175
  newval
176
176
  end
@@ -5,7 +5,6 @@ require 'jinx/helpers/collections'
5
5
 
6
6
  require 'jinx/helpers/validation'
7
7
  require 'jinx/helpers/visitor'
8
- require 'jinx/helpers/math'
9
8
 
10
9
  module Jinx
11
10
  # A ReferenceEnumerator iterates over domain property references.
@@ -28,7 +28,7 @@ module Jinx
28
28
  super(opts) do |ref|
29
29
  # Collect the path attributes whose type is the ref type up to the
30
30
  # next position in the path.
31
- max = lineage.size.min(path.size)
31
+ max = Math.min(lineage.size, path.size)
32
32
  pas = (0...max).map { |i| path[i].attribute if path[i].declarer === ref }
33
33
  pas.compact!
34
34
  ref.class.attribute_filter(pas)
@@ -5,7 +5,6 @@ require 'jinx/helpers/collections'
5
5
 
6
6
  require 'jinx/helpers/validation'
7
7
  require 'jinx/helpers/visitor'
8
- require 'jinx/helpers/math'
9
8
 
10
9
  module Jinx
11
10
  # A ReferenceVisitor traverses reference attributes.
@@ -23,7 +22,7 @@ module Jinx
23
22
  # current domain object
24
23
  # @yieldparam [Resource] obj the current domain object
25
24
  def initialize(opts=nil, &selector)
26
- Jinx.fail(ArgumentError, "Reference visitor missing domain reference selector") unless block_given?
25
+ raise ArgumentError.new("Reference visitor missing domain reference selector") unless block_given?
27
26
  # the property selector
28
27
  @flt_sel = selector
29
28
  # the reference filter
@@ -33,11 +32,11 @@ module Jinx
33
32
  @ref_enums = {}
34
33
  super do |ref|
35
34
  # the reference property filter
36
- attrs = attributes_to_visit(ref)
37
- if attrs then
38
- logger.debug { "#{qp} visiting #{ref} attributes #{attrs.pp_s(:single_line)}..." } if @verbose
35
+ ras = attributes_to_visit(ref)
36
+ if ras then
37
+ logger.debug { "#{qp} visiting #{ref} attributes #{ras.pp_s(:single_line)}..." } if @verbose
39
38
  # an enumerator on the reference properties
40
- enum = ReferenceEnumerator.new(ref, attrs.properties)
39
+ enum = ReferenceEnumerator.new(ref, ras.properties)
41
40
  # If there is a reference filter, then apply it to the enum references.
42
41
  flt ? enum.filter(&flt) : enum
43
42
  end
@@ -8,7 +8,7 @@ module Jinx
8
8
  # Raises TypeError if value is neither nil nor a String.
9
9
  def uniquify_value(value)
10
10
  unless String === value or value.nil? then
11
- Jinx.fail(TypeError, "Cannot uniquify #{qp} non-String value #{value}")
11
+ raise TypeError.new("Cannot uniquify #{qp} non-String value #{value}")
12
12
  end
13
13
  Uniquifier.instance.uniquify(self, value)
14
14
  end
@@ -1,3 +1,3 @@
1
1
  module Jinx
2
- VERSION = "2.1.1"
2
+ VERSION = '2.1.2'
3
3
  end
@@ -0,0 +1,26 @@
1
+ require File.dirname(__FILE__) + '/../../../helper'
2
+ require 'test/unit'
3
+ require 'jinx/helpers/associative'
4
+
5
+ class AssociativeTest < Test::Unit::TestCase
6
+ def test_get
7
+ hash = {'a' => 1}
8
+ assoc = Jinx::Associative.new { |k| hash[k.to_s] }
9
+ assert_equal(1, assoc[:a], "Associative access incorrect.")
10
+ assert_nil(assoc[:b], "Associative access incorrectly returns a value.")
11
+ end
12
+
13
+ def test_set
14
+ hash = {'a' => 1}
15
+ assoc = Jinx::Associative.new { |k| hash[k.to_s] }.writer { |k, v| hash[k.to_s] = v }
16
+ assert_equal(1, assoc[:a], "Associative access incorrect.")
17
+ assert_nil(assoc[:b], "Associative access incorrectly returns a value.")
18
+ assoc[:b] = 2
19
+ assert_equal(2, assoc[:b], "Associative writer incorrect.")
20
+ # reset the value
21
+ assoc[:a] = 3
22
+ assert_equal(3, assoc[:a], "Associative writer incorrect.")
23
+ # test the ||= idiom
24
+ assert_equal(4, assoc[:c] ||= 4, "Associative writer incorrect.")
25
+ end
26
+ end
@@ -3,7 +3,6 @@ require 'test/unit'
3
3
  require 'jinx/helpers/collections'
4
4
  require 'jinx/helpers/lazy_hash'
5
5
  require 'jinx/helpers/case_insensitive_hash'
6
- require 'jinx/helpers/key_transformer_hash'
7
6
  require 'jinx/helpers/conditional_enumerator'
8
7
  require 'jinx/helpers/multi_enumerator'
9
8
 
@@ -313,7 +312,7 @@ class CollectionsTest < Test::Unit::TestCase
313
312
  assert_equal(:a, hash[3], 'Key transformer hash equivalent value not found')
314
313
  end
315
314
 
316
- def test_transformed_hash
315
+ def test_transform_value
317
316
  hash = {:a => 1, :b => 2}
318
317
  xfm = hash.transform_value { |v| v * 2 }
319
318
  assert_equal(2, xfm[:a], 'Transformed hash accessor incorrect')
@@ -326,6 +325,19 @@ class CollectionsTest < Test::Unit::TestCase
326
325
  assert_equal(8, xfm[:c], 'Transformed hash does not reflect base hash change')
327
326
  end
328
327
 
328
+ def test_transform_key
329
+ hash = {'a' => 1, 'b' => 2}
330
+ xfm = hash.transform_key { |k| k.to_sym }
331
+ assert_equal(1, xfm[:a], 'Transformed hash accessor incorrect')
332
+ assert_equal([:a, :b], xfm.keys.sort { |k1, k2| k1.to_s <=> k2.to_s }, 'Transformed hash keys incorrect')
333
+ assert(xfm.has_key?(:a), 'Transformed hash key query incorrect')
334
+ assert(!xfm.has_key?('a'), 'Transformed hash key query incorrect')
335
+ # base hash should be reflected in transformed hash
336
+ hash['b'] = 3; hash['c'] = 4
337
+ assert_equal(3, xfm[:b], 'Transformed hash does not reflect base hash change')
338
+ assert_equal(4, xfm[:c], 'Transformed hash does not reflect base hash change')
339
+ end
340
+
329
341
  def test_hashinator
330
342
  base = {:a => 1, :b => 2}.to_a
331
343
  hash = Jinx::Hashinator.new(base)
@@ -0,0 +1,11 @@
1
+ require File.dirname(__FILE__) + '/../../../helper'
2
+ require 'test/unit'
3
+ require 'jinx/helpers/uniquifier'
4
+
5
+ class UniquifierTest < Test::Unit::TestCase
6
+ def test_uniquify
7
+ u1 = Jinx::Uniquifier.instance.uniquify('Groucho')
8
+ u2 = Jinx::Uniquifier.instance.uniquify('Groucho')
9
+ assert_not_equal(u1, u2, "Consequetive uniquifier calls not unique")
10
+ end
11
+ end