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
@@ -0,0 +1,615 @@
1
+ require 'enumerator'
2
+ require 'caruby/util/collection'
3
+ require 'caruby/domain/merge'
4
+ require 'caruby/domain/attribute'
5
+
6
+ module CaRuby
7
+ module Domain
8
+ # Meta-data mix-in for attribute accessors.
9
+ module Attributes
10
+
11
+ # @return [<Symbol>] this class's attributes
12
+ attr_reader :attributes
13
+
14
+ # @return [Hashable] the default attribute => value associations
15
+ attr_reader :defaults
16
+
17
+ # Returns whether this class has an attribute with the given symbol.
18
+ #
19
+ # @param [Symbol] symbol the potential attribute
20
+ # @return [Boolean] whether there is a corresponding attribute
21
+ def attribute_defined?(symbol)
22
+ unless Symbol === symbol then
23
+ raise ArgumentError.new("Attribute argument #{symbol.qp} of type #{symbol.class.qp} is not a symbol")
24
+ end
25
+ @attr_md_hash.has_key?(symbol)
26
+ end
27
+
28
+ # Adds the given attribute to this Class.
29
+ #
30
+ # @param [Symbol] attribute the attribute to add
31
+ # @param [Class] type (see Attribute#initialize)
32
+ # @param flags (see Attribute#initialize)
33
+ # @return [Attribute] the attribute meta-data
34
+ def add_attribute(attribute, type, *flags)
35
+ attr_md = Attribute.new(attribute, self, type, *flags)
36
+ add_attribute_metadata(attr_md)
37
+ attr_md
38
+ end
39
+
40
+ # Returns the +[:identifier]+ primary key attribute array.
41
+ def primary_key_attributes
42
+ IDENTIFIER_ATTR_ARRAY
43
+ end
44
+
45
+ # Returns this class's secondary key attribute array.
46
+ # If this class's secondary key is not set, then the secondary key is the Metadata superclass
47
+ # secondary key, if any.
48
+ def secondary_key_attributes
49
+ @scndy_key_attrs or superclass < Resource ? superclass.secondary_key_attributes : Array::EMPTY_ARRAY
50
+ end
51
+
52
+ # Returns this class's alternate key attribute array.
53
+ # If this class's secondary key is not set, then the alternate key is the Metadata superclass
54
+ # alternate key, if any.
55
+ def alternate_key_attributes
56
+ @alt_key_attrs or superclass < Resource ? superclass.alternate_key_attributes : Array::EMPTY_ARRAY
57
+ end
58
+
59
+ def each_attribute_metadata(&block)
60
+ @attr_md_hash.each_value(&block)
61
+ end
62
+
63
+ # @return the Attribute for the given attribute symbol or alias
64
+ # @raise [NameError] if the attribute is not recognized
65
+ def attribute_metadata(attribute)
66
+ # Simple and predominant case is that attribute is a standard attribute.
67
+ # Otherwise, resolve attribute to the standard symbol.
68
+ attr_md = @attr_md_hash[attribute] || @attr_md_hash[standard_attribute(attribute)]
69
+ # If not found, then delegate to handler which will either make the new
70
+ # attribute or raise a NameError.
71
+ attr_md || (attribute_missing(attribute) && @local_attr_md_hash[attribute])
72
+ end
73
+
74
+ # Returns the standard attribute symbol for the given name_or_alias.
75
+ #
76
+ # Raises NameError if the attribute is not found
77
+ def standard_attribute(name_or_alias)
78
+ if name_or_alias.nil? then
79
+ raise ArgumentError.new("#{qp} standard attribute call is missing the attribute name/alias parameter")
80
+ end
81
+ @alias_std_attr_map[name_or_alias.to_sym] or raise NameError.new("#{self} attribute not found: #{name_or_alias}")
82
+ end
83
+
84
+ ## the built-in Metadata attribute filters ##
85
+
86
+ # @return [<Symbol>] the domain attributes which wrap a java property
87
+ # @see Attribute#java_property?
88
+ def java_attributes
89
+ @java_attrs ||= attribute_filter { |attr_md| attr_md.java_property? }
90
+ end
91
+
92
+ alias :printable_attributes :java_attributes
93
+
94
+ # @return [<Symbol>] the domain attributes
95
+ def domain_attributes
96
+ @dom_attrs ||= attribute_filter { |attr_md| attr_md.domain? }
97
+ end
98
+
99
+ # @return [<Symbol>] the non-domain Java attributes
100
+ def nondomain_attributes
101
+ @nondom_attrs ||= attribute_filter { |attr_md| attr_md.java_property? and attr_md.nondomain? }
102
+ end
103
+
104
+ # @return [<Symbol>] the non-domain Java property wrapper attributes
105
+ def nondomain_java_attributes
106
+ @nondom_java_attrs ||= nondomain_attributes.compose { |attr_md| attr_md.java_property? }
107
+ end
108
+
109
+ # @return [<Symbol>] the standard attributes which can be merged into an instance of the subject class.
110
+ # The default mergeable attributes consist of the {#nondomain_java_attributes}.
111
+ # @see Mergeable#mergeable_attributes
112
+ alias :mergeable_attributes :nondomain_java_attributes
113
+
114
+ # @param [Boolean, nil] inc_super flag indicating whether to include dependents defined in the superclass
115
+ # @return [<Symbol>] the dependent attributes
116
+ def dependent_attributes(inc_super=true)
117
+ if inc_super then
118
+ @dep_attrs ||= attribute_filter { |attr_md| attr_md.dependent? }
119
+ else
120
+ @local_dep_attrs ||= dependent_attributes.compose { |attr_md| attr_md.declarer == self }
121
+ end
122
+ end
123
+
124
+ # @return [<Symbol>] the dependent attributes
125
+ def autogenerated_dependent_attributes
126
+ @ag_dep_attrs ||= dependent_attributes.compose { |attr_md| attr_md.autogenerated? }
127
+ end
128
+
129
+ # @return [<Symbol>] the autogenerated logical dependent attributes
130
+ # @see #logical_dependent_attributes
131
+ # @see Attribute#autogenerated?
132
+ def autogenerated_logical_dependent_attributes
133
+ @ag_log_dep_attrs ||= dependent_attributes.compose { |attr_md| attr_md.autogenerated? and attr_md.logical? }
134
+ end
135
+
136
+ # @return [<Symbol>] the {Attribute#saved_fetch?} attributes
137
+ def saved_fetch_attributes
138
+ @svd_ftch_attrs ||= domain_attributes.compose { |attr_md| attr_md.saved_fetch? }
139
+ end
140
+
141
+ # @return [<Symbol>] the logical dependent attributes
142
+ # @see Attribute#logical?
143
+ def logical_dependent_attributes
144
+ @log_dep_attrs ||= dependent_attributes.compose { |attr_md| attr_md.logical? }
145
+ end
146
+
147
+ # @return [<Symbol>] the unidirectional dependent attributes
148
+ # @see Attribute#unidirectional?
149
+ def unidirectional_dependent_attributes
150
+ @uni_dep_attrs ||= dependent_attributes.compose { |attr_md| attr_md.unidirectional? }
151
+ end
152
+
153
+ # @return [<Symbol>] the auto-generated attributes
154
+ # @see Attribute#autogenerated?
155
+ def autogenerated_attributes
156
+ @ag_attrs ||= attribute_filter { |attr_md| attr_md.autogenerated? }
157
+ end
158
+
159
+ # @return [<Symbol>] the auto-generated non-domain attributes
160
+ # @see Attribute#nondomain?
161
+ # @see Attribute#autogenerated?
162
+ def autogenerated_nondomain_attributes
163
+ @ag_nd_attrs ||= attribute_filter { |attr_md| attr_md.autogenerated? and attr_md.nondomain? }
164
+ end
165
+
166
+ # @return [<Symbol>] the {Attribute#volatile?} non-domain attributes
167
+ def volatile_nondomain_attributes
168
+ @unsvd_nd_attrs ||= attribute_filter { |attr_md| attr_md.volatile? and attr_md.nondomain? }
169
+ end
170
+
171
+ # @return [<Symbol>] the domain attributes which can serve as a query parameter
172
+ # @see Attribute#searchable?
173
+ def searchable_attributes
174
+ @srchbl_attrs ||= attribute_filter { |attr_md| attr_md.searchable? }
175
+ end
176
+
177
+ # @return [<Symbol>] the create/update cascaded domain attributes
178
+ # @see Attribute#cascaded?
179
+ def cascaded_attributes
180
+ @cscd_attrs ||= domain_attributes.compose { |attr_md| attr_md.cascaded? }
181
+ end
182
+
183
+ # @return [<Symbol>] the {#cascaded_attributes} which are saved with a proxy
184
+ # using the dependent saver_proxy method
185
+ def proxied_savable_template_attributes
186
+ @px_cscd_attrs ||= savable_template_attributes.compose { |attr_md| attr_md.proxied_save? }
187
+ end
188
+
189
+ # @return [<Symbol>] the {#cascaded_attributes} which do not have a
190
+ # #{Attribute#proxied_save?}
191
+ def unproxied_savable_template_attributes
192
+ @unpx_sv_tmpl_attrs ||= savable_template_attributes.compose { |attr_md| not attr_md.proxied_save? }
193
+ end
194
+
195
+ # @return [<Symbol>] the {#domain_attributes} to {Attribute#include_in_save_template?}
196
+ def savable_template_attributes
197
+ @sv_tmpl_attrs ||= domain_attributes.compose { |attr_md| attr_md.include_in_save_template? }
198
+ end
199
+
200
+ # Returns the physical or auto-generated logical dependent attributes that can
201
+ # be copied from a save result to the given save argument object.
202
+ #
203
+ # @return [<Symbol>] the attributes that can be copied from a save result to a
204
+ # save argument object
205
+ # @see Attribute#autogenerated?
206
+ def copyable_saved_attributes
207
+ @cp_sv_attrs ||= dependent_attributes.compose { |attr_md| attr_md.autogenerated? or not attr_md.logical? }
208
+ end
209
+
210
+ # Returns the subject class's required attributes, determined as follows:
211
+ # * An attribute marked with the :mandatory flag is mandatory.
212
+ # * An attribute marked with the :optional or :autogenerated flag is not mandatory.
213
+ # * Otherwise, A secondary key or owner attribute is mandatory.
214
+ def mandatory_attributes
215
+ @mndtry_attrs ||= collect_mandatory_attributes
216
+ end
217
+
218
+ # @return [<Symbol>] the attributes which are {Attribute#creatable?}
219
+ def creatable_attributes
220
+ @cr_attrs ||= attribute_filter { |attr_md| attr_md.creatable? }
221
+ end
222
+
223
+ # @return [<Symbol>] the attributes which are {Attribute#updatable?}
224
+ def updatable_attributes
225
+ @upd_attrs ||= attribute_filter { |attr_md| attr_md.updatable? }
226
+ end
227
+
228
+ def fetched_dependent_attributes
229
+ @ftchd_dep_attrs ||= (fetched_domain_attributes & dependent_attributes).to_a
230
+ end
231
+
232
+ # @return [<Symbol>] the saved dependent attributes
233
+ # @see Attribute#dependent?
234
+ # @see Attribute#saved?
235
+ def saved_dependent_attributes
236
+ @svd_dep_attrs ||= attribute_filter { |attr_md| attr_md.dependent? and attr_md.saved? }
237
+ end
238
+
239
+ # @return [<Symbol>] the saved independent attributes
240
+ # @see Attribute#independent?
241
+ # @see Attribute#saved?
242
+ def saved_independent_attributes
243
+ @svd_ind_attrs ||= attribute_filter { |attr_md| attr_md.independent? and attr_md.saved? }
244
+ end
245
+
246
+ # @return [<Symbol>] the domain {Attribute#saved?} attributes
247
+ def saved_domain_attributes
248
+ @svd_dom_attrs ||= domain_attributes.compose { |attr_md| attr_md.saved? }
249
+ end
250
+
251
+ # @return [<Symbol>] the non-domain {Attribute#saved?} attributes
252
+ def saved_nondomain_attributes
253
+ @svd_nondom_attrs ||= nondomain_attributes.compose { |attr_md| attr_md.saved? }
254
+ end
255
+
256
+ # @return [<Symbol>] the {Attribute#volatile?} {#nondomain_attributes}
257
+ def volatile_nondomain_attributes
258
+ @vlt_nondom_attrs ||= nondomain_attributes.compose { |attr_md| attr_md.volatile? }
259
+ end
260
+
261
+ # @return [<Symbol>] the domain {#creatable_attributes}
262
+ def creatable_domain_attributes
263
+ @cr_dom_attrs ||= domain_attributes.compose { |attr_md| attr_md.creatable? }
264
+ end
265
+
266
+ # @return [<Symbol>] the domain {#updatable_attributes}
267
+ def updatable_domain_attributes
268
+ @upd_dom_attrs ||= domain_attributes.compose { |attr_md| attr_md.updatable? }
269
+ end
270
+
271
+ # @return [<Symbol>] the domain attributes whose referents must exist before an instance of this
272
+ # metadata's subject classcan be created
273
+ # @see Attribute#storable_prerequisite?
274
+ def storable_prerequisite_attributes
275
+ @stbl_prereq_dom_attrs ||= attribute_filter { |attr_md| attr_md.storable_prerequisite? }
276
+ end
277
+
278
+ # @return [<Symbol>] the attributes which are populated from the database
279
+ # @see Attribute#fetched?
280
+ def fetched_attributes
281
+ @ftchd_attrs ||= attribute_filter { |attr_md| attr_md.fetched? }
282
+ end
283
+
284
+ # Returns the domain attributes which are populated in a query on the given fetched instance of
285
+ # this metadata's subject class. The domain attribute is fetched if it satisfies the following
286
+ # conditions:
287
+ # * the attribute is a dependent attribute or of abstract domain type
288
+ # * the attribute is not specified as unfetched in the configuration
289
+ #
290
+ # @return [<Symbol>] the attributes which are {Attribute#fetched?}
291
+ def fetched_domain_attributes
292
+ @ftchd_dom_attrs ||= domain_attributes.compose { |attr_md| attr_md.fetched? }
293
+ end
294
+
295
+ #@return [<Symbol>] the #domain_attributes which are not #fetched_domain_attributes
296
+ def unfetched_attributes
297
+ @unftchd_attrs ||= domain_attributes.compose { |attr_md| not attr_md.fetched? }
298
+ end
299
+
300
+ alias :toxic_attributes :unfetched_attributes
301
+
302
+ # @return [<Symbol>] the Java property non-abstract {#unfetched_attributes}
303
+ def loadable_attributes
304
+ @ld_attrs ||= unfetched_attributes.compose { |attr_md| attr_md.java_property? and not attr_md.type.abstract? }
305
+ end
306
+
307
+ # @param [Symbol] attribute the attribute to check
308
+ # @return [Boolean] whether attribute return type is a domain object or collection thereof
309
+ def domain_attribute?(attribute)
310
+ attribute_metadata(attribute).domain?
311
+ end
312
+
313
+ # @param [Symbol] attribute the attribute to check
314
+ # @return [Boolean] whether attribute is not a domain attribute
315
+ def nondomain_attribute?(attribute)
316
+ not domain_attribute?(attribute)
317
+ end
318
+
319
+ # @param [Symbol] attribute the attribute to check
320
+ # @return [Boolean] whether attribute is an instance of a Java domain class
321
+ def collection_attribute?(attribute)
322
+ attribute_metadata(attribute).collection?
323
+ end
324
+
325
+ protected
326
+
327
+ # @return [{Symbol => Attribute}] the attribute => metadata hash
328
+ def attribute_metadata_hash
329
+ @attr_md_hash
330
+ end
331
+
332
+ # @return [{Symbol => Symbol}] the attribute alias => standard hash
333
+ def alias_standard_attribute_hash
334
+ @alias_std_attr_map
335
+ end
336
+
337
+ private
338
+
339
+ IDENTIFIER_ATTR_ARRAY = [:identifier]
340
+
341
+ # A filter on the standard attribute symbol => metadata hash that yields
342
+ # each attribute which satisfies the attribute metadata condition.
343
+ class Filter
344
+ include Enumerable
345
+
346
+ # @param [Class] the class whose attributes are filtered
347
+ # @param [{Symbol => Attribute}] hash the attribute symbol => metadata hash
348
+ # @yield [attr_md] condition which determines whether the attribute is selected
349
+ # @yieldparam [Attribute] the metadata for the standard attribute
350
+ # @raise [ArgumentError] if a parameter is missing
351
+ def initialize(klass, hash, &filter)
352
+ raise ArgumentError.new("#{klass.qp} attribute filter missing hash argument") if hash.nil?
353
+ raise ArgumentError.new("#{klass.qp} attribute filter missing filter block") unless block_given?
354
+ @hash = hash
355
+ @filter = filter
356
+ end
357
+
358
+ # @yield [attribute, attr_md] the block to apply to the filtered attribute metadata and attribute
359
+ # @yieldparam [Symbol] attribute the attribute
360
+ # @yieldparam [Attribute] attr_md the attribute metadata
361
+ def each_pair
362
+ @hash.each { |attr, attr_md| yield(attr, attr_md) if @filter.call(attr_md) }
363
+ end
364
+
365
+ # @return [<(Symbol, Attribute)>] the (symbol, attribute) enumerator
366
+ def enum_pairs
367
+ enum_for(:each_pair)
368
+ end
369
+
370
+ # @yield [attribute] block to apply to each filtered attribute
371
+ # @yieldparam [Symbol] the attribute which satisfies the filter condition
372
+ def each_attribute(&block)
373
+ each_pair { |attr, attr_md| yield(attr) }
374
+ end
375
+
376
+ alias :each :each_attribute
377
+
378
+ # @yield [attr_md] the block to apply to the filtered attribute metadata
379
+ # @yieldparam [Attribute] attr_md the attribute metadata
380
+ def each_metadata
381
+ each_pair { |attr, attr_md| yield(attr_md) }
382
+ end
383
+
384
+ # @return [<Attribute>] the attribute metadata enumerator
385
+ def enum_metadata
386
+ enum_for(:each_metadata)
387
+ end
388
+
389
+ # @yield [attribute] the block to apply to the attribute
390
+ # @yieldparam [Symbol] attribute the attribute
391
+ # @return [Attribute] the first attribute metadata satisfies the block
392
+ def detect_metadata
393
+ each_pair { |attr, attr_md| return attr_md if yield(attr) }
394
+ nil
395
+ end
396
+
397
+ # @yield [attr_md] the block to apply to the attribute metadata
398
+ # @yieldparam [Attribute] attr_md the attribute metadata
399
+ # @return [Symbol] the first attribute whose metadata satisfies the block
400
+ def detect_with_metadata
401
+ each_pair { |attr, attr_md| return attr if yield(attr_md) }
402
+ nil
403
+ end
404
+
405
+ # @yield [attr_md] the attribute selection filter
406
+ # @yieldparam [Attribute] attr_md the candidate attribute metadata
407
+ # @return [Filter] a new Filter which applies the filter block given to this
408
+ # method with the Attribute enumerated by this filter
409
+ def compose
410
+ Filter.new(self, @hash) { |attr_md| @filter.call(attr_md) and yield(attr_md) }
411
+ end
412
+ end
413
+
414
+ # Returns an Enumerable on this Resource class's attributes which iterates on each attribute whose
415
+ # corresponding Attribute satisfies the given filter block.
416
+ #
417
+ # @yield [attr_md] the attribute selector
418
+ # @yieldparam [Attribute] attr_md the candidate attribute
419
+ def attribute_filter(&filter)
420
+ # initialize the attributes on demand
421
+ unless introspected? then introspect end
422
+ # make the attribute filter
423
+ Filter.new(self, @attr_md_hash, &filter)
424
+ end
425
+
426
+ # Initializes the attribute meta-data structures.
427
+ def init_attributes
428
+ @local_std_attr_hash = {}
429
+ @alias_std_attr_map = append_ancestor_enum(@local_std_attr_hash) { |par| par.alias_standard_attribute_hash }
430
+ @local_attr_md_hash = {}
431
+ @attr_md_hash = append_ancestor_enum(@local_attr_md_hash) { |par| par.attribute_metadata_hash }
432
+ @attributes = Enumerable::Enumerator.new(@attr_md_hash, :each_key)
433
+ @local_mndty_attrs = Set.new
434
+ @local_defaults = {}
435
+ @defaults = append_ancestor_enum(@local_defaults) { |par| par.defaults }
436
+ end
437
+
438
+ # Detects the first attribute with the given type.
439
+ #
440
+ # @param [Class] klass the target attribute type
441
+ # @return [Symbol, nil] the attribute with the given type
442
+ def detect_attribute_with_type(klass)
443
+ attribute_metadata_hash.detect_key_with_value { |attr_md| attr_md.type == klass }
444
+ end
445
+
446
+ # Creates the given attribute alias. If the attribute metadata is registered with this class, then
447
+ # this method overrides {Class#alias_attribute} to create a new alias reader (writer) method
448
+ # which delegates to the attribute reader (writer, resp.). This aliasing mechanism differs from
449
+ # {Class#alias_attribute}, which directly aliases the existing reader or writer method.
450
+ # Delegation allows the alias to pick up run-time redefinitions of the aliased reader and writer.
451
+ # If the attribute metadata is not registered with this class, then this method delegates to
452
+ # {Class#alias_attribute}.
453
+ #
454
+ # @param [Symbol] aliaz the attribute alias
455
+ # @param [Symbol] attribute the attribute to alias
456
+ def alias_attribute(aliaz, attribute)
457
+ if attribute_defined?(attribute) then
458
+ add_attribute_aliases(aliaz => attribute)
459
+ else
460
+ super
461
+ end
462
+ end
463
+
464
+ # Creates the given aliases to attributes.
465
+ #
466
+ # @param [{Symbol => Symbol}] hash the alias => attribute hash
467
+ # @see #attribute_alias
468
+ def add_attribute_aliases(hash)
469
+ hash.each { |aliaz, attr| delegate_to_attribute(aliaz, attr) }
470
+ end
471
+
472
+ # Sets this class's secondary key attributes to the given attributes.
473
+ # If attributes is set to nil, then the secondary key is cleared.
474
+ def set_secondary_key_attributes(*attributes)
475
+ attributes.compact!
476
+ @scndy_key_attrs = attributes.map { |attr| standard_attribute(attr) }
477
+ end
478
+
479
+ # Sets this class's alternate key attributes to the given attributes.
480
+ # If attributes is set to nil, then the alternate key is cleared.
481
+ def set_alternate_key_attributes(*attributes)
482
+ attributes.compact!
483
+ @alt_key_attrs = attributes.map { |attr| standard_attribute(attr) }
484
+ end
485
+
486
+ # Sets the given attribute type to klass. If attribute is defined in a superclass,
487
+ # then klass must be a subclass of the superclass attribute type.
488
+ #
489
+ # Raises ArgumentError if klass is incompatible with the current attribute type.
490
+ def set_attribute_type(attribute, klass)
491
+ attr_md = attribute_metadata(attribute)
492
+ # If this class is the declarer, then simply set the attribute type.
493
+ # Otherwise, if the attribute type is unspecified or is a superclass of the given class,
494
+ # then make a new attribute metadata for this class.
495
+ if attr_md.declarer == self then
496
+ logger.debug { "Set #{qp}.#{attribute} type to #{klass.qp}." }
497
+ attr_md.type = klass
498
+ elsif attr_md.type.nil? or klass < attr_md.type then
499
+ new_attr_md = attr_md.restrict_type(self, klass)
500
+ logger.debug { "Restricted #{attr_md.declarer.qp}.#{attribute}(#{attr_md.type.qp}) to #{qp} with return type #{klass.qp}." }
501
+ add_attribute_metadata(new_attr_md)
502
+ elsif klass != attr_md.type then
503
+ raise ArgumentError.new("Cannot reset #{qp}.#{attribute} type #{attr_md.type} to incompatible #{klass.qp}")
504
+ end
505
+ end
506
+
507
+ def add_attribute_defaults(hash)
508
+ hash.each { |attr, value| @local_defaults[standard_attribute(attr)] = value }
509
+ end
510
+
511
+ def add_mandatory_attributes(*attributes)
512
+ attributes.each { |attr| @local_mndty_attrs << standard_attribute(attr) }
513
+ end
514
+
515
+ # Marks the given attribute with flags supported by {Attribute#qualify}.
516
+ def qualify_attribute(attribute, *flags)
517
+ attr_md = attribute_metadata(attribute)
518
+ if attr_md.declarer == self then
519
+ attr_md.qualify(*flags)
520
+ else
521
+ logger.debug { "Restricting #{attr_md.declarer.qp}.#{attribute} to #{qp} with additional flags #{flags.to_series}" }
522
+ new_attr_md = attr_md.restrict_flags(self, *flags)
523
+ add_attribute_metadata(new_attr_md)
524
+ end
525
+ end
526
+
527
+ # Removes the given attribute from this Resource.
528
+ # An attribute declared in a superclass Resource is hidden from this Resource but retained in
529
+ # the declaring Resource.
530
+ def remove_attribute(attribute)
531
+ std_attr = standard_attribute(attribute)
532
+ # if the attribute is local, then delete it, otherwise filter out the superclass attribute
533
+ if @local_attr_md_hash.delete(std_attr) then
534
+ @local_mndty_attrs.delete(std_attr)
535
+ @local_std_attr_hash.delete_if { |aliaz, attr| attr == std_attr }
536
+ else
537
+ # Filter the superclass hashes.
538
+ anc_md_hash = @attr_md_hash.components[1]
539
+ @attr_md_hash.components[1] = anc_md_hash.filter_on_key { |attr| attr != attribute }
540
+ anc_alias_hash = @alias_std_attr_map.components[1]
541
+ @alias_std_attr_map.components[1] = anc_alias_hash.filter_on_key { |attr| attr != attribute }
542
+ end
543
+ end
544
+
545
+ def add_attribute_metadata(attr_md)
546
+ attr = attr_md.to_sym
547
+ @local_attr_md_hash[attr] = attr_md
548
+ # map the attribute symbol to itself in the alias map
549
+ @local_std_attr_hash[attr] = attr
550
+ end
551
+
552
+ # Records that the given aliaz aliases a standard attribute.
553
+ def add_alias(aliaz, attribute)
554
+ std_attr = standard_attribute(attribute)
555
+ raise ArgumentError.new("#{self} attribute not found: #{attribute}") if std_attr.nil?
556
+ @local_std_attr_hash[aliaz.to_sym] = std_attr
557
+ end
558
+
559
+ # Appends to the given enumerable the result of evaluating the block given to this method
560
+ # on the superclass, if the superclass is in the same parent module as this class.
561
+ #
562
+ # @param [Enumerable] enum the base collection
563
+ # @return [Enumerable] the {Enumerable#union} of the base collection with the superclass
564
+ # collection, if applicable
565
+ def append_ancestor_enum(enum)
566
+ return enum unless superclass.parent_module == parent_module
567
+ anc_enum = yield superclass
568
+ if anc_enum.nil? then raise MetadataError.new("#{qp} superclass #{superclass.qp} does not have required metadata") end
569
+ enum.union(anc_enum)
570
+ end
571
+
572
+ # Collects the {Attribute#fetched_dependent?} and {Attribute#fetched_independent?}
573
+ # standard domain attributes.
574
+ #
575
+ # @return [<Symbol>] the fetched attributes
576
+ def collect_default_fetched_domain_attributes
577
+ attribute_filter do |attr_md|
578
+ if attr_md.domain? then
579
+ attr_md.dependent? ? fetched_dependent?(attr_md) : fetched_independent?(attr_md)
580
+ end
581
+ end
582
+ end
583
+
584
+ # Merges the secondary key, owner and additional mandatory attributes defined in the properties.
585
+ #
586
+ # @see #mandatory_attributes
587
+ def collect_mandatory_attributes
588
+ mandatory = Set.new
589
+ # add the secondary key
590
+ mandatory.merge(secondary_key_attributes)
591
+ # add the owner attribute, if any
592
+ oattr = mandatory_owner_attribute
593
+ mandatory << oattr if oattr
594
+ # remove autogenerated or optional attributes
595
+ mandatory.delete_if { |attr| attribute_metadata(attr).autogenerated? or attribute_metadata(attr).optional? }
596
+ @local_mndty_attrs.merge!(mandatory)
597
+ append_ancestor_enum(@local_mndty_attrs) { |par| par.mandatory_attributes }
598
+ end
599
+
600
+ # @return [Symbol, nil] the unique non-self-referential owner attribute, if one exists
601
+ def mandatory_owner_attribute
602
+ attr = owner_attribute || return
603
+ attr_md = attribute_metadata(attr)
604
+ attr if attr_md.java_property? and attr_md.type != self
605
+ end
606
+
607
+ # Raises a NameError. Domain classes can override this method to dynamically create a new reference attribute.
608
+ #
609
+ # @raise [NameError] always
610
+ def attribute_missing(attribute)
611
+ raise NameError.new("#{name.demodulize} attribute not found: #{attribute}")
612
+ end
613
+ end
614
+ end
615
+ end