caruby-core 1.4.1

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