caruby-core 1.4.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.txt +4 -0
- data/LEGAL +5 -0
- data/LICENSE +22 -0
- data/README.md +51 -0
- data/doc/website/css/site.css +1 -5
- data/doc/website/images/avatar.png +0 -0
- data/doc/website/images/favicon.ico +0 -0
- data/doc/website/images/logo.png +0 -0
- data/doc/website/index.html +82 -0
- data/doc/website/install.html +87 -0
- data/doc/website/quick_start.html +87 -0
- data/doc/website/tissue.html +85 -0
- data/doc/website/uom.html +10 -0
- data/lib/caruby.rb +3 -0
- data/lib/caruby/active_support/README.txt +2 -0
- data/lib/caruby/active_support/core_ext/string.rb +7 -0
- data/lib/caruby/active_support/core_ext/string/inflections.rb +167 -0
- data/lib/caruby/active_support/inflections.rb +55 -0
- data/lib/caruby/active_support/inflector.rb +398 -0
- data/lib/caruby/cli/application.rb +36 -0
- data/lib/caruby/cli/command.rb +169 -0
- data/lib/caruby/csv/csv_mapper.rb +157 -0
- data/lib/caruby/csv/csvio.rb +185 -0
- data/lib/caruby/database.rb +252 -0
- data/lib/caruby/database/fetched_matcher.rb +66 -0
- data/lib/caruby/database/persistable.rb +432 -0
- data/lib/caruby/database/persistence_service.rb +162 -0
- data/lib/caruby/database/reader.rb +599 -0
- data/lib/caruby/database/saved_merger.rb +131 -0
- data/lib/caruby/database/search_template_builder.rb +59 -0
- data/lib/caruby/database/sql_executor.rb +75 -0
- data/lib/caruby/database/store_template_builder.rb +200 -0
- data/lib/caruby/database/writer.rb +469 -0
- data/lib/caruby/domain/annotatable.rb +25 -0
- data/lib/caruby/domain/annotation.rb +23 -0
- data/lib/caruby/domain/attribute_metadata.rb +447 -0
- data/lib/caruby/domain/java_attribute_metadata.rb +160 -0
- data/lib/caruby/domain/merge.rb +91 -0
- data/lib/caruby/domain/properties.rb +95 -0
- data/lib/caruby/domain/reference_visitor.rb +289 -0
- data/lib/caruby/domain/resource_attributes.rb +528 -0
- data/lib/caruby/domain/resource_dependency.rb +205 -0
- data/lib/caruby/domain/resource_introspection.rb +159 -0
- data/lib/caruby/domain/resource_metadata.rb +117 -0
- data/lib/caruby/domain/resource_module.rb +285 -0
- data/lib/caruby/domain/uniquify.rb +38 -0
- data/lib/caruby/import/annotatable_class.rb +28 -0
- data/lib/caruby/import/annotation_class.rb +27 -0
- data/lib/caruby/import/annotation_module.rb +67 -0
- data/lib/caruby/import/java.rb +338 -0
- data/lib/caruby/migration/migratable.rb +167 -0
- data/lib/caruby/migration/migrator.rb +533 -0
- data/lib/caruby/migration/resource.rb +8 -0
- data/lib/caruby/migration/resource_module.rb +11 -0
- data/lib/caruby/migration/uniquify.rb +20 -0
- data/lib/caruby/resource.rb +969 -0
- data/lib/caruby/util/attribute_path.rb +46 -0
- data/lib/caruby/util/cache.rb +53 -0
- data/lib/caruby/util/class.rb +99 -0
- data/lib/caruby/util/collection.rb +1053 -0
- data/lib/caruby/util/controlled_value.rb +35 -0
- data/lib/caruby/util/coordinate.rb +75 -0
- data/lib/caruby/util/domain_extent.rb +49 -0
- data/lib/caruby/util/file_separator.rb +65 -0
- data/lib/caruby/util/inflector.rb +20 -0
- data/lib/caruby/util/log.rb +95 -0
- data/lib/caruby/util/math.rb +12 -0
- data/lib/caruby/util/merge.rb +59 -0
- data/lib/caruby/util/module.rb +34 -0
- data/lib/caruby/util/options.rb +92 -0
- data/lib/caruby/util/partial_order.rb +36 -0
- data/lib/caruby/util/person.rb +119 -0
- data/lib/caruby/util/pretty_print.rb +184 -0
- data/lib/caruby/util/properties.rb +112 -0
- data/lib/caruby/util/stopwatch.rb +66 -0
- data/lib/caruby/util/topological_sync_enumerator.rb +53 -0
- data/lib/caruby/util/transitive_closure.rb +45 -0
- data/lib/caruby/util/tree.rb +48 -0
- data/lib/caruby/util/trie.rb +37 -0
- data/lib/caruby/util/uniquifier.rb +30 -0
- data/lib/caruby/util/validation.rb +48 -0
- data/lib/caruby/util/version.rb +56 -0
- data/lib/caruby/util/visitor.rb +351 -0
- data/lib/caruby/util/weak_hash.rb +36 -0
- data/lib/caruby/version.rb +3 -0
- metadata +186 -0
@@ -0,0 +1,205 @@
|
|
1
|
+
module CaRuby
|
2
|
+
# ResourceMetadata mix-in to capture Resource dependency.
|
3
|
+
module ResourceDependency
|
4
|
+
|
5
|
+
attr_reader :owners, :owner_attributes
|
6
|
+
|
7
|
+
# Returns the attribute which references the dependent type, or nil if none.
|
8
|
+
def dependent_attribute(type)
|
9
|
+
dependent_attributes.detect { |attr| type <= domain_type(attr) }
|
10
|
+
end
|
11
|
+
|
12
|
+
# Adds the given attribute as a dependent.
|
13
|
+
#
|
14
|
+
# Supported flags include the following:
|
15
|
+
# * :logical - the dependency relation is not cascaded by the application
|
16
|
+
# * :autogenerated - a dependent can be created by the application as a side-effect of creating the owner
|
17
|
+
# * :disjoint - the dependent owner has more than one owner attribute, but only one owner instance
|
18
|
+
#
|
19
|
+
# If the attribute inverse is not a collection, then the attribute writer
|
20
|
+
# is modified to delegate to the dependent owner writer. This enforces
|
21
|
+
# referential integrity by ensuring that the following post-condition holds:
|
22
|
+
# * _owner_._attribute_._inverse_ == _owner_
|
23
|
+
# where:
|
24
|
+
# * _owner_ is an instance this attribute's declaring class
|
25
|
+
# * _inverse_ is the owner inverse attribute defined in the dependent class
|
26
|
+
#
|
27
|
+
# @param [Symbol] attribute the dependent to add
|
28
|
+
# @param [Symbol] inverse the owner attribute defined in the dependent
|
29
|
+
# @param [<Symbol>] the attribute qualifier flags
|
30
|
+
def add_dependent_attribute(attribute, *flags)
|
31
|
+
attr_md = attribute_metadata(attribute)
|
32
|
+
unless attr_md.inverse_attribute_metadata.collection? then
|
33
|
+
delegate_writer_to_dependent(attribute)
|
34
|
+
end
|
35
|
+
flags << :dependent unless flags.include?(:dependent)
|
36
|
+
attr_md.qualify(*flags)
|
37
|
+
inverse = attr_md.inverse
|
38
|
+
inv_type = attr_md.type
|
39
|
+
# example: Parent.add_dependent_attribute(:children) with inverse :parent calls
|
40
|
+
# Child.add_owner(Parent, :parent, :children)
|
41
|
+
inv_type.add_owner(self, inverse, attribute)
|
42
|
+
end
|
43
|
+
|
44
|
+
# Returns whether this metadata's subject class depends on an owner.
|
45
|
+
def dependent?
|
46
|
+
not owners.empty?
|
47
|
+
end
|
48
|
+
|
49
|
+
# Returns whether this metadata's subject class depends the given other class.
|
50
|
+
def depends_on?(other)
|
51
|
+
owners.detect { |owner| owner === other }
|
52
|
+
end
|
53
|
+
|
54
|
+
# Returns the attribute which references the dependent type, or nil if none.
|
55
|
+
def dependent_attribute(dep_type)
|
56
|
+
type = dependent_attributes.detect { |attr| domain_type(attr) == dep_type }
|
57
|
+
return type if type
|
58
|
+
dependent_attribute(dep_type.superclass) if dep_type.superclass < Resource
|
59
|
+
end
|
60
|
+
|
61
|
+
# Returns the sole owner attribute of this class, or nil if there is not exactly one owner.
|
62
|
+
def owner_attribute
|
63
|
+
if @local_owner_attr_hash then
|
64
|
+
@local_owner_attr_hash.each_value { |attr| return attr } if @local_owner_attr_hash.size == 1
|
65
|
+
elsif superclass < Resource
|
66
|
+
superclass.owner_attribute
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# Returns this Resource class's owner types.
|
71
|
+
def owner_attributes
|
72
|
+
if @local_owner_attr_hash then
|
73
|
+
@local_owner_attrs ||= Enumerable::Enumerator.new(@local_owner_attr_hash, :each_value).filter
|
74
|
+
elsif superclass < Resource
|
75
|
+
superclass.owner_attributes
|
76
|
+
else
|
77
|
+
Array::EMPTY_ARRAY
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# Returns this Resource class's dependent types.
|
82
|
+
def dependents
|
83
|
+
dependent_attributes.wrap { |attr| attr.type }
|
84
|
+
end
|
85
|
+
|
86
|
+
# Returns this Resource class's owner types.
|
87
|
+
def owners
|
88
|
+
if @local_owner_attr_hash then
|
89
|
+
@local_owners ||= Enumerable::Enumerator.new(@local_owner_attr_hash, :each_key)
|
90
|
+
elsif superclass < Resource
|
91
|
+
superclass.owners
|
92
|
+
else
|
93
|
+
Array::EMPTY_ARRAY
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
protected
|
98
|
+
|
99
|
+
# If attribute is nil, then the owner attribute is inferred as follows:
|
100
|
+
# * If there is exactly one reference attribute from this dependent to the owner klass, then that
|
101
|
+
# attribute is the owner attribute.
|
102
|
+
# * Otherwise, if this dependent class has a default attribute name given by the demodulized,
|
103
|
+
# underscored owner class name and that attribute references the owner klass, then that attribute
|
104
|
+
# is the owner attribute.
|
105
|
+
# * Otherwise, there is no owner attribute.
|
106
|
+
def add_owner(klass, attribute=nil, inverse=nil)
|
107
|
+
logger.debug { "Adding #{qp} owner #{klass.qp}..." }
|
108
|
+
if @owner_attr_hash then
|
109
|
+
raise MetadataError.new("Can't add #{qp} owner #{klass.qp} after dependencies have been accessed.")
|
110
|
+
end
|
111
|
+
@local_owner_attr_hash ||= {}
|
112
|
+
@local_owner_attr_hash[klass] = attribute ||= detect_owner_attribute(klass, inverse)
|
113
|
+
|
114
|
+
# augment the owner writer method
|
115
|
+
if attribute then
|
116
|
+
raise ValidationError.new("Owner #{klass.qp} missing dependent attribute for dependent #{qp}") if inverse.nil?
|
117
|
+
set_attribute_inverse(attribute, inverse)
|
118
|
+
attribute_metadata(attribute).qualify(:owner)
|
119
|
+
else
|
120
|
+
logger.debug { "No #{qp} owner attribute detected for #{klass.qp}." }
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# Returns this Resource class's owner type => attribute hash.
|
125
|
+
def owner_attribute_hash
|
126
|
+
@local_owner_attr_hash or (superclass.owner_attribute_hash if superclass < Resource) or Hash::EMPTY_HASH
|
127
|
+
end
|
128
|
+
|
129
|
+
private
|
130
|
+
|
131
|
+
def domain_class?(klass)
|
132
|
+
Class === klass and klass.include?(CaRuby::Resource)
|
133
|
+
end
|
134
|
+
|
135
|
+
# Redefines the attribute writer method to delegate to its inverse writer.
|
136
|
+
#
|
137
|
+
# For an attribute +dep+ with setter +setDep+ and inverse +owner+ with setter +setOwner+,
|
138
|
+
# this is equivalent to the following:
|
139
|
+
# class Owner
|
140
|
+
# def dep=(d)
|
141
|
+
# d.setOwner(self) if d
|
142
|
+
# setDep(self)
|
143
|
+
# end
|
144
|
+
# end
|
145
|
+
def delegate_writer_to_dependent(attribute)
|
146
|
+
attr_md = attribute_metadata(attribute)
|
147
|
+
# nothing to do if no inverse
|
148
|
+
inv_attr_md = attr_md.inverse_attribute_metadata || return
|
149
|
+
logger.debug { "Delegating #{qp}.#{attribute} update to the inverse #{attr_md.type}.#{inv_attr_md}..." }
|
150
|
+
# redefine the write to set the dependent inverse
|
151
|
+
redefine_method(attr_md.writer) do |old_writer|
|
152
|
+
# delegate to the CaRuby::Resource set_exclusive_dependent method
|
153
|
+
lambda { |dep| set_exclusive_dependent(dep, old_writer, inv_attr_md.writer) }
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
# Returns the owner attribute for the given owner klass and inverse, or nil if no
|
158
|
+
# owner attribute was detected.
|
159
|
+
def detect_owner_attribute(klass, inverse=nil)
|
160
|
+
# example: Parent.add_dependent_attribute(:children) without inverse calls
|
161
|
+
# Child.add_owner(Parent, nil, :children) which calls
|
162
|
+
# Child.detect_owner_attribute(klass, :children)
|
163
|
+
|
164
|
+
# the candidate attributes which return the owner type
|
165
|
+
candidates = domain_attributes.map do |attr|
|
166
|
+
attr_md = attribute_metadata(attr)
|
167
|
+
# possible hit if there is a match on the type
|
168
|
+
attr_md if klass.equal?(attr_md.type) or klass <= attr_md.type
|
169
|
+
end
|
170
|
+
candidates.compact!
|
171
|
+
return if candidates.empty?
|
172
|
+
|
173
|
+
# there can be at most one owner attribute per owner.
|
174
|
+
return candidates.first.to_sym if candidates.size == 1
|
175
|
+
|
176
|
+
# we have a hit if there is a match on the inverse. in the above example,
|
177
|
+
# attribute :parent with inverse :children => :parent is the owner attribute
|
178
|
+
candidates.each { |attr_md| return attr_md.to_sym if attr_md.inverse == inverse }
|
179
|
+
|
180
|
+
# by convention, if more than one attribute references the owner type,
|
181
|
+
# then the attribute named after the owner type is the owner attribute
|
182
|
+
hit = klass.name[/\w+$/].downcase.to_sym
|
183
|
+
hit if candidates.detect { |attr_md| attr_md.to_sym == hit }
|
184
|
+
end
|
185
|
+
|
186
|
+
# Infers annotation dependent attributes based on whether a domain attribute satisfies the
|
187
|
+
# following criteria:
|
188
|
+
# 1. the referenced type has an attribute which refers back to this classifier's subject class
|
189
|
+
# 2. the referenced type is not an owner of this classifier's subject class
|
190
|
+
# Annotation dependencies are not specified in a configuration and follow the above convention.
|
191
|
+
def infer_annotation_dependent_attributes
|
192
|
+
dep_attrs = []
|
193
|
+
domain_attributes.each do |attr|
|
194
|
+
next if owner_attribute?(attr)
|
195
|
+
ref_md = domain_type(attr).metadata
|
196
|
+
owner_attr = ref_md.detect_owner_attribute(subject_class)
|
197
|
+
if owner_attr then
|
198
|
+
ref_md.add_owner(subject_class, owner_attr)
|
199
|
+
dep_attrs << attr
|
200
|
+
end
|
201
|
+
end
|
202
|
+
dep_attrs
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
@@ -0,0 +1,159 @@
|
|
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
|
+
# @return a new attribute symbol created for the given PropertyDescriptor pd
|
110
|
+
def create_java_attribute(pd)
|
111
|
+
# make the attribute metadata
|
112
|
+
attr_md = JavaAttributeMetadata.new(pd, self)
|
113
|
+
add_attribute_metadata(attr_md)
|
114
|
+
# the property name is an alias for the standard attribute
|
115
|
+
std_attr = attr_md.to_sym
|
116
|
+
prop_attr = pd.name.to_sym
|
117
|
+
delegate_to_attribute(prop_attr, std_attr) unless prop_attr == std_attr
|
118
|
+
std_attr
|
119
|
+
end
|
120
|
+
|
121
|
+
# Defines methods _aliaz_ and _aliaz=_ which calls the standard _attribute_ and
|
122
|
+
# _attribute=_ accessor methods, resp.
|
123
|
+
# Calling rather than aliasing the attribute accessor allows the aliaz accessor to
|
124
|
+
# reflect a change to the attribute accessor.
|
125
|
+
def delegate_to_attribute(aliaz, attribute)
|
126
|
+
rdr, wtr = attribute_metadata(attribute).accessors
|
127
|
+
define_method(aliaz) { send(rdr) }
|
128
|
+
define_method("#{aliaz}=".to_sym) { |value| send(wtr, value) }
|
129
|
+
add_alias(aliaz, attribute)
|
130
|
+
end
|
131
|
+
|
132
|
+
# Modifies the given attribute writer method if necessary to update the given inverse_attr value.
|
133
|
+
# This method is called on dependent and attributes qualified as inversible.
|
134
|
+
#
|
135
|
+
# @see ResourceDependency#add_owner
|
136
|
+
# @see ResourceAttributes#set_attribute_inverse
|
137
|
+
def add_inverse_updater(attribute, inverse)
|
138
|
+
attr_md = attribute_metadata(attribute)
|
139
|
+
# the reader and writer methods
|
140
|
+
reader, writer = attr_md.accessors
|
141
|
+
logger.debug { "Injecting inverse #{inverse} updater into #{qp}.#{attribute} writer method #{writer}..." }
|
142
|
+
# the inverse atttribute metadata
|
143
|
+
inv_attr_md = attr_md.inverse_attribute_metadata
|
144
|
+
# the inverse attribute reader and writer
|
145
|
+
inv_rdr, inv_wtr = inv_accessors = inv_attr_md.accessors
|
146
|
+
# redefine the writer method to update the inverse
|
147
|
+
# by delegating to the Resource instance set_inversible_attribute
|
148
|
+
redefine_method(writer) do |old_wtr|
|
149
|
+
# the attribute reader and (superseded) writer
|
150
|
+
accessors = [reader, old_wtr]
|
151
|
+
if inv_attr_md.collection? then
|
152
|
+
lambda { |owner| add_to_inverse_collection(owner, accessors, inv_rdr) }
|
153
|
+
else
|
154
|
+
lambda { |owner| set_inversible_noncollection_attribute(owner, accessors, inv_wtr) }
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
require 'caruby/util/collection'
|
2
|
+
require 'caruby/import/java'
|
3
|
+
require 'caruby/domain/java_attribute_metadata'
|
4
|
+
require 'caruby/domain/resource_attributes'
|
5
|
+
require 'caruby/domain/resource_introspection'
|
6
|
+
require 'caruby/domain/resource_dependency'
|
7
|
+
|
8
|
+
module CaRuby
|
9
|
+
# Exception raised if a meta-data setting is missing or invalid.
|
10
|
+
class MetadataError < RuntimeError; end
|
11
|
+
|
12
|
+
# Adds introspected metadata to a Class.
|
13
|
+
module ResourceMetadata
|
14
|
+
include ResourceIntrospection, ResourceDependency, ResourceAttributes
|
15
|
+
|
16
|
+
attr_reader :domain_module
|
17
|
+
|
18
|
+
# Associates meta-data with a Resource class. When a Resource class is added to a ResourceModule,
|
19
|
+
# the ResourceModule extends the Resource class with ResourceMetadata and calls this
|
20
|
+
# {#add_metadata} method with itself as the mod argument. The ResourceModule is accessed
|
21
|
+
# by the {#domain_module} method.
|
22
|
+
def add_metadata(mod)
|
23
|
+
@domain_module = mod
|
24
|
+
init_attributes # in ResourceAttributes
|
25
|
+
introspect # in ResourceIntrospection
|
26
|
+
end
|
27
|
+
|
28
|
+
# @return the domain type for attribute, or nil if attribute is not a domain attribute
|
29
|
+
def domain_type(attribute)
|
30
|
+
attr_md = attribute_metadata(attribute)
|
31
|
+
attr_md.type if attr_md.domain?
|
32
|
+
end
|
33
|
+
|
34
|
+
# Prints this classifier's content to the log.
|
35
|
+
def pretty_print(q)
|
36
|
+
# the Java property descriptors
|
37
|
+
property_descriptors = java_attributes.wrap { |attr| attribute_metadata(attr).property_descriptor }
|
38
|
+
# build a map of relevant display label => attributes
|
39
|
+
prop_printer = property_descriptors.wrap { |pd| PROP_DESC_PRINTER.wrap(pd) }
|
40
|
+
prop_syms = property_descriptors.map { |pd| pd.name.to_sym }.to_set
|
41
|
+
aliases = @alias_std_attr_map.keys - attributes.to_a - prop_syms
|
42
|
+
alias_attr_hash = aliases.to_compact_hash { |aliaz| @alias_std_attr_map[aliaz] }
|
43
|
+
dependents_printer = dependent_attributes.wrap { |attr| DEPENDENT_ATTR_PRINTER.wrap(attribute_metadata(attr)) }
|
44
|
+
owner_printer = owners.wrap { |type| TYPE_PRINTER.wrap(type) }
|
45
|
+
inverses = @attributes.to_compact_hash do |attr|
|
46
|
+
attr_md = attribute_metadata(attr)
|
47
|
+
"#{attr_md.type.qp}.#{attr_md.inverse}" if attr_md.inverse
|
48
|
+
end
|
49
|
+
domain_attr_printer = domain_attributes.to_compact_hash { |attr| domain_type(attr).qp }
|
50
|
+
map = {
|
51
|
+
"Java properties" => prop_printer,
|
52
|
+
"standard attributes" => attributes,
|
53
|
+
"aliases to standard attributes" => alias_attr_hash,
|
54
|
+
"secondary key" => secondary_key_attributes,
|
55
|
+
"mandatory attributes" => mandatory_attributes,
|
56
|
+
"domain attributes" => domain_attr_printer,
|
57
|
+
"creatable domain attributes" => creatable_domain_attributes,
|
58
|
+
"updatable domain attributes" => updatable_domain_attributes,
|
59
|
+
"fetched domain attributes" => fetched_domain_attributes,
|
60
|
+
"cascaded domain attributes" => cascaded_attributes,
|
61
|
+
"owners" => owner_printer,
|
62
|
+
"owner attributes" => owner_attributes,
|
63
|
+
"inverse attributes" => inverses,
|
64
|
+
"dependent attributes" => dependents_printer,
|
65
|
+
"default values" => defaults
|
66
|
+
}.delete_if { |key, value| value.nil_or_empty? }
|
67
|
+
# one indented line per entry, all but the last line ending in a comma
|
68
|
+
content = map.map { |label, value| " #{label}=>#{format_print_value(value)}" }.join(",\n")
|
69
|
+
# print the content to the log
|
70
|
+
q.text("#{qp} structure:\n#{content}")
|
71
|
+
end
|
72
|
+
|
73
|
+
protected
|
74
|
+
|
75
|
+
# Adds the given subclass to this class's {ResourceMetadata#domain_module}.
|
76
|
+
def introspect_subclass(klass)
|
77
|
+
# introspect this class if necessary
|
78
|
+
unless @domain_module then
|
79
|
+
raise TypeError.new("Can't introspect #{qp}") unless superclass and superclass.include?(Resource)
|
80
|
+
superclass.introspect_subclass(self)
|
81
|
+
end
|
82
|
+
@domain_module.add_class(klass)
|
83
|
+
end
|
84
|
+
|
85
|
+
def self.extend_class(klass, mod)
|
86
|
+
klass.extend(self)
|
87
|
+
klass.add_metadata(mod)
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
91
|
+
|
92
|
+
# A proc to print the unqualified class name.
|
93
|
+
TYPE_PRINTER = PrintWrapper.new { |type| type.qp }
|
94
|
+
|
95
|
+
DEPENDENT_ATTR_PRINTER = PrintWrapper.new do |attr_md|
|
96
|
+
flags = []
|
97
|
+
flags << :logical if attr_md.logical?
|
98
|
+
flags << :autogenerated if attr_md.autogenerated?
|
99
|
+
flags << :disjoint if attr_md.disjoint?
|
100
|
+
flags.empty? ? "#{attr_md}" : "#{attr_md}(#{flags.join(',')})"
|
101
|
+
end
|
102
|
+
|
103
|
+
# A proc to print the property descriptor name.
|
104
|
+
PROP_DESC_PRINTER = PrintWrapper.new { |pd| pd.name }
|
105
|
+
|
106
|
+
def format_print_value(value)
|
107
|
+
case value
|
108
|
+
when String then
|
109
|
+
value
|
110
|
+
when Class then
|
111
|
+
value.qp
|
112
|
+
else
|
113
|
+
value.pp_s(:single_line)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|