jinx 2.1.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 (149) hide show
  1. data/.gitignore +14 -0
  2. data/.rspec +3 -0
  3. data/.yardopts +1 -0
  4. data/Gemfile +6 -0
  5. data/Gemfile.lock +27 -0
  6. data/History.md +6 -0
  7. data/LEGAL +5 -0
  8. data/LICENSE +22 -0
  9. data/README.md +44 -0
  10. data/Rakefile +41 -0
  11. data/examples/family/README.md +10 -0
  12. data/examples/family/ext/build.xml +35 -0
  13. data/examples/family/ext/src/family/Address.java +68 -0
  14. data/examples/family/ext/src/family/Child.java +24 -0
  15. data/examples/family/ext/src/family/DomainObject.java +26 -0
  16. data/examples/family/ext/src/family/Household.java +36 -0
  17. data/examples/family/ext/src/family/Parent.java +48 -0
  18. data/examples/family/ext/src/family/Person.java +42 -0
  19. data/examples/family/lib/family.rb +15 -0
  20. data/examples/family/lib/family/address.rb +6 -0
  21. data/examples/family/lib/family/domain_object.rb +6 -0
  22. data/examples/family/lib/family/household.rb +6 -0
  23. data/examples/family/lib/family/parent.rb +16 -0
  24. data/examples/family/lib/family/person.rb +6 -0
  25. data/examples/model/README.md +25 -0
  26. data/examples/model/ext/build.xml +35 -0
  27. data/examples/model/ext/src/domain/Child.java +192 -0
  28. data/examples/model/ext/src/domain/Dependent.java +29 -0
  29. data/examples/model/ext/src/domain/DomainObject.java +26 -0
  30. data/examples/model/ext/src/domain/Independent.java +83 -0
  31. data/examples/model/ext/src/domain/Parent.java +129 -0
  32. data/examples/model/ext/src/domain/Person.java +14 -0
  33. data/examples/model/lib/model.rb +13 -0
  34. data/examples/model/lib/model/child.rb +13 -0
  35. data/examples/model/lib/model/domain_object.rb +6 -0
  36. data/examples/model/lib/model/independent.rb +11 -0
  37. data/examples/model/lib/model/parent.rb +17 -0
  38. data/jinx.gemspec +22 -0
  39. data/lib/jinx.rb +3 -0
  40. data/lib/jinx/active_support/README.txt +2 -0
  41. data/lib/jinx/active_support/core_ext/string.rb +7 -0
  42. data/lib/jinx/active_support/core_ext/string/inflections.rb +167 -0
  43. data/lib/jinx/active_support/inflections.rb +55 -0
  44. data/lib/jinx/active_support/inflector.rb +398 -0
  45. data/lib/jinx/cli/application.rb +36 -0
  46. data/lib/jinx/cli/command.rb +214 -0
  47. data/lib/jinx/helpers/array.rb +108 -0
  48. data/lib/jinx/helpers/boolean.rb +42 -0
  49. data/lib/jinx/helpers/case_insensitive_hash.rb +39 -0
  50. data/lib/jinx/helpers/class.rb +149 -0
  51. data/lib/jinx/helpers/collection.rb +33 -0
  52. data/lib/jinx/helpers/collections.rb +11 -0
  53. data/lib/jinx/helpers/collector.rb +20 -0
  54. data/lib/jinx/helpers/conditional_enumerator.rb +21 -0
  55. data/lib/jinx/helpers/enumerable.rb +242 -0
  56. data/lib/jinx/helpers/enumerate.rb +35 -0
  57. data/lib/jinx/helpers/error.rb +15 -0
  58. data/lib/jinx/helpers/file_separator.rb +65 -0
  59. data/lib/jinx/helpers/filter.rb +52 -0
  60. data/lib/jinx/helpers/flattener.rb +38 -0
  61. data/lib/jinx/helpers/hash.rb +12 -0
  62. data/lib/jinx/helpers/hashable.rb +502 -0
  63. data/lib/jinx/helpers/inflector.rb +36 -0
  64. data/lib/jinx/helpers/key_transformer_hash.rb +43 -0
  65. data/lib/jinx/helpers/lazy_hash.rb +44 -0
  66. data/lib/jinx/helpers/log.rb +106 -0
  67. data/lib/jinx/helpers/math.rb +12 -0
  68. data/lib/jinx/helpers/merge.rb +60 -0
  69. data/lib/jinx/helpers/module.rb +18 -0
  70. data/lib/jinx/helpers/multi_enumerator.rb +31 -0
  71. data/lib/jinx/helpers/options.rb +92 -0
  72. data/lib/jinx/helpers/os.rb +19 -0
  73. data/lib/jinx/helpers/partial_order.rb +37 -0
  74. data/lib/jinx/helpers/pretty_print.rb +207 -0
  75. data/lib/jinx/helpers/set.rb +8 -0
  76. data/lib/jinx/helpers/stopwatch.rb +76 -0
  77. data/lib/jinx/helpers/transformer.rb +24 -0
  78. data/lib/jinx/helpers/transitive_closure.rb +55 -0
  79. data/lib/jinx/helpers/uniquifier.rb +50 -0
  80. data/lib/jinx/helpers/validation.rb +33 -0
  81. data/lib/jinx/helpers/visitor.rb +370 -0
  82. data/lib/jinx/import/class_path_modifier.rb +77 -0
  83. data/lib/jinx/import/java.rb +337 -0
  84. data/lib/jinx/importer.rb +240 -0
  85. data/lib/jinx/metadata.rb +155 -0
  86. data/lib/jinx/metadata/attribute_enumerator.rb +73 -0
  87. data/lib/jinx/metadata/dependency.rb +244 -0
  88. data/lib/jinx/metadata/id_alias.rb +23 -0
  89. data/lib/jinx/metadata/introspector.rb +179 -0
  90. data/lib/jinx/metadata/inverse.rb +170 -0
  91. data/lib/jinx/metadata/java_property.rb +169 -0
  92. data/lib/jinx/metadata/propertied.rb +500 -0
  93. data/lib/jinx/metadata/property.rb +401 -0
  94. data/lib/jinx/metadata/property_characteristics.rb +114 -0
  95. data/lib/jinx/resource.rb +862 -0
  96. data/lib/jinx/resource/copy_visitor.rb +36 -0
  97. data/lib/jinx/resource/inversible.rb +90 -0
  98. data/lib/jinx/resource/match_visitor.rb +180 -0
  99. data/lib/jinx/resource/matcher.rb +20 -0
  100. data/lib/jinx/resource/merge_visitor.rb +73 -0
  101. data/lib/jinx/resource/mergeable.rb +185 -0
  102. data/lib/jinx/resource/reference_enumerator.rb +49 -0
  103. data/lib/jinx/resource/reference_path_visitor.rb +38 -0
  104. data/lib/jinx/resource/reference_visitor.rb +55 -0
  105. data/lib/jinx/resource/unique.rb +35 -0
  106. data/lib/jinx/version.rb +3 -0
  107. data/spec/defaults_spec.rb +30 -0
  108. data/spec/definitions/model/alias/child.rb +5 -0
  109. data/spec/definitions/model/base/child.rb +5 -0
  110. data/spec/definitions/model/base/domain_object.rb +5 -0
  111. data/spec/definitions/model/base/independent.rb +5 -0
  112. data/spec/definitions/model/defaults/child.rb +5 -0
  113. data/spec/definitions/model/dependency/child.rb +5 -0
  114. data/spec/definitions/model/dependency/parent.rb +6 -0
  115. data/spec/definitions/model/inverse/child.rb +5 -0
  116. data/spec/definitions/model/inverse/independent.rb +5 -0
  117. data/spec/definitions/model/inverse/parent.rb +5 -0
  118. data/spec/definitions/model/mandatory/child.rb +6 -0
  119. data/spec/dependency_spec.rb +47 -0
  120. data/spec/family_spec.rb +64 -0
  121. data/spec/inverse_spec.rb +53 -0
  122. data/spec/mandatory_spec.rb +43 -0
  123. data/spec/metadata_spec.rb +68 -0
  124. data/spec/resource_spec.rb +30 -0
  125. data/spec/spec_helper.rb +3 -0
  126. data/spec/support/model.rb +19 -0
  127. data/test/fixtures/line_separator/cr_line_sep.txt +1 -0
  128. data/test/fixtures/line_separator/crlf_line_sep.txt +3 -0
  129. data/test/fixtures/line_separator/lf_line_sep.txt +3 -0
  130. data/test/fixtures/mixed/ext/build.xml +35 -0
  131. data/test/fixtures/mixed/ext/src/mixed/Case/Example.java +5 -0
  132. data/test/helper.rb +7 -0
  133. data/test/lib/jinx/command_test.rb +41 -0
  134. data/test/lib/jinx/helpers/boolean_test.rb +27 -0
  135. data/test/lib/jinx/helpers/class_test.rb +60 -0
  136. data/test/lib/jinx/helpers/collections_test.rb +402 -0
  137. data/test/lib/jinx/helpers/file_separator_test.rb +29 -0
  138. data/test/lib/jinx/helpers/inflector_test.rb +11 -0
  139. data/test/lib/jinx/helpers/lazy_hash_test.rb +32 -0
  140. data/test/lib/jinx/helpers/module_test.rb +24 -0
  141. data/test/lib/jinx/helpers/options_test.rb +66 -0
  142. data/test/lib/jinx/helpers/partial_order_test.rb +41 -0
  143. data/test/lib/jinx/helpers/pretty_print_test.rb +83 -0
  144. data/test/lib/jinx/helpers/stopwatch_test.rb +16 -0
  145. data/test/lib/jinx/helpers/transitive_closure_test.rb +80 -0
  146. data/test/lib/jinx/helpers/visitor_test.rb +288 -0
  147. data/test/lib/jinx/import/java_test.rb +78 -0
  148. data/test/lib/jinx/import/mixed_case_test.rb +16 -0
  149. metadata +272 -0
@@ -0,0 +1,500 @@
1
+ require 'enumerator'
2
+ require 'jinx/helpers/collections'
3
+
4
+ require 'jinx/metadata/property'
5
+ require 'jinx/metadata/attribute_enumerator'
6
+
7
+ module Jinx
8
+ # Meta-data mix-in for attribute accessors.
9
+ module Propertied
10
+ # @return [<Symbol>] this class's attributes
11
+ attr_reader :attributes
12
+
13
+ # @return [Hashable] the default attribute => value associations
14
+ attr_reader :defaults
15
+
16
+ # Returns whether this class has an attribute with the given symbol.
17
+ #
18
+ # @param [Symbol] symbol the potential attribute
19
+ # @return [Boolean] whether there is a corresponding attribute
20
+ def property_defined?(symbol)
21
+ unless Symbol === symbol then
22
+ Jinx.fail(ArgumentError, "Property argument #{symbol.qp} of type #{symbol.class.qp} is not a symbol")
23
+ end
24
+ !!@alias_std_prop_map[symbol.to_sym]
25
+ end
26
+
27
+ # Adds the given attribute to this Class.
28
+ #
29
+ # @param [Symbol] attribute the attribute to add
30
+ # @param [Class] type (see Property#initialize)
31
+ # @param flags (see Property#initialize)
32
+ # @return [Property] the attribute meta-data
33
+ def add_attribute(attribute, type, *flags)
34
+ prop = create_nonjava_property(attribute, type, *flags)
35
+ add_property(prop)
36
+ prop
37
+ end
38
+
39
+ # Adds the given attribute restriction to this Class.
40
+ # This method is intended for the exclusive use of {Property.restrict}.
41
+ # Clients restrict an attribute by calling that method.
42
+ #
43
+ # @param [Property] attribute the restricted attribute
44
+ def add_restriction(attribute)
45
+ add_property(attribute)
46
+ logger.debug { "Added restriction #{attribute} to #{qp}." }
47
+ end
48
+
49
+ # @return [<Symbol>] the primary key attributes
50
+ def primary_key_attributes
51
+ @prm_key or Class === self && superclass < Resource ? superclass.primary_key_attributes : Array::EMPTY_ARRAY
52
+ end
53
+
54
+ # Returns this class's secondary key attribute array.
55
+ # If this class's secondary key is not set, then the secondary key is the Metadata superclass
56
+ # secondary key, if any.
57
+ #
58
+ # @return [<Symbol>] the secondary key attributes
59
+ def secondary_key_attributes
60
+ @scnd_key or Class === self && superclass < Resource ? superclass.secondary_key_attributes : Array::EMPTY_ARRAY
61
+ end
62
+
63
+ # Returns this class's alternate key attribute array.
64
+ # If this class's secondary key is not set, then the alternate key is the {Metadata} superclass
65
+ # alternate key, if any.
66
+ #
67
+ # @return [<Symbol>] the alternate key attributes
68
+ def alternate_key_attributes
69
+ @alt_key or superclass < Resource ? superclass.alternate_key_attributes : Array::EMPTY_ARRAY
70
+ end
71
+
72
+ # @return [<Symbol>] the primary, secondary and alternate key attributes
73
+ def all_key_attributes
74
+ primary_key_attributes + secondary_key_attributes + alternate_key_attributes
75
+ end
76
+
77
+ # @yield [prop] operate on the given property
78
+ # @yieldparam [Property] prop the property in this class
79
+ def each_property(&block)
80
+ @prop_hash.each_value(&block)
81
+ end
82
+
83
+ # @return the Property for the given attribute symbol or alias
84
+ # @raise [NameError] if the attribute is not recognized
85
+ def property(attribute)
86
+ # Simple and predominant case is that the attribute is a standard attribute.
87
+ # Otherwise, resolve attribute to the standard symbol.
88
+ prop = @prop_hash[attribute] || @prop_hash[standard_attribute(attribute)]
89
+ # If not found, then raise a NameError.
90
+ if prop.nil? then
91
+ Jinx.fail(NameError, "#{name.demodulize} attribute not found: #{attribute}")
92
+ end
93
+ prop
94
+ end
95
+
96
+ # @param [<Symbol>] attributes an attribute reference path leading from this class
97
+ # @return [<Property>] the corresponding property path
98
+ # @raise [ArgumentError] if there are no attributes or one of the attributes besides the last
99
+ # is not a domain attribute
100
+ # @raise (see #property)
101
+ def property_path(*attributes)
102
+ raise ArgumentError.new("#{self} property path attributes is missing") if attributes.empty?
103
+ # the property of the first attribute
104
+ prop = property(attributes.shift)
105
+ return [prop] if attributes.empty?
106
+ unless prop.type < Resource then
107
+ raise ArgumentError.new("#{self} property path attribute #{prop} is not a domain type")
108
+ end
109
+ # Prepend the first property to the remaining properties.
110
+ prop.type.property_path(*attributes).unshift(prop)
111
+ end
112
+
113
+ # @param [Symbol, String] name_or_alias the attribute name or alias
114
+ # @return [Symbol] the standard attribute symbol for the given name or alias
115
+ # @raise [ArgumentError] if the attribute name or alias argument is missing
116
+ # @raise [NameError] if the attribute is not found
117
+ def standard_attribute(name_or_alias)
118
+ if name_or_alias.nil? then
119
+ Jinx.fail(ArgumentError, "#{qp} standard attribute call is missing the attribute name/alias parameter")
120
+ end
121
+ @alias_std_prop_map[name_or_alias.to_sym] or Jinx.fail(NameError, "#{self} attribute not found: #{name_or_alias}")
122
+ end
123
+
124
+ ## Metadata ATTRIBUTE FILTERS ##
125
+
126
+ # @return [<Symbol>] the domain attributes which wrap a java attribute
127
+ # @see Property#java_property?
128
+ def java_attributes
129
+ @java_flt ||= attribute_filter { |prop| prop.java_property? }
130
+ end
131
+
132
+ alias :printable_attributes :java_attributes
133
+
134
+ # @return [<Symbol>] the domain attributes
135
+ def domain_attributes
136
+ @dom_flt ||= attribute_filter { |prop| prop.domain? }
137
+ end
138
+
139
+ # @return [<Symbol>] the non-domain Java attributes
140
+ def nondomain_attributes
141
+ @ndom_flt ||= attribute_filter { |prop| prop.java_property? and prop.nondomain? }
142
+ end
143
+
144
+ # @return [<Symbol>] the non-domain Java attribute wrapper attributes
145
+ def nondomain_java_attributes
146
+ @ndom_java_flt ||= nondomain_attributes.compose { |prop| prop.java_property? }
147
+ end
148
+
149
+ # @return [<Symbol>] the standard attributes which can be merged into an instance of the subject class.
150
+ # The default mergeable attributes consist of the {#nondomain_java_attributes}.
151
+ # @see Mergeable#mergeable_attributes
152
+ alias :mergeable_attributes :nondomain_java_attributes
153
+
154
+ # @return [<Symbol>] the {Property#independent?} attributes
155
+ def independent_attributes
156
+ @ind_flt ||= attribute_filter { |prop| prop.independent? }
157
+ end
158
+
159
+ # @param [Boolean, nil] inc_super flag indicating whether to include dependents defined in the superclass
160
+ # @return [<Symbol>] the dependent attributes
161
+ def dependent_attributes(inc_super=true)
162
+ if inc_super then
163
+ @dep_flt ||= attribute_filter { |prop| prop.dependent? }
164
+ else
165
+ @local_dep_flt ||= dependent_attributes.compose { |prop| prop.declarer == self }
166
+ end
167
+ end
168
+
169
+ # @return [<Symbol>] the unidirectional dependent attributes
170
+ # @see Property#unidirectional?
171
+ def unidirectional_dependent_attributes
172
+ @uni_dep_flt ||= dependent_attributes.compose { |prop| prop.unidirectional? }
173
+ end
174
+
175
+ # Returns the subject class's required attributes, determined as follows:
176
+ # * An attribute marked with the :mandatory flag is mandatory.
177
+ # * An attribute marked with the :optional or :autogenerated flag is not mandatory.
178
+ # * Otherwise, A secondary key or owner attribute is mandatory.
179
+ def mandatory_attributes
180
+ @mnd_flt ||= collect_mandatory_attributes
181
+ end
182
+
183
+ def nonowner_attributes
184
+ @nownr_atts ||= attribute_filter { |prop| not prop.owner? }
185
+ end
186
+
187
+ # @return [<Symbol>] # the non-owner secondary key domain attributes
188
+ def secondary_key_non_owner_domain_attributes
189
+ @scd_key_nown_flt ||= attribute_filter(secondary_key_attributes) { |prop| prop.domain? and not prop.owner? }
190
+ end
191
+
192
+ # @param [Symbol] attribute the attribute to check
193
+ # @return [Boolean] whether attribute return type is a domain object or collection thereof
194
+ def domain_attribute?(attribute)
195
+ property(attribute).domain?
196
+ end
197
+
198
+ # @param [Symbol] attribute the attribute to check
199
+ # @return [Boolean] whether attribute is not a domain attribute
200
+ def nondomain_attribute?(attribute)
201
+ not domain_attribute?(attribute)
202
+ end
203
+
204
+ # @param [Symbol] attribute the attribute to check
205
+ # @return [Boolean] whether attribute is an instance of a Java domain class
206
+ def collection_attribute?(attribute)
207
+ property(attribute).collection?
208
+ end
209
+
210
+ # Returns an {AttributeEnumerator} on this Resource class's attributes which iterates on each
211
+ # of the given attributes. If a filter block is given, then only those properties which
212
+ # satisfy the filter block are enumerated.
213
+ #
214
+ # @param [<Symbol>, nil] attributes the optional attributes to filter on (default all attributes)
215
+ # @yield [prop] the optional attribute selector
216
+ # @yieldparam [Property] prop the candidate attribute
217
+ # @return [AttributeEnumerator] a new attribute enumerator
218
+ def attribute_filter(attributes=nil, &filter)
219
+ # make the attribute filter
220
+ raise MetadataError.new("#{self} has not been introspected") if @prop_hash.nil?
221
+ ph = attributes ? attributes.to_compact_hash { |pa| @prop_hash[pa] } : @prop_hash
222
+ AttributeEnumerator.new(ph, &filter)
223
+ end
224
+
225
+ protected
226
+
227
+ # @return [{Symbol => Property}] the attribute => metadata hash
228
+ def property_hash
229
+ @prop_hash
230
+ end
231
+
232
+ # @return [{Symbol => Symbol}] the attribute alias => standard hash
233
+ def alias_standard_attribute_hash
234
+ @alias_std_prop_map
235
+ end
236
+
237
+ private
238
+
239
+ # Initializes the property meta-data structures.
240
+ def init_property_classifiers
241
+ @local_std_prop_hash = {}
242
+ @alias_std_prop_map = append_ancestor_enum(@local_std_prop_hash) { |par| par.alias_standard_attribute_hash }
243
+ @local_prop_hash = {}
244
+ @prop_hash = append_ancestor_enum(@local_prop_hash) { |par| par.property_hash }
245
+ @attributes = Enumerable::Enumerator.new(@prop_hash, :each_key)
246
+ @local_mndty_flt = Set.new
247
+ @local_defaults = {}
248
+ @defaults = append_ancestor_enum(@local_defaults) { |par| par.defaults }
249
+ end
250
+
251
+ # @param (see #add_attribute)
252
+ # @return (see #add_attribute)
253
+ def create_nonjava_property(attribute, type, *flags)
254
+ Property.new(attribute, self, type, *flags)
255
+ end
256
+
257
+ # Returns the most specific attribute which references the given target type, or nil if none.
258
+ # If the given class can be returned by more than on of the attributes, then the attribute
259
+ # is chosen whose return type most closely matches the given class.
260
+ #
261
+ # @param [Class] klass the target type
262
+ # @param [AttributeEnumerator, nil] attributes the attributes to check (default all domain attributes)
263
+ # @return [Symbol, nil] the most specific reference attribute, or nil if none
264
+ def most_specific_domain_attribute(klass, attributes=nil)
265
+ attributes ||= domain_attributes
266
+ candidates = attributes.properties
267
+ best = candidates.inject(nil) do |better, prop|
268
+ # If the attribute can return the klass then the return type is a candidate.
269
+ # In that case, the klass replaces the best candidate if it is more specific than
270
+ # the best candidate so far.
271
+ klass <= prop.type ? (better && better.type <= prop.type ? better : prop) : better
272
+ end
273
+ if best then
274
+ logger.debug { "Most specific #{qp} -> #{klass.qp} reference from among #{candidates.qp} is #{best.declarer.qp}.#{best}." }
275
+ best.to_sym
276
+ end
277
+ end
278
+
279
+ # Detects the first attribute with the given type.
280
+ #
281
+ # @param [Class] klass the target attribute type
282
+ # @return [Symbol, nil] the attribute with the given type
283
+ def detect_attribute_with_type(klass)
284
+ property_hash.detect_key_with_value { |prop| prop.type == klass }
285
+ end
286
+
287
+ # Creates the given attribute alias. If the attribute metadata is registered with this class, then
288
+ # this method overrides +Class.alias_attribute+ to create a new alias reader (writer) method
289
+ # which delegates to the attribute reader (writer, resp.). This aliasing mechanism differs from
290
+ # {Class#alias_attribute}, which directly aliases the existing reader or writer method.
291
+ # Delegation allows the alias to pick up run-time redefinitions of the aliased reader and writer.
292
+ # If the attribute metadata is not registered with this class, then this method delegates to
293
+ # {Class#alias_attribute}.
294
+ #
295
+ # @param [Symbol] aliaz the attribute alias
296
+ # @param [Symbol] attribute the attribute to alias
297
+ def alias_attribute(aliaz, attribute)
298
+ if property_defined?(attribute) then
299
+ delegate_to_attribute(aliaz, attribute)
300
+ register_property_alias(aliaz, attribute)
301
+ else
302
+ super
303
+ end
304
+ end
305
+
306
+ # Creates the given aliases to attributes.
307
+ #
308
+ # @param [{Symbol => Symbol}] hash the alias => attribute hash
309
+ # @see #attribute_alias
310
+ # @deprecated Use {#alias_attribute} instead
311
+ def add_attribute_aliases(hash)
312
+ hash.each { |aliaz, pa| alias_attribute(aliaz, pa) }
313
+ end
314
+
315
+ # Adds the given attribute to this class's primary key.
316
+ def add_primary_key_attribute(attribute)
317
+ @prm_key ||= []
318
+ @prm_key << standard_attribute(attribute)
319
+ end
320
+
321
+ # Sets this class's primary key attributes to the given attributes.
322
+ # If attributes is set to nil, then the primary key is cleared.
323
+ def set_primary_key_attributes(*attributes)
324
+ attributes.each { |a| add_primary_key_attribute(a) }
325
+ end
326
+
327
+ # Adds the given attribute to this class's secondary key.
328
+ def add_secondary_key_attribute(attribute)
329
+ @scnd_key ||= []
330
+ @scnd_key << standard_attribute(attribute)
331
+ end
332
+
333
+ # Sets this class's secondary key attributes to the given attributes.
334
+ # If attributes is set to nil, then the secondary key is cleared.
335
+ def set_secondary_key_attributes(*attributes)
336
+ attributes.each { |a| add_secondary_key_attribute(a) }
337
+ end
338
+
339
+ # Adds the given attribute to this class's alternate key.
340
+ def add_alternate_key_attribute(attribute)
341
+ @alt_key ||= []
342
+ @alt_key << standard_attribute(attribute)
343
+ end
344
+
345
+ # Sets this class's alternate key attributes to the given attributes.
346
+ # If attributes is set to nil, then the alternate key is cleared.
347
+ def set_alternate_key_attributes(*attributes)
348
+ attributes.each { |a| add_alternate_key_attribute(a) }
349
+ end
350
+
351
+ # Sets the given attribute type to klass. If attribute is defined in a superclass,
352
+ # then klass must be a subclass of the superclass attribute type.
353
+ #
354
+ # @param [Symbol] attribute the attribute to modify
355
+ # @param [Class] klass the attribute type
356
+ # @raise [ArgumentError] if the new type is incompatible with the current attribute type
357
+ def set_attribute_type(attribute, klass)
358
+ prop = property(attribute)
359
+ # degenerate no-op case
360
+ return if klass == prop.type
361
+ # If this class is the declarer, then simply set the attribute type.
362
+ # Otherwise, if the attribute type is unspecified or is a superclass of the given class,
363
+ # then make a new attribute metadata for this class.
364
+ if prop.declarer == self then
365
+ prop.type = klass
366
+ logger.debug { "Set #{qp}.#{attribute} type to #{klass.qp}." }
367
+ elsif prop.type.nil? or klass < prop.type then
368
+ prop.restrict(self, :type => klass)
369
+ logger.debug { "Restricted #{prop.declarer.qp}.#{attribute}(#{prop.type.qp}) to #{qp} with return type #{klass.qp}." }
370
+ else
371
+ Jinx.fail(ArgumentError, "Cannot reset #{qp}.#{attribute} type #{prop.type.qp} to incompatible #{klass.qp}")
372
+ end
373
+ end
374
+
375
+ # @param [Hash] hash the attribute => value defaults
376
+ def add_attribute_defaults(hash)
377
+ hash.each { |da, value| add_attribute_default(da, value) }
378
+ end
379
+
380
+ # @param [Symbol] attribute the attribute
381
+ # @param value the default value
382
+ def add_attribute_default(attribute, value)
383
+ @local_defaults[standard_attribute(attribute)] = value
384
+ end
385
+
386
+ # @param [<Symbol>] attributes the mandatory attributes
387
+ def add_mandatory_attributes(*attributes)
388
+ attributes.each { |ma| add_mandatory_attribute(ma) }
389
+ end
390
+
391
+ # @param [Symbol] attribute the mandatory attribute
392
+ def add_mandatory_attribute(attribute)
393
+ @local_mndty_flt << standard_attribute(attribute)
394
+ end
395
+
396
+ # Marks the given attribute with flags supported by {Property#qualify}.
397
+ #
398
+ # @param [Symbol] attribute the attribute to qualify
399
+ # @param [{Symbol => Object}] the flags to apply to the restricted attribute
400
+ def qualify_attribute(attribute, *flags)
401
+ prop = property(attribute)
402
+ if prop.declarer == self then
403
+ prop.qualify(*flags)
404
+ else
405
+ logger.debug { "Restricting #{prop.declarer.qp}.#{attribute} to #{qp} with additional flags #{flags.to_series}" }
406
+ prop.restrict_flags(self, *flags)
407
+ end
408
+ end
409
+
410
+ # Removes the given attribute from this Resource.
411
+ # An attribute declared in a superclass Resource is hidden from this Resource but retained in
412
+ # the declaring Resource.
413
+ def remove_attribute(attribute)
414
+ std_prop = standard_attribute(attribute)
415
+ # if the attribute is local, then delete it, otherwise filter out the superclass attribute
416
+ prop = @local_prop_hash.delete(std_prop)
417
+ if prop then
418
+ # clear the inverse, if any
419
+ prop.inverse = nil
420
+ # remove from the mandatory attributes, if necessary
421
+ @local_mndty_flt.delete(std_prop)
422
+ # remove from the attribute => metadata hash
423
+ @local_std_prop_hash.delete_if { |aliaz, pa| pa == std_prop }
424
+ else
425
+ # Filter the superclass hashes.
426
+ anc_prop_hash = @prop_hash.components[1]
427
+ @prop_hash.components[1] = anc_prop_hash.filter_on_key { |pa| pa != attribute }
428
+ anc_alias_hash = @alias_std_prop_map.components[1]
429
+ @alias_std_prop_map.components[1] = anc_alias_hash.filter_on_key { |pa| pa != attribute }
430
+ end
431
+ end
432
+
433
+ # @param [Property] the property to add
434
+ def add_property(property)
435
+ pa = property.attribute
436
+ @local_prop_hash[pa] = property
437
+ # map the attribute symbol to itself in the alias map
438
+ @local_std_prop_hash[pa] = pa
439
+ end
440
+
441
+ # Registers an alias to an attribute.
442
+ #
443
+ # @param (see #alias_attribute)
444
+ def register_property_alias(aliaz, attribute)
445
+ std = standard_attribute(attribute)
446
+ Jinx.fail(ArgumentError, "#{self} attribute not found: #{attribute}") if std.nil?
447
+ @local_std_prop_hash[aliaz.to_sym] = std
448
+ end
449
+
450
+ # Appends to the given enumerable the result of evaluating the block given to this method
451
+ # on the superclass, if the superclass is in the same parent module as this class.
452
+ #
453
+ # @param [Enumerable] enum the base collection
454
+ # @return [Enumerable] the {Enumerable#union} of the base collection with the superclass
455
+ # collection, if applicable
456
+ def append_ancestor_enum(enum)
457
+ return enum unless Class === self and superclass.parent_module == parent_module
458
+ anc_enum = yield superclass
459
+ if anc_enum.nil? then
460
+ Jinx.fail(MetadataError, "#{qp} superclass #{superclass.qp} does not have required metadata")
461
+ end
462
+ enum.union(anc_enum)
463
+ end
464
+
465
+ # Makes a new synthetic {Class#offset_attr_accessor} attribute for each
466
+ # _method_ => _original_ hash entry.
467
+ #
468
+ # @param (see Class#offset_attr_accessor)
469
+ def offset_attribute(hash, offset=nil)
470
+ offset_attr_accessor(hash, offset)
471
+ hash.each { |ja, original| add_attribute(ja, property(original).type) }
472
+ end
473
+
474
+ # Merges the secondary key, owner and additional mandatory attributes defined in the attributes.
475
+ #
476
+ # @see #mandatory_attributes
477
+ def collect_mandatory_attributes
478
+ @local_mndty_flt.merge!(default_mandatory_local_attributes)
479
+ append_ancestor_enum(@local_mndty_flt) { |par| par.mandatory_attributes }
480
+ end
481
+
482
+ def default_mandatory_local_attributes
483
+ mandatory = Set.new
484
+ # add the secondary key
485
+ mandatory.merge(secondary_key_attributes)
486
+ # add the owner attribute, if any
487
+ oa = mandatory_owner_attribute
488
+ mandatory << oa if oa
489
+ # remove optional attributes
490
+ mandatory.delete_if { |ma| property(ma).flags.include?(:optional) }
491
+ end
492
+
493
+ # @return [Symbol, nil] the unique non-self-referential owner attribute, if one exists
494
+ def mandatory_owner_attribute
495
+ oa = owner_attribute || return
496
+ prop = property(oa)
497
+ oa if prop.java_property? and prop.type != self
498
+ end
499
+ end
500
+ end