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