caruby-core 1.5.5 → 2.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +9 -0
- data/History.md +5 -1
- data/lib/caruby.rb +3 -5
- data/lib/caruby/caruby-src.tar.gz +0 -0
- data/lib/caruby/database.rb +53 -69
- data/lib/caruby/database/application_service.rb +25 -0
- data/lib/caruby/database/cache.rb +60 -0
- data/lib/caruby/database/fetched_matcher.rb +52 -38
- data/lib/caruby/database/lazy_loader.rb +4 -4
- data/lib/caruby/database/operation.rb +34 -0
- data/lib/caruby/database/persistable.rb +171 -86
- data/lib/caruby/database/persistence_service.rb +32 -34
- data/lib/caruby/database/persistifier.rb +100 -43
- data/lib/caruby/database/reader.rb +107 -85
- data/lib/caruby/database/reader_template_builder.rb +60 -0
- data/lib/caruby/database/saved_matcher.rb +3 -3
- data/lib/caruby/database/sql_executor.rb +88 -17
- data/lib/caruby/database/writer.rb +213 -177
- data/lib/caruby/database/writer_template_builder.rb +334 -0
- data/lib/caruby/{util → helpers}/controlled_value.rb +0 -0
- data/lib/caruby/{util → helpers}/coordinate.rb +4 -4
- data/lib/caruby/{util → helpers}/person.rb +3 -3
- data/lib/caruby/{util → helpers}/properties.rb +7 -9
- data/lib/caruby/{util → helpers}/roman.rb +2 -2
- data/lib/caruby/{util → helpers}/version.rb +1 -1
- data/lib/caruby/json/deserializer.rb +2 -2
- data/lib/caruby/json/serializer.rb +49 -7
- data/lib/caruby/metadata.rb +30 -0
- data/lib/caruby/metadata/java_property.rb +21 -0
- data/lib/caruby/metadata/propertied.rb +191 -0
- data/lib/caruby/metadata/property.rb +22 -0
- data/lib/caruby/metadata/property_characteristics.rb +201 -0
- data/lib/caruby/migration/migratable.rb +11 -182
- data/lib/caruby/rdbi/driver/jdbc.rb +446 -0
- data/lib/caruby/resource.rb +20 -823
- data/lib/caruby/version.rb +1 -1
- data/test/lib/caruby/database/cache_test.rb +54 -0
- data/test/lib/caruby/{util → helpers}/controlled_value_test.rb +3 -5
- data/test/lib/caruby/{util → helpers}/person_test.rb +4 -6
- data/test/lib/caruby/helpers/properties_test.rb +34 -0
- data/test/lib/caruby/{util → helpers}/roman_test.rb +2 -3
- data/test/lib/caruby/{util → helpers}/version_test.rb +2 -3
- data/test/lib/helper.rb +7 -0
- metadata +161 -214
- data/lib/caruby/cli/application.rb +0 -36
- data/lib/caruby/cli/command.rb +0 -202
- data/lib/caruby/csv/csv_mapper.rb +0 -159
- data/lib/caruby/csv/csvio.rb +0 -203
- data/lib/caruby/database/search_template_builder.rb +0 -56
- data/lib/caruby/database/store_template_builder.rb +0 -278
- data/lib/caruby/domain.rb +0 -193
- data/lib/caruby/domain/attribute.rb +0 -584
- data/lib/caruby/domain/attributes.rb +0 -628
- data/lib/caruby/domain/dependency.rb +0 -225
- data/lib/caruby/domain/id_alias.rb +0 -22
- data/lib/caruby/domain/importer.rb +0 -183
- data/lib/caruby/domain/introspection.rb +0 -176
- data/lib/caruby/domain/inverse.rb +0 -172
- data/lib/caruby/domain/inversible.rb +0 -90
- data/lib/caruby/domain/java_attribute.rb +0 -173
- data/lib/caruby/domain/merge.rb +0 -185
- data/lib/caruby/domain/metadata.rb +0 -142
- data/lib/caruby/domain/mixin.rb +0 -35
- data/lib/caruby/domain/properties.rb +0 -95
- data/lib/caruby/domain/reference_visitor.rb +0 -428
- data/lib/caruby/domain/uniquify.rb +0 -50
- data/lib/caruby/import/java.rb +0 -387
- data/lib/caruby/migration/migrator.rb +0 -918
- data/lib/caruby/migration/resource_module.rb +0 -9
- data/lib/caruby/migration/uniquify.rb +0 -17
- data/lib/caruby/util/attribute_path.rb +0 -44
- data/lib/caruby/util/cache.rb +0 -56
- data/lib/caruby/util/class.rb +0 -149
- data/lib/caruby/util/collection.rb +0 -1152
- data/lib/caruby/util/domain_extent.rb +0 -46
- data/lib/caruby/util/file_separator.rb +0 -65
- data/lib/caruby/util/inflector.rb +0 -27
- data/lib/caruby/util/log.rb +0 -95
- data/lib/caruby/util/math.rb +0 -12
- data/lib/caruby/util/merge.rb +0 -59
- data/lib/caruby/util/module.rb +0 -18
- data/lib/caruby/util/options.rb +0 -97
- data/lib/caruby/util/partial_order.rb +0 -35
- data/lib/caruby/util/pretty_print.rb +0 -204
- data/lib/caruby/util/stopwatch.rb +0 -74
- data/lib/caruby/util/topological_sync_enumerator.rb +0 -62
- data/lib/caruby/util/transitive_closure.rb +0 -55
- data/lib/caruby/util/tree.rb +0 -48
- data/lib/caruby/util/trie.rb +0 -37
- data/lib/caruby/util/uniquifier.rb +0 -30
- data/lib/caruby/util/validation.rb +0 -20
- data/lib/caruby/util/visitor.rb +0 -365
- data/lib/caruby/util/weak_hash.rb +0 -36
- data/test/lib/caruby/csv/csv_mapper_test.rb +0 -40
- data/test/lib/caruby/csv/csvio_test.rb +0 -69
- data/test/lib/caruby/database/persistable_test.rb +0 -92
- data/test/lib/caruby/domain/domain_test.rb +0 -112
- data/test/lib/caruby/domain/inversible_test.rb +0 -99
- data/test/lib/caruby/domain/reference_visitor_test.rb +0 -130
- data/test/lib/caruby/import/java_test.rb +0 -80
- data/test/lib/caruby/import/mixed_case_test.rb +0 -14
- data/test/lib/caruby/migration/test_case.rb +0 -102
- data/test/lib/caruby/test_case.rb +0 -230
- data/test/lib/caruby/util/cache_test.rb +0 -23
- data/test/lib/caruby/util/class_test.rb +0 -61
- data/test/lib/caruby/util/collection_test.rb +0 -398
- data/test/lib/caruby/util/command_test.rb +0 -55
- data/test/lib/caruby/util/domain_extent_test.rb +0 -60
- data/test/lib/caruby/util/file_separator_test.rb +0 -30
- data/test/lib/caruby/util/inflector_test.rb +0 -12
- data/test/lib/caruby/util/lazy_hash_test.rb +0 -34
- data/test/lib/caruby/util/merge_test.rb +0 -83
- data/test/lib/caruby/util/module_test.rb +0 -25
- data/test/lib/caruby/util/options_test.rb +0 -59
- data/test/lib/caruby/util/partial_order_test.rb +0 -42
- data/test/lib/caruby/util/pretty_print_test.rb +0 -85
- data/test/lib/caruby/util/properties_test.rb +0 -50
- data/test/lib/caruby/util/stopwatch_test.rb +0 -18
- data/test/lib/caruby/util/topological_sync_enumerator_test.rb +0 -69
- data/test/lib/caruby/util/transitive_closure_test.rb +0 -67
- data/test/lib/caruby/util/tree_test.rb +0 -23
- data/test/lib/caruby/util/trie_test.rb +0 -14
- data/test/lib/caruby/util/visitor_test.rb +0 -278
- data/test/lib/caruby/util/weak_hash_test.rb +0 -45
- data/test/lib/examples/clinical_trials/migration/migration_test.rb +0 -58
- data/test/lib/examples/clinical_trials/migration/test_case.rb +0 -38
@@ -1,172 +0,0 @@
|
|
1
|
-
require 'caruby/import/java'
|
2
|
-
require 'caruby/domain/java_attribute'
|
3
|
-
|
4
|
-
module CaRuby
|
5
|
-
module Domain
|
6
|
-
# Meta-data mix-in to infer and set inverse attributes.
|
7
|
-
module Inverse
|
8
|
-
# Returns the inverse of the given attribute. If the attribute has an #{Attribute#inverse_metadata},
|
9
|
-
# then that attribute's inverse is returned. Otherwise, if the attribute is an #{Attribute#owner?},
|
10
|
-
# then the target class dependent attribute which matches this type is returned, if it exists.
|
11
|
-
#
|
12
|
-
# @param [Attribute] attr_md the subject attribute
|
13
|
-
# @param [Class, nil] klass the target class
|
14
|
-
# @return [Attribute, nil] the inverse attribute, if any
|
15
|
-
def inverse_attribute_metadata(attr_md, klass=nil)
|
16
|
-
inv_md = attr_md.inverse_metadata
|
17
|
-
return inv_md if inv_md
|
18
|
-
if attr_md.dependent? and klass then
|
19
|
-
klass.owner_attribute_metadata_hash.each { |otype, oattr_md|
|
20
|
-
return oattr_md if self <= otype }
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
protected
|
25
|
-
|
26
|
-
# Infers the inverse of the given attribute declared by this class. A domain attribute is
|
27
|
-
# recognized as an inverse according to the {Inverse#detect_inverse_attribute}
|
28
|
-
# criterion.
|
29
|
-
#
|
30
|
-
# @param [Attribute] attr_md the attribute to check
|
31
|
-
def infer_attribute_inverse(attr_md)
|
32
|
-
inv = attr_md.type.detect_inverse_attribute(self)
|
33
|
-
if inv then set_attribute_inverse(attr_md.to_sym, inv) end
|
34
|
-
end
|
35
|
-
|
36
|
-
# Sets the given bi-directional association attribute's inverse.
|
37
|
-
#
|
38
|
-
# @param [Symbol] attribute the subject attribute
|
39
|
-
# @param [Symbol] the attribute inverse
|
40
|
-
# @raise [TypeError] if the inverse type is incompatible with this Resource
|
41
|
-
def set_attribute_inverse(attribute, inverse)
|
42
|
-
attr_md = attribute_metadata(attribute)
|
43
|
-
# return if inverse is already set
|
44
|
-
return if attr_md.inverse == inverse
|
45
|
-
# the default inverse
|
46
|
-
inverse ||= attr_md.type.detect_inverse_attribute(self)
|
47
|
-
# the inverse attribute meta-data
|
48
|
-
inv_md = attr_md.type.attribute_metadata(inverse)
|
49
|
-
# If the attribute is the many side of a 1:M relation, then delegate to the one side.
|
50
|
-
if attr_md.collection? and not inv_md.collection? then
|
51
|
-
return attr_md.type.set_attribute_inverse(inverse, attribute)
|
52
|
-
end
|
53
|
-
# This class must be the same as or a subclass of the inverse attribute type.
|
54
|
-
unless self <= inv_md.type then
|
55
|
-
raise TypeError.new("Cannot set #{qp}.#{attribute} inverse to #{attr_md.type.qp}.#{attribute} with incompatible type #{inv_md.type.qp}")
|
56
|
-
end
|
57
|
-
# If the attribute is not declared by this class, then make a new attribute
|
58
|
-
# metadata specialized for this class.
|
59
|
-
unless attr_md.declarer == self then
|
60
|
-
attr_md = restrict_attribute_inverse(attr_md, inverse)
|
61
|
-
end
|
62
|
-
# Set the inverse in the attribute metadata.
|
63
|
-
attr_md.inverse = inverse
|
64
|
-
# If attribute is the one side of a 1:M or non-reflexive 1:1 relation, then add the inverse updater.
|
65
|
-
unless attr_md.collection? then
|
66
|
-
# Make the
|
67
|
-
add_inverse_updater(attribute, inverse)
|
68
|
-
unless attr_md.type == inv_md.type or inv_md.collection? then
|
69
|
-
attr_md.type.delegate_writer_to_inverse(inverse, attribute)
|
70
|
-
end
|
71
|
-
end
|
72
|
-
end
|
73
|
-
|
74
|
-
# Detects an unambiguous attribute which refers to the given referencing class.
|
75
|
-
# If there is exactly one attribute with the given return type, then that attribute is chosen.
|
76
|
-
# Otherwise, the attribute whose name matches the underscored referencing class name is chosen,
|
77
|
-
# if any.
|
78
|
-
#
|
79
|
-
# @param [Class] klass the referencing class
|
80
|
-
# @return [Symbol, nil] the inverse attribute for the given referencing class and inverse,
|
81
|
-
# or nil if no owner attribute was detected
|
82
|
-
def detect_inverse_attribute(klass)
|
83
|
-
# The candidate attributes return the referencing type and don't already have an inverse.
|
84
|
-
candidates = domain_attributes.compose { |attr_md| klass <= attr_md.type and attr_md.inverse.nil? }
|
85
|
-
attr = detect_inverse_attribute_from_candidates(klass, candidates)
|
86
|
-
if attr then
|
87
|
-
logger.debug { "#{qp} #{klass.qp} inverse attribute is #{attr}." }
|
88
|
-
else
|
89
|
-
logger.debug { "#{qp} #{klass.qp} inverse attribute was not detected." }
|
90
|
-
end
|
91
|
-
attr
|
92
|
-
end
|
93
|
-
|
94
|
-
# Redefines the attribute writer method to delegate to its inverse writer.
|
95
|
-
# This is done to enforce inverse integrity.
|
96
|
-
#
|
97
|
-
# For a +Person+ attribute +account+ with inverse +holder+, this is equivalent to the following:
|
98
|
-
# class Person
|
99
|
-
# alias :set_account :account=
|
100
|
-
# def account=(acct)
|
101
|
-
# acct.holder = self if acct
|
102
|
-
# set_account(acct)
|
103
|
-
# end
|
104
|
-
# end
|
105
|
-
def delegate_writer_to_inverse(attribute, inverse)
|
106
|
-
attr_md = attribute_metadata(attribute)
|
107
|
-
# nothing to do if no inverse
|
108
|
-
inv_attr_md = attr_md.inverse_metadata || return
|
109
|
-
logger.debug { "Delegating #{qp}.#{attribute} update to the inverse #{attr_md.type.qp}.#{inv_attr_md}..." }
|
110
|
-
# redefine the write to set the dependent inverse
|
111
|
-
redefine_method(attr_md.writer) do |old_writer|
|
112
|
-
# delegate to the CaRuby::Resource set_inverse method
|
113
|
-
lambda { |dep| set_inverse(dep, old_writer, inv_attr_md.writer) }
|
114
|
-
end
|
115
|
-
end
|
116
|
-
|
117
|
-
private
|
118
|
-
|
119
|
-
# Copies the given attribute metadata from its declarer to this class. The new attribute metadata
|
120
|
-
# has the same attribute access methods, but the declarer is this class and the inverse is the
|
121
|
-
# given inverse attribute.
|
122
|
-
#
|
123
|
-
# @param [Attribute] attr_md the attribute to copy
|
124
|
-
# @param [Symbol] the attribute inverse
|
125
|
-
# @return [Attribute] the copied attribute metadata
|
126
|
-
def restrict_attribute_inverse(attr_md, inverse)
|
127
|
-
rst_attr_md = attr_md.dup
|
128
|
-
rst_attr_md.declarer = self
|
129
|
-
add_attribute_metadata(rst_attr_md)
|
130
|
-
logger.debug { "Copied #{attr_md.declarer}.#{attr_md} to #{qp} with inverse #{inverse}." }
|
131
|
-
rst_attr_md
|
132
|
-
end
|
133
|
-
|
134
|
-
# @param klass (see #detect_inverse_attribute)
|
135
|
-
# @param [<Symbol>] candidates the attributes constrained to the target type
|
136
|
-
# @return (see #detect_inverse_attribute)
|
137
|
-
def detect_inverse_attribute_from_candidates(klass, candidates)
|
138
|
-
return if candidates.empty?
|
139
|
-
# there can be at most one owner attribute per owner.
|
140
|
-
return candidates.first.to_sym if candidates.size == 1
|
141
|
-
# by convention, if more than one attribute references the owner type,
|
142
|
-
# then the attribute named after the owner type is the owner attribute
|
143
|
-
tgt = klass.name[/\w+$/].underscore.to_sym
|
144
|
-
tgt if candidates.detect { |attr| attr == tgt }
|
145
|
-
end
|
146
|
-
|
147
|
-
# Modifies the given attribute writer method to update the given inverse.
|
148
|
-
#
|
149
|
-
# @param (see #set_attribute_inverse)
|
150
|
-
def add_inverse_updater(attribute, inverse)
|
151
|
-
attr_md = attribute_metadata(attribute)
|
152
|
-
# the reader and writer methods
|
153
|
-
rdr, wtr = attr_md.accessors
|
154
|
-
logger.debug { "Injecting inverse #{inverse} updater into #{qp}.#{attribute} writer method #{wtr}..." }
|
155
|
-
# the inverse atttribute metadata
|
156
|
-
inv_attr_md = attr_md.inverse_metadata
|
157
|
-
# the inverse attribute reader and writer
|
158
|
-
inv_rdr, inv_wtr = inv_accessors = inv_attr_md.accessors
|
159
|
-
# Redefine the writer method to update the inverse by delegating to the inverse
|
160
|
-
redefine_method(wtr) do |old_wtr|
|
161
|
-
# the attribute reader and (superseded) writer
|
162
|
-
accessors = [rdr, old_wtr]
|
163
|
-
if inv_attr_md.collection? then
|
164
|
-
lambda { |other| add_to_inverse_collection(other, accessors, inv_rdr) }
|
165
|
-
else
|
166
|
-
lambda { |other| set_inversible_noncollection_attribute(other, accessors, inv_wtr) }
|
167
|
-
end
|
168
|
-
end
|
169
|
-
end
|
170
|
-
end
|
171
|
-
end
|
172
|
-
end
|
@@ -1,90 +0,0 @@
|
|
1
|
-
require 'caruby/util/log'
|
2
|
-
require 'caruby/database/persistable'
|
3
|
-
require 'caruby/util/pretty_print'
|
4
|
-
|
5
|
-
module CaRuby
|
6
|
-
# {Resource} inverse integrity aspect mix-in.
|
7
|
-
module Inversible
|
8
|
-
# Sets an attribute inverse by calling the attribute writer method with the other argument.
|
9
|
-
# If other is non-nil, then the inverse writer method is called on self.
|
10
|
-
#
|
11
|
-
# @param other [Resource] the attribute value to set
|
12
|
-
# @param [Symbol] writer the attribute writer method
|
13
|
-
# @param [Symbol] inv_writer the attribute inverse writer method defined for the other object
|
14
|
-
def set_inverse(other, writer, inv_writer)
|
15
|
-
other.send(inv_writer, self) if other
|
16
|
-
send(writer, other)
|
17
|
-
end
|
18
|
-
|
19
|
-
# Sets a non-collection attribute value in a way which enforces inverse integrity.
|
20
|
-
#
|
21
|
-
# @param [Object] newval the value to set
|
22
|
-
# @param [(Symbol, Symbol)] accessors the reader and writer methods to use in setting the
|
23
|
-
# attribute
|
24
|
-
# @param [Symbol] inverse_writer the inverse attribute writer method
|
25
|
-
def set_inversible_noncollection_attribute(newval, accessors, inverse_writer)
|
26
|
-
rdr, wtr = accessors
|
27
|
-
# the previous value
|
28
|
-
oldval = send(rdr)
|
29
|
-
# bail if no change
|
30
|
-
return newval if newval.equal?(oldval)
|
31
|
-
|
32
|
-
# clear the previous inverse
|
33
|
-
logger.debug { "Moving #{qp} from #{oldval.qp} to #{newval.qp}..." } if oldval and newval
|
34
|
-
if oldval then
|
35
|
-
clr_wtr = self.class === oldval && oldval.send(rdr).equal?(self) ? wtr : inverse_writer
|
36
|
-
oldval.send(clr_wtr, nil)
|
37
|
-
end
|
38
|
-
# call the writer
|
39
|
-
send(wtr, newval)
|
40
|
-
# call the inverse writer on self
|
41
|
-
if newval then
|
42
|
-
newval.send(inverse_writer, self)
|
43
|
-
logger.debug { "Moved #{qp} from #{oldval.qp} to #{newval.qp}." } if oldval
|
44
|
-
end
|
45
|
-
|
46
|
-
newval
|
47
|
-
end
|
48
|
-
|
49
|
-
# Sets a collection attribute value in a way which enforces inverse integrity.
|
50
|
-
# The inverse of the attribute is a collection accessed by calling inverse on newval.
|
51
|
-
#
|
52
|
-
# @param [Resource] newval the new attribute reference value
|
53
|
-
# @param [(Symbol, Symbol)] accessors the reader and writer to use in setting
|
54
|
-
# the attribute
|
55
|
-
# @param [Symbol] inverse the inverse collection attribute to which
|
56
|
-
# this domain object will be added
|
57
|
-
# @yield a factory to create a new collection on demand (default is an Array)
|
58
|
-
def add_to_inverse_collection(newval, accessors, inverse)
|
59
|
-
rdr, wtr = accessors
|
60
|
-
# the current inverse
|
61
|
-
oldval = send(rdr)
|
62
|
-
# no-op if no change
|
63
|
-
return newval if newval == oldval
|
64
|
-
|
65
|
-
# delete self from the current inverse reference collection
|
66
|
-
if oldval then
|
67
|
-
coll = oldval.send(inverse)
|
68
|
-
coll.delete(self) if coll
|
69
|
-
end
|
70
|
-
# call the writer on this object
|
71
|
-
send(wtr, newval)
|
72
|
-
# add self to the inverse collection
|
73
|
-
if newval then
|
74
|
-
coll = newval.send(inverse)
|
75
|
-
if coll.nil? then
|
76
|
-
coll = block_given? ? yield : Array.new
|
77
|
-
newval.set_attribute(inverse, coll)
|
78
|
-
end
|
79
|
-
coll << self
|
80
|
-
if oldval then
|
81
|
-
logger.debug { "Moved #{qp} from #{rdr} #{oldval.qp} #{inverse} to #{newval.qp}." }
|
82
|
-
else
|
83
|
-
logger.debug { "Added #{qp} to #{rdr} #{newval.qp} #{inverse}." }
|
84
|
-
end
|
85
|
-
end
|
86
|
-
|
87
|
-
newval
|
88
|
-
end
|
89
|
-
end
|
90
|
-
end
|
@@ -1,173 +0,0 @@
|
|
1
|
-
require 'caruby/util/inflector'
|
2
|
-
require 'caruby/domain/attribute'
|
3
|
-
|
4
|
-
module CaRuby
|
5
|
-
module Domain
|
6
|
-
# The attribute metadata for an introspected Java property.
|
7
|
-
class JavaAttribute < Attribute
|
8
|
-
|
9
|
-
# This attribute's Java property descriptor.
|
10
|
-
attr_reader :property_descriptor
|
11
|
-
|
12
|
-
# This attribute's Java property [reader, writer] accessors, e.g. +[:getActivityStatus, :setActivityStatus]+.
|
13
|
-
attr_reader :property_accessors
|
14
|
-
|
15
|
-
# Creates a Ruby Attribute symbol corresponding to the given Ruby Java class wrapper klazz
|
16
|
-
# and Java property_descriptor.
|
17
|
-
#
|
18
|
-
# The attribute name is the lower-case, underscore property descriptor name with the alterations
|
19
|
-
# described in {JavaAttribute.to_attribute_symbol} and {Class#unocclude_reserved_method}.
|
20
|
-
#
|
21
|
-
# The attribute type is inferred as follows:
|
22
|
-
# * If the property descriptor return type is a primitive Java type, then that type is returned.
|
23
|
-
# * If the return type is a parameterized collection, then the parameter type is returned.
|
24
|
-
# * If the return type is an unparameterized collection, then this method infers the type from
|
25
|
-
# the property name, e.g. +StudyProtocolCollection+type is inferred as +StudyProtocol+
|
26
|
-
# by stripping the +Collection+ suffix, capitalizing the prefix and looking for a class of
|
27
|
-
# that name in the {Metadata#domain_module}.
|
28
|
-
# * If the declarer class metadata configuration includes a +domain_attributes+ property, then
|
29
|
-
# the type specified in that property is returned.
|
30
|
-
# * Otherwise, this method returns Java::Javalang::Object.
|
31
|
-
#
|
32
|
-
# The optional restricted_type argument restricts the attribute to a subclass of the declared
|
33
|
-
# property type.
|
34
|
-
def initialize(pd, declarer, restricted_type=nil)
|
35
|
-
symbol = create_standard_attribute_symbol(pd, declarer)
|
36
|
-
super(symbol, declarer, restricted_type)
|
37
|
-
@property_descriptor = pd
|
38
|
-
# deficient Java introspector does not recognize 'is' prefix for a Boolean property
|
39
|
-
rm = declarer.property_read_method(pd)
|
40
|
-
raise ArgumentError.new("Property does not have a read method: #{declarer.qp}.#{pd.name}") unless rm
|
41
|
-
reader = rm.name.to_sym
|
42
|
-
unless declarer.method_defined?(reader) then
|
43
|
-
reader = "is#{reader.to_s.capitalize_first}".to_sym
|
44
|
-
unless declarer.method_defined?(reader) then
|
45
|
-
raise ArgumentError.new("Reader method not found for #{declarer} property #{pd.name}")
|
46
|
-
end
|
47
|
-
end
|
48
|
-
unless pd.write_method then
|
49
|
-
raise ArgumentError.new("Property does not have a write method: #{declarer.qp}.#{pd.name}")
|
50
|
-
end
|
51
|
-
writer = pd.write_method.name.to_sym
|
52
|
-
unless declarer.method_defined?(writer) then
|
53
|
-
raise ArgumentError.new("Writer method not found for #{declarer} property #{pd.name}")
|
54
|
-
end
|
55
|
-
@property_accessors = [reader, writer]
|
56
|
-
qualify(:collection) if collection_java_class?
|
57
|
-
@type = infer_type
|
58
|
-
end
|
59
|
-
|
60
|
-
# @return [Symbol] the JRuby wrapper method for the Java property reader
|
61
|
-
def property_reader
|
62
|
-
property_accessors.first
|
63
|
-
end
|
64
|
-
|
65
|
-
# @return [Symbol] the JRuby wrapper method for the Java property writer
|
66
|
-
def property_writer
|
67
|
-
property_accessors.last
|
68
|
-
end
|
69
|
-
|
70
|
-
# Returns a lower-case, underscore symbol for the given property_name.
|
71
|
-
# A name ending in 'Collection' is changed to a pluralization.
|
72
|
-
#
|
73
|
-
# @example
|
74
|
-
# JavaAttribute.to_attribute_symbol('specimenEventCollection') #=> :specimen_events
|
75
|
-
def self.to_attribute_symbol(property_name)
|
76
|
-
name = if property_name =~ /(.+)Collection$/ then
|
77
|
-
property_name[0...-'Collection'.length].pluralize.underscore
|
78
|
-
else
|
79
|
-
property_name.underscore
|
80
|
-
end
|
81
|
-
name.to_sym
|
82
|
-
end
|
83
|
-
|
84
|
-
private
|
85
|
-
|
86
|
-
# @param pd the Java property descriptor
|
87
|
-
# @param [Class] klass the declarer
|
88
|
-
# @return [String] the lower-case, underscore symbol for the given property descriptor
|
89
|
-
def create_standard_attribute_symbol(pd, klass)
|
90
|
-
propname = pd.name
|
91
|
-
name = propname.underscore
|
92
|
-
renamed = klass.unocclude_reserved_method(pd)
|
93
|
-
if renamed then
|
94
|
-
logger.debug { "Renamed #{klass.qp} reserved Ruby method #{name} to #{renamed}." }
|
95
|
-
renamed
|
96
|
-
else
|
97
|
-
JavaAttribute.to_attribute_symbol(propname)
|
98
|
-
end
|
99
|
-
end
|
100
|
-
|
101
|
-
# @return [Boolean] whether this property's Java type is +Iterable+
|
102
|
-
def collection_java_class?
|
103
|
-
# the Java property type
|
104
|
-
ptype = @property_descriptor.property_type
|
105
|
-
# Test whether the corresponding JRuby wrapper class or module is an Iterable.
|
106
|
-
Class.to_ruby(ptype) < Java::JavaLang::Iterable
|
107
|
-
end
|
108
|
-
|
109
|
-
# @return [Class] the type for the specified klass property descriptor pd as described in {#initialize}
|
110
|
-
def infer_type
|
111
|
-
collection? ? infer_collection_type : infer_non_collection_type
|
112
|
-
end
|
113
|
-
|
114
|
-
# Returns the domain type for this attribute's Java Collection property descriptor.
|
115
|
-
# If the property type is parameterized by a single domain class, then that generic type argument is the domain type.
|
116
|
-
# Otherwise, the type is inferred from the property name as described in {#infer_collection_type_from_name}.
|
117
|
-
#
|
118
|
-
# @return [Class] this property's Ruby type
|
119
|
-
def infer_collection_type
|
120
|
-
generic_parameter_type or infer_collection_type_from_name or Java::JavaLang::Object
|
121
|
-
end
|
122
|
-
|
123
|
-
# @return [Class] this property's Ruby type
|
124
|
-
def infer_non_collection_type
|
125
|
-
jtype = @property_descriptor.property_type
|
126
|
-
Class.to_ruby(jtype)
|
127
|
-
end
|
128
|
-
|
129
|
-
# @return [Class, nil] the domain type of this attribute's property descriptor Collection generic
|
130
|
-
# type argument, or nil if none
|
131
|
-
def generic_parameter_type
|
132
|
-
method = @property_descriptor.readMethod || return
|
133
|
-
gtype = method.genericReturnType
|
134
|
-
return unless Java::JavaLangReflect::ParameterizedType === gtype
|
135
|
-
atypes = gtype.actualTypeArguments
|
136
|
-
return unless atypes.size == 1
|
137
|
-
atype = atypes[0]
|
138
|
-
klass = java_to_ruby_class(atype)
|
139
|
-
logger.debug { "Inferred #{declarer.qp} #{self} domain type #{klass.qp} from generic parameter #{atype.name}." } if klass
|
140
|
-
klass
|
141
|
-
end
|
142
|
-
|
143
|
-
# @param [Class, String] jtype the Java class or class name
|
144
|
-
# @return [Class] the corresponding Ruby type
|
145
|
-
def java_to_ruby_class(jtype)
|
146
|
-
name = String === jtype ? jtype : jtype.name
|
147
|
-
Class.to_ruby(name)
|
148
|
-
end
|
149
|
-
|
150
|
-
# Returns the domain type for this attribute's collection Java property descriptor name.
|
151
|
-
# By convention, caBIG domain collection properties often begin with a domain type
|
152
|
-
# name and end in 'Collection'. This method strips the Collection suffix and checks
|
153
|
-
# whether the prefix is a domain class.
|
154
|
-
#
|
155
|
-
# For example, the type of the property named +distributionProtocolCollection+
|
156
|
-
# is inferred as +DistributionProtocol+ by stripping the +Collection+ suffix,
|
157
|
-
# capitalizing the prefix and looking for a class of that name in this classifier's
|
158
|
-
# domain_module.
|
159
|
-
#
|
160
|
-
# @return [Class] the collection item type
|
161
|
-
def infer_collection_type_from_name
|
162
|
-
# the property name
|
163
|
-
pname = @property_descriptor.name
|
164
|
-
# The potential class name is the capitalized property name without a 'Collection' suffix.
|
165
|
-
cname = pname.capitalize_first.sub(/Collection$/, '')
|
166
|
-
jname = [@declarer.parent_module, cname].join('::')
|
167
|
-
klass = eval jname rescue nil
|
168
|
-
if klass then logger.debug { "Inferred #{declarer.qp} #{self} collection domain type #{klass.qp} from the attribute name." } end
|
169
|
-
klass
|
170
|
-
end
|
171
|
-
end
|
172
|
-
end
|
173
|
-
end
|
data/lib/caruby/domain/merge.rb
DELETED
@@ -1,185 +0,0 @@
|
|
1
|
-
require 'caruby/util/validation'
|
2
|
-
|
3
|
-
module CaRuby
|
4
|
-
# A Mergeable supports merging {Resource} attribute values.
|
5
|
-
module Mergeable
|
6
|
-
# Merges the values of the other attributes into this object and returns self.
|
7
|
-
# The other argument can be either a Hash or an object whose class responds to the
|
8
|
-
# +mergeable_attributes+ method.
|
9
|
-
# The optional attributes argument can be either a single attribute symbol or a
|
10
|
-
# collection of attribute symbols.
|
11
|
-
#
|
12
|
-
# A hash argument consists of attribute name => value associations.
|
13
|
-
# For example, given a Mergeable +person+ object with attributes +ssn+ and +children+, the call:
|
14
|
-
# person.merge_attributes(:ssn => '555-55-5555', :children => children)
|
15
|
-
# is equivalent to:
|
16
|
-
# person.ssn ||= '555-55-5555'
|
17
|
-
# person.children ||= []
|
18
|
-
# person.children.merge(children, :deep)
|
19
|
-
# An unrecognized attribute is ignored.
|
20
|
-
#
|
21
|
-
# If other is not a Hash, then the other object's attributes values are merged into
|
22
|
-
# this object. The default attributes is this mergeable's class
|
23
|
-
# {Domain::Attributes#mergeable_attributes}.
|
24
|
-
#
|
25
|
-
# The merge is performed by calling {#merge_attribute} on each attribute with the matches
|
26
|
-
# and merger block given to this method.
|
27
|
-
#
|
28
|
-
# @param [Mergeable, {Symbol => Object}] other the source domain object or value hash to merge from
|
29
|
-
# @param [<Symbol>, nil] attributes the attributes to merge (default {Domain::Attributes#nondomain_attributes})
|
30
|
-
# @param [{Resource => Resource}, nil] the optional merge source => target reference matches
|
31
|
-
# @yield [attribute, oldval, newval] the optional merger block
|
32
|
-
# @yieldparam [Symbol] attribute the merge target attribute
|
33
|
-
# @yieldparam oldval the current merge attribute value
|
34
|
-
# @yieldparam newval the new merge attribute value
|
35
|
-
# @return [Mergeable] self
|
36
|
-
# @raise [ArgumentError] if none of the following are true:
|
37
|
-
# * other is a Hash
|
38
|
-
# * attributes is non-nil
|
39
|
-
# * the other class responds to +mergeable_attributes+
|
40
|
-
def merge_attributes(other, attributes=nil, matches=nil, &merger)
|
41
|
-
return self if other.nil? or other.equal?(self)
|
42
|
-
attributes = [attributes] if Symbol === attributes
|
43
|
-
attributes ||= self.class.mergeable_attributes
|
44
|
-
|
45
|
-
# if the source object is not a hash, then convert it to an attribute => value hash
|
46
|
-
vh = Hashable === other ? other : other.value_hash(attributes)
|
47
|
-
# merge the value hash
|
48
|
-
vh.each { |attr, value| merge_attribute(attr, value, matches, &merger) }
|
49
|
-
self
|
50
|
-
end
|
51
|
-
|
52
|
-
alias :merge :merge_attributes
|
53
|
-
|
54
|
-
alias :merge! :merge
|
55
|
-
|
56
|
-
# Merges the value newval into the attribute as follows:
|
57
|
-
# * If the value is nil, empty or equal to the current attribute value, then no merge
|
58
|
-
# is performed.
|
59
|
-
# * Otherwise, if a merger block is given to this method, then that block is called
|
60
|
-
# to perform the merge.
|
61
|
-
# * Otherwise, if the attribute is a non-domain attribute and the current value is non-nil,
|
62
|
-
# then no merge is performed.
|
63
|
-
# * Otherwise, if the attribute is a non-domain attribute and the current value is nil,
|
64
|
-
# then set the attribute to the newval.
|
65
|
-
# * Otherwise, if the attribute is a domain non-collection attribute, then newval is recursively
|
66
|
-
# merged into the current referenced domain object.
|
67
|
-
# * Otherwise, attribute is a domain collection attribute and matching newval members are
|
68
|
-
# merged into the corresponding current collection members and non-matching newval members
|
69
|
-
# are added to the current collection.
|
70
|
-
#
|
71
|
-
# @param [Symbol] attribute the merge attribute
|
72
|
-
# @param newval the value to merge
|
73
|
-
# @param [{Resource => Resource}, nil] the optional merge source => target reference matches
|
74
|
-
# @yield (see #merge_attributes)
|
75
|
-
# @yieldparam (see #merge_attributes)
|
76
|
-
# @return the merged attribute value
|
77
|
-
def merge_attribute(attribute, newval, matches=nil)
|
78
|
-
# the previous value
|
79
|
-
oldval = send(attribute)
|
80
|
-
# If nothing to merge or a block can take over, then bail.
|
81
|
-
if newval.nil? or mergeable__equal?(oldval, newval) then
|
82
|
-
return oldval
|
83
|
-
elsif block_given? then
|
84
|
-
return yield(attribute, oldval, value)
|
85
|
-
end
|
86
|
-
|
87
|
-
# Discriminate between a domain and non-domain attribute.
|
88
|
-
attr_md = self.class.attribute_metadata(attribute)
|
89
|
-
if attr_md.domain? then
|
90
|
-
merge_domain_attribute_value(attr_md, oldval, newval, matches)
|
91
|
-
else
|
92
|
-
merge_nondomain_attribute_value(attr_md, oldval, newval)
|
93
|
-
end
|
94
|
-
end
|
95
|
-
|
96
|
-
private
|
97
|
-
|
98
|
-
# @see #merge_attribute
|
99
|
-
def merge_nondomain_attribute_value(attr_md, oldval, newval)
|
100
|
-
if oldval.nil? then
|
101
|
-
send(attr_md.writer, newval)
|
102
|
-
elsif attr_md.collection? then
|
103
|
-
oldval.merge(newval)
|
104
|
-
else
|
105
|
-
oldval
|
106
|
-
end
|
107
|
-
end
|
108
|
-
|
109
|
-
# @see #merge_attribute
|
110
|
-
def merge_domain_attribute_value(attr_md, oldval, newval, matches)
|
111
|
-
# the dependent owner writer method, if any
|
112
|
-
if attr_md.dependent? then
|
113
|
-
val = attr_md.collection? ? newval.first : newval
|
114
|
-
klass = val.class if val
|
115
|
-
inv_md = self.class.inverse_attribute_metadata(attr_md, klass)
|
116
|
-
if inv_md and not inv_md.collection? then
|
117
|
-
owtr = inv_md.writer
|
118
|
-
end
|
119
|
-
end
|
120
|
-
|
121
|
-
# If the attribute is a collection, then merge the matches into the current attribute
|
122
|
-
# collection value and add each unmatched source to the collection.
|
123
|
-
# Otherwise, if the attribute is not yet set and there is a new value, then set it
|
124
|
-
# to the new value match or the new value itself if unmatched.
|
125
|
-
if attr_md.collection? then
|
126
|
-
# TODO - refactor into method
|
127
|
-
if oldval.nil? then
|
128
|
-
raise ValidationError.new("Merge into #{qp} #{attr_md} with nil collection value is not supported")
|
129
|
-
end
|
130
|
-
# the references to add
|
131
|
-
adds = []
|
132
|
-
logger.debug { "Merging #{newval.qp} into #{qp} #{attr_md} #{oldval.qp}..." } unless newval.empty?
|
133
|
-
newval.each do |src|
|
134
|
-
# If the match target is in the current collection, then update the matched
|
135
|
-
# target from the source.
|
136
|
-
# Otherwise, if there is no match or the match is a new reference created
|
137
|
-
# from the match, then add the match to the oldval collection.
|
138
|
-
if matches && matches.has_key?(src) then
|
139
|
-
# the source match
|
140
|
-
tgt = matches[src]
|
141
|
-
if tgt then
|
142
|
-
if oldval.include?(tgt) then
|
143
|
-
tgt.merge_attributes(src)
|
144
|
-
else
|
145
|
-
adds << tgt
|
146
|
-
end
|
147
|
-
end
|
148
|
-
else
|
149
|
-
adds << src
|
150
|
-
end
|
151
|
-
end
|
152
|
-
# add the unmatched sources
|
153
|
-
logger.debug { "Adding #{qp} #{attr_md} unmatched #{adds.qp}..." } unless adds.empty?
|
154
|
-
adds.each do |ref|
|
155
|
-
# If there is an owner writer attribute, then add the ref to the attribute collection by
|
156
|
-
# delegating to the owner writer. Otherwise, add the ref to the attribute collection directly.
|
157
|
-
owtr ? delegate_to_inverse_setter(attr_md, ref, owtr) : oldval << ref
|
158
|
-
end
|
159
|
-
oldval
|
160
|
-
elsif newval.nil? then
|
161
|
-
# no merge source
|
162
|
-
oldval
|
163
|
-
elsif oldval then
|
164
|
-
# merge the source into the target
|
165
|
-
oldval.merge(newval)
|
166
|
-
else
|
167
|
-
# No target; set the attribute to the source.
|
168
|
-
# The target is either a source match or the source itself.
|
169
|
-
ref = (matches[newval] if matches) || newval
|
170
|
-
logger.debug { "Setting #{qp} #{attr_md} reference #{ref.qp}..." }
|
171
|
-
# If the target is a dependent, then set the dependent owner, which will in turn
|
172
|
-
# set the attribute to the dependent. Otherwise, set the attribute to the target.
|
173
|
-
owtr ? delegate_to_inverse_setter(attr_md, ref, owtr) : send(attr_md.writer, ref)
|
174
|
-
end
|
175
|
-
newval
|
176
|
-
end
|
177
|
-
|
178
|
-
# Java Java TreeSet comparison uses the TreeSet comparator rather than an
|
179
|
-
# element-wise comparator. Work around this rare aberration by converting the TreeSet
|
180
|
-
# to a Ruby Set.
|
181
|
-
def mergeable__equal?(v1, v2)
|
182
|
-
Java::JavaUtil::TreeSet === v1 && Java::JavaUtil::TreeSet === v2 ? v1.to_set == v2.to_set : v1 == v2
|
183
|
-
end
|
184
|
-
end
|
185
|
-
end
|