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,401 @@
1
+ require 'set'
2
+ require 'jinx/helpers/inflector'
3
+ require 'jinx/helpers/collections'
4
+ require 'jinx/helpers/validation'
5
+ require 'jinx/metadata/property_characteristics'
6
+
7
+ module Jinx
8
+ # A Property captures the following metadata about a domain class attribute:
9
+ # * attribute symbol
10
+ # * declarer type
11
+ # * return type
12
+ # * reader method symbol
13
+ # * writer method symbol
14
+ class Property
15
+ include PropertyCharacteristics
16
+
17
+ # The supported property qualifier flags. See the complementary methods for an explanation of
18
+ # the flag option, e.g. {#dependent?} for the +:dependent+ flag.
19
+ #
20
+ # Included persistence adapters should add specialized flags to this set. An unsupported flag
21
+ # is allowed and can be used by adapters, but a warning log message is issued in that case.
22
+ SUPPORTED_FLAGS = [
23
+ :collection, :dependent, :disjoint, :owner, :mandatory, :optional].to_set
24
+
25
+ # @return [Symbol] the standard attribute symbol for this property
26
+ attr_reader :attribute
27
+
28
+ # @return [(Symbol, Symbol)] the standard attribute reader and writer methods
29
+ attr_reader :accessors
30
+
31
+ # @return [Class] the declaring class
32
+ attr_reader :declarer
33
+
34
+ # @return [Class] the return type
35
+ attr_reader :type
36
+
37
+ # @return [<Symbol>] the qualifier flags
38
+ # @see SUPPORTED_FLAGS
39
+ attr_reader :flags
40
+
41
+ # Creates a new Property from the given attribute.
42
+ #
43
+ # The return type is the referenced entity type. An attribute whose return type is a
44
+ # collection of domain objects is thus the domain object class rather than a collection
45
+ # class.
46
+ #
47
+ # @param [String, Symbol] pa the subject attribute
48
+ # @param [Class] declarer the declaring class
49
+ # @param [Class] type the return type
50
+ # @param [<Symbol>] flags the qualifying {#flags}
51
+ def initialize(attribute, declarer, type=nil, *flags)
52
+ # the attribute symbol
53
+ @attribute = attribute.to_sym
54
+ # the declaring class
55
+ @declarer = declarer
56
+ # the Ruby class
57
+ @type = Class.to_ruby(type) if type
58
+ # the read and write methods
59
+ @accessors = [@attribute, "#{attribute}=".to_sym]
60
+ # the qualifier flags
61
+ @flags = Set.new
62
+ qualify(*flags)
63
+ end
64
+
65
+ # @return [Symbol] the reader method
66
+ def reader
67
+ accessors.first
68
+ end
69
+
70
+ # @return [Symbol] the writer method
71
+ def writer
72
+ accessors.last
73
+ end
74
+
75
+ # @return [Symbol, nil] the inverse of this attribute, if any
76
+ def inverse
77
+ @inv_prop.attribute if @inv_prop
78
+ end
79
+
80
+ # An attribute is unidirectional if both of the following is true:
81
+ # * there is no distinct {#inverse} attribute
82
+ # * the attribute is not a {#dependent?} with more than one owner
83
+ #
84
+ # @return [Boolean] whether this attribute does not have an inverse
85
+ def unidirectional?
86
+ inverse.nil? and not (dependent? and type.owner_attributes.size > 1)
87
+ end
88
+
89
+ # @param [Class] the attribute return type
90
+ def type=(klass)
91
+ return if klass == @type
92
+ @type = klass
93
+ if @inv_prop then
94
+ self.inverse = @inv_prop.attribute
95
+ logger.debug { "Reset #{@declarer.qp}.#{self} inverse from #{@inv_prop.type}.#{@inv_prop} to #{klass}#{@inv_prop}." }
96
+ end
97
+ end
98
+
99
+ # Creates a new declarer attribute which qualifies this attribute for the given declarer.
100
+ #
101
+ # @param declarer (see #restrict)
102
+ # @param [<Symbol>] flags the additional flags for the restricted attribute
103
+ # @return (see #restrict)
104
+ def restrict_flags(declarer, *flags)
105
+ copy = restrict(declarer)
106
+ copy.qualify(*flags)
107
+ copy
108
+ end
109
+
110
+ # Sets the inverse of the subject attribute to the given attribute.
111
+ # The inverse relation is symmetric, i.e. the inverse of the referenced Property
112
+ # is set to this Property's subject attribute.
113
+ #
114
+ # @param [Symbol, nil] attribute the inverse attribute
115
+ # @raise [MetadataError] if the the inverse of the inverse is already set to a different attribute
116
+ def inverse=(attribute)
117
+ return if inverse == attribute
118
+ # if no attribute, then the clear the existing inverse, if any
119
+ return clear_inverse if attribute.nil?
120
+ # the inverse attribute meta-data
121
+ begin
122
+ @inv_prop = type.property(attribute)
123
+ rescue NameError => e
124
+ Jinx.fail(MetadataError, "#{@declarer.qp}.#{self} inverse attribute #{type.qp}.#{attribute} not found", e)
125
+ end
126
+ # the inverse of the inverse
127
+ inv_inv_prop = @inv_prop.inverse_property
128
+ # If the inverse of the inverse is already set to a different attribute, then raise an exception.
129
+ if inv_inv_prop and not (inv_inv_prop == self or inv_inv_prop.restriction?(self))
130
+ Jinx.fail(MetadataError, "Cannot set #{type.qp}.#{attribute} inverse attribute to #{@declarer.qp}.#{self}@#{object_id} since it conflicts with existing inverse #{inv_inv_prop.declarer.qp}.#{inv_inv_prop}@#{inv_inv_prop.object_id}")
131
+ end
132
+ # Set the inverse of the inverse to this attribute.
133
+ @inv_prop.inverse = @attribute
134
+ # If this attribute is disjoint, then so is the inverse.
135
+ @inv_prop.qualify(:disjoint) if disjoint?
136
+ logger.debug { "Assigned #{@declarer.qp}.#{self} attribute inverse to #{type.qp}.#{attribute}." }
137
+ end
138
+
139
+ # @return [Boolean] whether this property has an inverse
140
+ def bidirectional?
141
+ !!@inv_prop
142
+ end
143
+
144
+ # @return [Property, nil] the property for the {#inverse} attribute, if any
145
+ def inverse_property
146
+ @inv_prop
147
+ end
148
+
149
+ # Qualifies this attribute with the given flags. Supported flags are listed in {SUPPORTED_FLAGS}.
150
+ #
151
+ # @param [<Symbol>] the flags to add
152
+ # @raise [ArgumentError] if the flag is not supported
153
+ def qualify(*flags)
154
+ flags.each { |flag| set_flag(flag) }
155
+ # propagate to restrictions
156
+ if @restrictions then @restrictions.each { |prop| prop.qualify(*flags) } end
157
+ end
158
+
159
+ # @return [Boolean] whether the subject attribute encapsulates a Java attribute
160
+ def java_property?
161
+ JavaProperty === self
162
+ end
163
+
164
+ # @return [Boolean] whether the subject attribute returns a domain object or collection of domain objects
165
+ def domain?
166
+ # the type must be a Ruby class rather than a Java Class, and include the Domain mix-in
167
+ Class === type and type < Resource
168
+ end
169
+
170
+ # @return [Boolean] whether the subject attribute is not a domain object attribute
171
+ def nondomain?
172
+ not domain?
173
+ end
174
+
175
+ # @return [Boolean] whether the subject attribute return type is a collection
176
+ def collection?
177
+ @flags.include?(:collection)
178
+ end
179
+
180
+ # Returns whether the subject attribute is a dependent on a parent. See the Jinx configuration
181
+ # documentation for a dependency description.
182
+ #
183
+ # @return [Boolean] whether the attribute references a dependent
184
+ def dependent?
185
+ @flags.include?(:dependent)
186
+ end
187
+
188
+ # Returns whether the subject attribute must have a value when it is saved
189
+ #
190
+ # @return [Boolean] whether the attribute is mandatory
191
+ def mandatory?
192
+ @declarer.mandatory_attributes.include?(attribute)
193
+ end
194
+
195
+ # An attribute is derived if the attribute value is set by setting another attribute, e.g. if this
196
+ # attribute is the inverse of a dependent owner attribute.
197
+ #
198
+ # @return [Boolean] whether this attribute is derived from another attribute
199
+ def derived?
200
+ dependent? and !!inverse
201
+ end
202
+
203
+ # @return [Boolean] this attribute's inverse attribute if the inverse is a derived attribute, or nil otherwise
204
+ def derived_inverse
205
+ @inv_prop.attribute if @inv_prop and @inv_prop.derived?
206
+ end
207
+
208
+ # An independent attribute is a reference to one or more non-dependent Resource objects.
209
+ # An {#owner?} attribute is independent.
210
+ #
211
+ # @return [Boolean] whether the subject attribute is a non-dependent domain attribute
212
+ def independent?
213
+ domain? and not dependent?
214
+ end
215
+
216
+ # @return [Boolean] whether this attribute is a collection with a collection inverse
217
+ def many_to_many?
218
+ return false unless collection?
219
+ inv_prop = inverse_property
220
+ inv_prop and inv_prop.collection?
221
+ end
222
+
223
+ # @return [Boolean] whether the subject attribute is a dependency owner
224
+ def owner?
225
+ @flags.include?(:owner)
226
+ end
227
+
228
+ # @return [Boolean] whether this is a dependent attribute which has exactly one owner value
229
+ # chosen from several owner attributes
230
+ def disjoint?
231
+ @flags.include?(:disjoint)
232
+ end
233
+
234
+ # @return [Boolean] whether this attribute is a dependent which does not have a Java
235
+ # inverse owner attribute
236
+ def unidirectional_java_dependent?
237
+ dependent? and java_property? and not bidirectional_java_association?
238
+ end
239
+
240
+ # @return [Boolean] whether this is a Java attribute which has a Java inverse
241
+ def bidirectional_java_association?
242
+ inverse and java_property? and inverse_property.java_property?
243
+ end
244
+
245
+ # Creates a new declarer attribute which restricts this attribute.
246
+ # This method should only be called by a {Resource} class, since the class is responsible
247
+ # for resetting the attribute symbol => meta-data association to point to the new restricted
248
+ # attribute.
249
+ #
250
+ # If this attribute has an inverse, then the restriction inverse is set to the attribute
251
+ # declared by the restriction declarer'. For example, if:
252
+ # * +AbstractProtocol.coordinator+ has inverse +Administrator.protocol+
253
+ # * +AbstractProtocol+ has subclass +StudyProtocol+
254
+ # * +StudyProtocol.coordinator+ returns a +StudyCoordinator+
255
+ # * +AbstractProtocol.coordinator+ is restricted to +StudyProtocol+
256
+ # then calling this method on the +StudyProtocol.coordinator+ restriction
257
+ # sets the +StudyProtocol.coordinator+ inverse to +StudyCoordinator.coordinator+.
258
+ #
259
+ # @param [Class] declarer the subclass which declares the new restricted attribute
260
+ # @param [Hash, nil] opts the restriction options
261
+ # @option opts [Class] type the restriction return type (default this attribute's return type)
262
+ # @option opts [Symbol] type the restriction inverse (default this attribute's inverse)
263
+ # @return [Property] the new restricted attribute
264
+ # @raise [ArgumentError] if the restricted declarer is not a subclass of this attribute's declarer
265
+ # @raise [ArgumentError] if there is a restricted return type and it is not a subclass of this
266
+ # attribute's return type
267
+ # @raise [MetadataError] if this attribute has an inverse that is not independently declared by
268
+ # the restricted declarer subclass
269
+ def restrict(declarer, opts={})
270
+ rtype = opts[:type] || @type
271
+ rinv = opts[:inverse] || inverse
272
+ unless declarer < @declarer then
273
+ Jinx.fail(ArgumentError, "Cannot restrict #{@declarer.qp}.#{self} to an incompatible declarer type #{declarer.qp}")
274
+ end
275
+ unless rtype <= @type then
276
+ Jinx.fail(ArgumentError, "Cannot restrict #{@declarer.qp}.#{self}({@type.qp}) to an incompatible return type #{rtype.qp}")
277
+ end
278
+ # Copy this attribute and its instance variables minus the restrictions and make a deep copy of the flags.
279
+ rst = deep_copy
280
+ # specialize the copy declarer
281
+ rst.set_restricted_declarer(declarer)
282
+ # Capture the restriction to propagate modifications to this metadata, esp. adding an inverse.
283
+ @restrictions ||= []
284
+ @restrictions << rst
285
+ # Set the restriction type
286
+ rst.type = rtype
287
+ # Specialize the inverse to the restricted type attribute, if necessary.
288
+ rst.inverse = rinv
289
+ rst
290
+ end
291
+
292
+ alias :to_sym :attribute
293
+
294
+ def to_s
295
+ attribute.to_s
296
+ end
297
+
298
+ alias :inspect :to_s
299
+
300
+ alias :qp :to_s
301
+
302
+ protected
303
+
304
+ # Duplicates the mutable content as part of a {#deep_copy}.
305
+ def dup_content
306
+ # keep the copied flags but don't share them
307
+ @flags = @flags.dup
308
+ # restrictions and inverse are neither shared nor copied
309
+ @inv_prop = @restrictions = nil
310
+ end
311
+
312
+ # @param [Property] other the other attribute to check
313
+ # @return [Boolean] whether the other attribute restricts this attribute
314
+ def restriction?(other)
315
+ @restrictions and @restrictions.include?(other)
316
+ end
317
+
318
+ # @param [Class] klass the declaring class of this restriction attribute
319
+ def set_restricted_declarer(klass)
320
+ if @declarer and not klass < @declarer then
321
+ Jinx.fail(MetadataError, "Cannot reset #{declarer.qp}.#{self} declarer to #{type.qp}")
322
+ end
323
+ @declarer = klass
324
+ @declarer.add_restriction(self)
325
+ end
326
+
327
+ private
328
+
329
+ # @param [Symbol] the flag to set
330
+ # @return [Boolean] whether the flag is supported
331
+ def flag_supported?(flag)
332
+ SUPPORTED_FLAGS.include?(flag)
333
+ end
334
+
335
+ # Creates a copy of this metadata which does not share mutable content.
336
+ #
337
+ # The copy instance variables are as follows:
338
+ # * the copy inverse and restrictions are empty
339
+ # * the copy flags is a deep copy of this attribute's flags
340
+ # * other instance variable references are shared between the copy and this attribute
341
+ #
342
+ # @return [Property] the copied attribute
343
+ def deep_copy
344
+ other = dup
345
+ other.dup_content
346
+ other
347
+ end
348
+
349
+ def clear_inverse
350
+ return unless @inv_prop
351
+ logger.debug { "Clearing #{@declarer.qp}.#{self} inverse #{type.qp}.#{inverse}..." }
352
+ # Capture the inverse before unsetting it.
353
+ inv_prop = @inv_prop
354
+ # Unset the inverse.
355
+ @inv_prop = nil
356
+ # Clear the inverse of the inverse.
357
+ inv_prop.inverse = nil
358
+ logger.debug { "Cleared #{@declarer.qp}.#{self} inverse." }
359
+ end
360
+
361
+ # @param [Symbol] the flag to set
362
+ # @raise [ArgumentError] if the flag is not supported
363
+ def set_flag(flag)
364
+ return if @flags.include?(flag)
365
+ unless flag_supported?(flag) then
366
+ Jinx.fail(ArgumentError, "Property #{declarer.name}.#{self} flag not supported: #{flag.qp}")
367
+ end
368
+ @flags << flag
369
+ case flag
370
+ when :owner then owner_flag_set
371
+ when :dependent then dependent_flag_set
372
+ end
373
+ end
374
+
375
+ # This method is called when the owner flag is set.
376
+ # The inverse is inferred as the referenced owner type's dependent attribute which references
377
+ # this attribute's type.
378
+ #
379
+ # @raise [MetadataError] if this attribute is dependent or an inverse could not be inferred
380
+ def owner_flag_set
381
+ if dependent? then
382
+ Jinx.fail(MetadataError, "#{declarer.qp}.#{self} cannot be set as a #{type.qp} owner since it is already defined as a #{type.qp} dependent")
383
+ end
384
+ inv_attr = type.dependent_attribute(@declarer)
385
+ if inv_attr.nil? then
386
+ Jinx.fail(MetadataError, "#{@declarer.qp} owner attribute #{self} does not have a #{type.qp} dependent inverse")
387
+ end
388
+ logger.debug { "#{declarer.qp}.#{self} inverse is the #{type.qp} dependent attribute #{inv_attr}." }
389
+ self.inverse = inv_attr
390
+ end
391
+
392
+ # Validates that this is not an owner attribute.
393
+ #
394
+ # @raise [MetadataError] if this is an owner attribute
395
+ def dependent_flag_set
396
+ if owner? then
397
+ Jinx.fail(MetadataError, "#{declarer.qp}.#{self} cannot be set as a #{type.qp} dependent since it is already defined as a #{type.qp} owner")
398
+ end
399
+ end
400
+ end
401
+ end
@@ -0,0 +1,114 @@
1
+ require 'set'
2
+ require 'jinx/helpers/inflector'
3
+ require 'jinx/helpers/collections'
4
+
5
+ require 'jinx/helpers/validation'
6
+ require 'jinx/metadata/java_property'
7
+
8
+ module Jinx
9
+ # The PropertyCharacteristics mix-in queries the {Property} flags and features.
10
+ module PropertyCharacteristics
11
+ # An attribute is unidirectional if both of the following is true:
12
+ # * there is no distinct {#inverse} attribute
13
+ # * the attribute is not a {#dependent?} with more than one owner
14
+ #
15
+ # @return [Boolean] whether this attribute does not have an inverse
16
+ def unidirectional?
17
+ inverse.nil? and not (dependent? and type.owner_attributes.size > 1)
18
+ end
19
+
20
+ # @return [Boolean] whether this property has an inverse
21
+ def bidirectional?
22
+ !!@inv_prop
23
+ end
24
+
25
+ # @return [Boolean] whether the subject attribute encapsulates a Java attribute
26
+ def java_property?
27
+ JavaProperty === self
28
+ end
29
+
30
+ # @return [Boolean] whether the subject attribute returns a domain object or collection of domain objects
31
+ def domain?
32
+ # the type must be a Ruby class rather than a Java Class, and include the Domain mix-in
33
+ Class === type and type < Resource
34
+ end
35
+
36
+ # @return [Boolean] whether the subject attribute is not a domain object attribute
37
+ def nondomain?
38
+ not domain?
39
+ end
40
+
41
+ # @return [Boolean] whether the subject attribute return type is a collection
42
+ def collection?
43
+ @flags.include?(:collection)
44
+ end
45
+
46
+ # Returns whether the subject attribute is a dependent on a parent. See the Jinx configuration
47
+ # documentation for a dependency description.
48
+ #
49
+ # @return [Boolean] whether the attribute references a dependent
50
+ def dependent?
51
+ @flags.include?(:dependent)
52
+ end
53
+
54
+ # Returns whether the subject attribute must have a value when it is saved
55
+ #
56
+ # @return [Boolean] whether the attribute is mandatory
57
+ def mandatory?
58
+ @declarer.mandatory_attributes.include?(attribute)
59
+ end
60
+
61
+ # An attribute is derived if the attribute value is set by setting another attribute, e.g. if this
62
+ # attribute is the inverse of a dependent owner attribute.
63
+ #
64
+ # @return [Boolean] whether this attribute is derived from another attribute
65
+ def derived?
66
+ dependent? and !!inverse
67
+ end
68
+
69
+ # An independent attribute is a reference to one or more non-dependent Resource objects.
70
+ # An {#owner?} attribute is independent.
71
+ #
72
+ # @return [Boolean] whether the subject attribute is a non-dependent domain attribute
73
+ def independent?
74
+ domain? and not dependent?
75
+ end
76
+
77
+ # @return [Boolean] whether this attribute is a collection with a collection inverse
78
+ def many_to_many?
79
+ return false unless collection?
80
+ inv_prop = inverse_property
81
+ inv_prop and inv_prop.collection?
82
+ end
83
+
84
+ # @return [Boolean] whether the subject attribute is a dependency owner
85
+ def owner?
86
+ @flags.include?(:owner)
87
+ end
88
+
89
+ # @return [Boolean] whether this is a dependent attribute which has exactly one owner value
90
+ # chosen from several owner attributes.
91
+ def disjoint?
92
+ @flags.include?(:disjoint)
93
+ end
94
+
95
+ # @return [Boolean] whether this attribute is a dependent which does not have a Java
96
+ # inverse owner attribute
97
+ def unidirectional_java_dependent?
98
+ dependent? and java_property? and not bidirectional_java_association?
99
+ end
100
+
101
+ # @return [Boolean] whether this is a Java attribute which has a Java inverse
102
+ def bidirectional_java_association?
103
+ inverse and java_property? and inverse_property.java_property?
104
+ end
105
+
106
+ protected
107
+
108
+ # @param [Property] other the other attribute to check
109
+ # @return [Boolean] whether the other attribute restricts this attribute
110
+ def restriction?(other)
111
+ @restrictions and @restrictions.include?(other)
112
+ end
113
+ end
114
+ end