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