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.
Files changed (86) hide show
  1. data/History.txt +4 -0
  2. data/LEGAL +5 -0
  3. data/LICENSE +22 -0
  4. data/README.md +51 -0
  5. data/doc/website/css/site.css +1 -5
  6. data/doc/website/images/avatar.png +0 -0
  7. data/doc/website/images/favicon.ico +0 -0
  8. data/doc/website/images/logo.png +0 -0
  9. data/doc/website/index.html +82 -0
  10. data/doc/website/install.html +87 -0
  11. data/doc/website/quick_start.html +87 -0
  12. data/doc/website/tissue.html +85 -0
  13. data/doc/website/uom.html +10 -0
  14. data/lib/caruby.rb +3 -0
  15. data/lib/caruby/active_support/README.txt +2 -0
  16. data/lib/caruby/active_support/core_ext/string.rb +7 -0
  17. data/lib/caruby/active_support/core_ext/string/inflections.rb +167 -0
  18. data/lib/caruby/active_support/inflections.rb +55 -0
  19. data/lib/caruby/active_support/inflector.rb +398 -0
  20. data/lib/caruby/cli/application.rb +36 -0
  21. data/lib/caruby/cli/command.rb +169 -0
  22. data/lib/caruby/csv/csv_mapper.rb +157 -0
  23. data/lib/caruby/csv/csvio.rb +185 -0
  24. data/lib/caruby/database.rb +252 -0
  25. data/lib/caruby/database/fetched_matcher.rb +66 -0
  26. data/lib/caruby/database/persistable.rb +432 -0
  27. data/lib/caruby/database/persistence_service.rb +162 -0
  28. data/lib/caruby/database/reader.rb +599 -0
  29. data/lib/caruby/database/saved_merger.rb +131 -0
  30. data/lib/caruby/database/search_template_builder.rb +59 -0
  31. data/lib/caruby/database/sql_executor.rb +75 -0
  32. data/lib/caruby/database/store_template_builder.rb +200 -0
  33. data/lib/caruby/database/writer.rb +469 -0
  34. data/lib/caruby/domain/annotatable.rb +25 -0
  35. data/lib/caruby/domain/annotation.rb +23 -0
  36. data/lib/caruby/domain/attribute_metadata.rb +447 -0
  37. data/lib/caruby/domain/java_attribute_metadata.rb +160 -0
  38. data/lib/caruby/domain/merge.rb +91 -0
  39. data/lib/caruby/domain/properties.rb +95 -0
  40. data/lib/caruby/domain/reference_visitor.rb +289 -0
  41. data/lib/caruby/domain/resource_attributes.rb +528 -0
  42. data/lib/caruby/domain/resource_dependency.rb +205 -0
  43. data/lib/caruby/domain/resource_introspection.rb +159 -0
  44. data/lib/caruby/domain/resource_metadata.rb +117 -0
  45. data/lib/caruby/domain/resource_module.rb +285 -0
  46. data/lib/caruby/domain/uniquify.rb +38 -0
  47. data/lib/caruby/import/annotatable_class.rb +28 -0
  48. data/lib/caruby/import/annotation_class.rb +27 -0
  49. data/lib/caruby/import/annotation_module.rb +67 -0
  50. data/lib/caruby/import/java.rb +338 -0
  51. data/lib/caruby/migration/migratable.rb +167 -0
  52. data/lib/caruby/migration/migrator.rb +533 -0
  53. data/lib/caruby/migration/resource.rb +8 -0
  54. data/lib/caruby/migration/resource_module.rb +11 -0
  55. data/lib/caruby/migration/uniquify.rb +20 -0
  56. data/lib/caruby/resource.rb +969 -0
  57. data/lib/caruby/util/attribute_path.rb +46 -0
  58. data/lib/caruby/util/cache.rb +53 -0
  59. data/lib/caruby/util/class.rb +99 -0
  60. data/lib/caruby/util/collection.rb +1053 -0
  61. data/lib/caruby/util/controlled_value.rb +35 -0
  62. data/lib/caruby/util/coordinate.rb +75 -0
  63. data/lib/caruby/util/domain_extent.rb +49 -0
  64. data/lib/caruby/util/file_separator.rb +65 -0
  65. data/lib/caruby/util/inflector.rb +20 -0
  66. data/lib/caruby/util/log.rb +95 -0
  67. data/lib/caruby/util/math.rb +12 -0
  68. data/lib/caruby/util/merge.rb +59 -0
  69. data/lib/caruby/util/module.rb +34 -0
  70. data/lib/caruby/util/options.rb +92 -0
  71. data/lib/caruby/util/partial_order.rb +36 -0
  72. data/lib/caruby/util/person.rb +119 -0
  73. data/lib/caruby/util/pretty_print.rb +184 -0
  74. data/lib/caruby/util/properties.rb +112 -0
  75. data/lib/caruby/util/stopwatch.rb +66 -0
  76. data/lib/caruby/util/topological_sync_enumerator.rb +53 -0
  77. data/lib/caruby/util/transitive_closure.rb +45 -0
  78. data/lib/caruby/util/tree.rb +48 -0
  79. data/lib/caruby/util/trie.rb +37 -0
  80. data/lib/caruby/util/uniquifier.rb +30 -0
  81. data/lib/caruby/util/validation.rb +48 -0
  82. data/lib/caruby/util/version.rb +56 -0
  83. data/lib/caruby/util/visitor.rb +351 -0
  84. data/lib/caruby/util/weak_hash.rb +36 -0
  85. data/lib/caruby/version.rb +3 -0
  86. 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