jinx 2.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +14 -0
- data/.rspec +3 -0
- data/.yardopts +1 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +27 -0
- data/History.md +6 -0
- data/LEGAL +5 -0
- data/LICENSE +22 -0
- data/README.md +44 -0
- data/Rakefile +41 -0
- data/examples/family/README.md +10 -0
- data/examples/family/ext/build.xml +35 -0
- data/examples/family/ext/src/family/Address.java +68 -0
- data/examples/family/ext/src/family/Child.java +24 -0
- data/examples/family/ext/src/family/DomainObject.java +26 -0
- data/examples/family/ext/src/family/Household.java +36 -0
- data/examples/family/ext/src/family/Parent.java +48 -0
- data/examples/family/ext/src/family/Person.java +42 -0
- data/examples/family/lib/family.rb +15 -0
- data/examples/family/lib/family/address.rb +6 -0
- data/examples/family/lib/family/domain_object.rb +6 -0
- data/examples/family/lib/family/household.rb +6 -0
- data/examples/family/lib/family/parent.rb +16 -0
- data/examples/family/lib/family/person.rb +6 -0
- data/examples/model/README.md +25 -0
- data/examples/model/ext/build.xml +35 -0
- data/examples/model/ext/src/domain/Child.java +192 -0
- data/examples/model/ext/src/domain/Dependent.java +29 -0
- data/examples/model/ext/src/domain/DomainObject.java +26 -0
- data/examples/model/ext/src/domain/Independent.java +83 -0
- data/examples/model/ext/src/domain/Parent.java +129 -0
- data/examples/model/ext/src/domain/Person.java +14 -0
- data/examples/model/lib/model.rb +13 -0
- data/examples/model/lib/model/child.rb +13 -0
- data/examples/model/lib/model/domain_object.rb +6 -0
- data/examples/model/lib/model/independent.rb +11 -0
- data/examples/model/lib/model/parent.rb +17 -0
- data/jinx.gemspec +22 -0
- data/lib/jinx.rb +3 -0
- data/lib/jinx/active_support/README.txt +2 -0
- data/lib/jinx/active_support/core_ext/string.rb +7 -0
- data/lib/jinx/active_support/core_ext/string/inflections.rb +167 -0
- data/lib/jinx/active_support/inflections.rb +55 -0
- data/lib/jinx/active_support/inflector.rb +398 -0
- data/lib/jinx/cli/application.rb +36 -0
- data/lib/jinx/cli/command.rb +214 -0
- data/lib/jinx/helpers/array.rb +108 -0
- data/lib/jinx/helpers/boolean.rb +42 -0
- data/lib/jinx/helpers/case_insensitive_hash.rb +39 -0
- data/lib/jinx/helpers/class.rb +149 -0
- data/lib/jinx/helpers/collection.rb +33 -0
- data/lib/jinx/helpers/collections.rb +11 -0
- data/lib/jinx/helpers/collector.rb +20 -0
- data/lib/jinx/helpers/conditional_enumerator.rb +21 -0
- data/lib/jinx/helpers/enumerable.rb +242 -0
- data/lib/jinx/helpers/enumerate.rb +35 -0
- data/lib/jinx/helpers/error.rb +15 -0
- data/lib/jinx/helpers/file_separator.rb +65 -0
- data/lib/jinx/helpers/filter.rb +52 -0
- data/lib/jinx/helpers/flattener.rb +38 -0
- data/lib/jinx/helpers/hash.rb +12 -0
- data/lib/jinx/helpers/hashable.rb +502 -0
- data/lib/jinx/helpers/inflector.rb +36 -0
- data/lib/jinx/helpers/key_transformer_hash.rb +43 -0
- data/lib/jinx/helpers/lazy_hash.rb +44 -0
- data/lib/jinx/helpers/log.rb +106 -0
- data/lib/jinx/helpers/math.rb +12 -0
- data/lib/jinx/helpers/merge.rb +60 -0
- data/lib/jinx/helpers/module.rb +18 -0
- data/lib/jinx/helpers/multi_enumerator.rb +31 -0
- data/lib/jinx/helpers/options.rb +92 -0
- data/lib/jinx/helpers/os.rb +19 -0
- data/lib/jinx/helpers/partial_order.rb +37 -0
- data/lib/jinx/helpers/pretty_print.rb +207 -0
- data/lib/jinx/helpers/set.rb +8 -0
- data/lib/jinx/helpers/stopwatch.rb +76 -0
- data/lib/jinx/helpers/transformer.rb +24 -0
- data/lib/jinx/helpers/transitive_closure.rb +55 -0
- data/lib/jinx/helpers/uniquifier.rb +50 -0
- data/lib/jinx/helpers/validation.rb +33 -0
- data/lib/jinx/helpers/visitor.rb +370 -0
- data/lib/jinx/import/class_path_modifier.rb +77 -0
- data/lib/jinx/import/java.rb +337 -0
- data/lib/jinx/importer.rb +240 -0
- data/lib/jinx/metadata.rb +155 -0
- data/lib/jinx/metadata/attribute_enumerator.rb +73 -0
- data/lib/jinx/metadata/dependency.rb +244 -0
- data/lib/jinx/metadata/id_alias.rb +23 -0
- data/lib/jinx/metadata/introspector.rb +179 -0
- data/lib/jinx/metadata/inverse.rb +170 -0
- data/lib/jinx/metadata/java_property.rb +169 -0
- data/lib/jinx/metadata/propertied.rb +500 -0
- data/lib/jinx/metadata/property.rb +401 -0
- data/lib/jinx/metadata/property_characteristics.rb +114 -0
- data/lib/jinx/resource.rb +862 -0
- data/lib/jinx/resource/copy_visitor.rb +36 -0
- data/lib/jinx/resource/inversible.rb +90 -0
- data/lib/jinx/resource/match_visitor.rb +180 -0
- data/lib/jinx/resource/matcher.rb +20 -0
- data/lib/jinx/resource/merge_visitor.rb +73 -0
- data/lib/jinx/resource/mergeable.rb +185 -0
- data/lib/jinx/resource/reference_enumerator.rb +49 -0
- data/lib/jinx/resource/reference_path_visitor.rb +38 -0
- data/lib/jinx/resource/reference_visitor.rb +55 -0
- data/lib/jinx/resource/unique.rb +35 -0
- data/lib/jinx/version.rb +3 -0
- data/spec/defaults_spec.rb +30 -0
- data/spec/definitions/model/alias/child.rb +5 -0
- data/spec/definitions/model/base/child.rb +5 -0
- data/spec/definitions/model/base/domain_object.rb +5 -0
- data/spec/definitions/model/base/independent.rb +5 -0
- data/spec/definitions/model/defaults/child.rb +5 -0
- data/spec/definitions/model/dependency/child.rb +5 -0
- data/spec/definitions/model/dependency/parent.rb +6 -0
- data/spec/definitions/model/inverse/child.rb +5 -0
- data/spec/definitions/model/inverse/independent.rb +5 -0
- data/spec/definitions/model/inverse/parent.rb +5 -0
- data/spec/definitions/model/mandatory/child.rb +6 -0
- data/spec/dependency_spec.rb +47 -0
- data/spec/family_spec.rb +64 -0
- data/spec/inverse_spec.rb +53 -0
- data/spec/mandatory_spec.rb +43 -0
- data/spec/metadata_spec.rb +68 -0
- data/spec/resource_spec.rb +30 -0
- data/spec/spec_helper.rb +3 -0
- data/spec/support/model.rb +19 -0
- data/test/fixtures/line_separator/cr_line_sep.txt +1 -0
- data/test/fixtures/line_separator/crlf_line_sep.txt +3 -0
- data/test/fixtures/line_separator/lf_line_sep.txt +3 -0
- data/test/fixtures/mixed/ext/build.xml +35 -0
- data/test/fixtures/mixed/ext/src/mixed/Case/Example.java +5 -0
- data/test/helper.rb +7 -0
- data/test/lib/jinx/command_test.rb +41 -0
- data/test/lib/jinx/helpers/boolean_test.rb +27 -0
- data/test/lib/jinx/helpers/class_test.rb +60 -0
- data/test/lib/jinx/helpers/collections_test.rb +402 -0
- data/test/lib/jinx/helpers/file_separator_test.rb +29 -0
- data/test/lib/jinx/helpers/inflector_test.rb +11 -0
- data/test/lib/jinx/helpers/lazy_hash_test.rb +32 -0
- data/test/lib/jinx/helpers/module_test.rb +24 -0
- data/test/lib/jinx/helpers/options_test.rb +66 -0
- data/test/lib/jinx/helpers/partial_order_test.rb +41 -0
- data/test/lib/jinx/helpers/pretty_print_test.rb +83 -0
- data/test/lib/jinx/helpers/stopwatch_test.rb +16 -0
- data/test/lib/jinx/helpers/transitive_closure_test.rb +80 -0
- data/test/lib/jinx/helpers/visitor_test.rb +288 -0
- data/test/lib/jinx/import/java_test.rb +78 -0
- data/test/lib/jinx/import/mixed_case_test.rb +16 -0
- 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
|