jinx 2.1.1 → 2.1.2

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 (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