jinx 2.1.1

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