jinx 2.1.3 → 2.1.4
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile.lock +27 -0
- data/History.md +4 -0
- data/lib/jinx/helpers/class.rb +16 -12
- data/lib/jinx/helpers/collection.rb +277 -29
- data/lib/jinx/helpers/collections.rb +35 -2
- data/lib/jinx/helpers/conditional_enumerator.rb +2 -2
- data/lib/jinx/helpers/filter.rb +8 -2
- data/lib/jinx/helpers/flattener.rb +2 -2
- data/lib/jinx/helpers/hash.rb +3 -2
- data/lib/jinx/helpers/{hashable.rb → hasher.rb} +125 -77
- data/lib/jinx/helpers/module.rb +1 -1
- data/lib/jinx/helpers/multi_enumerator.rb +25 -9
- data/lib/jinx/helpers/options.rb +4 -3
- data/lib/jinx/helpers/partial_order.rb +16 -8
- data/lib/jinx/helpers/pretty_print.rb +14 -4
- data/lib/jinx/helpers/transformer.rb +3 -1
- data/lib/jinx/helpers/transitive_closure.rb +3 -3
- data/lib/jinx/helpers/visitor.rb +33 -42
- data/lib/jinx/import/java.rb +40 -27
- data/lib/jinx/importer.rb +86 -33
- data/lib/jinx/metadata/attribute_enumerator.rb +5 -11
- data/lib/jinx/metadata/dependency.rb +65 -30
- data/lib/jinx/metadata/id_alias.rb +1 -0
- data/lib/jinx/metadata/introspector.rb +21 -9
- data/lib/jinx/metadata/inverse.rb +14 -11
- data/lib/jinx/metadata/java_property.rb +15 -26
- data/lib/jinx/metadata/propertied.rb +80 -19
- data/lib/jinx/metadata/property.rb +13 -8
- data/lib/jinx/metadata/property_characteristics.rb +2 -2
- data/lib/jinx/resource.rb +62 -32
- data/lib/jinx/resource/inversible.rb +4 -0
- data/lib/jinx/resource/match_visitor.rb +0 -1
- data/lib/jinx/resource/mergeable.rb +16 -6
- data/lib/jinx/resource/reference_enumerator.rb +1 -2
- data/lib/jinx/version.rb +1 -1
- data/test/lib/jinx/helpers/collections_test.rb +29 -14
- data/test/lib/jinx/helpers/visitor_test.rb +7 -20
- data/test/lib/jinx/import/mixed_case_test.rb +17 -3
- metadata +4 -4
- data/lib/jinx/helpers/enumerable.rb +0 -245
@@ -1,8 +1,10 @@
|
|
1
|
+
require 'jinx/helpers/collection'
|
2
|
+
|
1
3
|
module Jinx
|
2
4
|
# A filter on the standard attribute symbol => metadata hash that yields
|
3
5
|
# each attribute which satisfies the attribute metadata condition.
|
4
6
|
class AttributeEnumerator
|
5
|
-
include Enumerable
|
7
|
+
include Enumerable, Collection
|
6
8
|
|
7
9
|
# @param [{Symbol => Property}] hash the attribute symbol => metadata hash
|
8
10
|
# @yield [prop] optional condition which determines whether the attribute is
|
@@ -46,18 +48,10 @@ module Jinx
|
|
46
48
|
@prop_enum ||= enum_for(:each_property)
|
47
49
|
end
|
48
50
|
|
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
51
|
# @yield [prop] the block to apply to the attribute metadata
|
58
52
|
# @yieldparam [Property] prop the attribute metadata
|
59
53
|
# @return [Symbol] the first attribute whose metadata satisfies the block
|
60
|
-
def
|
54
|
+
def detect_attribute_with_property
|
61
55
|
each_pair { |pa, prop| return pa if yield(prop) }
|
62
56
|
nil
|
63
57
|
end
|
@@ -67,7 +61,7 @@ module Jinx
|
|
67
61
|
# @return [AttributeEnumerator] a new eumerator which applies the filter block given to this
|
68
62
|
# method with the Property enumerated by this enumerator
|
69
63
|
def compose
|
70
|
-
AttributeEnumerator.new(@hash) { |prop|
|
64
|
+
AttributeEnumerator.new(@hash) { |prop| yield(prop) if @filter.call(prop) }
|
71
65
|
end
|
72
66
|
end
|
73
67
|
end
|
@@ -3,35 +3,44 @@ require 'jinx/helpers/validation'
|
|
3
3
|
module Jinx
|
4
4
|
# Metadata mix-in to capture Resource dependency.
|
5
5
|
module Dependency
|
6
|
-
# @return [<Class>] the owner classes
|
7
|
-
attr_reader :owners
|
8
|
-
|
9
6
|
# @return [<Symbol>] the owner reference attributes
|
10
7
|
attr_reader :owner_attributes
|
11
8
|
|
12
|
-
# Adds the given
|
9
|
+
# Adds the given property as a dependent.
|
13
10
|
#
|
14
|
-
# If the
|
11
|
+
# If the property inverse is not a collection, then the property writer
|
15
12
|
# is modified to delegate to the dependent owner writer. This enforces
|
16
13
|
# referential integrity by ensuring that the following post-condition holds:
|
17
14
|
# * _owner_._attribute_._inverse_ == _owner_
|
18
15
|
# where:
|
19
|
-
# * _owner_ is an instance this
|
16
|
+
# * _owner_ is an instance this property's declaring class
|
20
17
|
# * _inverse_ is the owner inverse attribute defined in the dependent class
|
21
18
|
#
|
22
|
-
# @param [
|
19
|
+
# @param [Property] property the dependent to add
|
23
20
|
# @param [<Symbol>] flags the attribute qualifier flags
|
24
|
-
def
|
25
|
-
|
26
|
-
logger.debug { "Marking #{qp}.#{attribute} as a dependent attribute of type #{prop.type.qp}..." }
|
21
|
+
def add_dependent_property(property, *flags)
|
22
|
+
logger.debug { "Marking #{qp}.#{property} as a dependent attribute of type #{property.type.qp}..." }
|
27
23
|
flags << :dependent unless flags.include?(:dependent)
|
28
|
-
|
29
|
-
|
30
|
-
inv_type =
|
31
|
-
# example: Parent.
|
24
|
+
property.qualify(*flags)
|
25
|
+
inv = property.inverse
|
26
|
+
inv_type = property.type
|
27
|
+
# example: Parent.add_dependent_property(child_prop) with inverse :parent calls the following:
|
32
28
|
# Child.add_owner(Parent, :children, :parent)
|
33
|
-
inv_type.add_owner(self, attribute,
|
34
|
-
|
29
|
+
inv_type.add_owner(self, property.attribute, inv)
|
30
|
+
if inv then
|
31
|
+
logger.debug "Marked #{qp}.#{property} as a dependent attribute with inverse #{inv_type.qp}.#{inv}."
|
32
|
+
else
|
33
|
+
logger.debug "Marked #{qp}.#{property} as a uni-directional dependent attribute."
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Adds the given attribute as a dependent.
|
38
|
+
#
|
39
|
+
# @see #add_dependent_property
|
40
|
+
# @param [Symbol] attribute the dependent to add
|
41
|
+
# @param (see #add_dependent_property)
|
42
|
+
def add_dependent_attribute(attribute, *flags)
|
43
|
+
add_dependent_property(property(attribute), *flags)
|
35
44
|
end
|
36
45
|
|
37
46
|
# @return [Boolean] whether this class depends on an owner
|
@@ -66,19 +75,40 @@ module Jinx
|
|
66
75
|
end
|
67
76
|
|
68
77
|
# @return [Boolean] whether this {Resource} class is dependent and reference its owners
|
69
|
-
def
|
70
|
-
dependent? and
|
78
|
+
def bidirectional_java_dependent?
|
79
|
+
dependent? and owner_properties.any? { |prop| prop.java_property? }
|
71
80
|
end
|
72
81
|
|
73
82
|
# @return [<Class>] this class's dependent types
|
74
|
-
def
|
75
|
-
|
83
|
+
def dependent_types
|
84
|
+
dependent_properties.wrap { |dp| dp.type }
|
85
|
+
end
|
86
|
+
|
87
|
+
# @return [<Property>, nil] the path of this class to the given class through dependent
|
88
|
+
# properties, or nil if there is no such path exists
|
89
|
+
def dependency_path_to(klass)
|
90
|
+
return if klass.owner_types.empty?
|
91
|
+
op = klass.owner_properties.detect { |prop| self <= prop.type }
|
92
|
+
dp = op.inverse if op
|
93
|
+
dp ||= if klass.owner_properties.size < klass.owner_types.size then
|
94
|
+
dependent_properties.detect { |prop| klass <= prop.type }
|
95
|
+
end
|
96
|
+
return [dp] if dp
|
97
|
+
dependent_properties.detect_value do |dp|
|
98
|
+
next if self <= dp.type
|
99
|
+
path = dp.type.dependency_path_to(klass)
|
100
|
+
path.unshift(dp) if path
|
101
|
+
end
|
76
102
|
end
|
103
|
+
|
104
|
+
alias :dependents :dependent_types
|
77
105
|
|
78
106
|
# @return [<Class>] this class's owner types
|
79
|
-
def
|
107
|
+
def owner_types
|
80
108
|
@owners ||= Enumerable::Enumerator.new(owner_property_hash, :each_key)
|
81
109
|
end
|
110
|
+
|
111
|
+
alias :owners :owner_types
|
82
112
|
|
83
113
|
# @return [Property, nil] the sole owner attribute metadata of this class, or nil if there
|
84
114
|
# is not exactly one owner
|
@@ -122,29 +152,34 @@ module Jinx
|
|
122
152
|
raise MetadataError.new("Can't add #{qp} owner #{klass.qp} after dependencies have been accessed")
|
123
153
|
end
|
124
154
|
|
125
|
-
#
|
155
|
+
# Detect the owner attribute, if necessary.
|
126
156
|
attribute ||= detect_owner_attribute(klass, inverse)
|
127
|
-
|
157
|
+
hash = local_owner_property_hash
|
158
|
+
# Guard against a conflicting owner reference attribute.
|
159
|
+
if hash[klass] then
|
160
|
+
raise MetadataError.new("Cannot set #{qp} owner attribute to #{attribute or 'nil'} since it is already set to #{hash[klass]}")
|
161
|
+
end
|
128
162
|
# Add the owner class => attribute entry.
|
129
163
|
# The attribute is nil if the dependency is unidirectional, i.e. there is an owner class which
|
130
164
|
# references this class via a dependency attribute but there is no inverse owner attribute.
|
131
|
-
|
165
|
+
prop = property(attribute) if attribute
|
166
|
+
hash[klass] = prop
|
132
167
|
# If the dependency is unidirectional, then our job is done.
|
133
168
|
if attribute.nil? then
|
134
|
-
logger.debug { "#{qp} owner #{klass.qp} has unidirectional
|
169
|
+
logger.debug { "#{qp} owner #{klass.qp} has unidirectional dependent attribute #{inverse}." }
|
135
170
|
return
|
136
171
|
end
|
137
172
|
|
138
|
-
# Bi-directional: add the owner property
|
173
|
+
# Bi-directional: add the owner property.
|
139
174
|
local_owner_properties << prop
|
140
|
-
#
|
175
|
+
# Set the inverse if necessary.
|
141
176
|
unless prop.inverse then
|
142
177
|
set_attribute_inverse(attribute, inverse)
|
143
178
|
end
|
144
|
-
#
|
145
|
-
|
179
|
+
# Set the owner flag if necessary.
|
180
|
+
prop.qualify(:owner) unless prop.owner?
|
146
181
|
|
147
|
-
# Redefine the writer method to
|
182
|
+
# Redefine the writer method to issue a warning if the owner is changed.
|
148
183
|
rdr, wtr = prop.accessors
|
149
184
|
redefine_method(wtr) do |old_wtr|
|
150
185
|
lambda do |ref|
|
@@ -8,18 +8,32 @@ module Jinx
|
|
8
8
|
# Meta-data mix-in to infer attribute meta-data from Java properties.
|
9
9
|
module Introspector
|
10
10
|
include Propertied
|
11
|
-
|
12
|
-
#
|
13
|
-
|
14
|
-
|
11
|
+
|
12
|
+
# Introspects the given class, if necessary. Some member of the class hierarchy
|
13
|
+
# must first be introspected.
|
14
|
+
#
|
15
|
+
# @param [Class] klass the class to introspect if necessary
|
16
|
+
# @raise [NoSuchMethodError] if the class or an ancestor of the class is not
|
17
|
+
# introspected
|
18
|
+
def self.ensure_introspected(klass)
|
19
|
+
return if klass < Resource and klass.introspected?
|
20
|
+
sc = klass.superclass
|
21
|
+
ensure_introspected(sc) unless sc == Java::java.lang.Object
|
22
|
+
logger.debug { "Introspecting the fetched object class #{klass}..." }
|
23
|
+
# Resolving the class name in the context of the domain module
|
24
|
+
# introspects the class.
|
25
|
+
sc.domain_module.const_get(klass.name.demodulize)
|
15
26
|
end
|
16
27
|
|
17
|
-
#
|
28
|
+
# Augments the introspected class +new+ method as follows:
|
29
|
+
# * Adds an optional {attribute=>value} constructor parameter.
|
30
|
+
# * Calls the {Resource#post_initialize} method after initialization.
|
18
31
|
def add_attribute_value_initializer
|
19
32
|
class << self
|
20
|
-
def new(
|
33
|
+
def new(opts=nil)
|
21
34
|
obj = super()
|
22
|
-
obj.
|
35
|
+
obj.post_initialize
|
36
|
+
obj.merge_attributes(opts) if opts
|
23
37
|
obj
|
24
38
|
end
|
25
39
|
end
|
@@ -52,8 +66,6 @@ module Jinx
|
|
52
66
|
# Define the standard Java attribute methods.
|
53
67
|
pds.each { |pd| define_java_property(pd) }
|
54
68
|
end
|
55
|
-
# Mark this class as introspected.
|
56
|
-
@introspected = true
|
57
69
|
logger.debug { "Introspection of #{qp} metadata complete." }
|
58
70
|
self
|
59
71
|
end
|
@@ -23,10 +23,13 @@ module Jinx
|
|
23
23
|
# A domain attribute is recognized as an inverse according to the
|
24
24
|
# {Inverse#detect_inverse_attribute} criterion.
|
25
25
|
#
|
26
|
-
# @param [
|
26
|
+
# @param [Property] property the property to check
|
27
|
+
# @return [Symbol, nil] the inverse attribute, or nil if none was
|
28
|
+
# detected
|
27
29
|
def infer_property_inverse(property)
|
28
30
|
inv = property.type.detect_inverse_attribute(self)
|
29
|
-
|
31
|
+
set_attribute_inverse(property.attribute, inv) if inv
|
32
|
+
inv
|
30
33
|
end
|
31
34
|
|
32
35
|
# Sets the given bi-directional association attribute's inverse.
|
@@ -63,7 +66,7 @@ module Jinx
|
|
63
66
|
# If attribute is the one side of a 1:M or non-reflexive 1:1 relation, then add the inverse updater.
|
64
67
|
unless prop.collection? then
|
65
68
|
# Inject adding to the inverse collection into the attribute writer method.
|
66
|
-
add_inverse_updater(pa
|
69
|
+
add_inverse_updater(pa)
|
67
70
|
unless prop.type == inv_prop.type or inv_prop.collection? then
|
68
71
|
prop.type.delegate_writer_to_inverse(inverse, pa)
|
69
72
|
end
|
@@ -151,26 +154,26 @@ module Jinx
|
|
151
154
|
# @return (see #detect_inverse_attribute)
|
152
155
|
def detect_inverse_attribute_from_candidates(klass, candidates)
|
153
156
|
return if candidates.empty?
|
154
|
-
#
|
157
|
+
# There can be at most one owner attribute per owner.
|
155
158
|
return candidates.first.to_sym if candidates.size == 1
|
156
|
-
#
|
157
|
-
# then the attribute named after the owner type is the owner attribute
|
158
|
-
tgt = klass.name
|
159
|
+
# By convention, if more than one attribute references the owner type,
|
160
|
+
# then the attribute named after the owner type is the owner attribute.
|
161
|
+
tgt = klass.name.demodulize.underscore.to_sym
|
159
162
|
tgt if candidates.detect { |pa| pa == tgt }
|
160
163
|
end
|
161
164
|
|
162
165
|
# Modifies the given attribute writer method to update the given inverse.
|
163
166
|
#
|
164
167
|
# @param (see #set_attribute_inverse)
|
165
|
-
def add_inverse_updater(attribute
|
168
|
+
def add_inverse_updater(attribute)
|
166
169
|
prop = property(attribute)
|
167
170
|
# the reader and writer methods
|
168
171
|
rdr, wtr = prop.accessors
|
169
|
-
# the inverse
|
172
|
+
# the inverse attribute metadata
|
170
173
|
inv_prop = prop.inverse_property
|
171
174
|
# the inverse attribute reader and writer
|
172
175
|
inv_rdr, inv_wtr = inv_accessors = inv_prop.accessors
|
173
|
-
# Redefine the writer method to update the inverse by delegating to the inverse
|
176
|
+
# Redefine the writer method to update the inverse by delegating to the inverse.
|
174
177
|
redefine_method(wtr) do |old_wtr|
|
175
178
|
# the attribute reader and (superseded) writer
|
176
179
|
accessors = [rdr, old_wtr]
|
@@ -180,7 +183,7 @@ module Jinx
|
|
180
183
|
lambda { |other| set_inversible_noncollection_attribute(other, accessors, inv_wtr) }
|
181
184
|
end
|
182
185
|
end
|
183
|
-
logger.debug { "Injected inverse #{
|
186
|
+
logger.debug { "Injected inverse #{inv_prop} updater into #{qp}.#{attribute} writer method #{wtr}." }
|
184
187
|
end
|
185
188
|
end
|
186
189
|
end
|
@@ -5,6 +5,9 @@ module Jinx
|
|
5
5
|
# The attribute metadata for an introspected Java property.
|
6
6
|
class JavaProperty < Property
|
7
7
|
|
8
|
+
# This property's Java property descriptor type JRuby class wrapper.
|
9
|
+
attr_reader :java_wrapper_class
|
10
|
+
|
8
11
|
# This property's Java property descriptor.
|
9
12
|
attr_reader :property_descriptor
|
10
13
|
|
@@ -35,21 +38,22 @@ module Jinx
|
|
35
38
|
# deficient Java introspector does not recognize 'is' prefix for a Boolean property
|
36
39
|
rm = declarer.property_read_method(pd)
|
37
40
|
raise ArgumentError.new("Property does not have a read method: #{declarer.qp}.#{pd.name}") unless rm
|
38
|
-
|
39
|
-
unless declarer.method_defined?(
|
40
|
-
|
41
|
-
unless declarer.method_defined?(
|
41
|
+
rdr = rm.name.to_sym
|
42
|
+
unless declarer.method_defined?(rdr) then
|
43
|
+
rdr = "is#{rdr.to_s.capitalize_first}".to_sym
|
44
|
+
unless declarer.method_defined?(rdr) then
|
42
45
|
raise ArgumentError.new("Reader method not found for #{declarer} property #{pd.name}")
|
43
46
|
end
|
44
47
|
end
|
45
48
|
unless pd.write_method then
|
46
49
|
raise ArgumentError.new("Property does not have a write method: #{declarer.qp}.#{pd.name}")
|
47
50
|
end
|
48
|
-
|
49
|
-
unless declarer.method_defined?(
|
51
|
+
wtr = pd.write_method.name.to_sym
|
52
|
+
unless declarer.method_defined?(wtr) then
|
50
53
|
raise ArgumentError.new("Writer method not found for #{declarer} property #{pd.name}")
|
51
54
|
end
|
52
|
-
@java_accessors = [
|
55
|
+
@java_accessors = [rdr, wtr]
|
56
|
+
@java_wrapper_class = Class.to_ruby(pd.property_type)
|
53
57
|
qualify(:collection) if collection_java_class?
|
54
58
|
@type = infer_type
|
55
59
|
end
|
@@ -97,15 +101,13 @@ module Jinx
|
|
97
101
|
|
98
102
|
# @return [Boolean] whether this property's Java type is +Iterable+
|
99
103
|
def collection_java_class?
|
100
|
-
# the
|
101
|
-
|
102
|
-
# Test whether the corresponding JRuby wrapper class or module is an Iterable.
|
103
|
-
Class.to_ruby(ptype) < Java::JavaLang::Iterable
|
104
|
+
# Test whether the JRuby wrapper class or module is an Iterable.
|
105
|
+
@java_wrapper_class < Java::JavaLang::Iterable
|
104
106
|
end
|
105
107
|
|
106
108
|
# @return [Class] the type for the specified klass property descriptor pd as described in {#initialize}
|
107
109
|
def infer_type
|
108
|
-
collection? ? infer_collection_type :
|
110
|
+
collection? ? infer_collection_type : @java_wrapper_class
|
109
111
|
end
|
110
112
|
|
111
113
|
# Returns the domain type for this property's Java Collection property descriptor.
|
@@ -117,12 +119,6 @@ module Jinx
|
|
117
119
|
generic_parameter_type or infer_collection_type_from_name or Java::JavaLang::Object
|
118
120
|
end
|
119
121
|
|
120
|
-
# @return [Class] this property's Ruby type
|
121
|
-
def infer_non_collection_type
|
122
|
-
jtype = @property_descriptor.property_type
|
123
|
-
Class.to_ruby(jtype)
|
124
|
-
end
|
125
|
-
|
126
122
|
# @return [Class, nil] the domain type of this property's property descriptor Collection generic
|
127
123
|
# type argument, or nil if none
|
128
124
|
def generic_parameter_type
|
@@ -132,18 +128,11 @@ module Jinx
|
|
132
128
|
atypes = gtype.actualTypeArguments
|
133
129
|
return unless atypes.size == 1
|
134
130
|
atype = atypes[0]
|
135
|
-
klass =
|
131
|
+
klass = Class.to_ruby(atype)
|
136
132
|
logger.debug { "Inferred #{declarer.qp} #{self} domain type #{klass.qp} from generic parameter #{atype.name}." } if klass
|
137
133
|
klass
|
138
134
|
end
|
139
135
|
|
140
|
-
# @param [Class, String] jtype the Java class or class name
|
141
|
-
# @return [Class] the corresponding Ruby type
|
142
|
-
def java_to_ruby_class(jtype)
|
143
|
-
name = String === jtype ? jtype : jtype.name
|
144
|
-
Class.to_ruby(name)
|
145
|
-
end
|
146
|
-
|
147
136
|
# Returns the domain type for this property's collection Java property descriptor name.
|
148
137
|
# By convention, Jinx domain collection propertys often begin with a domain type
|
149
138
|
# name and end in 'Collection'. This method strips the Collection suffix and checks
|
@@ -10,7 +10,7 @@ module Jinx
|
|
10
10
|
# @return [<Symbol>] this class's attributes
|
11
11
|
attr_reader :attributes
|
12
12
|
|
13
|
-
# @return [
|
13
|
+
# @return [Hasher] the default attribute => value associations
|
14
14
|
attr_reader :defaults
|
15
15
|
|
16
16
|
# Returns whether this class has an attribute with the given symbol.
|
@@ -77,6 +77,11 @@ module Jinx
|
|
77
77
|
@prop_hash.each_value(&block)
|
78
78
|
end
|
79
79
|
|
80
|
+
# @return [<Property>] this domain class's properties
|
81
|
+
def properties
|
82
|
+
@props ||= enum_for(:each_property)
|
83
|
+
end
|
84
|
+
|
80
85
|
# @param [Symbol] attribute the property attribute symbol or alias
|
81
86
|
# @return [Property] the corresponding property
|
82
87
|
# @raise [NameError] if the attribute is not recognized
|
@@ -133,6 +138,11 @@ module Jinx
|
|
133
138
|
def domain_attributes
|
134
139
|
@dom_flt ||= attribute_filter { |prop| prop.domain? }
|
135
140
|
end
|
141
|
+
|
142
|
+
# @return [<Property>] the domain properties
|
143
|
+
def domain_properties
|
144
|
+
domain_attributes.properties
|
145
|
+
end
|
136
146
|
|
137
147
|
# @return [<Symbol>] the non-domain Java attributes
|
138
148
|
def nondomain_attributes
|
@@ -164,6 +174,12 @@ module Jinx
|
|
164
174
|
end
|
165
175
|
end
|
166
176
|
|
177
|
+
# @param (see #dependent_attributes)
|
178
|
+
# @return [<Property>] the dependent properties
|
179
|
+
def dependent_properties(inc_super=true)
|
180
|
+
dependent_attributes(inc_super).properties
|
181
|
+
end
|
182
|
+
|
167
183
|
# @return [<Symbol>] the unidirectional dependent attributes
|
168
184
|
# @see Property#unidirectional?
|
169
185
|
def unidirectional_dependent_attributes
|
@@ -250,7 +266,48 @@ module Jinx
|
|
250
266
|
@local_defaults = {}
|
251
267
|
@defaults = append_ancestor_enum(@local_defaults) { |par| par.defaults }
|
252
268
|
end
|
253
|
-
|
269
|
+
|
270
|
+
# Creates a new convenience property in this source class which composes
|
271
|
+
# the given property and the other property. The new property symbol is
|
272
|
+
# the same as the other property symbol. The new property reader and
|
273
|
+
# writer methods delegate to the respective composed property reader and
|
274
|
+
# writer methods.
|
275
|
+
#
|
276
|
+
# @param [Property] property the reference from the source to the intermediary
|
277
|
+
# @param [Property] other the reference from the intermediary to the target
|
278
|
+
# @yield [target] obtain the intermediary object from the target
|
279
|
+
# @yieldparam [Resource] target the target object
|
280
|
+
# @return [Property] the new property
|
281
|
+
# @raise [ArgumentError] if the other property does not have an inverse
|
282
|
+
def compose_property(property, other)
|
283
|
+
if other.inverse.nil? then
|
284
|
+
raise ArgumentError.new("Can't compose #{qp}.#{property} with inverseless #{other.declarer.qp}.#{other}")
|
285
|
+
end
|
286
|
+
# the source -> intermediary access methods
|
287
|
+
sir, siw = property.accessors
|
288
|
+
# the intermediary -> target access methods
|
289
|
+
itr, itw = other.accessors
|
290
|
+
# the target -> intermediary reader method
|
291
|
+
tir = other.inverse
|
292
|
+
# The reader composes the source -> intermediary -> target readers.
|
293
|
+
define_method(itr) do
|
294
|
+
ref = send(sir)
|
295
|
+
ref.send(itr) if ref
|
296
|
+
end
|
297
|
+
# The writer sets the source intermediary to the target intermediary.
|
298
|
+
define_method(itw) do |tgt|
|
299
|
+
if tgt then
|
300
|
+
ref = block_given? ? yield(tgt) : tgt.send(tir)
|
301
|
+
raise ArgumentError.new("#{tgt} does not reference a #{other.inverse}") if ref.nil?
|
302
|
+
end
|
303
|
+
send(siw, ref)
|
304
|
+
end
|
305
|
+
prop = add_attribute(itr, other.type)
|
306
|
+
logger.debug { "Created #{qp}.#{prop} which composes #{qp}.#{property} and #{other.declarer.qp}.#{other}." }
|
307
|
+
prop.qualify(:collection) if other.collection?
|
308
|
+
prop
|
309
|
+
end
|
310
|
+
|
254
311
|
# @param (see #add_attribute)
|
255
312
|
# @return (see #add_attribute)
|
256
313
|
def create_nonjava_property(attribute, type, *flags)
|
@@ -279,14 +336,6 @@ module Jinx
|
|
279
336
|
end
|
280
337
|
end
|
281
338
|
|
282
|
-
# Detects the first attribute with the given type.
|
283
|
-
#
|
284
|
-
# @param [Class] klass the target attribute type
|
285
|
-
# @return [Symbol, nil] the attribute with the given type
|
286
|
-
def detect_attribute_with_type(klass)
|
287
|
-
property_hash.detect_key_with_value { |prop| prop.type == klass }
|
288
|
-
end
|
289
|
-
|
290
339
|
# Creates the given attribute alias. If the attribute metadata is registered with this class, then
|
291
340
|
# this method overrides +Class.alias_attribute+ to create a new alias reader (writer) method
|
292
341
|
# which delegates to the attribute reader (writer, resp.). This aliasing mechanism differs from
|
@@ -398,18 +447,25 @@ module Jinx
|
|
398
447
|
|
399
448
|
# Marks the given attribute with flags supported by {Property#qualify}.
|
400
449
|
#
|
401
|
-
# @param [
|
450
|
+
# @param [Property] property the property to qualify
|
402
451
|
# @param [{Symbol => Object}] the flags to apply to the restricted attribute
|
403
|
-
def
|
404
|
-
|
405
|
-
|
406
|
-
prop.qualify(*flags)
|
452
|
+
def qualify_property(property, *flags)
|
453
|
+
if property.declarer == self then
|
454
|
+
property.qualify(*flags)
|
407
455
|
else
|
408
|
-
logger.debug { "Restricting #{
|
409
|
-
|
456
|
+
logger.debug { "Restricting #{property.declarer.qp}.#{property} to #{qp} with additional flags #{flags.to_series}" }
|
457
|
+
property.restrict_flags(self, *flags)
|
410
458
|
end
|
411
459
|
end
|
412
460
|
|
461
|
+
# Convenience method which delegates to {#qualify_property}.
|
462
|
+
#
|
463
|
+
# @param [Symbol] attribute the attribute to qualify
|
464
|
+
# @param [{Symbol => Object}] (see #qualify_property)
|
465
|
+
def qualify_attribute(attribute, *flags)
|
466
|
+
qualify_property(property(attribute), *flags)
|
467
|
+
end
|
468
|
+
|
413
469
|
# Removes the given attribute from this Resource.
|
414
470
|
# An attribute declared in a superclass Resource is hidden from this Resource but retained in
|
415
471
|
# the declaring Resource.
|
@@ -431,11 +487,16 @@ module Jinx
|
|
431
487
|
anc_alias_hash = @alias_std_prop_map.components[1]
|
432
488
|
@alias_std_prop_map.components[1] = anc_alias_hash.filter_on_key { |pa| pa != attribute }
|
433
489
|
end
|
490
|
+
logger.debug { "Removed the #{qp} #{attribute} property." }
|
434
491
|
end
|
435
492
|
|
436
493
|
# @param [Property] the property to add
|
437
494
|
def add_property(property)
|
438
495
|
pa = property.attribute
|
496
|
+
# Guard against redundant property
|
497
|
+
if @local_prop_hash.has_key?(pa) then
|
498
|
+
raise ArgumentError.new("#{self} property already exists: #{pa}")
|
499
|
+
end
|
439
500
|
@local_prop_hash[pa] = property
|
440
501
|
# map the attribute symbol to itself in the alias map
|
441
502
|
@local_std_prop_hash[pa] = pa
|
@@ -451,13 +512,13 @@ module Jinx
|
|
451
512
|
end
|
452
513
|
|
453
514
|
# Appends to the given enumerable the result of evaluating the block given to this method
|
454
|
-
# on the superclass, if the superclass is
|
515
|
+
# on the superclass, if the superclass is also a {Resource} class.
|
455
516
|
#
|
456
517
|
# @param [Enumerable] enum the base collection
|
457
518
|
# @return [Enumerable] the {Enumerable#union} of the base collection with the superclass
|
458
519
|
# collection, if applicable
|
459
520
|
def append_ancestor_enum(enum)
|
460
|
-
return enum unless Class === self and superclass
|
521
|
+
return enum unless Class === self and superclass < Resource and superclass.introspected?
|
461
522
|
anc_enum = yield superclass
|
462
523
|
if anc_enum.nil? then
|
463
524
|
raise MetadataError.new("#{qp} superclass #{superclass.qp} does not have required metadata")
|