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