caruby-core 1.4.9 → 1.5.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/History.md +48 -0
- data/lib/caruby/cli/command.rb +2 -1
- data/lib/caruby/csv/csv_mapper.rb +8 -8
- data/lib/caruby/database/persistable.rb +44 -65
- data/lib/caruby/database/persistence_service.rb +12 -9
- data/lib/caruby/database/persistifier.rb +14 -14
- data/lib/caruby/database/reader.rb +53 -51
- data/lib/caruby/database/search_template_builder.rb +9 -10
- data/lib/caruby/database/store_template_builder.rb +58 -58
- data/lib/caruby/database/writer.rb +96 -96
- data/lib/caruby/database.rb +19 -19
- data/lib/caruby/domain/attribute.rb +581 -0
- data/lib/caruby/domain/attributes.rb +615 -0
- data/lib/caruby/domain/dependency.rb +240 -0
- data/lib/caruby/domain/importer.rb +183 -0
- data/lib/caruby/domain/introspection.rb +176 -0
- data/lib/caruby/domain/inverse.rb +173 -0
- data/lib/caruby/domain/inversible.rb +1 -2
- data/lib/caruby/domain/java_attribute.rb +173 -0
- data/lib/caruby/domain/merge.rb +13 -10
- data/lib/caruby/domain/metadata.rb +141 -0
- data/lib/caruby/domain/mixin.rb +35 -0
- data/lib/caruby/domain/reference_visitor.rb +5 -3
- data/lib/caruby/domain.rb +340 -0
- data/lib/caruby/import/java.rb +29 -25
- data/lib/caruby/migration/migratable.rb +5 -5
- data/lib/caruby/migration/migrator.rb +19 -15
- data/lib/caruby/migration/resource_module.rb +1 -1
- data/lib/caruby/resource.rb +39 -30
- data/lib/caruby/util/collection.rb +94 -33
- data/lib/caruby/util/coordinate.rb +28 -2
- data/lib/caruby/util/log.rb +4 -4
- data/lib/caruby/util/module.rb +12 -28
- data/lib/caruby/util/partial_order.rb +9 -10
- data/lib/caruby/util/pretty_print.rb +46 -26
- data/lib/caruby/util/topological_sync_enumerator.rb +10 -4
- data/lib/caruby/util/transitive_closure.rb +2 -2
- data/lib/caruby/util/visitor.rb +1 -1
- data/lib/caruby/version.rb +1 -1
- data/test/lib/caruby/database/persistable_test.rb +1 -1
- data/test/lib/caruby/domain/domain_test.rb +14 -28
- data/test/lib/caruby/domain/inversible_test.rb +1 -1
- data/test/lib/caruby/import/java_test.rb +5 -0
- data/test/lib/caruby/migration/test_case.rb +0 -1
- data/test/lib/caruby/test_case.rb +9 -10
- data/test/lib/caruby/util/collection_test.rb +23 -5
- data/test/lib/caruby/util/module_test.rb +10 -14
- data/test/lib/caruby/util/partial_order_test.rb +16 -15
- data/test/lib/caruby/util/visitor_test.rb +1 -1
- data/test/lib/examples/galena/clinical_trials/migration/test_case.rb +1 -1
- metadata +16 -15
- data/History.txt +0 -44
- data/lib/caruby/domain/attribute_metadata.rb +0 -551
- data/lib/caruby/domain/java_attribute_metadata.rb +0 -183
- data/lib/caruby/domain/resource_attributes.rb +0 -565
- data/lib/caruby/domain/resource_dependency.rb +0 -217
- data/lib/caruby/domain/resource_introspection.rb +0 -160
- data/lib/caruby/domain/resource_inverse.rb +0 -151
- data/lib/caruby/domain/resource_metadata.rb +0 -155
- data/lib/caruby/domain/resource_module.rb +0 -370
- data/lib/caruby/yard/resource_metadata_handler.rb +0 -8
@@ -1,217 +0,0 @@
|
|
1
|
-
require 'caruby/util/validation'
|
2
|
-
|
3
|
-
module CaRuby
|
4
|
-
# ResourceMetadata mix-in to capture Resource dependency.
|
5
|
-
module ResourceDependency
|
6
|
-
|
7
|
-
attr_reader :owners, :owner_attributes
|
8
|
-
|
9
|
-
# Returns the attribute which references the dependent type, or nil if none.
|
10
|
-
def dependent_attribute(type)
|
11
|
-
dependent_attributes.detect { |attr| type <= domain_type(attr) }
|
12
|
-
end
|
13
|
-
|
14
|
-
# Adds the given attribute as a dependent.
|
15
|
-
#
|
16
|
-
# Supported flags include the following:
|
17
|
-
# * :logical - the dependency relation is not cascaded by the application
|
18
|
-
# * :autogenerated - a dependent can be created by the application as a side-effect of creating the owner
|
19
|
-
# * :disjoint - the dependent owner has more than one owner attribute, but only one owner instance
|
20
|
-
#
|
21
|
-
# If the attribute inverse is not a collection, then the attribute writer
|
22
|
-
# is modified to delegate to the dependent owner writer. This enforces
|
23
|
-
# referential integrity by ensuring that the following post-condition holds:
|
24
|
-
# * _owner_._attribute_._inverse_ == _owner_
|
25
|
-
# where:
|
26
|
-
# * _owner_ is an instance this attribute's declaring class
|
27
|
-
# * _inverse_ is the owner inverse attribute defined in the dependent class
|
28
|
-
#
|
29
|
-
# @param [Symbol] attribute the dependent to add
|
30
|
-
# @param [<Symbol>] flags the attribute qualifier flags
|
31
|
-
def add_dependent_attribute(attribute, *flags)
|
32
|
-
attr_md = attribute_metadata(attribute)
|
33
|
-
flags << :dependent unless flags.include?(:dependent)
|
34
|
-
attr_md.qualify(*flags)
|
35
|
-
inverse = attr_md.inverse
|
36
|
-
inv_type = attr_md.type
|
37
|
-
# example: Parent.add_dependent_attribute(:children) with inverse :parent calls the following:
|
38
|
-
# Child.add_owner(Parent, :children, :parent)
|
39
|
-
inv_type.add_owner(self, attribute, inverse)
|
40
|
-
end
|
41
|
-
|
42
|
-
# Makes a new owner attribute. The attribute name is the lower-case demodulized
|
43
|
-
# owner class name. The owner class must reference this class via the given
|
44
|
-
# inverse dependent attribute.
|
45
|
-
#
|
46
|
-
# @param klass (see #detect_owner_attribute)
|
47
|
-
# @param [Symbol] the owner -> dependent inverse attribute
|
48
|
-
# @return [Symbol] this class's new owner attribute
|
49
|
-
# @raise [ArgumentError] if the inverse is nil
|
50
|
-
def create_owner_attribute(klass, inverse)
|
51
|
-
if inverse.nil? then
|
52
|
-
raise ArgumentError.new("Cannot create a #{qp} owner attribute to #{klass} without a dependent attribute to this class.")
|
53
|
-
end
|
54
|
-
attr = klass.name.demodulize.underscore.to_sym
|
55
|
-
attr_accessor(attr)
|
56
|
-
add_attribute(attr, klass)
|
57
|
-
attribute_metadata(attr).inverse = inverse
|
58
|
-
logger.debug { "Created #{qp} owner attribute #{attr} with inverse #{klass.qp}.#{inverse}." }
|
59
|
-
attr
|
60
|
-
end
|
61
|
-
|
62
|
-
# @return [Boolean] whether this class depends on an owner
|
63
|
-
def dependent?
|
64
|
-
not owners.empty?
|
65
|
-
end
|
66
|
-
|
67
|
-
# @return [Boolean] whether this class has an owner which cascades save operations to this dependent
|
68
|
-
def cascaded_dependent?
|
69
|
-
owner_attribute_metadata_enumerator.any? { |attr_md| attr_md.cascaded? }
|
70
|
-
end
|
71
|
-
|
72
|
-
# @return [Boolean] whether this class depends the given other class
|
73
|
-
def depends_on?(other)
|
74
|
-
owners.detect { |owner| owner === other }
|
75
|
-
end
|
76
|
-
|
77
|
-
# @return [Symbol, nil] the attribute which references the dependent type,
|
78
|
-
# or nil if none
|
79
|
-
def dependent_attribute(dep_type)
|
80
|
-
type = dependent_attributes.detect { |attr| domain_type(attr) == dep_type }
|
81
|
-
return type if type
|
82
|
-
dependent_attribute(dep_type.superclass) if dep_type.superclass < Resource
|
83
|
-
end
|
84
|
-
|
85
|
-
# @return [Symbol, nil] the sole owner attribute of this class, or nil if there
|
86
|
-
# is not exactly one owner
|
87
|
-
def owner_attribute
|
88
|
-
attr_md = owner_attribute_metadata_enumerator.first || return
|
89
|
-
# There is at most one non-nil value in the owner class => AttributeMetadata hash.
|
90
|
-
# If there is such a value, then return the attribute symbol.
|
91
|
-
if owner_attribute_metadata_enumerator.size == 1 then
|
92
|
-
attr_md.to_sym
|
93
|
-
end
|
94
|
-
end
|
95
|
-
|
96
|
-
# @return [<Symbol>] this class's owner attributes
|
97
|
-
def owner_attributes
|
98
|
-
owner_attribute_metadata_enumerator.transform { |attr_md| attr_md.to_sym }
|
99
|
-
end
|
100
|
-
|
101
|
-
# @return [<Class>] this class's dependent types
|
102
|
-
def dependents
|
103
|
-
dependent_attributes.wrap { |attr| attr.type }
|
104
|
-
end
|
105
|
-
|
106
|
-
# @return [<Class>] this class's owner types
|
107
|
-
def owners
|
108
|
-
@owners ||= Enumerable::Enumerator.new(owner_attribute_metadata_hash, :each_key)
|
109
|
-
end
|
110
|
-
|
111
|
-
protected
|
112
|
-
|
113
|
-
# Adds the given owner class to this dependent class.
|
114
|
-
# This method must be called before any dependent attribute is accessed.
|
115
|
-
# If the attribute is given, then the attribute inverse is set.
|
116
|
-
# Otherwise, if there is not already an owner attribute, then a new owner attribute is created.
|
117
|
-
# The name of the new attribute is the lower-case demodulized owner class name.
|
118
|
-
#
|
119
|
-
# @param [Class] the owner class
|
120
|
-
# @param [Symbol] inverse the owner -> dependent attribute
|
121
|
-
# @param [Symbol, nil] attribute the dependent -> owner attribute, if known
|
122
|
-
# @raise [ValidationError] if the inverse is nil
|
123
|
-
def add_owner(klass, inverse, attribute=nil)
|
124
|
-
if inverse.nil? then raise ValidationError.new("Owner #{klass.qp} missing dependent attribute for dependent #{qp}") end
|
125
|
-
logger.debug { "Adding #{qp} owner #{klass.qp}#{' attribute ' + attribute.to_s if attribute}#{' inverse ' + inverse.to_s if inverse}..." }
|
126
|
-
if @owner_attr_hash then
|
127
|
-
raise MetadataError.new("Can't add #{qp} owner #{klass.qp} after dependencies have been accessed")
|
128
|
-
end
|
129
|
-
|
130
|
-
# detect the owner attribute, if necessary
|
131
|
-
attribute ||= detect_owner_attribute(klass, inverse)
|
132
|
-
attr_md = attribute_metadata(attribute) if attribute
|
133
|
-
# Add the owner class => attribute entry.
|
134
|
-
# The attribute is nil if the dependency is unidirectional, i.e. there is an owner class which
|
135
|
-
# references this class via a dependency attribute but there is no inverse owner attribute.
|
136
|
-
local_owner_attribute_metadata_hash[klass] = attr_md
|
137
|
-
# If the dependency is unidirectional, then our job is done.
|
138
|
-
return if attribute.nil?
|
139
|
-
|
140
|
-
# set the inverse if necessary
|
141
|
-
unless attr_md.inverse then
|
142
|
-
set_attribute_inverse(attribute, inverse)
|
143
|
-
end
|
144
|
-
# set the owner flag if necessary
|
145
|
-
unless attr_md.owner? then attr_md.qualify(:owner) end
|
146
|
-
# Redefine the writer method to warn when changing the owner
|
147
|
-
rdr, wtr = attr_md.accessors
|
148
|
-
logger.debug { "Injecting owner change warning into #{qp}.#{attribute} writer method #{wtr}..." }
|
149
|
-
redefine_method(wtr) do |old_wtr|
|
150
|
-
lambda do |ref|
|
151
|
-
prev = send(rdr)
|
152
|
-
if prev and prev != ref then
|
153
|
-
if ref.nil? then
|
154
|
-
logger.warn("Unsetting the #{self} owner #{attribute} #{prev}.")
|
155
|
-
elsif ref.identifier != prev.identifier then
|
156
|
-
logger.warn("Resetting the #{self} owner #{attribute} from #{prev} to #{ref}.")
|
157
|
-
end
|
158
|
-
end
|
159
|
-
send(old_wtr, ref)
|
160
|
-
end
|
161
|
-
end
|
162
|
-
end
|
163
|
-
|
164
|
-
# Adds the given attribute as an owner. This method is called when a new attribute is added that
|
165
|
-
# references an existing owner.
|
166
|
-
#
|
167
|
-
# @param [Symbol] attribute the owner attribute
|
168
|
-
def add_owner_attribute(attribute)
|
169
|
-
attr_md = attribute_metadata(attribute)
|
170
|
-
otype = attr_md.type
|
171
|
-
hash = local_owner_attribute_metadata_hash
|
172
|
-
if hash.include?(otype) then
|
173
|
-
oattr = hash[otype]
|
174
|
-
unless oattr.nil? then
|
175
|
-
raise MetadataError.new("Cannot set #{qp} owner attribute to #{attribute} since it is already set to #{oattr}")
|
176
|
-
end
|
177
|
-
hash[otype] = attr_md
|
178
|
-
else
|
179
|
-
add_owner(otype, attr_md.inverse, attribute)
|
180
|
-
end
|
181
|
-
end
|
182
|
-
|
183
|
-
# @return [{Class => AttributeMetadata}] this class's owner type => attribute hash
|
184
|
-
def owner_attribute_metadata_hash
|
185
|
-
@oa_hash ||= create_owner_attribute_metadata_hash
|
186
|
-
end
|
187
|
-
|
188
|
-
private
|
189
|
-
|
190
|
-
def local_owner_attribute_metadata_hash
|
191
|
-
@local_oa_hash ||= {}
|
192
|
-
end
|
193
|
-
|
194
|
-
# @return [{Class => AttributeMetadata}] a new owner type => attribute hash
|
195
|
-
def create_owner_attribute_metadata_hash
|
196
|
-
local = local_owner_attribute_metadata_hash
|
197
|
-
superclass < Resource ? local.union(superclass.owner_attribute_metadata_hash) : local
|
198
|
-
end
|
199
|
-
|
200
|
-
# @return [<AttributeMetadata>] the owner attributes
|
201
|
-
def owner_attribute_metadata_enumerator
|
202
|
-
# Enumerate each owner AttributeMetadata, filtering out nil values.
|
203
|
-
@oa_enum ||= Enumerable::Enumerator.new(owner_attribute_metadata_hash, :each_value).filter
|
204
|
-
end
|
205
|
-
|
206
|
-
# Returns the attribute which references the owner. The owner attribute is the inverse
|
207
|
-
# of the given owner class inverse attribute, if it exists. Otherwise, the owner
|
208
|
-
# attribute is inferred by #{ResourceInverse#detect_inverse_attribute}.
|
209
|
-
|
210
|
-
# @param klass (see #add_owner)
|
211
|
-
# @param [Symbol] inverse the owner -> dependent attribute
|
212
|
-
# @return [Symbol, nil] this class's owner attribute
|
213
|
-
def detect_owner_attribute(klass, inverse)
|
214
|
-
klass.attribute_metadata(inverse).inverse or detect_inverse_attribute(klass)
|
215
|
-
end
|
216
|
-
end
|
217
|
-
end
|
@@ -1,160 +0,0 @@
|
|
1
|
-
require 'caruby/import/java'
|
2
|
-
require 'caruby/domain/java_attribute_metadata'
|
3
|
-
|
4
|
-
module CaRuby
|
5
|
-
# ResourceMetadata mix-in to infer attribute meta-data from Java properties.
|
6
|
-
module ResourceIntrospection
|
7
|
-
|
8
|
-
private
|
9
|
-
|
10
|
-
# Defines the Java property attribute and standard attribute methods, e.g.
|
11
|
-
# +study_protocol+ and +studyProtocol+. A boolean attribute is provisioned
|
12
|
-
# with an additional reader alias, e.g. +available?+ for +is_available+.
|
13
|
-
#
|
14
|
-
# Each Java property attribute delegates to the Java property getter and setter.
|
15
|
-
# Each standard attribute delegates to the Java property attribute.
|
16
|
-
# Redefining these methods results in a call to the redefined method.
|
17
|
-
# This contrasts with a Ruby alias, where the alias remains bound to the original method body.
|
18
|
-
def introspect
|
19
|
-
init_attributes # in ResourceAttributes
|
20
|
-
logger.debug { "Introspecting #{qp} metadata..." }
|
21
|
-
# filter properties for those with both a read and write method
|
22
|
-
pds = java_properties(false)
|
23
|
-
# define the standard Java attribute methods
|
24
|
-
pds.each { |pd| define_java_attribute(pd) }
|
25
|
-
logger.debug { "Introspection of #{qp} metadata complete." }
|
26
|
-
self
|
27
|
-
end
|
28
|
-
|
29
|
-
# Defines the Java property attribute and standard attribute methods, e.g.
|
30
|
-
# +study_protocol+ and +studyProtocol+. A boolean attribute is provisioned
|
31
|
-
# with an additional reader alias, e.g. +available?+ for +is_available+.
|
32
|
-
#
|
33
|
-
# A standard attribute which differs from the property attribute delegates
|
34
|
-
# to the property attribute, e.g. +study_protocol+ delegates to +studyProtocol+
|
35
|
-
# rather than aliasing +setStudyProtocol+. Redefining these methods results
|
36
|
-
# in a call to the redefined method. This contrasts with a Ruby alias,
|
37
|
-
# where each attribute alias is bound to the respective property reader or
|
38
|
-
# writer.
|
39
|
-
def define_java_attribute(pd)
|
40
|
-
if transient?(pd) then
|
41
|
-
logger.debug { "Ignoring #{name.demodulize} transient property #{pd.name}." }
|
42
|
-
return
|
43
|
-
end
|
44
|
-
# the standard underscore lower-case attributes
|
45
|
-
attr = create_java_attribute(pd)
|
46
|
-
# delegate the standard attribute accessors to the property accessors
|
47
|
-
alias_attribute_property(attr, pd.name)
|
48
|
-
# add special wrappers
|
49
|
-
wrap_java_attribute(attr, pd)
|
50
|
-
# create Ruby alias for boolean, e.g. alias :empty? for :empty
|
51
|
-
if pd.property_type.name[/\w+$/].downcase == 'boolean' then
|
52
|
-
# strip leading is_, if any, before appending question mark
|
53
|
-
aliaz = attr.to_s[/^(is_)?(\w+)/, 2] << '?'
|
54
|
-
delegate_to_attribute(aliaz, attr)
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
|
-
# Adds a filter to the attribute access method for the property descriptor pd if it is a String or Date.
|
59
|
-
def wrap_java_attribute(attribute, pd)
|
60
|
-
if pd.propertyType == Java::JavaLang::String.java_class then
|
61
|
-
wrap_java_string_attribute(attribute, pd)
|
62
|
-
elsif pd.propertyType == Java::JavaUtil::Date.java_class then
|
63
|
-
wrap_java_date_attribute(attribute, pd)
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
|
-
# Adds a to_s filter to this Class's String property access methods.
|
68
|
-
def wrap_java_string_attribute(attribute, pd)
|
69
|
-
# filter the attribute writer
|
70
|
-
awtr = "#{attribute}=".to_sym
|
71
|
-
pwtr = pd.write_method.name.to_sym
|
72
|
-
define_method(awtr) do |value|
|
73
|
-
stdval = value.to_s unless value.nil_or_empty?
|
74
|
-
send(pwtr, stdval)
|
75
|
-
end
|
76
|
-
logger.debug { "Filtered #{qp} #{awtr} method with non-String -> String converter." }
|
77
|
-
end
|
78
|
-
|
79
|
-
# Adds a date parser filter to this Class's Date property access methods.
|
80
|
-
def wrap_java_date_attribute(attribute, pd)
|
81
|
-
# filter the attribute reader
|
82
|
-
prdr = pd.read_method.name.to_sym
|
83
|
-
define_method(attribute) do
|
84
|
-
value = send(prdr)
|
85
|
-
Java::JavaUtil::Date === value ? value.to_ruby_date : value
|
86
|
-
end
|
87
|
-
|
88
|
-
# filter the attribute writer
|
89
|
-
awtr = "#{attribute}=".to_sym
|
90
|
-
pwtr = pd.write_method.name.to_sym
|
91
|
-
define_method(awtr) do |value|
|
92
|
-
value = Java::JavaUtil::Date.from_ruby_date(value) if ::Date === value
|
93
|
-
send(pwtr, value)
|
94
|
-
end
|
95
|
-
|
96
|
-
logger.debug { "Filtered #{qp} #{attribute} and #{awtr} methods with Java Date <-> Ruby Date converter." }
|
97
|
-
end
|
98
|
-
|
99
|
-
# Aliases the methods _aliaz_ and _aliaz=_ to _property_ and _property=_, resp.,
|
100
|
-
# where _property_ is the Java property name for the attribute.
|
101
|
-
def alias_attribute_property(aliaz, attribute)
|
102
|
-
# strip the Java reader and writer is/get/set prefix and make a symbol
|
103
|
-
prdr, pwtr = attribute_metadata(attribute).property_accessors
|
104
|
-
alias_method(aliaz, prdr)
|
105
|
-
writer = "#{aliaz}=".to_sym
|
106
|
-
alias_method(writer, pwtr)
|
107
|
-
end
|
108
|
-
|
109
|
-
# Makes a standard attribute for the given property descriptor.
|
110
|
-
# Adds a camelized Java-like alias to the standard attribute.
|
111
|
-
#
|
112
|
-
# caTissue alert - DE annotation collection attributes are often misnamed,
|
113
|
-
# e.g. +histologic_grade+ for a +HistologicGrade+ collection attribute.
|
114
|
-
# This is fixed by adding a pluralized alias, e.g. +histologic_grades+.
|
115
|
-
#
|
116
|
-
# @return a new attribute symbol created for the given PropertyDescriptor pd
|
117
|
-
def create_java_attribute(pd)
|
118
|
-
# make the attribute metadata
|
119
|
-
attr_md = JavaAttributeMetadata.new(pd, self)
|
120
|
-
add_attribute_metadata(attr_md)
|
121
|
-
# the property name is an alias for the standard attribute
|
122
|
-
std_attr = attr_md.to_sym
|
123
|
-
prop_attr = pd.name.to_sym
|
124
|
-
delegate_to_attribute(prop_attr, std_attr) unless prop_attr == std_attr
|
125
|
-
|
126
|
-
# alias a misnamed collection attribute, if necessary
|
127
|
-
if attr_md.collection? then
|
128
|
-
name = std_attr.to_s
|
129
|
-
if name.singularize == name then
|
130
|
-
aliaz = name.pluralize.to_sym
|
131
|
-
if aliaz != name then
|
132
|
-
logger.debug { "Adding annotation #{qp} alias #{aliaz} to the misnamed collection attribute #{std_attr}..." }
|
133
|
-
delegate_to_attribute(aliaz, std_attr)
|
134
|
-
end
|
135
|
-
end
|
136
|
-
end
|
137
|
-
|
138
|
-
std_attr
|
139
|
-
end
|
140
|
-
|
141
|
-
# Defines methods _aliaz_ and _aliaz=_ which calls the standard _attribute_ and
|
142
|
-
# _attribute=_ accessor methods, resp.
|
143
|
-
# Calling rather than aliasing the attribute accessor allows the aliaz accessor to
|
144
|
-
# reflect a change to the attribute accessor.
|
145
|
-
def delegate_to_attribute(aliaz, attribute)
|
146
|
-
rdr, wtr = attribute_metadata(attribute).accessors
|
147
|
-
define_method(aliaz) { send(rdr) }
|
148
|
-
define_method("#{aliaz}=".to_sym) { |value| send(wtr, value) }
|
149
|
-
add_alias(aliaz, attribute)
|
150
|
-
end
|
151
|
-
|
152
|
-
# Makes a new synthetic attribute for each _method_ => _original_ hash entry.
|
153
|
-
#
|
154
|
-
# @param (see Class#offset_attr_accessor)
|
155
|
-
def offset_attribute(hash, offset=nil)
|
156
|
-
offset_attr_accessor(hash, offset)
|
157
|
-
hash.each { |attr, original| add_attribute(attr, attribute_metadata(original).type) }
|
158
|
-
end
|
159
|
-
end
|
160
|
-
end
|
@@ -1,151 +0,0 @@
|
|
1
|
-
require 'caruby/import/java'
|
2
|
-
require 'caruby/domain/java_attribute_metadata'
|
3
|
-
|
4
|
-
module CaRuby
|
5
|
-
# ResourceMetadata mix-in to infer and set inverse attributes.
|
6
|
-
module ResourceInverse
|
7
|
-
|
8
|
-
protected
|
9
|
-
|
10
|
-
# Infers the inverse of the given attribute declared by this class. A domain attribute is
|
11
|
-
# recognized as an inverse according to the {ResourceInverse#detect_inverse_attribute}
|
12
|
-
# criterion.
|
13
|
-
#
|
14
|
-
# @param [AttributeMetadata] attr_md the attribute to check
|
15
|
-
def infer_attribute_inverse(attr_md)
|
16
|
-
inv = attr_md.type.detect_inverse_attribute(self)
|
17
|
-
if inv then
|
18
|
-
logger.debug { "Detected #{qp} #{attr_md} inverse #{inv.qp}." }
|
19
|
-
set_attribute_inverse(attr_md.to_sym, inv)
|
20
|
-
else
|
21
|
-
logger.debug { "No inverse detected for #{qp} #{attr_md}." }
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
# Sets the given bi-directional association attribute's inverse.
|
26
|
-
#
|
27
|
-
# @param [Symbol] attribute the subject attribute
|
28
|
-
# @param [Symbol] the attribute inverse
|
29
|
-
# @raise [TypeError] if the inverse type is incompatible with this Resource
|
30
|
-
def set_attribute_inverse(attribute, inverse)
|
31
|
-
attr_md = attribute_metadata(attribute)
|
32
|
-
# return if inverse is already set
|
33
|
-
return if attr_md.inverse == inverse
|
34
|
-
# the default inverse
|
35
|
-
inverse ||= attr_md.type.detect_inverse_attribute(self)
|
36
|
-
# the inverse attribute meta-data
|
37
|
-
inv_md = attr_md.type.attribute_metadata(inverse)
|
38
|
-
# if the attribute is the many side of a 1:M relation, then delegate to the one side.
|
39
|
-
if attr_md.collection? and not inv_md.collection? then
|
40
|
-
return attr_md.type.set_attribute_inverse(inverse, attribute)
|
41
|
-
end
|
42
|
-
# this class must be the same as or a subclass of the inverse attribute type
|
43
|
-
unless self <= inv_md.type then
|
44
|
-
raise TypeError.new("Cannot set #{qp}.#{attribute} inverse to #{attr_md.type.qp}.#{attribute} with incompatible type #{inv_md.type.qp}")
|
45
|
-
end
|
46
|
-
|
47
|
-
# if the attribute is not declared by this class, then make a new attribute
|
48
|
-
# metadata specialized for this class.
|
49
|
-
unless attr_md.declarer == self then
|
50
|
-
attr_md = attr_md.dup
|
51
|
-
attr_md.declarer = self
|
52
|
-
add_attribute_metadata(attribute, inverse)
|
53
|
-
logger.debug { "Copied #{attr_md.declarer}.#{attribute} to #{qp} with restricted inverse return type #{qp}." }
|
54
|
-
end
|
55
|
-
# Set the inverse in the attribute metadata.
|
56
|
-
attr_md.inverse = inverse
|
57
|
-
|
58
|
-
# If attribute is the one side of a 1:M or non-reflexive 1:1 relation, then add the inverse updater.
|
59
|
-
unless attr_md.collection? then
|
60
|
-
add_inverse_updater(attribute, inverse)
|
61
|
-
unless attr_md.type == inv_md.type or inv_md.collection? then
|
62
|
-
attr_md.type.delegate_writer_to_inverse(inverse, attribute)
|
63
|
-
end
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
|
-
# Detects an unambiguous attribute which refers to the given referencing class.
|
68
|
-
# If there is exactly one attribute with the given return type, then that attribute is chosen.
|
69
|
-
# Otherwise, the attribute whose name matches the underscored referencing class name is chosen,
|
70
|
-
# if any.
|
71
|
-
#
|
72
|
-
# @param [Class] klass the referencing class
|
73
|
-
# @return [Symbol, nil] the inverse attribute for the given referencing class and inverse,
|
74
|
-
# or nil if no owner attribute was detected
|
75
|
-
def detect_inverse_attribute(klass)
|
76
|
-
# the candidate attributes which return the referencing type
|
77
|
-
candidates = domain_attributes.compose { |attr_md| klass <= attr_md.type }
|
78
|
-
attr = detect_inverse_attribute_from_candidates(klass, candidates)
|
79
|
-
if attr then
|
80
|
-
logger.debug { "#{qp} #{klass.qp} inverse attribute is #{attr}." }
|
81
|
-
else
|
82
|
-
logger.debug { "#{qp} #{klass.qp} inverse attribute was not detected." }
|
83
|
-
end
|
84
|
-
attr
|
85
|
-
end
|
86
|
-
|
87
|
-
# Redefines the attribute writer method to delegate to its inverse writer.
|
88
|
-
# This is done to enforce inverse integrity.
|
89
|
-
#
|
90
|
-
# For a +Person+ attribute +account+ with inverse +holder+, this is equivalent to the following:
|
91
|
-
# class Person
|
92
|
-
# alias :set_account :account=
|
93
|
-
# def account=(acct)
|
94
|
-
# acct.holder = self if acct
|
95
|
-
# set_account(acct)
|
96
|
-
# end
|
97
|
-
# end
|
98
|
-
def delegate_writer_to_inverse(attribute, inverse)
|
99
|
-
attr_md = attribute_metadata(attribute)
|
100
|
-
# nothing to do if no inverse
|
101
|
-
inv_attr_md = attr_md.inverse_attribute_metadata || return
|
102
|
-
logger.debug { "Delegating #{qp}.#{attribute} update to the inverse #{attr_md.type.qp}.#{inv_attr_md}..." }
|
103
|
-
# redefine the write to set the dependent inverse
|
104
|
-
redefine_method(attr_md.writer) do |old_writer|
|
105
|
-
# delegate to the CaRuby::Resource set_inverse method
|
106
|
-
lambda { |dep| set_inverse(dep, old_writer, inv_attr_md.writer) }
|
107
|
-
end
|
108
|
-
end
|
109
|
-
|
110
|
-
private
|
111
|
-
|
112
|
-
# @param klass (see #detect_inverse_attribute)
|
113
|
-
# @param [<Symbol>] candidates the attributes constrained to the target type
|
114
|
-
# @return (see #detect_inverse_attribute)
|
115
|
-
def detect_inverse_attribute_from_candidates(klass, candidates)
|
116
|
-
return if candidates.empty?
|
117
|
-
# there can be at most one owner attribute per owner.
|
118
|
-
return candidates.first.to_sym if candidates.size == 1
|
119
|
-
# by convention, if more than one attribute references the owner type,
|
120
|
-
# then the attribute named after the owner type is the owner attribute
|
121
|
-
tgt = klass.name[/\w+$/].underscore.to_sym
|
122
|
-
tgt if candidates.detect { |attr| attr == tgt }
|
123
|
-
end
|
124
|
-
|
125
|
-
# Modifies the given attribute writer method if necessary to update the given inverse_attr value.
|
126
|
-
# This method is called on dependent and attributes qualified as inversible.
|
127
|
-
#
|
128
|
-
# @see #set_attribute_inverse
|
129
|
-
def add_inverse_updater(attribute, inverse)
|
130
|
-
attr_md = attribute_metadata(attribute)
|
131
|
-
# the reader and writer methods
|
132
|
-
rdr, wtr = attr_md.accessors
|
133
|
-
logger.debug { "Injecting inverse #{inverse} updater into #{qp}.#{attribute} writer method #{wtr}..." }
|
134
|
-
# the inverse atttribute metadata
|
135
|
-
inv_attr_md = attr_md.inverse_attribute_metadata
|
136
|
-
# the inverse attribute reader and writer
|
137
|
-
inv_rdr, inv_wtr = inv_accessors = inv_attr_md.accessors
|
138
|
-
|
139
|
-
# Redefine the writer method to update the inverse by delegating to the inverse
|
140
|
-
redefine_method(wtr) do |old_wtr|
|
141
|
-
# the attribute reader and (superseded) writer
|
142
|
-
accessors = [rdr, old_wtr]
|
143
|
-
if inv_attr_md.collection? then
|
144
|
-
lambda { |other| add_to_inverse_collection(other, accessors, inv_rdr) }
|
145
|
-
else
|
146
|
-
lambda { |other| set_inversible_noncollection_attribute(other, accessors, inv_wtr) }
|
147
|
-
end
|
148
|
-
end
|
149
|
-
end
|
150
|
-
end
|
151
|
-
end
|
@@ -1,155 +0,0 @@
|
|
1
|
-
require 'caruby/util/collection'
|
2
|
-
require 'caruby/import/java'
|
3
|
-
require 'caruby/domain/java_attribute_metadata'
|
4
|
-
require 'caruby/domain/resource_introspection'
|
5
|
-
require 'caruby/domain/resource_inverse'
|
6
|
-
require 'caruby/domain/resource_dependency'
|
7
|
-
require 'caruby/domain/resource_attributes'
|
8
|
-
|
9
|
-
module CaRuby
|
10
|
-
# Exception raised if a meta-data setting is missing or invalid.
|
11
|
-
class MetadataError < RuntimeError; end
|
12
|
-
|
13
|
-
# Adds introspected metadata to a Class.
|
14
|
-
module ResourceMetadata
|
15
|
-
include ResourceIntrospection, ResourceInverse, ResourceDependency, ResourceAttributes
|
16
|
-
|
17
|
-
attr_reader :domain_module
|
18
|
-
|
19
|
-
# JRuby 1.6 alert - if a concrete class implements a constructor, then calling super in an
|
20
|
-
# included module initialize method results in a recursive call back into that initialize.
|
21
|
-
# The work-around is to redefine each domain class new method to call the initalize_content
|
22
|
-
# method instead. All Resource domain classes or included modules must not override
|
23
|
-
# initialize. They can implement initialize_content instead.
|
24
|
-
def self.extended(klass)
|
25
|
-
klass.class_eval do
|
26
|
-
class << self
|
27
|
-
def new(opts=nil)
|
28
|
-
obj = super()
|
29
|
-
obj.merge_attributes(opts) if opts
|
30
|
-
obj
|
31
|
-
end
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
# Associates meta-data with a Resource class. When a Resource class is added to a ResourceModule,
|
37
|
-
# the ResourceModule extends the Resource class with ResourceMetadata and calls this
|
38
|
-
# {#add_metadata} method with itself as the mod argument. The ResourceModule is accessed
|
39
|
-
# by the {#domain_module} method.
|
40
|
-
def add_metadata(mod)
|
41
|
-
@domain_module = mod
|
42
|
-
introspect # in ResourceIntrospection
|
43
|
-
end
|
44
|
-
|
45
|
-
# @return the domain type for attribute, or nil if attribute is not a domain attribute
|
46
|
-
def domain_type(attribute)
|
47
|
-
attr_md = attribute_metadata(attribute)
|
48
|
-
attr_md.type if attr_md.domain?
|
49
|
-
end
|
50
|
-
|
51
|
-
# Returns an empty value for the given attribute.
|
52
|
-
# * If this class is not abstract, then the empty value is the initialized value.
|
53
|
-
# * Otherwise, if the attribute is a Java primitive number then zero.
|
54
|
-
# * Otherwise, if the attribute is a Java primitive boolean then +false+.
|
55
|
-
# * Otherwise, the empty value is nil.
|
56
|
-
#
|
57
|
-
# @param [Symbol] attribute the target attribute
|
58
|
-
# @return [Numeric, Boolean, Enumerable, nil] the empty attribute value
|
59
|
-
def empty_value(attribute)
|
60
|
-
if abstract? then
|
61
|
-
attr_md = attribute_metadata(attribute)
|
62
|
-
# the Java property type
|
63
|
-
jtype = attr_md.property_descriptor.property_type if JavaAttributeMetadata === attr_md
|
64
|
-
# A primitive is either a boolean or a number (String is not primitive).
|
65
|
-
if jtype and jtype.primitive? then
|
66
|
-
type.name == 'boolean' ? false : 0
|
67
|
-
end
|
68
|
-
else
|
69
|
-
# Since this class is not abstract, create a prototype instance on demand and make
|
70
|
-
# a copy of the initialized collection value from that instance.
|
71
|
-
@prototype ||= new
|
72
|
-
value = @prototype.send(attribute) || return
|
73
|
-
value.class.new
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
|
-
# Prints this classifier's content to the log.
|
78
|
-
def pretty_print(q)
|
79
|
-
# KLUDGE - if not inited then bail.
|
80
|
-
# TODO - isolate when this occurs.
|
81
|
-
if @attr_md_hash.nil? then
|
82
|
-
q.text(qp)
|
83
|
-
return
|
84
|
-
end
|
85
|
-
|
86
|
-
# the Java property descriptors
|
87
|
-
property_descriptors = java_attributes.wrap { |attr| attribute_metadata(attr).property_descriptor }
|
88
|
-
# build a map of relevant display label => attributes
|
89
|
-
prop_printer = property_descriptors.wrap { |pd| PROP_DESC_PRINTER.wrap(pd) }
|
90
|
-
prop_syms = property_descriptors.map { |pd| pd.name.to_sym }.to_set
|
91
|
-
aliases = @alias_std_attr_map.keys - attributes.to_a - prop_syms
|
92
|
-
alias_attr_hash = aliases.to_compact_hash { |aliaz| @alias_std_attr_map[aliaz] }
|
93
|
-
dependents_printer = dependent_attributes.wrap { |attr| DEPENDENT_ATTR_PRINTER.wrap(attribute_metadata(attr)) }
|
94
|
-
owner_printer = owners.wrap { |type| TYPE_PRINTER.wrap(type) }
|
95
|
-
inverses = @attributes.to_compact_hash do |attr|
|
96
|
-
attr_md = attribute_metadata(attr)
|
97
|
-
"#{attr_md.type.qp}.#{attr_md.inverse}" if attr_md.inverse
|
98
|
-
end
|
99
|
-
domain_attr_printer = domain_attributes.to_compact_hash { |attr| domain_type(attr).qp }
|
100
|
-
map = {
|
101
|
-
"Java properties" => prop_printer,
|
102
|
-
"standard attributes" => attributes,
|
103
|
-
"aliases to standard attributes" => alias_attr_hash,
|
104
|
-
"secondary key" => secondary_key_attributes,
|
105
|
-
"mandatory attributes" => mandatory_attributes,
|
106
|
-
"domain attributes" => domain_attr_printer,
|
107
|
-
"creatable domain attributes" => creatable_domain_attributes,
|
108
|
-
"updatable domain attributes" => updatable_domain_attributes,
|
109
|
-
"fetched domain attributes" => fetched_domain_attributes,
|
110
|
-
"cascaded domain attributes" => cascaded_attributes,
|
111
|
-
"owners" => owner_printer,
|
112
|
-
"owner attributes" => owner_attributes,
|
113
|
-
"inverse attributes" => inverses,
|
114
|
-
"dependent attributes" => dependents_printer,
|
115
|
-
"default values" => defaults
|
116
|
-
}.delete_if { |key, value| value.nil_or_empty? }
|
117
|
-
|
118
|
-
# one indented line per entry, all but the last line ending in a comma
|
119
|
-
content = map.map { |label, value| " #{label}=>#{format_print_value(value)}" }.join(",\n")
|
120
|
-
# print the content to the log
|
121
|
-
q.text("#{qp} structure:\n#{content}")
|
122
|
-
end
|
123
|
-
|
124
|
-
protected
|
125
|
-
|
126
|
-
def self.extend_class(klass, mod)
|
127
|
-
klass.extend(self)
|
128
|
-
klass.add_metadata(mod)
|
129
|
-
end
|
130
|
-
|
131
|
-
private
|
132
|
-
|
133
|
-
# A proc to print the unqualified class name.
|
134
|
-
TYPE_PRINTER = PrintWrapper.new { |type| type.qp }
|
135
|
-
|
136
|
-
DEPENDENT_ATTR_PRINTER = PrintWrapper.new do |attr_md|
|
137
|
-
flags = []
|
138
|
-
flags << :logical if attr_md.logical?
|
139
|
-
flags << :autogenerated if attr_md.autogenerated?
|
140
|
-
flags << :disjoint if attr_md.disjoint?
|
141
|
-
flags.empty? ? "#{attr_md}" : "#{attr_md}(#{flags.join(',')})"
|
142
|
-
end
|
143
|
-
|
144
|
-
# A proc to print the property descriptor name.
|
145
|
-
PROP_DESC_PRINTER = PrintWrapper.new { |pd| pd.name }
|
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
|