caruby-core 1.4.9 → 1.5.1

Sign up to get free protection for your applications and to get access to all the features.
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