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,155 @@
|
|
|
1
|
+
require 'jinx/helpers/collections'
|
|
2
|
+
require 'jinx/import/java'
|
|
3
|
+
require 'jinx/metadata/java_property'
|
|
4
|
+
require 'jinx/metadata/introspector'
|
|
5
|
+
require 'jinx/metadata/inverse'
|
|
6
|
+
require 'jinx/metadata/dependency'
|
|
7
|
+
|
|
8
|
+
module Jinx
|
|
9
|
+
# Exception raised if a meta-data setting is missing or invalid.
|
|
10
|
+
class MetadataError < RuntimeError; end
|
|
11
|
+
|
|
12
|
+
# The metadata introspection mix-in for a Java application domain class or interface.
|
|
13
|
+
module Metadata
|
|
14
|
+
include Introspector, Inverse, Dependency
|
|
15
|
+
|
|
16
|
+
# @return [Module] the application domain {Resource} module which introspected this class
|
|
17
|
+
attr_accessor :domain_module
|
|
18
|
+
|
|
19
|
+
# @param [Symbol] attribute the property attribute
|
|
20
|
+
# @param [{Symbol => Object}] opts the property options
|
|
21
|
+
# @option opts [true, Symbol, <Symbol>] :dependent whether this property is a dependent reference
|
|
22
|
+
# qualified by the given {Property} flags
|
|
23
|
+
def property(attribute, *opts)
|
|
24
|
+
return super(attribute) if opts.empty?
|
|
25
|
+
Options.to_hash(*opts).each do |k, v|
|
|
26
|
+
case k
|
|
27
|
+
when :alias then alias_attribute(v, attribute)
|
|
28
|
+
when :default then add_attribute_default(attribute, v)
|
|
29
|
+
when :dependent then add_dependent_attribute(attribute) if v
|
|
30
|
+
when :inverse then set_attribute_inverse(attribute, v)
|
|
31
|
+
when :mandatory then add_mandatory_attribute(attribute) if v
|
|
32
|
+
when :primary_key then add_primary_key_attribute(attribute) if v
|
|
33
|
+
when :secondary_key then add_secondary_key_attribute(attribute) if v
|
|
34
|
+
when :alternate_key then add_alternate_key_attribute(attribute) if v
|
|
35
|
+
when :type then set_attribute_type(attribute, v)
|
|
36
|
+
else qualify_attribute(attribute, k) if v
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# @return [Class, nil] the domain type for attribute, or nil if attribute is not a domain attribute
|
|
42
|
+
def domain_type(attribute)
|
|
43
|
+
prop = property(attribute)
|
|
44
|
+
prop.type if prop.domain?
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Returns an empty value for the given attribute.
|
|
48
|
+
# * If this class is not abstract, then the empty value is the initialized value.
|
|
49
|
+
# * Otherwise, if the attribute is a Java primitive number then zero.
|
|
50
|
+
# * Otherwise, if the attribute is a Java primitive boolean then +false+.
|
|
51
|
+
# * Otherwise, the empty value is nil.
|
|
52
|
+
#
|
|
53
|
+
# @param [Symbol] attribute the target attribute
|
|
54
|
+
# @return [Numeric, Boolean, Enumerable, nil] the empty attribute value
|
|
55
|
+
def empty_value(attribute)
|
|
56
|
+
if abstract? then
|
|
57
|
+
prop = property(attribute)
|
|
58
|
+
# the Java attribute type
|
|
59
|
+
jtype = prop.property_descriptor.attribute_type if JavaProperty === prop
|
|
60
|
+
# A primitive is either a boolean or a number (String is not primitive).
|
|
61
|
+
if jtype and jtype.primitive? then
|
|
62
|
+
type.name == 'boolean' ? false : 0
|
|
63
|
+
end
|
|
64
|
+
else
|
|
65
|
+
# Since this class is not abstract, create a prototype instance on demand and make
|
|
66
|
+
# a copy of the initialized collection value from that instance.
|
|
67
|
+
@prototype ||= new
|
|
68
|
+
value = @prototype.send(attribute) || return
|
|
69
|
+
value.class.new
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Prints this classifier's content to the log.
|
|
74
|
+
def pretty_print(q)
|
|
75
|
+
map = pretty_print_attribute_hash.delete_if { |k, v| v.nil_or_empty? }
|
|
76
|
+
# one indented line per entry, all but the last line ending in a comma
|
|
77
|
+
content = map.map { |label, value| " #{label}=>#{format_print_value(value)}" }.join(",\n")
|
|
78
|
+
# print the content to the log
|
|
79
|
+
q.text("#{qp} structure:\n#{content}")
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
private
|
|
83
|
+
|
|
84
|
+
# A proc to print the unqualified class name.
|
|
85
|
+
# @private
|
|
86
|
+
TYPE_PRINTER = PrintWrapper.new { |type| type.qp }
|
|
87
|
+
|
|
88
|
+
# A proc to print the property descriptor name.
|
|
89
|
+
# @private
|
|
90
|
+
PROP_DESC_PRINTER = PrintWrapper.new { |pd| pd.name }
|
|
91
|
+
|
|
92
|
+
# @param [Property] the property to print
|
|
93
|
+
# @return [<Symbol>] the flags to modify the property
|
|
94
|
+
def pretty_print_attribute_flags(prop)
|
|
95
|
+
flags = []
|
|
96
|
+
flags << :disjoint if prop.disjoint?
|
|
97
|
+
flags
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# @return [{String => <Symbol>}] the attributes to print
|
|
101
|
+
def pretty_print_attribute_hash
|
|
102
|
+
# the Java property descriptors
|
|
103
|
+
pds = java_attributes.wrap { |pa| property(pa).property_descriptor }
|
|
104
|
+
# the display label => properties printer
|
|
105
|
+
prop_prn = pds.wrap { |pd| PROP_DESC_PRINTER.wrap(pd) }
|
|
106
|
+
# the Java attributes
|
|
107
|
+
prop_syms = pds.map { |pd| pd.name.to_sym }.to_set
|
|
108
|
+
# the attribute aliases
|
|
109
|
+
aliases = @alias_std_prop_map.keys - attributes.to_a - prop_syms
|
|
110
|
+
alias_hash = aliases.to_compact_hash { |aliaz| @alias_std_prop_map[aliaz] }
|
|
111
|
+
# the dependent attributes printer
|
|
112
|
+
dep_prn_wrapper = dependent_attributes_print_wrapper
|
|
113
|
+
dep_prn = dependent_attributes.wrap { |pa| dep_prn_wrapper.wrap(property(pa)) }
|
|
114
|
+
# the owner classes printer
|
|
115
|
+
own_prn = owners.wrap { |type| TYPE_PRINTER.wrap(type) }
|
|
116
|
+
# the inverse attributes printer
|
|
117
|
+
inv_prn = @attributes.to_compact_hash do |pa|
|
|
118
|
+
prop = property(pa)
|
|
119
|
+
"#{prop.type.qp}.#{prop.inverse}" if prop.inverse
|
|
120
|
+
end
|
|
121
|
+
# the domain attribute printer
|
|
122
|
+
dom_prn = domain_attributes.to_compact_hash { |pa| domain_type(pa).qp }
|
|
123
|
+
# the description => printable hash
|
|
124
|
+
{
|
|
125
|
+
'Java attributes' => prop_prn,
|
|
126
|
+
'standard attributes' => attributes,
|
|
127
|
+
'aliases to standard attributes' => alias_hash,
|
|
128
|
+
'secondary key' => secondary_key_attributes,
|
|
129
|
+
'mandatory attributes' => mandatory_attributes,
|
|
130
|
+
'domain attributes' => dom_prn,
|
|
131
|
+
'owners' => own_prn,
|
|
132
|
+
'owner attributes' => owner_attributes,
|
|
133
|
+
'inverse attributes' => inv_prn,
|
|
134
|
+
'dependent attributes' => dep_prn,
|
|
135
|
+
'default values' => defaults
|
|
136
|
+
}
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# @return [PrintWrapper] a proc to print the dependent attributes
|
|
140
|
+
def dependent_attributes_print_wrapper
|
|
141
|
+
PrintWrapper.new do |prop|
|
|
142
|
+
flags = pretty_print_attribute_flags(prop)
|
|
143
|
+
flags.empty? ? "#{prop}" : "#{prop}(#{flags.join(',')})"
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def format_print_value(value)
|
|
148
|
+
case value
|
|
149
|
+
when String then value
|
|
150
|
+
when Class then value.qp
|
|
151
|
+
else value.pp_s(:single_line)
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
module Jinx
|
|
2
|
+
# A filter on the standard attribute symbol => metadata hash that yields
|
|
3
|
+
# each attribute which satisfies the attribute metadata condition.
|
|
4
|
+
class AttributeEnumerator
|
|
5
|
+
include Enumerable
|
|
6
|
+
|
|
7
|
+
# @param [{Symbol => Property}] hash the attribute symbol => metadata hash
|
|
8
|
+
# @yield [prop] optional condition which determines whether the attribute is
|
|
9
|
+
# selected (default is all attributes)
|
|
10
|
+
# @yieldparam [Property] the metadata for the standard attribute
|
|
11
|
+
# @raise [ArgumentError] if a parameter is missing
|
|
12
|
+
def initialize(hash, &filter)
|
|
13
|
+
Jinx.fail(ArgumentError, "Attribute filter missing hash argument") if hash.nil?
|
|
14
|
+
@hash = hash
|
|
15
|
+
@filter = block_given? ? filter : Proc.new { true }
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# @yield [attribute, prop] the block to apply to the filtered attribute metadata and attribute
|
|
19
|
+
# @yieldparam [Symbol] attribute the attribute
|
|
20
|
+
# @yieldparam [Property] prop the attribute metadata
|
|
21
|
+
def each_pair
|
|
22
|
+
@hash.each { |pa, prop| yield(pa, prop) if @filter.call(prop) }
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# @return [<(Symbol, Property)>] the (symbol, attribute) enumerator
|
|
26
|
+
def enum_pairs
|
|
27
|
+
enum_for(:each_pair)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# @yield [attribute] block to apply to each filtered attribute
|
|
31
|
+
# @yieldparam [Symbol] the attribute which satisfies the filter condition
|
|
32
|
+
def each_attribute(&block)
|
|
33
|
+
each_pair { |pa, prop| yield(pa) }
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
alias :each :each_attribute
|
|
37
|
+
|
|
38
|
+
# @yield [prop] the block to apply to the filtered attribute metadata
|
|
39
|
+
# @yieldparam [Property] prop the attribute metadata
|
|
40
|
+
def each_property
|
|
41
|
+
each_pair { |pa, prop| yield(prop) }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# @return [<Property>] the property enumerator
|
|
45
|
+
def properties
|
|
46
|
+
@prop_enum ||= enum_for(:each_property)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# @yield [attribute] the block to apply to the attribute
|
|
50
|
+
# @yieldparam [Symbol] attribute the attribute to detect
|
|
51
|
+
# @return [Property] the first attribute metadata whose attribute satisfies the block
|
|
52
|
+
def detect_property
|
|
53
|
+
each_pair { |pa, prop| return prop if yield(pa) }
|
|
54
|
+
nil
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# @yield [prop] the block to apply to the attribute metadata
|
|
58
|
+
# @yieldparam [Property] prop the attribute metadata
|
|
59
|
+
# @return [Symbol] the first attribute whose metadata satisfies the block
|
|
60
|
+
def detect_with_property
|
|
61
|
+
each_pair { |pa, prop| return pa if yield(prop) }
|
|
62
|
+
nil
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# @yield [prop] the attribute selection filter
|
|
66
|
+
# @yieldparam [Property] prop the candidate attribute metadata
|
|
67
|
+
# @return [AttributeEnumerator] a new eumerator which applies the filter block given to this
|
|
68
|
+
# method with the Property enumerated by this enumerator
|
|
69
|
+
def compose
|
|
70
|
+
AttributeEnumerator.new(@hash) { |prop| @filter.call(prop) and yield(prop) }
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
require 'jinx/helpers/validation'
|
|
2
|
+
|
|
3
|
+
module Jinx
|
|
4
|
+
# Metadata mix-in to capture Resource dependency.
|
|
5
|
+
module Dependency
|
|
6
|
+
# @return [<Class>] the owner classes
|
|
7
|
+
attr_reader :owners
|
|
8
|
+
|
|
9
|
+
# @return [<Symbol>] the owner reference attributes
|
|
10
|
+
attr_reader :owner_attributes
|
|
11
|
+
|
|
12
|
+
# Adds the given attribute as a dependent.
|
|
13
|
+
#
|
|
14
|
+
# If the attribute inverse is not a collection, then the attribute writer
|
|
15
|
+
# is modified to delegate to the dependent owner writer. This enforces
|
|
16
|
+
# referential integrity by ensuring that the following post-condition holds:
|
|
17
|
+
# * _owner_._attribute_._inverse_ == _owner_
|
|
18
|
+
# where:
|
|
19
|
+
# * _owner_ is an instance this attribute's declaring class
|
|
20
|
+
# * _inverse_ is the owner inverse attribute defined in the dependent class
|
|
21
|
+
#
|
|
22
|
+
# @param [Symbol] attribute the dependent to add
|
|
23
|
+
# @param [<Symbol>] flags the attribute qualifier flags
|
|
24
|
+
def add_dependent_attribute(attribute, *flags)
|
|
25
|
+
prop = property(attribute)
|
|
26
|
+
logger.debug { "Marking #{qp}.#{attribute} as a dependent attribute of type #{prop.type.qp}..." }
|
|
27
|
+
flags << :dependent unless flags.include?(:dependent)
|
|
28
|
+
prop.qualify(*flags)
|
|
29
|
+
inverse = prop.inverse
|
|
30
|
+
inv_type = prop.type
|
|
31
|
+
# example: Parent.add_dependent_attribute(:children) with inverse :parent calls the following:
|
|
32
|
+
# Child.add_owner(Parent, :children, :parent)
|
|
33
|
+
inv_type.add_owner(self, attribute, inverse)
|
|
34
|
+
logger.debug { "Marked #{qp}.#{attribute} as a dependent attribute with inverse #{inv_type.qp}#{inverse}." }
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# @return [Boolean] whether this class depends on an owner
|
|
38
|
+
def dependent?
|
|
39
|
+
not owners.empty?
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# @param [Class] other the class to check
|
|
43
|
+
# @param [Boolean] recursive whether to check if this class depends on a dependent
|
|
44
|
+
# of the other class
|
|
45
|
+
# @return [Boolean] whether this class depends on the other class
|
|
46
|
+
def depends_on?(other, recursive=false)
|
|
47
|
+
owners.detect do |owner|
|
|
48
|
+
other <= owner or (recursive and depends_on_recursive?(owner, other))
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# @param [Class] klass the dependent type
|
|
53
|
+
# @return [Symbol, nil] the attribute which references the dependent type, or nil if none
|
|
54
|
+
def dependent_attribute(klass)
|
|
55
|
+
most_specific_domain_attribute(klass, dependent_attributes)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# @return [<Property>] the owner properties
|
|
59
|
+
def owner_properties
|
|
60
|
+
@ops ||= create_owner_properties_enumerator
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# @return [<Symbol>] this class's owner attributes
|
|
64
|
+
def owner_attributes
|
|
65
|
+
@oas ||= owner_properties.transform { |op| op.attribute }
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# @return [Boolean] whether this {Resource} class is dependent and reference its owners
|
|
69
|
+
def bidirectional_dependent?
|
|
70
|
+
dependent? and not owner_attributes.empty?
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# @return [<Class>] this class's dependent types
|
|
74
|
+
def dependents
|
|
75
|
+
dependent_attributes.wrap { |da| da.type }
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# @return [<Class>] this class's owner types
|
|
79
|
+
def owners
|
|
80
|
+
@owners ||= Enumerable::Enumerator.new(owner_property_hash, :each_key)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# @return [Property, nil] the sole owner attribute metadata of this class, or nil if there
|
|
84
|
+
# is not exactly one owner
|
|
85
|
+
def owner_property
|
|
86
|
+
props = owner_properties
|
|
87
|
+
props.first if props.size == 1
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# @return [Symbol, nil] the sole owner attribute of this class, or nil if there
|
|
91
|
+
# is not exactly one owner
|
|
92
|
+
def owner_attribute
|
|
93
|
+
prop = owner_property || return
|
|
94
|
+
prop.attribute
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# @return [Class, nil] the sole owner type of this class, or nil if there
|
|
98
|
+
# is not exactly one owner
|
|
99
|
+
def owner_type
|
|
100
|
+
prop = owner_property || return
|
|
101
|
+
prop.type
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
protected
|
|
105
|
+
|
|
106
|
+
# Adds the given owner class to this dependent class.
|
|
107
|
+
# This method must be called before any dependent attribute is accessed.
|
|
108
|
+
# If the attribute is given, then the attribute inverse is set.
|
|
109
|
+
# Otherwise, if there is not already an owner attribute, then a new owner attribute is created.
|
|
110
|
+
# The name of the new attribute is the lower-case demodulized owner class name.
|
|
111
|
+
#
|
|
112
|
+
# @param [Class] the owner class
|
|
113
|
+
# @param [Symbol] inverse the owner -> dependent attribute
|
|
114
|
+
# @param [Symbol, nil] attribute the dependent -> owner attribute, if known
|
|
115
|
+
# @raise [ValidationError] if the inverse is nil
|
|
116
|
+
def add_owner(klass, inverse, attribute=nil)
|
|
117
|
+
if inverse.nil? then
|
|
118
|
+
Jinx.fail(ValidationError, "Owner #{klass.qp} missing dependent attribute for dependent #{qp}")
|
|
119
|
+
end
|
|
120
|
+
logger.debug { "Adding #{qp} owner #{klass.qp}#{' attribute ' + attribute.to_s if attribute} with inverse #{inverse}..." }
|
|
121
|
+
if @owner_prop_hash then
|
|
122
|
+
Jinx.fail(MetadataError, "Can't add #{qp} owner #{klass.qp} after dependencies have been accessed")
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# detect the owner attribute, if necessary
|
|
126
|
+
attribute ||= detect_owner_attribute(klass, inverse)
|
|
127
|
+
prop = property(attribute) if attribute
|
|
128
|
+
# Add the owner class => attribute entry.
|
|
129
|
+
# The attribute is nil if the dependency is unidirectional, i.e. there is an owner class which
|
|
130
|
+
# references this class via a dependency attribute but there is no inverse owner attribute.
|
|
131
|
+
local_owner_property_hash[klass] = prop
|
|
132
|
+
# If the dependency is unidirectional, then our job is done.
|
|
133
|
+
if attribute.nil? then
|
|
134
|
+
logger.debug { "#{qp} owner #{klass.qp} has unidirectional inverse #{inverse}." }
|
|
135
|
+
return
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Bi-directional: add the owner property
|
|
139
|
+
local_owner_properties << prop
|
|
140
|
+
# set the inverse if necessary
|
|
141
|
+
unless prop.inverse then
|
|
142
|
+
set_attribute_inverse(attribute, inverse)
|
|
143
|
+
end
|
|
144
|
+
# set the owner flag if necessary
|
|
145
|
+
unless prop.owner? then prop.qualify(:owner) end
|
|
146
|
+
|
|
147
|
+
# Redefine the writer method to warn when changing the owner.
|
|
148
|
+
rdr, wtr = prop.accessors
|
|
149
|
+
redefine_method(wtr) do |old_wtr|
|
|
150
|
+
lambda do |ref|
|
|
151
|
+
prev = send(rdr)
|
|
152
|
+
send(old_wtr, ref)
|
|
153
|
+
if prev and prev != ref then
|
|
154
|
+
if ref.nil? then
|
|
155
|
+
logger.warn("Unset the #{self} owner #{attribute} #{prev}.")
|
|
156
|
+
elsif ref.key != prev.key then
|
|
157
|
+
logger.warn("Reset the #{self} owner #{attribute} from #{prev} to #{ref}.")
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
ref
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
logger.debug { "Injected owner change warning into #{qp}.#{attribute} writer method #{wtr}." }
|
|
164
|
+
logger.debug { "#{qp} owner #{klass.qp} attribute is #{attribute} with inverse #{inverse}." }
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Adds the given attribute as an owner. This method is called when a new attribute is added that
|
|
168
|
+
# references an existing owner.
|
|
169
|
+
#
|
|
170
|
+
# @param [Symbol] attribute the owner attribute
|
|
171
|
+
def add_owner_attribute(attribute)
|
|
172
|
+
prop = property(attribute)
|
|
173
|
+
otype = prop.type
|
|
174
|
+
hash = local_owner_property_hash
|
|
175
|
+
if hash.include?(otype) then
|
|
176
|
+
oa = hash[otype]
|
|
177
|
+
unless oa.nil? then
|
|
178
|
+
Jinx.fail(MetadataError, "Cannot set #{qp} owner attribute to #{attribute} since it is already set to #{oa}")
|
|
179
|
+
end
|
|
180
|
+
hash[otype] = prop
|
|
181
|
+
else
|
|
182
|
+
add_owner(otype, prop.inverse, attribute)
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# @return [{Class => Property}] this class's owner type => property hash
|
|
187
|
+
def owner_property_hash
|
|
188
|
+
@op_hash ||= create_owner_property_hash
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
private
|
|
192
|
+
|
|
193
|
+
# @param [Class] klass the class to check
|
|
194
|
+
# @param [Boolean] recursive whether to check whether this class depends on a dependent
|
|
195
|
+
# of the other class
|
|
196
|
+
# @return [Boolean] whether the owner class depends on the other class
|
|
197
|
+
def depends_on_recursive?(klass, other)
|
|
198
|
+
klass != self and klass.owners.any? { |owner| owner.depends_on?(other, true) }
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# @param [<Symbol>] attributes the order in which the effective owner attribute should be determined
|
|
202
|
+
def order_owner_attributes(*attributes)
|
|
203
|
+
@ops = @ops_local = attributes.map { |oa| property(oa) }
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def local_owner_property_hash
|
|
207
|
+
@local_oa_hash ||= {}
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
# @return [{Class => Property}] a new owner type => attribute hash
|
|
211
|
+
def create_owner_property_hash
|
|
212
|
+
local = local_owner_property_hash
|
|
213
|
+
Class === self && superclass < Resource ? local.union(superclass.owner_property_hash) : local
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
# @return [<Property>] the owner properties defined in this class
|
|
217
|
+
def local_owner_properties
|
|
218
|
+
@ops_local ||= []
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# @return [<Property>] the owner properties defined in the class hierarchy
|
|
222
|
+
def create_owner_properties_enumerator
|
|
223
|
+
local = local_owner_properties
|
|
224
|
+
Class === self && superclass < Resource ? local.union(superclass.owner_properties) : local
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
# Returns the attribute which references the owner. The owner attribute is the inverse
|
|
228
|
+
# of the given owner class inverse attribute, if it exists. Otherwise, the owner
|
|
229
|
+
# attribute is inferred by #{Inverse#detect_inverse_attribute}.
|
|
230
|
+
|
|
231
|
+
# @param klass (see #add_owner)
|
|
232
|
+
# @param [Symbol] inverse the owner -> dependent attribute
|
|
233
|
+
# @return [Symbol, nil] this class's owner attribute
|
|
234
|
+
def detect_owner_attribute(klass, inverse)
|
|
235
|
+
ia = klass.property(inverse).inverse || detect_inverse_attribute(klass)
|
|
236
|
+
if ia then
|
|
237
|
+
logger.debug { "#{qp} reference to owner #{klass.qp} with inverse #{inverse} is #{ia}." }
|
|
238
|
+
else
|
|
239
|
+
logger.debug { "#{qp} reference to owner #{klass.qp} with inverse #{inverse} was not detected." }
|
|
240
|
+
end
|
|
241
|
+
ia
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
end
|