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