caruby-core 1.4.9 → 1.5.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 (61) hide show
  1. data/History.md +48 -0
  2. data/lib/caruby/cli/command.rb +2 -1
  3. data/lib/caruby/csv/csv_mapper.rb +8 -8
  4. data/lib/caruby/database/persistable.rb +44 -65
  5. data/lib/caruby/database/persistence_service.rb +12 -9
  6. data/lib/caruby/database/persistifier.rb +14 -14
  7. data/lib/caruby/database/reader.rb +53 -51
  8. data/lib/caruby/database/search_template_builder.rb +9 -10
  9. data/lib/caruby/database/store_template_builder.rb +58 -58
  10. data/lib/caruby/database/writer.rb +96 -96
  11. data/lib/caruby/database.rb +19 -19
  12. data/lib/caruby/domain/attribute.rb +581 -0
  13. data/lib/caruby/domain/attributes.rb +615 -0
  14. data/lib/caruby/domain/dependency.rb +240 -0
  15. data/lib/caruby/domain/importer.rb +183 -0
  16. data/lib/caruby/domain/introspection.rb +176 -0
  17. data/lib/caruby/domain/inverse.rb +173 -0
  18. data/lib/caruby/domain/inversible.rb +1 -2
  19. data/lib/caruby/domain/java_attribute.rb +173 -0
  20. data/lib/caruby/domain/merge.rb +13 -10
  21. data/lib/caruby/domain/metadata.rb +141 -0
  22. data/lib/caruby/domain/mixin.rb +35 -0
  23. data/lib/caruby/domain/reference_visitor.rb +5 -3
  24. data/lib/caruby/domain.rb +340 -0
  25. data/lib/caruby/import/java.rb +29 -25
  26. data/lib/caruby/migration/migratable.rb +5 -5
  27. data/lib/caruby/migration/migrator.rb +19 -15
  28. data/lib/caruby/migration/resource_module.rb +1 -1
  29. data/lib/caruby/resource.rb +39 -30
  30. data/lib/caruby/util/collection.rb +94 -33
  31. data/lib/caruby/util/coordinate.rb +28 -2
  32. data/lib/caruby/util/log.rb +4 -4
  33. data/lib/caruby/util/module.rb +12 -28
  34. data/lib/caruby/util/partial_order.rb +9 -10
  35. data/lib/caruby/util/pretty_print.rb +46 -26
  36. data/lib/caruby/util/topological_sync_enumerator.rb +10 -4
  37. data/lib/caruby/util/transitive_closure.rb +2 -2
  38. data/lib/caruby/util/visitor.rb +1 -1
  39. data/lib/caruby/version.rb +1 -1
  40. data/test/lib/caruby/database/persistable_test.rb +1 -1
  41. data/test/lib/caruby/domain/domain_test.rb +14 -28
  42. data/test/lib/caruby/domain/inversible_test.rb +1 -1
  43. data/test/lib/caruby/import/java_test.rb +5 -0
  44. data/test/lib/caruby/migration/test_case.rb +0 -1
  45. data/test/lib/caruby/test_case.rb +9 -10
  46. data/test/lib/caruby/util/collection_test.rb +23 -5
  47. data/test/lib/caruby/util/module_test.rb +10 -14
  48. data/test/lib/caruby/util/partial_order_test.rb +16 -15
  49. data/test/lib/caruby/util/visitor_test.rb +1 -1
  50. data/test/lib/examples/galena/clinical_trials/migration/test_case.rb +1 -1
  51. metadata +16 -15
  52. data/History.txt +0 -44
  53. data/lib/caruby/domain/attribute_metadata.rb +0 -551
  54. data/lib/caruby/domain/java_attribute_metadata.rb +0 -183
  55. data/lib/caruby/domain/resource_attributes.rb +0 -565
  56. data/lib/caruby/domain/resource_dependency.rb +0 -217
  57. data/lib/caruby/domain/resource_introspection.rb +0 -160
  58. data/lib/caruby/domain/resource_inverse.rb +0 -151
  59. data/lib/caruby/domain/resource_metadata.rb +0 -155
  60. data/lib/caruby/domain/resource_module.rb +0 -370
  61. data/lib/caruby/yard/resource_metadata_handler.rb +0 -8
@@ -1,217 +0,0 @@
1
- require 'caruby/util/validation'
2
-
3
- module CaRuby
4
- # ResourceMetadata mix-in to capture Resource dependency.
5
- module ResourceDependency
6
-
7
- attr_reader :owners, :owner_attributes
8
-
9
- # Returns the attribute which references the dependent type, or nil if none.
10
- def dependent_attribute(type)
11
- dependent_attributes.detect { |attr| type <= domain_type(attr) }
12
- end
13
-
14
- # Adds the given attribute as a dependent.
15
- #
16
- # Supported flags include the following:
17
- # * :logical - the dependency relation is not cascaded by the application
18
- # * :autogenerated - a dependent can be created by the application as a side-effect of creating the owner
19
- # * :disjoint - the dependent owner has more than one owner attribute, but only one owner instance
20
- #
21
- # If the attribute inverse is not a collection, then the attribute writer
22
- # is modified to delegate to the dependent owner writer. This enforces
23
- # referential integrity by ensuring that the following post-condition holds:
24
- # * _owner_._attribute_._inverse_ == _owner_
25
- # where:
26
- # * _owner_ is an instance this attribute's declaring class
27
- # * _inverse_ is the owner inverse attribute defined in the dependent class
28
- #
29
- # @param [Symbol] attribute the dependent to add
30
- # @param [<Symbol>] flags the attribute qualifier flags
31
- def add_dependent_attribute(attribute, *flags)
32
- attr_md = attribute_metadata(attribute)
33
- flags << :dependent unless flags.include?(:dependent)
34
- attr_md.qualify(*flags)
35
- inverse = attr_md.inverse
36
- inv_type = attr_md.type
37
- # example: Parent.add_dependent_attribute(:children) with inverse :parent calls the following:
38
- # Child.add_owner(Parent, :children, :parent)
39
- inv_type.add_owner(self, attribute, inverse)
40
- end
41
-
42
- # Makes a new owner attribute. The attribute name is the lower-case demodulized
43
- # owner class name. The owner class must reference this class via the given
44
- # inverse dependent attribute.
45
- #
46
- # @param klass (see #detect_owner_attribute)
47
- # @param [Symbol] the owner -> dependent inverse attribute
48
- # @return [Symbol] this class's new owner attribute
49
- # @raise [ArgumentError] if the inverse is nil
50
- def create_owner_attribute(klass, inverse)
51
- if inverse.nil? then
52
- raise ArgumentError.new("Cannot create a #{qp} owner attribute to #{klass} without a dependent attribute to this class.")
53
- end
54
- attr = klass.name.demodulize.underscore.to_sym
55
- attr_accessor(attr)
56
- add_attribute(attr, klass)
57
- attribute_metadata(attr).inverse = inverse
58
- logger.debug { "Created #{qp} owner attribute #{attr} with inverse #{klass.qp}.#{inverse}." }
59
- attr
60
- end
61
-
62
- # @return [Boolean] whether this class depends on an owner
63
- def dependent?
64
- not owners.empty?
65
- end
66
-
67
- # @return [Boolean] whether this class has an owner which cascades save operations to this dependent
68
- def cascaded_dependent?
69
- owner_attribute_metadata_enumerator.any? { |attr_md| attr_md.cascaded? }
70
- end
71
-
72
- # @return [Boolean] whether this class depends the given other class
73
- def depends_on?(other)
74
- owners.detect { |owner| owner === other }
75
- end
76
-
77
- # @return [Symbol, nil] the attribute which references the dependent type,
78
- # or nil if none
79
- def dependent_attribute(dep_type)
80
- type = dependent_attributes.detect { |attr| domain_type(attr) == dep_type }
81
- return type if type
82
- dependent_attribute(dep_type.superclass) if dep_type.superclass < Resource
83
- end
84
-
85
- # @return [Symbol, nil] the sole owner attribute of this class, or nil if there
86
- # is not exactly one owner
87
- def owner_attribute
88
- attr_md = owner_attribute_metadata_enumerator.first || return
89
- # There is at most one non-nil value in the owner class => AttributeMetadata hash.
90
- # If there is such a value, then return the attribute symbol.
91
- if owner_attribute_metadata_enumerator.size == 1 then
92
- attr_md.to_sym
93
- end
94
- end
95
-
96
- # @return [<Symbol>] this class's owner attributes
97
- def owner_attributes
98
- owner_attribute_metadata_enumerator.transform { |attr_md| attr_md.to_sym }
99
- end
100
-
101
- # @return [<Class>] this class's dependent types
102
- def dependents
103
- dependent_attributes.wrap { |attr| attr.type }
104
- end
105
-
106
- # @return [<Class>] this class's owner types
107
- def owners
108
- @owners ||= Enumerable::Enumerator.new(owner_attribute_metadata_hash, :each_key)
109
- end
110
-
111
- protected
112
-
113
- # Adds the given owner class to this dependent class.
114
- # This method must be called before any dependent attribute is accessed.
115
- # If the attribute is given, then the attribute inverse is set.
116
- # Otherwise, if there is not already an owner attribute, then a new owner attribute is created.
117
- # The name of the new attribute is the lower-case demodulized owner class name.
118
- #
119
- # @param [Class] the owner class
120
- # @param [Symbol] inverse the owner -> dependent attribute
121
- # @param [Symbol, nil] attribute the dependent -> owner attribute, if known
122
- # @raise [ValidationError] if the inverse is nil
123
- def add_owner(klass, inverse, attribute=nil)
124
- if inverse.nil? then raise ValidationError.new("Owner #{klass.qp} missing dependent attribute for dependent #{qp}") end
125
- logger.debug { "Adding #{qp} owner #{klass.qp}#{' attribute ' + attribute.to_s if attribute}#{' inverse ' + inverse.to_s if inverse}..." }
126
- if @owner_attr_hash then
127
- raise MetadataError.new("Can't add #{qp} owner #{klass.qp} after dependencies have been accessed")
128
- end
129
-
130
- # detect the owner attribute, if necessary
131
- attribute ||= detect_owner_attribute(klass, inverse)
132
- attr_md = attribute_metadata(attribute) if attribute
133
- # Add the owner class => attribute entry.
134
- # The attribute is nil if the dependency is unidirectional, i.e. there is an owner class which
135
- # references this class via a dependency attribute but there is no inverse owner attribute.
136
- local_owner_attribute_metadata_hash[klass] = attr_md
137
- # If the dependency is unidirectional, then our job is done.
138
- return if attribute.nil?
139
-
140
- # set the inverse if necessary
141
- unless attr_md.inverse then
142
- set_attribute_inverse(attribute, inverse)
143
- end
144
- # set the owner flag if necessary
145
- unless attr_md.owner? then attr_md.qualify(:owner) end
146
- # Redefine the writer method to warn when changing the owner
147
- rdr, wtr = attr_md.accessors
148
- logger.debug { "Injecting owner change warning into #{qp}.#{attribute} writer method #{wtr}..." }
149
- redefine_method(wtr) do |old_wtr|
150
- lambda do |ref|
151
- prev = send(rdr)
152
- if prev and prev != ref then
153
- if ref.nil? then
154
- logger.warn("Unsetting the #{self} owner #{attribute} #{prev}.")
155
- elsif ref.identifier != prev.identifier then
156
- logger.warn("Resetting the #{self} owner #{attribute} from #{prev} to #{ref}.")
157
- end
158
- end
159
- send(old_wtr, ref)
160
- end
161
- end
162
- end
163
-
164
- # Adds the given attribute as an owner. This method is called when a new attribute is added that
165
- # references an existing owner.
166
- #
167
- # @param [Symbol] attribute the owner attribute
168
- def add_owner_attribute(attribute)
169
- attr_md = attribute_metadata(attribute)
170
- otype = attr_md.type
171
- hash = local_owner_attribute_metadata_hash
172
- if hash.include?(otype) then
173
- oattr = hash[otype]
174
- unless oattr.nil? then
175
- raise MetadataError.new("Cannot set #{qp} owner attribute to #{attribute} since it is already set to #{oattr}")
176
- end
177
- hash[otype] = attr_md
178
- else
179
- add_owner(otype, attr_md.inverse, attribute)
180
- end
181
- end
182
-
183
- # @return [{Class => AttributeMetadata}] this class's owner type => attribute hash
184
- def owner_attribute_metadata_hash
185
- @oa_hash ||= create_owner_attribute_metadata_hash
186
- end
187
-
188
- private
189
-
190
- def local_owner_attribute_metadata_hash
191
- @local_oa_hash ||= {}
192
- end
193
-
194
- # @return [{Class => AttributeMetadata}] a new owner type => attribute hash
195
- def create_owner_attribute_metadata_hash
196
- local = local_owner_attribute_metadata_hash
197
- superclass < Resource ? local.union(superclass.owner_attribute_metadata_hash) : local
198
- end
199
-
200
- # @return [<AttributeMetadata>] the owner attributes
201
- def owner_attribute_metadata_enumerator
202
- # Enumerate each owner AttributeMetadata, filtering out nil values.
203
- @oa_enum ||= Enumerable::Enumerator.new(owner_attribute_metadata_hash, :each_value).filter
204
- end
205
-
206
- # Returns the attribute which references the owner. The owner attribute is the inverse
207
- # of the given owner class inverse attribute, if it exists. Otherwise, the owner
208
- # attribute is inferred by #{ResourceInverse#detect_inverse_attribute}.
209
-
210
- # @param klass (see #add_owner)
211
- # @param [Symbol] inverse the owner -> dependent attribute
212
- # @return [Symbol, nil] this class's owner attribute
213
- def detect_owner_attribute(klass, inverse)
214
- klass.attribute_metadata(inverse).inverse or detect_inverse_attribute(klass)
215
- end
216
- end
217
- end
@@ -1,160 +0,0 @@
1
- require 'caruby/import/java'
2
- require 'caruby/domain/java_attribute_metadata'
3
-
4
- module CaRuby
5
- # ResourceMetadata mix-in to infer attribute meta-data from Java properties.
6
- module ResourceIntrospection
7
-
8
- private
9
-
10
- # Defines the Java property attribute and standard attribute methods, e.g.
11
- # +study_protocol+ and +studyProtocol+. A boolean attribute is provisioned
12
- # with an additional reader alias, e.g. +available?+ for +is_available+.
13
- #
14
- # Each Java property attribute delegates to the Java property getter and setter.
15
- # Each standard attribute delegates to the Java property attribute.
16
- # Redefining these methods results in a call to the redefined method.
17
- # This contrasts with a Ruby alias, where the alias remains bound to the original method body.
18
- def introspect
19
- init_attributes # in ResourceAttributes
20
- logger.debug { "Introspecting #{qp} metadata..." }
21
- # filter properties for those with both a read and write method
22
- pds = java_properties(false)
23
- # define the standard Java attribute methods
24
- pds.each { |pd| define_java_attribute(pd) }
25
- logger.debug { "Introspection of #{qp} metadata complete." }
26
- self
27
- end
28
-
29
- # Defines the Java property attribute and standard attribute methods, e.g.
30
- # +study_protocol+ and +studyProtocol+. A boolean attribute is provisioned
31
- # with an additional reader alias, e.g. +available?+ for +is_available+.
32
- #
33
- # A standard attribute which differs from the property attribute delegates
34
- # to the property attribute, e.g. +study_protocol+ delegates to +studyProtocol+
35
- # rather than aliasing +setStudyProtocol+. Redefining these methods results
36
- # in a call to the redefined method. This contrasts with a Ruby alias,
37
- # where each attribute alias is bound to the respective property reader or
38
- # writer.
39
- def define_java_attribute(pd)
40
- if transient?(pd) then
41
- logger.debug { "Ignoring #{name.demodulize} transient property #{pd.name}." }
42
- return
43
- end
44
- # the standard underscore lower-case attributes
45
- attr = create_java_attribute(pd)
46
- # delegate the standard attribute accessors to the property accessors
47
- alias_attribute_property(attr, pd.name)
48
- # add special wrappers
49
- wrap_java_attribute(attr, pd)
50
- # create Ruby alias for boolean, e.g. alias :empty? for :empty
51
- if pd.property_type.name[/\w+$/].downcase == 'boolean' then
52
- # strip leading is_, if any, before appending question mark
53
- aliaz = attr.to_s[/^(is_)?(\w+)/, 2] << '?'
54
- delegate_to_attribute(aliaz, attr)
55
- end
56
- end
57
-
58
- # Adds a filter to the attribute access method for the property descriptor pd if it is a String or Date.
59
- def wrap_java_attribute(attribute, pd)
60
- if pd.propertyType == Java::JavaLang::String.java_class then
61
- wrap_java_string_attribute(attribute, pd)
62
- elsif pd.propertyType == Java::JavaUtil::Date.java_class then
63
- wrap_java_date_attribute(attribute, pd)
64
- end
65
- end
66
-
67
- # Adds a to_s filter to this Class's String property access methods.
68
- def wrap_java_string_attribute(attribute, pd)
69
- # filter the attribute writer
70
- awtr = "#{attribute}=".to_sym
71
- pwtr = pd.write_method.name.to_sym
72
- define_method(awtr) do |value|
73
- stdval = value.to_s unless value.nil_or_empty?
74
- send(pwtr, stdval)
75
- end
76
- logger.debug { "Filtered #{qp} #{awtr} method with non-String -> String converter." }
77
- end
78
-
79
- # Adds a date parser filter to this Class's Date property access methods.
80
- def wrap_java_date_attribute(attribute, pd)
81
- # filter the attribute reader
82
- prdr = pd.read_method.name.to_sym
83
- define_method(attribute) do
84
- value = send(prdr)
85
- Java::JavaUtil::Date === value ? value.to_ruby_date : value
86
- end
87
-
88
- # filter the attribute writer
89
- awtr = "#{attribute}=".to_sym
90
- pwtr = pd.write_method.name.to_sym
91
- define_method(awtr) do |value|
92
- value = Java::JavaUtil::Date.from_ruby_date(value) if ::Date === value
93
- send(pwtr, value)
94
- end
95
-
96
- logger.debug { "Filtered #{qp} #{attribute} and #{awtr} methods with Java Date <-> Ruby Date converter." }
97
- end
98
-
99
- # Aliases the methods _aliaz_ and _aliaz=_ to _property_ and _property=_, resp.,
100
- # where _property_ is the Java property name for the attribute.
101
- def alias_attribute_property(aliaz, attribute)
102
- # strip the Java reader and writer is/get/set prefix and make a symbol
103
- prdr, pwtr = attribute_metadata(attribute).property_accessors
104
- alias_method(aliaz, prdr)
105
- writer = "#{aliaz}=".to_sym
106
- alias_method(writer, pwtr)
107
- end
108
-
109
- # Makes a standard attribute for the given property descriptor.
110
- # Adds a camelized Java-like alias to the standard attribute.
111
- #
112
- # caTissue alert - DE annotation collection attributes are often misnamed,
113
- # e.g. +histologic_grade+ for a +HistologicGrade+ collection attribute.
114
- # This is fixed by adding a pluralized alias, e.g. +histologic_grades+.
115
- #
116
- # @return a new attribute symbol created for the given PropertyDescriptor pd
117
- def create_java_attribute(pd)
118
- # make the attribute metadata
119
- attr_md = JavaAttributeMetadata.new(pd, self)
120
- add_attribute_metadata(attr_md)
121
- # the property name is an alias for the standard attribute
122
- std_attr = attr_md.to_sym
123
- prop_attr = pd.name.to_sym
124
- delegate_to_attribute(prop_attr, std_attr) unless prop_attr == std_attr
125
-
126
- # alias a misnamed collection attribute, if necessary
127
- if attr_md.collection? then
128
- name = std_attr.to_s
129
- if name.singularize == name then
130
- aliaz = name.pluralize.to_sym
131
- if aliaz != name then
132
- logger.debug { "Adding annotation #{qp} alias #{aliaz} to the misnamed collection attribute #{std_attr}..." }
133
- delegate_to_attribute(aliaz, std_attr)
134
- end
135
- end
136
- end
137
-
138
- std_attr
139
- end
140
-
141
- # Defines methods _aliaz_ and _aliaz=_ which calls the standard _attribute_ and
142
- # _attribute=_ accessor methods, resp.
143
- # Calling rather than aliasing the attribute accessor allows the aliaz accessor to
144
- # reflect a change to the attribute accessor.
145
- def delegate_to_attribute(aliaz, attribute)
146
- rdr, wtr = attribute_metadata(attribute).accessors
147
- define_method(aliaz) { send(rdr) }
148
- define_method("#{aliaz}=".to_sym) { |value| send(wtr, value) }
149
- add_alias(aliaz, attribute)
150
- end
151
-
152
- # Makes a new synthetic attribute for each _method_ => _original_ hash entry.
153
- #
154
- # @param (see Class#offset_attr_accessor)
155
- def offset_attribute(hash, offset=nil)
156
- offset_attr_accessor(hash, offset)
157
- hash.each { |attr, original| add_attribute(attr, attribute_metadata(original).type) }
158
- end
159
- end
160
- end
@@ -1,151 +0,0 @@
1
- require 'caruby/import/java'
2
- require 'caruby/domain/java_attribute_metadata'
3
-
4
- module CaRuby
5
- # ResourceMetadata mix-in to infer and set inverse attributes.
6
- module ResourceInverse
7
-
8
- protected
9
-
10
- # Infers the inverse of the given attribute declared by this class. A domain attribute is
11
- # recognized as an inverse according to the {ResourceInverse#detect_inverse_attribute}
12
- # criterion.
13
- #
14
- # @param [AttributeMetadata] attr_md the attribute to check
15
- def infer_attribute_inverse(attr_md)
16
- inv = attr_md.type.detect_inverse_attribute(self)
17
- if inv then
18
- logger.debug { "Detected #{qp} #{attr_md} inverse #{inv.qp}." }
19
- set_attribute_inverse(attr_md.to_sym, inv)
20
- else
21
- logger.debug { "No inverse detected for #{qp} #{attr_md}." }
22
- end
23
- end
24
-
25
- # Sets the given bi-directional association attribute's inverse.
26
- #
27
- # @param [Symbol] attribute the subject attribute
28
- # @param [Symbol] the attribute inverse
29
- # @raise [TypeError] if the inverse type is incompatible with this Resource
30
- def set_attribute_inverse(attribute, inverse)
31
- attr_md = attribute_metadata(attribute)
32
- # return if inverse is already set
33
- return if attr_md.inverse == inverse
34
- # the default inverse
35
- inverse ||= attr_md.type.detect_inverse_attribute(self)
36
- # the inverse attribute meta-data
37
- inv_md = attr_md.type.attribute_metadata(inverse)
38
- # if the attribute is the many side of a 1:M relation, then delegate to the one side.
39
- if attr_md.collection? and not inv_md.collection? then
40
- return attr_md.type.set_attribute_inverse(inverse, attribute)
41
- end
42
- # this class must be the same as or a subclass of the inverse attribute type
43
- unless self <= inv_md.type then
44
- raise TypeError.new("Cannot set #{qp}.#{attribute} inverse to #{attr_md.type.qp}.#{attribute} with incompatible type #{inv_md.type.qp}")
45
- end
46
-
47
- # if the attribute is not declared by this class, then make a new attribute
48
- # metadata specialized for this class.
49
- unless attr_md.declarer == self then
50
- attr_md = attr_md.dup
51
- attr_md.declarer = self
52
- add_attribute_metadata(attribute, inverse)
53
- logger.debug { "Copied #{attr_md.declarer}.#{attribute} to #{qp} with restricted inverse return type #{qp}." }
54
- end
55
- # Set the inverse in the attribute metadata.
56
- attr_md.inverse = inverse
57
-
58
- # If attribute is the one side of a 1:M or non-reflexive 1:1 relation, then add the inverse updater.
59
- unless attr_md.collection? then
60
- add_inverse_updater(attribute, inverse)
61
- unless attr_md.type == inv_md.type or inv_md.collection? then
62
- attr_md.type.delegate_writer_to_inverse(inverse, attribute)
63
- end
64
- end
65
- end
66
-
67
- # Detects an unambiguous attribute which refers to the given referencing class.
68
- # If there is exactly one attribute with the given return type, then that attribute is chosen.
69
- # Otherwise, the attribute whose name matches the underscored referencing class name is chosen,
70
- # if any.
71
- #
72
- # @param [Class] klass the referencing class
73
- # @return [Symbol, nil] the inverse attribute for the given referencing class and inverse,
74
- # or nil if no owner attribute was detected
75
- def detect_inverse_attribute(klass)
76
- # the candidate attributes which return the referencing type
77
- candidates = domain_attributes.compose { |attr_md| klass <= attr_md.type }
78
- attr = detect_inverse_attribute_from_candidates(klass, candidates)
79
- if attr then
80
- logger.debug { "#{qp} #{klass.qp} inverse attribute is #{attr}." }
81
- else
82
- logger.debug { "#{qp} #{klass.qp} inverse attribute was not detected." }
83
- end
84
- attr
85
- end
86
-
87
- # Redefines the attribute writer method to delegate to its inverse writer.
88
- # This is done to enforce inverse integrity.
89
- #
90
- # For a +Person+ attribute +account+ with inverse +holder+, this is equivalent to the following:
91
- # class Person
92
- # alias :set_account :account=
93
- # def account=(acct)
94
- # acct.holder = self if acct
95
- # set_account(acct)
96
- # end
97
- # end
98
- def delegate_writer_to_inverse(attribute, inverse)
99
- attr_md = attribute_metadata(attribute)
100
- # nothing to do if no inverse
101
- inv_attr_md = attr_md.inverse_attribute_metadata || return
102
- logger.debug { "Delegating #{qp}.#{attribute} update to the inverse #{attr_md.type.qp}.#{inv_attr_md}..." }
103
- # redefine the write to set the dependent inverse
104
- redefine_method(attr_md.writer) do |old_writer|
105
- # delegate to the CaRuby::Resource set_inverse method
106
- lambda { |dep| set_inverse(dep, old_writer, inv_attr_md.writer) }
107
- end
108
- end
109
-
110
- private
111
-
112
- # @param klass (see #detect_inverse_attribute)
113
- # @param [<Symbol>] candidates the attributes constrained to the target type
114
- # @return (see #detect_inverse_attribute)
115
- def detect_inverse_attribute_from_candidates(klass, candidates)
116
- return if candidates.empty?
117
- # there can be at most one owner attribute per owner.
118
- return candidates.first.to_sym if candidates.size == 1
119
- # by convention, if more than one attribute references the owner type,
120
- # then the attribute named after the owner type is the owner attribute
121
- tgt = klass.name[/\w+$/].underscore.to_sym
122
- tgt if candidates.detect { |attr| attr == tgt }
123
- end
124
-
125
- # Modifies the given attribute writer method if necessary to update the given inverse_attr value.
126
- # This method is called on dependent and attributes qualified as inversible.
127
- #
128
- # @see #set_attribute_inverse
129
- def add_inverse_updater(attribute, inverse)
130
- attr_md = attribute_metadata(attribute)
131
- # the reader and writer methods
132
- rdr, wtr = attr_md.accessors
133
- logger.debug { "Injecting inverse #{inverse} updater into #{qp}.#{attribute} writer method #{wtr}..." }
134
- # the inverse atttribute metadata
135
- inv_attr_md = attr_md.inverse_attribute_metadata
136
- # the inverse attribute reader and writer
137
- inv_rdr, inv_wtr = inv_accessors = inv_attr_md.accessors
138
-
139
- # Redefine the writer method to update the inverse by delegating to the inverse
140
- redefine_method(wtr) do |old_wtr|
141
- # the attribute reader and (superseded) writer
142
- accessors = [rdr, old_wtr]
143
- if inv_attr_md.collection? then
144
- lambda { |other| add_to_inverse_collection(other, accessors, inv_rdr) }
145
- else
146
- lambda { |other| set_inversible_noncollection_attribute(other, accessors, inv_wtr) }
147
- end
148
- end
149
- end
150
- end
151
- end
@@ -1,155 +0,0 @@
1
- require 'caruby/util/collection'
2
- require 'caruby/import/java'
3
- require 'caruby/domain/java_attribute_metadata'
4
- require 'caruby/domain/resource_introspection'
5
- require 'caruby/domain/resource_inverse'
6
- require 'caruby/domain/resource_dependency'
7
- require 'caruby/domain/resource_attributes'
8
-
9
- module CaRuby
10
- # Exception raised if a meta-data setting is missing or invalid.
11
- class MetadataError < RuntimeError; end
12
-
13
- # Adds introspected metadata to a Class.
14
- module ResourceMetadata
15
- include ResourceIntrospection, ResourceInverse, ResourceDependency, ResourceAttributes
16
-
17
- attr_reader :domain_module
18
-
19
- # JRuby 1.6 alert - if a concrete class implements a constructor, then calling super in an
20
- # included module initialize method results in a recursive call back into that initialize.
21
- # The work-around is to redefine each domain class new method to call the initalize_content
22
- # method instead. All Resource domain classes or included modules must not override
23
- # initialize. They can implement initialize_content instead.
24
- def self.extended(klass)
25
- klass.class_eval do
26
- class << self
27
- def new(opts=nil)
28
- obj = super()
29
- obj.merge_attributes(opts) if opts
30
- obj
31
- end
32
- end
33
- end
34
- end
35
-
36
- # Associates meta-data with a Resource class. When a Resource class is added to a ResourceModule,
37
- # the ResourceModule extends the Resource class with ResourceMetadata and calls this
38
- # {#add_metadata} method with itself as the mod argument. The ResourceModule is accessed
39
- # by the {#domain_module} method.
40
- def add_metadata(mod)
41
- @domain_module = mod
42
- introspect # in ResourceIntrospection
43
- end
44
-
45
- # @return the domain type for attribute, or nil if attribute is not a domain attribute
46
- def domain_type(attribute)
47
- attr_md = attribute_metadata(attribute)
48
- attr_md.type if attr_md.domain?
49
- end
50
-
51
- # Returns an empty value for the given attribute.
52
- # * If this class is not abstract, then the empty value is the initialized value.
53
- # * Otherwise, if the attribute is a Java primitive number then zero.
54
- # * Otherwise, if the attribute is a Java primitive boolean then +false+.
55
- # * Otherwise, the empty value is nil.
56
- #
57
- # @param [Symbol] attribute the target attribute
58
- # @return [Numeric, Boolean, Enumerable, nil] the empty attribute value
59
- def empty_value(attribute)
60
- if abstract? then
61
- attr_md = attribute_metadata(attribute)
62
- # the Java property type
63
- jtype = attr_md.property_descriptor.property_type if JavaAttributeMetadata === attr_md
64
- # A primitive is either a boolean or a number (String is not primitive).
65
- if jtype and jtype.primitive? then
66
- type.name == 'boolean' ? false : 0
67
- end
68
- else
69
- # Since this class is not abstract, create a prototype instance on demand and make
70
- # a copy of the initialized collection value from that instance.
71
- @prototype ||= new
72
- value = @prototype.send(attribute) || return
73
- value.class.new
74
- end
75
- end
76
-
77
- # Prints this classifier's content to the log.
78
- def pretty_print(q)
79
- # KLUDGE - if not inited then bail.
80
- # TODO - isolate when this occurs.
81
- if @attr_md_hash.nil? then
82
- q.text(qp)
83
- return
84
- end
85
-
86
- # the Java property descriptors
87
- property_descriptors = java_attributes.wrap { |attr| attribute_metadata(attr).property_descriptor }
88
- # build a map of relevant display label => attributes
89
- prop_printer = property_descriptors.wrap { |pd| PROP_DESC_PRINTER.wrap(pd) }
90
- prop_syms = property_descriptors.map { |pd| pd.name.to_sym }.to_set
91
- aliases = @alias_std_attr_map.keys - attributes.to_a - prop_syms
92
- alias_attr_hash = aliases.to_compact_hash { |aliaz| @alias_std_attr_map[aliaz] }
93
- dependents_printer = dependent_attributes.wrap { |attr| DEPENDENT_ATTR_PRINTER.wrap(attribute_metadata(attr)) }
94
- owner_printer = owners.wrap { |type| TYPE_PRINTER.wrap(type) }
95
- inverses = @attributes.to_compact_hash do |attr|
96
- attr_md = attribute_metadata(attr)
97
- "#{attr_md.type.qp}.#{attr_md.inverse}" if attr_md.inverse
98
- end
99
- domain_attr_printer = domain_attributes.to_compact_hash { |attr| domain_type(attr).qp }
100
- map = {
101
- "Java properties" => prop_printer,
102
- "standard attributes" => attributes,
103
- "aliases to standard attributes" => alias_attr_hash,
104
- "secondary key" => secondary_key_attributes,
105
- "mandatory attributes" => mandatory_attributes,
106
- "domain attributes" => domain_attr_printer,
107
- "creatable domain attributes" => creatable_domain_attributes,
108
- "updatable domain attributes" => updatable_domain_attributes,
109
- "fetched domain attributes" => fetched_domain_attributes,
110
- "cascaded domain attributes" => cascaded_attributes,
111
- "owners" => owner_printer,
112
- "owner attributes" => owner_attributes,
113
- "inverse attributes" => inverses,
114
- "dependent attributes" => dependents_printer,
115
- "default values" => defaults
116
- }.delete_if { |key, value| value.nil_or_empty? }
117
-
118
- # one indented line per entry, all but the last line ending in a comma
119
- content = map.map { |label, value| " #{label}=>#{format_print_value(value)}" }.join(",\n")
120
- # print the content to the log
121
- q.text("#{qp} structure:\n#{content}")
122
- end
123
-
124
- protected
125
-
126
- def self.extend_class(klass, mod)
127
- klass.extend(self)
128
- klass.add_metadata(mod)
129
- end
130
-
131
- private
132
-
133
- # A proc to print the unqualified class name.
134
- TYPE_PRINTER = PrintWrapper.new { |type| type.qp }
135
-
136
- DEPENDENT_ATTR_PRINTER = PrintWrapper.new do |attr_md|
137
- flags = []
138
- flags << :logical if attr_md.logical?
139
- flags << :autogenerated if attr_md.autogenerated?
140
- flags << :disjoint if attr_md.disjoint?
141
- flags.empty? ? "#{attr_md}" : "#{attr_md}(#{flags.join(',')})"
142
- end
143
-
144
- # A proc to print the property descriptor name.
145
- PROP_DESC_PRINTER = PrintWrapper.new { |pd| pd.name }
146
-
147
- def format_print_value(value)
148
- case value
149
- when String then value
150
- when Class then value.qp
151
- else value.pp_s(:single_line)
152
- end
153
- end
154
- end
155
- end