caruby-core 1.4.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|