caruby-core 1.5.5 → 2.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (126) hide show
  1. data/Gemfile +9 -0
  2. data/History.md +5 -1
  3. data/lib/caruby.rb +3 -5
  4. data/lib/caruby/caruby-src.tar.gz +0 -0
  5. data/lib/caruby/database.rb +53 -69
  6. data/lib/caruby/database/application_service.rb +25 -0
  7. data/lib/caruby/database/cache.rb +60 -0
  8. data/lib/caruby/database/fetched_matcher.rb +52 -38
  9. data/lib/caruby/database/lazy_loader.rb +4 -4
  10. data/lib/caruby/database/operation.rb +34 -0
  11. data/lib/caruby/database/persistable.rb +171 -86
  12. data/lib/caruby/database/persistence_service.rb +32 -34
  13. data/lib/caruby/database/persistifier.rb +100 -43
  14. data/lib/caruby/database/reader.rb +107 -85
  15. data/lib/caruby/database/reader_template_builder.rb +60 -0
  16. data/lib/caruby/database/saved_matcher.rb +3 -3
  17. data/lib/caruby/database/sql_executor.rb +88 -17
  18. data/lib/caruby/database/writer.rb +213 -177
  19. data/lib/caruby/database/writer_template_builder.rb +334 -0
  20. data/lib/caruby/{util → helpers}/controlled_value.rb +0 -0
  21. data/lib/caruby/{util → helpers}/coordinate.rb +4 -4
  22. data/lib/caruby/{util → helpers}/person.rb +3 -3
  23. data/lib/caruby/{util → helpers}/properties.rb +7 -9
  24. data/lib/caruby/{util → helpers}/roman.rb +2 -2
  25. data/lib/caruby/{util → helpers}/version.rb +1 -1
  26. data/lib/caruby/json/deserializer.rb +2 -2
  27. data/lib/caruby/json/serializer.rb +49 -7
  28. data/lib/caruby/metadata.rb +30 -0
  29. data/lib/caruby/metadata/java_property.rb +21 -0
  30. data/lib/caruby/metadata/propertied.rb +191 -0
  31. data/lib/caruby/metadata/property.rb +22 -0
  32. data/lib/caruby/metadata/property_characteristics.rb +201 -0
  33. data/lib/caruby/migration/migratable.rb +11 -182
  34. data/lib/caruby/rdbi/driver/jdbc.rb +446 -0
  35. data/lib/caruby/resource.rb +20 -823
  36. data/lib/caruby/version.rb +1 -1
  37. data/test/lib/caruby/database/cache_test.rb +54 -0
  38. data/test/lib/caruby/{util → helpers}/controlled_value_test.rb +3 -5
  39. data/test/lib/caruby/{util → helpers}/person_test.rb +4 -6
  40. data/test/lib/caruby/helpers/properties_test.rb +34 -0
  41. data/test/lib/caruby/{util → helpers}/roman_test.rb +2 -3
  42. data/test/lib/caruby/{util → helpers}/version_test.rb +2 -3
  43. data/test/lib/helper.rb +7 -0
  44. metadata +161 -214
  45. data/lib/caruby/cli/application.rb +0 -36
  46. data/lib/caruby/cli/command.rb +0 -202
  47. data/lib/caruby/csv/csv_mapper.rb +0 -159
  48. data/lib/caruby/csv/csvio.rb +0 -203
  49. data/lib/caruby/database/search_template_builder.rb +0 -56
  50. data/lib/caruby/database/store_template_builder.rb +0 -278
  51. data/lib/caruby/domain.rb +0 -193
  52. data/lib/caruby/domain/attribute.rb +0 -584
  53. data/lib/caruby/domain/attributes.rb +0 -628
  54. data/lib/caruby/domain/dependency.rb +0 -225
  55. data/lib/caruby/domain/id_alias.rb +0 -22
  56. data/lib/caruby/domain/importer.rb +0 -183
  57. data/lib/caruby/domain/introspection.rb +0 -176
  58. data/lib/caruby/domain/inverse.rb +0 -172
  59. data/lib/caruby/domain/inversible.rb +0 -90
  60. data/lib/caruby/domain/java_attribute.rb +0 -173
  61. data/lib/caruby/domain/merge.rb +0 -185
  62. data/lib/caruby/domain/metadata.rb +0 -142
  63. data/lib/caruby/domain/mixin.rb +0 -35
  64. data/lib/caruby/domain/properties.rb +0 -95
  65. data/lib/caruby/domain/reference_visitor.rb +0 -428
  66. data/lib/caruby/domain/uniquify.rb +0 -50
  67. data/lib/caruby/import/java.rb +0 -387
  68. data/lib/caruby/migration/migrator.rb +0 -918
  69. data/lib/caruby/migration/resource_module.rb +0 -9
  70. data/lib/caruby/migration/uniquify.rb +0 -17
  71. data/lib/caruby/util/attribute_path.rb +0 -44
  72. data/lib/caruby/util/cache.rb +0 -56
  73. data/lib/caruby/util/class.rb +0 -149
  74. data/lib/caruby/util/collection.rb +0 -1152
  75. data/lib/caruby/util/domain_extent.rb +0 -46
  76. data/lib/caruby/util/file_separator.rb +0 -65
  77. data/lib/caruby/util/inflector.rb +0 -27
  78. data/lib/caruby/util/log.rb +0 -95
  79. data/lib/caruby/util/math.rb +0 -12
  80. data/lib/caruby/util/merge.rb +0 -59
  81. data/lib/caruby/util/module.rb +0 -18
  82. data/lib/caruby/util/options.rb +0 -97
  83. data/lib/caruby/util/partial_order.rb +0 -35
  84. data/lib/caruby/util/pretty_print.rb +0 -204
  85. data/lib/caruby/util/stopwatch.rb +0 -74
  86. data/lib/caruby/util/topological_sync_enumerator.rb +0 -62
  87. data/lib/caruby/util/transitive_closure.rb +0 -55
  88. data/lib/caruby/util/tree.rb +0 -48
  89. data/lib/caruby/util/trie.rb +0 -37
  90. data/lib/caruby/util/uniquifier.rb +0 -30
  91. data/lib/caruby/util/validation.rb +0 -20
  92. data/lib/caruby/util/visitor.rb +0 -365
  93. data/lib/caruby/util/weak_hash.rb +0 -36
  94. data/test/lib/caruby/csv/csv_mapper_test.rb +0 -40
  95. data/test/lib/caruby/csv/csvio_test.rb +0 -69
  96. data/test/lib/caruby/database/persistable_test.rb +0 -92
  97. data/test/lib/caruby/domain/domain_test.rb +0 -112
  98. data/test/lib/caruby/domain/inversible_test.rb +0 -99
  99. data/test/lib/caruby/domain/reference_visitor_test.rb +0 -130
  100. data/test/lib/caruby/import/java_test.rb +0 -80
  101. data/test/lib/caruby/import/mixed_case_test.rb +0 -14
  102. data/test/lib/caruby/migration/test_case.rb +0 -102
  103. data/test/lib/caruby/test_case.rb +0 -230
  104. data/test/lib/caruby/util/cache_test.rb +0 -23
  105. data/test/lib/caruby/util/class_test.rb +0 -61
  106. data/test/lib/caruby/util/collection_test.rb +0 -398
  107. data/test/lib/caruby/util/command_test.rb +0 -55
  108. data/test/lib/caruby/util/domain_extent_test.rb +0 -60
  109. data/test/lib/caruby/util/file_separator_test.rb +0 -30
  110. data/test/lib/caruby/util/inflector_test.rb +0 -12
  111. data/test/lib/caruby/util/lazy_hash_test.rb +0 -34
  112. data/test/lib/caruby/util/merge_test.rb +0 -83
  113. data/test/lib/caruby/util/module_test.rb +0 -25
  114. data/test/lib/caruby/util/options_test.rb +0 -59
  115. data/test/lib/caruby/util/partial_order_test.rb +0 -42
  116. data/test/lib/caruby/util/pretty_print_test.rb +0 -85
  117. data/test/lib/caruby/util/properties_test.rb +0 -50
  118. data/test/lib/caruby/util/stopwatch_test.rb +0 -18
  119. data/test/lib/caruby/util/topological_sync_enumerator_test.rb +0 -69
  120. data/test/lib/caruby/util/transitive_closure_test.rb +0 -67
  121. data/test/lib/caruby/util/tree_test.rb +0 -23
  122. data/test/lib/caruby/util/trie_test.rb +0 -14
  123. data/test/lib/caruby/util/visitor_test.rb +0 -278
  124. data/test/lib/caruby/util/weak_hash_test.rb +0 -45
  125. data/test/lib/examples/clinical_trials/migration/migration_test.rb +0 -58
  126. data/test/lib/examples/clinical_trials/migration/test_case.rb +0 -38
@@ -1,172 +0,0 @@
1
- require 'caruby/import/java'
2
- require 'caruby/domain/java_attribute'
3
-
4
- module CaRuby
5
- module Domain
6
- # Meta-data mix-in to infer and set inverse attributes.
7
- module Inverse
8
- # Returns the inverse of the given attribute. If the attribute has an #{Attribute#inverse_metadata},
9
- # then that attribute's inverse is returned. Otherwise, if the attribute is an #{Attribute#owner?},
10
- # then the target class dependent attribute which matches this type is returned, if it exists.
11
- #
12
- # @param [Attribute] attr_md the subject attribute
13
- # @param [Class, nil] klass the target class
14
- # @return [Attribute, nil] the inverse attribute, if any
15
- def inverse_attribute_metadata(attr_md, klass=nil)
16
- inv_md = attr_md.inverse_metadata
17
- return inv_md if inv_md
18
- if attr_md.dependent? and klass then
19
- klass.owner_attribute_metadata_hash.each { |otype, oattr_md|
20
- return oattr_md if self <= otype }
21
- end
22
- end
23
-
24
- protected
25
-
26
- # Infers the inverse of the given attribute declared by this class. A domain attribute is
27
- # recognized as an inverse according to the {Inverse#detect_inverse_attribute}
28
- # criterion.
29
- #
30
- # @param [Attribute] attr_md the attribute to check
31
- def infer_attribute_inverse(attr_md)
32
- inv = attr_md.type.detect_inverse_attribute(self)
33
- if inv then set_attribute_inverse(attr_md.to_sym, inv) end
34
- end
35
-
36
- # Sets the given bi-directional association attribute's inverse.
37
- #
38
- # @param [Symbol] attribute the subject attribute
39
- # @param [Symbol] the attribute inverse
40
- # @raise [TypeError] if the inverse type is incompatible with this Resource
41
- def set_attribute_inverse(attribute, inverse)
42
- attr_md = attribute_metadata(attribute)
43
- # return if inverse is already set
44
- return if attr_md.inverse == inverse
45
- # the default inverse
46
- inverse ||= attr_md.type.detect_inverse_attribute(self)
47
- # the inverse attribute meta-data
48
- inv_md = attr_md.type.attribute_metadata(inverse)
49
- # If the attribute is the many side of a 1:M relation, then delegate to the one side.
50
- if attr_md.collection? and not inv_md.collection? then
51
- return attr_md.type.set_attribute_inverse(inverse, attribute)
52
- end
53
- # This class must be the same as or a subclass of the inverse attribute type.
54
- unless self <= inv_md.type then
55
- raise TypeError.new("Cannot set #{qp}.#{attribute} inverse to #{attr_md.type.qp}.#{attribute} with incompatible type #{inv_md.type.qp}")
56
- end
57
- # If the attribute is not declared by this class, then make a new attribute
58
- # metadata specialized for this class.
59
- unless attr_md.declarer == self then
60
- attr_md = restrict_attribute_inverse(attr_md, inverse)
61
- end
62
- # Set the inverse in the attribute metadata.
63
- attr_md.inverse = inverse
64
- # If attribute is the one side of a 1:M or non-reflexive 1:1 relation, then add the inverse updater.
65
- unless attr_md.collection? then
66
- # Make the
67
- add_inverse_updater(attribute, inverse)
68
- unless attr_md.type == inv_md.type or inv_md.collection? then
69
- attr_md.type.delegate_writer_to_inverse(inverse, attribute)
70
- end
71
- end
72
- end
73
-
74
- # Detects an unambiguous attribute which refers to the given referencing class.
75
- # If there is exactly one attribute with the given return type, then that attribute is chosen.
76
- # Otherwise, the attribute whose name matches the underscored referencing class name is chosen,
77
- # if any.
78
- #
79
- # @param [Class] klass the referencing class
80
- # @return [Symbol, nil] the inverse attribute for the given referencing class and inverse,
81
- # or nil if no owner attribute was detected
82
- def detect_inverse_attribute(klass)
83
- # The candidate attributes return the referencing type and don't already have an inverse.
84
- candidates = domain_attributes.compose { |attr_md| klass <= attr_md.type and attr_md.inverse.nil? }
85
- attr = detect_inverse_attribute_from_candidates(klass, candidates)
86
- if attr then
87
- logger.debug { "#{qp} #{klass.qp} inverse attribute is #{attr}." }
88
- else
89
- logger.debug { "#{qp} #{klass.qp} inverse attribute was not detected." }
90
- end
91
- attr
92
- end
93
-
94
- # Redefines the attribute writer method to delegate to its inverse writer.
95
- # This is done to enforce inverse integrity.
96
- #
97
- # For a +Person+ attribute +account+ with inverse +holder+, this is equivalent to the following:
98
- # class Person
99
- # alias :set_account :account=
100
- # def account=(acct)
101
- # acct.holder = self if acct
102
- # set_account(acct)
103
- # end
104
- # end
105
- def delegate_writer_to_inverse(attribute, inverse)
106
- attr_md = attribute_metadata(attribute)
107
- # nothing to do if no inverse
108
- inv_attr_md = attr_md.inverse_metadata || return
109
- logger.debug { "Delegating #{qp}.#{attribute} update to the inverse #{attr_md.type.qp}.#{inv_attr_md}..." }
110
- # redefine the write to set the dependent inverse
111
- redefine_method(attr_md.writer) do |old_writer|
112
- # delegate to the CaRuby::Resource set_inverse method
113
- lambda { |dep| set_inverse(dep, old_writer, inv_attr_md.writer) }
114
- end
115
- end
116
-
117
- private
118
-
119
- # Copies the given attribute metadata from its declarer to this class. The new attribute metadata
120
- # has the same attribute access methods, but the declarer is this class and the inverse is the
121
- # given inverse attribute.
122
- #
123
- # @param [Attribute] attr_md the attribute to copy
124
- # @param [Symbol] the attribute inverse
125
- # @return [Attribute] the copied attribute metadata
126
- def restrict_attribute_inverse(attr_md, inverse)
127
- rst_attr_md = attr_md.dup
128
- rst_attr_md.declarer = self
129
- add_attribute_metadata(rst_attr_md)
130
- logger.debug { "Copied #{attr_md.declarer}.#{attr_md} to #{qp} with inverse #{inverse}." }
131
- rst_attr_md
132
- end
133
-
134
- # @param klass (see #detect_inverse_attribute)
135
- # @param [<Symbol>] candidates the attributes constrained to the target type
136
- # @return (see #detect_inverse_attribute)
137
- def detect_inverse_attribute_from_candidates(klass, candidates)
138
- return if candidates.empty?
139
- # there can be at most one owner attribute per owner.
140
- return candidates.first.to_sym if candidates.size == 1
141
- # by convention, if more than one attribute references the owner type,
142
- # then the attribute named after the owner type is the owner attribute
143
- tgt = klass.name[/\w+$/].underscore.to_sym
144
- tgt if candidates.detect { |attr| attr == tgt }
145
- end
146
-
147
- # Modifies the given attribute writer method to update the given inverse.
148
- #
149
- # @param (see #set_attribute_inverse)
150
- def add_inverse_updater(attribute, inverse)
151
- attr_md = attribute_metadata(attribute)
152
- # the reader and writer methods
153
- rdr, wtr = attr_md.accessors
154
- logger.debug { "Injecting inverse #{inverse} updater into #{qp}.#{attribute} writer method #{wtr}..." }
155
- # the inverse atttribute metadata
156
- inv_attr_md = attr_md.inverse_metadata
157
- # the inverse attribute reader and writer
158
- inv_rdr, inv_wtr = inv_accessors = inv_attr_md.accessors
159
- # Redefine the writer method to update the inverse by delegating to the inverse
160
- redefine_method(wtr) do |old_wtr|
161
- # the attribute reader and (superseded) writer
162
- accessors = [rdr, old_wtr]
163
- if inv_attr_md.collection? then
164
- lambda { |other| add_to_inverse_collection(other, accessors, inv_rdr) }
165
- else
166
- lambda { |other| set_inversible_noncollection_attribute(other, accessors, inv_wtr) }
167
- end
168
- end
169
- end
170
- end
171
- end
172
- end
@@ -1,90 +0,0 @@
1
- require 'caruby/util/log'
2
- require 'caruby/database/persistable'
3
- require 'caruby/util/pretty_print'
4
-
5
- module CaRuby
6
- # {Resource} inverse integrity aspect mix-in.
7
- module Inversible
8
- # Sets an attribute inverse by calling the attribute writer method with the other argument.
9
- # If other is non-nil, then the inverse writer method is called on self.
10
- #
11
- # @param other [Resource] the attribute value to set
12
- # @param [Symbol] writer the attribute writer method
13
- # @param [Symbol] inv_writer the attribute inverse writer method defined for the other object
14
- def set_inverse(other, writer, inv_writer)
15
- other.send(inv_writer, self) if other
16
- send(writer, other)
17
- end
18
-
19
- # Sets a non-collection attribute value in a way which enforces inverse integrity.
20
- #
21
- # @param [Object] newval the value to set
22
- # @param [(Symbol, Symbol)] accessors the reader and writer methods to use in setting the
23
- # attribute
24
- # @param [Symbol] inverse_writer the inverse attribute writer method
25
- def set_inversible_noncollection_attribute(newval, accessors, inverse_writer)
26
- rdr, wtr = accessors
27
- # the previous value
28
- oldval = send(rdr)
29
- # bail if no change
30
- return newval if newval.equal?(oldval)
31
-
32
- # clear the previous inverse
33
- logger.debug { "Moving #{qp} from #{oldval.qp} to #{newval.qp}..." } if oldval and newval
34
- if oldval then
35
- clr_wtr = self.class === oldval && oldval.send(rdr).equal?(self) ? wtr : inverse_writer
36
- oldval.send(clr_wtr, nil)
37
- end
38
- # call the writer
39
- send(wtr, newval)
40
- # call the inverse writer on self
41
- if newval then
42
- newval.send(inverse_writer, self)
43
- logger.debug { "Moved #{qp} from #{oldval.qp} to #{newval.qp}." } if oldval
44
- end
45
-
46
- newval
47
- end
48
-
49
- # Sets a collection attribute value in a way which enforces inverse integrity.
50
- # The inverse of the attribute is a collection accessed by calling inverse on newval.
51
- #
52
- # @param [Resource] newval the new attribute reference value
53
- # @param [(Symbol, Symbol)] accessors the reader and writer to use in setting
54
- # the attribute
55
- # @param [Symbol] inverse the inverse collection attribute to which
56
- # this domain object will be added
57
- # @yield a factory to create a new collection on demand (default is an Array)
58
- def add_to_inverse_collection(newval, accessors, inverse)
59
- rdr, wtr = accessors
60
- # the current inverse
61
- oldval = send(rdr)
62
- # no-op if no change
63
- return newval if newval == oldval
64
-
65
- # delete self from the current inverse reference collection
66
- if oldval then
67
- coll = oldval.send(inverse)
68
- coll.delete(self) if coll
69
- end
70
- # call the writer on this object
71
- send(wtr, newval)
72
- # add self to the inverse collection
73
- if newval then
74
- coll = newval.send(inverse)
75
- if coll.nil? then
76
- coll = block_given? ? yield : Array.new
77
- newval.set_attribute(inverse, coll)
78
- end
79
- coll << self
80
- if oldval then
81
- logger.debug { "Moved #{qp} from #{rdr} #{oldval.qp} #{inverse} to #{newval.qp}." }
82
- else
83
- logger.debug { "Added #{qp} to #{rdr} #{newval.qp} #{inverse}." }
84
- end
85
- end
86
-
87
- newval
88
- end
89
- end
90
- end
@@ -1,173 +0,0 @@
1
- require 'caruby/util/inflector'
2
- require 'caruby/domain/attribute'
3
-
4
- module CaRuby
5
- module Domain
6
- # The attribute metadata for an introspected Java property.
7
- class JavaAttribute < Attribute
8
-
9
- # This attribute's Java property descriptor.
10
- attr_reader :property_descriptor
11
-
12
- # This attribute's Java property [reader, writer] accessors, e.g. +[:getActivityStatus, :setActivityStatus]+.
13
- attr_reader :property_accessors
14
-
15
- # Creates a Ruby Attribute symbol corresponding to the given Ruby Java class wrapper klazz
16
- # and Java property_descriptor.
17
- #
18
- # The attribute name is the lower-case, underscore property descriptor name with the alterations
19
- # described in {JavaAttribute.to_attribute_symbol} and {Class#unocclude_reserved_method}.
20
- #
21
- # The attribute type is inferred as follows:
22
- # * If the property descriptor return type is a primitive Java type, then that type is returned.
23
- # * If the return type is a parameterized collection, then the parameter type is returned.
24
- # * If the return type is an unparameterized collection, then this method infers the type from
25
- # the property name, e.g. +StudyProtocolCollection+type is inferred as +StudyProtocol+
26
- # by stripping the +Collection+ suffix, capitalizing the prefix and looking for a class of
27
- # that name in the {Metadata#domain_module}.
28
- # * If the declarer class metadata configuration includes a +domain_attributes+ property, then
29
- # the type specified in that property is returned.
30
- # * Otherwise, this method returns Java::Javalang::Object.
31
- #
32
- # The optional restricted_type argument restricts the attribute to a subclass of the declared
33
- # property type.
34
- def initialize(pd, declarer, restricted_type=nil)
35
- symbol = create_standard_attribute_symbol(pd, declarer)
36
- super(symbol, declarer, restricted_type)
37
- @property_descriptor = pd
38
- # deficient Java introspector does not recognize 'is' prefix for a Boolean property
39
- rm = declarer.property_read_method(pd)
40
- raise ArgumentError.new("Property does not have a read method: #{declarer.qp}.#{pd.name}") unless rm
41
- reader = rm.name.to_sym
42
- unless declarer.method_defined?(reader) then
43
- reader = "is#{reader.to_s.capitalize_first}".to_sym
44
- unless declarer.method_defined?(reader) then
45
- raise ArgumentError.new("Reader method not found for #{declarer} property #{pd.name}")
46
- end
47
- end
48
- unless pd.write_method then
49
- raise ArgumentError.new("Property does not have a write method: #{declarer.qp}.#{pd.name}")
50
- end
51
- writer = pd.write_method.name.to_sym
52
- unless declarer.method_defined?(writer) then
53
- raise ArgumentError.new("Writer method not found for #{declarer} property #{pd.name}")
54
- end
55
- @property_accessors = [reader, writer]
56
- qualify(:collection) if collection_java_class?
57
- @type = infer_type
58
- end
59
-
60
- # @return [Symbol] the JRuby wrapper method for the Java property reader
61
- def property_reader
62
- property_accessors.first
63
- end
64
-
65
- # @return [Symbol] the JRuby wrapper method for the Java property writer
66
- def property_writer
67
- property_accessors.last
68
- end
69
-
70
- # Returns a lower-case, underscore symbol for the given property_name.
71
- # A name ending in 'Collection' is changed to a pluralization.
72
- #
73
- # @example
74
- # JavaAttribute.to_attribute_symbol('specimenEventCollection') #=> :specimen_events
75
- def self.to_attribute_symbol(property_name)
76
- name = if property_name =~ /(.+)Collection$/ then
77
- property_name[0...-'Collection'.length].pluralize.underscore
78
- else
79
- property_name.underscore
80
- end
81
- name.to_sym
82
- end
83
-
84
- private
85
-
86
- # @param pd the Java property descriptor
87
- # @param [Class] klass the declarer
88
- # @return [String] the lower-case, underscore symbol for the given property descriptor
89
- def create_standard_attribute_symbol(pd, klass)
90
- propname = pd.name
91
- name = propname.underscore
92
- renamed = klass.unocclude_reserved_method(pd)
93
- if renamed then
94
- logger.debug { "Renamed #{klass.qp} reserved Ruby method #{name} to #{renamed}." }
95
- renamed
96
- else
97
- JavaAttribute.to_attribute_symbol(propname)
98
- end
99
- end
100
-
101
- # @return [Boolean] whether this property's Java type is +Iterable+
102
- def collection_java_class?
103
- # the Java property type
104
- ptype = @property_descriptor.property_type
105
- # Test whether the corresponding JRuby wrapper class or module is an Iterable.
106
- Class.to_ruby(ptype) < Java::JavaLang::Iterable
107
- end
108
-
109
- # @return [Class] the type for the specified klass property descriptor pd as described in {#initialize}
110
- def infer_type
111
- collection? ? infer_collection_type : infer_non_collection_type
112
- end
113
-
114
- # Returns the domain type for this attribute's Java Collection property descriptor.
115
- # If the property type is parameterized by a single domain class, then that generic type argument is the domain type.
116
- # Otherwise, the type is inferred from the property name as described in {#infer_collection_type_from_name}.
117
- #
118
- # @return [Class] this property's Ruby type
119
- def infer_collection_type
120
- generic_parameter_type or infer_collection_type_from_name or Java::JavaLang::Object
121
- end
122
-
123
- # @return [Class] this property's Ruby type
124
- def infer_non_collection_type
125
- jtype = @property_descriptor.property_type
126
- Class.to_ruby(jtype)
127
- end
128
-
129
- # @return [Class, nil] the domain type of this attribute's property descriptor Collection generic
130
- # type argument, or nil if none
131
- def generic_parameter_type
132
- method = @property_descriptor.readMethod || return
133
- gtype = method.genericReturnType
134
- return unless Java::JavaLangReflect::ParameterizedType === gtype
135
- atypes = gtype.actualTypeArguments
136
- return unless atypes.size == 1
137
- atype = atypes[0]
138
- klass = java_to_ruby_class(atype)
139
- logger.debug { "Inferred #{declarer.qp} #{self} domain type #{klass.qp} from generic parameter #{atype.name}." } if klass
140
- klass
141
- end
142
-
143
- # @param [Class, String] jtype the Java class or class name
144
- # @return [Class] the corresponding Ruby type
145
- def java_to_ruby_class(jtype)
146
- name = String === jtype ? jtype : jtype.name
147
- Class.to_ruby(name)
148
- end
149
-
150
- # Returns the domain type for this attribute's collection Java property descriptor name.
151
- # By convention, caBIG domain collection properties often begin with a domain type
152
- # name and end in 'Collection'. This method strips the Collection suffix and checks
153
- # whether the prefix is a domain class.
154
- #
155
- # For example, the type of the property named +distributionProtocolCollection+
156
- # is inferred as +DistributionProtocol+ by stripping the +Collection+ suffix,
157
- # capitalizing the prefix and looking for a class of that name in this classifier's
158
- # domain_module.
159
- #
160
- # @return [Class] the collection item type
161
- def infer_collection_type_from_name
162
- # the property name
163
- pname = @property_descriptor.name
164
- # The potential class name is the capitalized property name without a 'Collection' suffix.
165
- cname = pname.capitalize_first.sub(/Collection$/, '')
166
- jname = [@declarer.parent_module, cname].join('::')
167
- klass = eval jname rescue nil
168
- if klass then logger.debug { "Inferred #{declarer.qp} #{self} collection domain type #{klass.qp} from the attribute name." } end
169
- klass
170
- end
171
- end
172
- end
173
- end
@@ -1,185 +0,0 @@
1
- require 'caruby/util/validation'
2
-
3
- module CaRuby
4
- # A Mergeable supports merging {Resource} attribute values.
5
- module Mergeable
6
- # Merges the values of the other attributes into this object and returns self.
7
- # The other argument can be either a Hash or an object whose class responds to the
8
- # +mergeable_attributes+ method.
9
- # The optional attributes argument can be either a single attribute symbol or a
10
- # collection of attribute symbols.
11
- #
12
- # A hash argument consists of attribute name => value associations.
13
- # For example, given a Mergeable +person+ object with attributes +ssn+ and +children+, the call:
14
- # person.merge_attributes(:ssn => '555-55-5555', :children => children)
15
- # is equivalent to:
16
- # person.ssn ||= '555-55-5555'
17
- # person.children ||= []
18
- # person.children.merge(children, :deep)
19
- # An unrecognized attribute is ignored.
20
- #
21
- # If other is not a Hash, then the other object's attributes values are merged into
22
- # this object. The default attributes is this mergeable's class
23
- # {Domain::Attributes#mergeable_attributes}.
24
- #
25
- # The merge is performed by calling {#merge_attribute} on each attribute with the matches
26
- # and merger block given to this method.
27
- #
28
- # @param [Mergeable, {Symbol => Object}] other the source domain object or value hash to merge from
29
- # @param [<Symbol>, nil] attributes the attributes to merge (default {Domain::Attributes#nondomain_attributes})
30
- # @param [{Resource => Resource}, nil] the optional merge source => target reference matches
31
- # @yield [attribute, oldval, newval] the optional merger block
32
- # @yieldparam [Symbol] attribute the merge target attribute
33
- # @yieldparam oldval the current merge attribute value
34
- # @yieldparam newval the new merge attribute value
35
- # @return [Mergeable] self
36
- # @raise [ArgumentError] if none of the following are true:
37
- # * other is a Hash
38
- # * attributes is non-nil
39
- # * the other class responds to +mergeable_attributes+
40
- def merge_attributes(other, attributes=nil, matches=nil, &merger)
41
- return self if other.nil? or other.equal?(self)
42
- attributes = [attributes] if Symbol === attributes
43
- attributes ||= self.class.mergeable_attributes
44
-
45
- # if the source object is not a hash, then convert it to an attribute => value hash
46
- vh = Hashable === other ? other : other.value_hash(attributes)
47
- # merge the value hash
48
- vh.each { |attr, value| merge_attribute(attr, value, matches, &merger) }
49
- self
50
- end
51
-
52
- alias :merge :merge_attributes
53
-
54
- alias :merge! :merge
55
-
56
- # Merges the value newval into the attribute as follows:
57
- # * If the value is nil, empty or equal to the current attribute value, then no merge
58
- # is performed.
59
- # * Otherwise, if a merger block is given to this method, then that block is called
60
- # to perform the merge.
61
- # * Otherwise, if the attribute is a non-domain attribute and the current value is non-nil,
62
- # then no merge is performed.
63
- # * Otherwise, if the attribute is a non-domain attribute and the current value is nil,
64
- # then set the attribute to the newval.
65
- # * Otherwise, if the attribute is a domain non-collection attribute, then newval is recursively
66
- # merged into the current referenced domain object.
67
- # * Otherwise, attribute is a domain collection attribute and matching newval members are
68
- # merged into the corresponding current collection members and non-matching newval members
69
- # are added to the current collection.
70
- #
71
- # @param [Symbol] attribute the merge attribute
72
- # @param newval the value to merge
73
- # @param [{Resource => Resource}, nil] the optional merge source => target reference matches
74
- # @yield (see #merge_attributes)
75
- # @yieldparam (see #merge_attributes)
76
- # @return the merged attribute value
77
- def merge_attribute(attribute, newval, matches=nil)
78
- # the previous value
79
- oldval = send(attribute)
80
- # If nothing to merge or a block can take over, then bail.
81
- if newval.nil? or mergeable__equal?(oldval, newval) then
82
- return oldval
83
- elsif block_given? then
84
- return yield(attribute, oldval, value)
85
- end
86
-
87
- # Discriminate between a domain and non-domain attribute.
88
- attr_md = self.class.attribute_metadata(attribute)
89
- if attr_md.domain? then
90
- merge_domain_attribute_value(attr_md, oldval, newval, matches)
91
- else
92
- merge_nondomain_attribute_value(attr_md, oldval, newval)
93
- end
94
- end
95
-
96
- private
97
-
98
- # @see #merge_attribute
99
- def merge_nondomain_attribute_value(attr_md, oldval, newval)
100
- if oldval.nil? then
101
- send(attr_md.writer, newval)
102
- elsif attr_md.collection? then
103
- oldval.merge(newval)
104
- else
105
- oldval
106
- end
107
- end
108
-
109
- # @see #merge_attribute
110
- def merge_domain_attribute_value(attr_md, oldval, newval, matches)
111
- # the dependent owner writer method, if any
112
- if attr_md.dependent? then
113
- val = attr_md.collection? ? newval.first : newval
114
- klass = val.class if val
115
- inv_md = self.class.inverse_attribute_metadata(attr_md, klass)
116
- if inv_md and not inv_md.collection? then
117
- owtr = inv_md.writer
118
- end
119
- end
120
-
121
- # If the attribute is a collection, then merge the matches into the current attribute
122
- # collection value and add each unmatched source to the collection.
123
- # Otherwise, if the attribute is not yet set and there is a new value, then set it
124
- # to the new value match or the new value itself if unmatched.
125
- if attr_md.collection? then
126
- # TODO - refactor into method
127
- if oldval.nil? then
128
- raise ValidationError.new("Merge into #{qp} #{attr_md} with nil collection value is not supported")
129
- end
130
- # the references to add
131
- adds = []
132
- logger.debug { "Merging #{newval.qp} into #{qp} #{attr_md} #{oldval.qp}..." } unless newval.empty?
133
- newval.each do |src|
134
- # If the match target is in the current collection, then update the matched
135
- # target from the source.
136
- # Otherwise, if there is no match or the match is a new reference created
137
- # from the match, then add the match to the oldval collection.
138
- if matches && matches.has_key?(src) then
139
- # the source match
140
- tgt = matches[src]
141
- if tgt then
142
- if oldval.include?(tgt) then
143
- tgt.merge_attributes(src)
144
- else
145
- adds << tgt
146
- end
147
- end
148
- else
149
- adds << src
150
- end
151
- end
152
- # add the unmatched sources
153
- logger.debug { "Adding #{qp} #{attr_md} unmatched #{adds.qp}..." } unless adds.empty?
154
- adds.each do |ref|
155
- # If there is an owner writer attribute, then add the ref to the attribute collection by
156
- # delegating to the owner writer. Otherwise, add the ref to the attribute collection directly.
157
- owtr ? delegate_to_inverse_setter(attr_md, ref, owtr) : oldval << ref
158
- end
159
- oldval
160
- elsif newval.nil? then
161
- # no merge source
162
- oldval
163
- elsif oldval then
164
- # merge the source into the target
165
- oldval.merge(newval)
166
- else
167
- # No target; set the attribute to the source.
168
- # The target is either a source match or the source itself.
169
- ref = (matches[newval] if matches) || newval
170
- logger.debug { "Setting #{qp} #{attr_md} reference #{ref.qp}..." }
171
- # If the target is a dependent, then set the dependent owner, which will in turn
172
- # set the attribute to the dependent. Otherwise, set the attribute to the target.
173
- owtr ? delegate_to_inverse_setter(attr_md, ref, owtr) : send(attr_md.writer, ref)
174
- end
175
- newval
176
- end
177
-
178
- # Java Java TreeSet comparison uses the TreeSet comparator rather than an
179
- # element-wise comparator. Work around this rare aberration by converting the TreeSet
180
- # to a Ruby Set.
181
- def mergeable__equal?(v1, v2)
182
- Java::JavaUtil::TreeSet === v1 && Java::JavaUtil::TreeSet === v2 ? v1.to_set == v2.to_set : v1 == v2
183
- end
184
- end
185
- end