jinx 2.1.3 → 2.1.4
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/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")
|