activefacts 1.6.0 → 1.7.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (169) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +14 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +4 -0
  5. data/Gemfile +14 -0
  6. data/LICENSE.txt +21 -0
  7. data/README.md +60 -0
  8. data/Rakefile +3 -80
  9. data/activefacts.gemspec +36 -0
  10. data/bin/afgen +4 -2
  11. data/bin/cql +5 -1
  12. data/lib/activefacts.rb +3 -12
  13. data/lib/activefacts/{vocabulary/query_evaluator.rb → query/evaluator.rb} +0 -0
  14. data/lib/activefacts/version.rb +2 -2
  15. metadata +48 -296
  16. data/History.txt +0 -4
  17. data/LICENSE +0 -19
  18. data/Manifest.txt +0 -165
  19. data/README.rdoc +0 -81
  20. data/css/offline.css +0 -3
  21. data/css/orm2.css +0 -124
  22. data/css/print.css +0 -8
  23. data/css/style-print.css +0 -357
  24. data/css/style.css +0 -387
  25. data/download.html +0 -110
  26. data/examples/CQL/Address.cql +0 -44
  27. data/examples/CQL/Blog.cql +0 -54
  28. data/examples/CQL/CompanyDirectorEmployee.cql +0 -56
  29. data/examples/CQL/Death.cql +0 -17
  30. data/examples/CQL/Diplomacy.cql +0 -48
  31. data/examples/CQL/Genealogy.cql +0 -98
  32. data/examples/CQL/Insurance.cql +0 -320
  33. data/examples/CQL/Marriage.cql +0 -18
  34. data/examples/CQL/Metamodel.cql +0 -493
  35. data/examples/CQL/Monogamy.cql +0 -24
  36. data/examples/CQL/MultiInheritance.cql +0 -22
  37. data/examples/CQL/NonRoleId.cql +0 -14
  38. data/examples/CQL/OddIdentifier.cql +0 -18
  39. data/examples/CQL/OilSupply.cql +0 -53
  40. data/examples/CQL/OneToOnes.cql +0 -17
  41. data/examples/CQL/Orienteering.cql +0 -111
  42. data/examples/CQL/PersonPlaysGame.cql +0 -18
  43. data/examples/CQL/RedundantDependency.cql +0 -34
  44. data/examples/CQL/SchoolActivities.cql +0 -33
  45. data/examples/CQL/SeparateSubtype.cql +0 -30
  46. data/examples/CQL/ServiceDirector.cql +0 -276
  47. data/examples/CQL/SimplestUnary.cql +0 -12
  48. data/examples/CQL/Supervision.cql +0 -34
  49. data/examples/CQL/WaiterTips.cql +0 -33
  50. data/examples/CQL/Warehousing.cql +0 -101
  51. data/examples/CQL/WindowInRoomInBldg.cql +0 -28
  52. data/examples/CQL/unit.cql +0 -474
  53. data/examples/index.html +0 -420
  54. data/examples/intro.html +0 -327
  55. data/examples/local.css +0 -24
  56. data/index.html +0 -111
  57. data/lib/activefacts/cql.rb +0 -35
  58. data/lib/activefacts/cql/CQLParser.treetop +0 -158
  59. data/lib/activefacts/cql/Context.treetop +0 -48
  60. data/lib/activefacts/cql/Expressions.treetop +0 -67
  61. data/lib/activefacts/cql/FactTypes.treetop +0 -358
  62. data/lib/activefacts/cql/Language/English.treetop +0 -315
  63. data/lib/activefacts/cql/LexicalRules.treetop +0 -253
  64. data/lib/activefacts/cql/ObjectTypes.treetop +0 -210
  65. data/lib/activefacts/cql/Rakefile +0 -14
  66. data/lib/activefacts/cql/Terms.treetop +0 -183
  67. data/lib/activefacts/cql/ValueTypes.treetop +0 -202
  68. data/lib/activefacts/cql/compiler.rb +0 -156
  69. data/lib/activefacts/cql/compiler/clause.rb +0 -1137
  70. data/lib/activefacts/cql/compiler/constraint.rb +0 -581
  71. data/lib/activefacts/cql/compiler/entity_type.rb +0 -457
  72. data/lib/activefacts/cql/compiler/expression.rb +0 -443
  73. data/lib/activefacts/cql/compiler/fact.rb +0 -390
  74. data/lib/activefacts/cql/compiler/fact_type.rb +0 -421
  75. data/lib/activefacts/cql/compiler/query.rb +0 -106
  76. data/lib/activefacts/cql/compiler/shared.rb +0 -161
  77. data/lib/activefacts/cql/compiler/value_type.rb +0 -174
  78. data/lib/activefacts/cql/nodes.rb +0 -49
  79. data/lib/activefacts/cql/parser.rb +0 -241
  80. data/lib/activefacts/dependency_analyser.rb +0 -182
  81. data/lib/activefacts/generate/absorption.rb +0 -70
  82. data/lib/activefacts/generate/composition.rb +0 -118
  83. data/lib/activefacts/generate/cql.rb +0 -714
  84. data/lib/activefacts/generate/dm.rb +0 -279
  85. data/lib/activefacts/generate/help.rb +0 -64
  86. data/lib/activefacts/generate/helpers/inject.rb +0 -16
  87. data/lib/activefacts/generate/helpers/oo.rb +0 -162
  88. data/lib/activefacts/generate/helpers/ordered.rb +0 -605
  89. data/lib/activefacts/generate/helpers/rails.rb +0 -57
  90. data/lib/activefacts/generate/html/glossary.rb +0 -461
  91. data/lib/activefacts/generate/json.rb +0 -337
  92. data/lib/activefacts/generate/null.rb +0 -32
  93. data/lib/activefacts/generate/rails/models.rb +0 -246
  94. data/lib/activefacts/generate/rails/schema.rb +0 -216
  95. data/lib/activefacts/generate/records.rb +0 -46
  96. data/lib/activefacts/generate/ruby.rb +0 -133
  97. data/lib/activefacts/generate/sql/mysql.rb +0 -280
  98. data/lib/activefacts/generate/sql/server.rb +0 -273
  99. data/lib/activefacts/generate/stats.rb +0 -69
  100. data/lib/activefacts/generate/text.rb +0 -27
  101. data/lib/activefacts/generate/topics.rb +0 -265
  102. data/lib/activefacts/generate/traits/datavault.rb +0 -241
  103. data/lib/activefacts/generate/traits/oo.rb +0 -73
  104. data/lib/activefacts/generate/traits/ordered.rb +0 -33
  105. data/lib/activefacts/generate/traits/ruby.rb +0 -210
  106. data/lib/activefacts/generate/transform/datavault.rb +0 -266
  107. data/lib/activefacts/generate/transform/surrogate.rb +0 -214
  108. data/lib/activefacts/generate/version.rb +0 -26
  109. data/lib/activefacts/input/cql.rb +0 -43
  110. data/lib/activefacts/input/orm.rb +0 -1636
  111. data/lib/activefacts/mapping/rails.rb +0 -132
  112. data/lib/activefacts/persistence.rb +0 -15
  113. data/lib/activefacts/persistence/columns.rb +0 -446
  114. data/lib/activefacts/persistence/foreignkey.rb +0 -187
  115. data/lib/activefacts/persistence/index.rb +0 -240
  116. data/lib/activefacts/persistence/object_type.rb +0 -198
  117. data/lib/activefacts/persistence/reference.rb +0 -434
  118. data/lib/activefacts/persistence/tables.rb +0 -380
  119. data/lib/activefacts/registry.rb +0 -11
  120. data/lib/activefacts/support.rb +0 -132
  121. data/lib/activefacts/vocabulary.rb +0 -9
  122. data/lib/activefacts/vocabulary/extensions.rb +0 -1348
  123. data/lib/activefacts/vocabulary/metamodel.rb +0 -570
  124. data/lib/activefacts/vocabulary/verbaliser.rb +0 -804
  125. data/script/txt2html +0 -71
  126. data/spec/absorption_spec.rb +0 -95
  127. data/spec/cql/comparison_spec.rb +0 -89
  128. data/spec/cql/context_spec.rb +0 -94
  129. data/spec/cql/contractions_spec.rb +0 -224
  130. data/spec/cql/deontic_spec.rb +0 -88
  131. data/spec/cql/entity_type_spec.rb +0 -320
  132. data/spec/cql/expressions_spec.rb +0 -66
  133. data/spec/cql/fact_type_matching_spec.rb +0 -338
  134. data/spec/cql/french_spec.rb +0 -21
  135. data/spec/cql/parser/bad_literals_spec.rb +0 -86
  136. data/spec/cql/parser/constraints_spec.rb +0 -19
  137. data/spec/cql/parser/entity_types_spec.rb +0 -106
  138. data/spec/cql/parser/expressions_spec.rb +0 -199
  139. data/spec/cql/parser/fact_types_spec.rb +0 -44
  140. data/spec/cql/parser/literals_spec.rb +0 -312
  141. data/spec/cql/parser/pragmas_spec.rb +0 -89
  142. data/spec/cql/parser/value_types_spec.rb +0 -42
  143. data/spec/cql/role_matching_spec.rb +0 -148
  144. data/spec/cql/samples_spec.rb +0 -244
  145. data/spec/cql_cql_spec.rb +0 -73
  146. data/spec/cql_dm_spec.rb +0 -136
  147. data/spec/cql_mysql_spec.rb +0 -69
  148. data/spec/cql_parse_spec.rb +0 -34
  149. data/spec/cql_ruby_spec.rb +0 -73
  150. data/spec/cql_sql_spec.rb +0 -72
  151. data/spec/cql_symbol_tables_spec.rb +0 -261
  152. data/spec/cqldump_spec.rb +0 -170
  153. data/spec/helpers/array_matcher.rb +0 -23
  154. data/spec/helpers/ctrl_c_support.rb +0 -52
  155. data/spec/helpers/diff_matcher.rb +0 -39
  156. data/spec/helpers/file_matcher.rb +0 -34
  157. data/spec/helpers/parse_to_ast_matcher.rb +0 -80
  158. data/spec/helpers/string_matcher.rb +0 -30
  159. data/spec/helpers/test_parser.rb +0 -15
  160. data/spec/norma_cql_spec.rb +0 -66
  161. data/spec/norma_ruby_spec.rb +0 -62
  162. data/spec/norma_ruby_sql_spec.rb +0 -107
  163. data/spec/norma_sql_spec.rb +0 -57
  164. data/spec/norma_tables_spec.rb +0 -95
  165. data/spec/ruby_api_spec.rb +0 -23
  166. data/spec/spec_helper.rb +0 -35
  167. data/spec/transform_surrogate_spec.rb +0 -59
  168. data/status.html +0 -138
  169. data/why.html +0 -60
@@ -1,26 +0,0 @@
1
- #
2
- # ActiveFacts Generators.
3
- # Provides version number from the --version option
4
- #
5
- # Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
6
- #
7
- require 'activefacts/persistence'
8
-
9
- module ActiveFacts
10
- module Generate
11
- # Generate nothing from an ActiveFacts vocabulary. This is useful to check the file can be read ok.
12
- # Invoke as
13
- # afgen --null <file>.cql
14
- class VERSION
15
- private
16
- def initialize(vocabulary, *options)
17
- puts ActiveFacts::VERSION
18
- exit 0
19
- end
20
-
21
- public
22
- end
23
- end
24
- end
25
-
26
- ActiveFacts::Registry.generator('version', ActiveFacts::Generate::VERSION)
@@ -1,43 +0,0 @@
1
- # Compile a CQL file into an ActiveFacts vocabulary.
2
- #
3
- # Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
4
- #
5
- require 'activefacts/vocabulary'
6
- require 'activefacts/cql/parser'
7
- require 'activefacts/cql/compiler'
8
-
9
- module ActiveFacts
10
- module Input #:nodoc:
11
- # Compile CQL to an ActiveFacts vocabulary.
12
- # Invoke as
13
- # afgen --<generator> <file>.cql
14
- class CQL
15
- # Read the specified file
16
- def self.readfile(filename)
17
- if File.basename(filename, '.cql') == "-"
18
- read(STDIN, "<standard input>")
19
- else
20
- File.open(filename) {|file|
21
- read(file, filename)
22
- }
23
- end
24
- rescue => e
25
- # Augment the exception message, but preserve the backtrace
26
- ne = StandardError.new("In #{filename} #{e.message.strip}")
27
- ne.set_backtrace(e.backtrace)
28
- raise ne
29
- end
30
-
31
- # Read the specified input stream
32
- def self.read(file, filename = "stdin")
33
- readstring(file.read, filename)
34
- end
35
-
36
- # Read the specified input string
37
- def self.readstring(str, filename = "string")
38
- compiler = ActiveFacts::CQL::Compiler.new(filename)
39
- compiler.compile(str)
40
- end
41
- end
42
- end
43
- end
@@ -1,1636 +0,0 @@
1
- #
2
- # ActiveFacts Vocabulary Input.
3
- # Read a NORMA file into an ActiveFacts vocabulary
4
- #
5
- # Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
6
- #
7
- # This code uses variables prefixed with x_ when they refer to Rexml nodes.
8
- # Every node having an id="..." is indexed in @x_by_id[] hash before processing.
9
- # As we build ActiveFacts objects to match, we index those in @by_id[].
10
- # Both these hashes may be looked up by any of the ref="..." values in the file.
11
- #
12
- require 'nokogiri'
13
- require 'activefacts/vocabulary'
14
-
15
- module Nokogiri
16
- module XML
17
- class Node
18
- def elements
19
- children.select{|n|
20
- Nokogiri::XML::Element === n
21
- }
22
- end
23
- end
24
- end
25
- end
26
-
27
- module ActiveFacts
28
- module Input
29
- # Compile a NORMA (.orm) file to an ActiveFacts vocabulary.
30
- # Invoke as
31
- # afgen --<generator> <file>.orm
32
- # This parser uses Rexml so it's very slow.
33
- class ORM
34
- module Gravity
35
- %w{NW N NE W C E SW S SE}.each_with_index { |dir, i| const_set(dir, i) }
36
- end
37
-
38
- DataTypeMapping = {
39
- "FixedLengthText" => "Char",
40
- "VariableLengthText" => "String",
41
- "LargeLengthText" => "Text",
42
- "SignedIntegerNumeric" => "Signed Integer(32)",
43
- "SignedSmallIntegerNumeric" => "Signed Integer(16)",
44
- "SignedLargeIntegerNumeric" => "Signed Integer(64)",
45
- "UnsignedIntegerNumeric" => "Unsigned Integer(32)",
46
- "UnsignedTinyIntegerNumeric" => "Unsigned Integer(8)",
47
- "UnsignedSmallIntegerNumeric" => "Unsigned Integer(16)",
48
- "UnsignedLargeIntegerNumeric" => "Unsigned Integer(64)",
49
- "AutoCounterNumeric" => "Auto Counter",
50
- "FloatingPointNumeric" => "Real(64)",
51
- "SinglePrecisionFloatingPointNumeric" => "Real(32)",
52
- "DoublePrecisionFloatingPointNumeric" => "Real(32)",
53
- "DecimalNumeric" => "Decimal",
54
- "MoneyNumeric" => "Money",
55
- "FixedLengthRawData" => "Blob",
56
- "VariableLengthRawData" => "Blob",
57
- "LargeLengthRawData" => "Blob",
58
- "PictureRawData" => "Image",
59
- "OleObjectRawData" => "Blob",
60
- "AutoTimestampTemporal" => "Auto Time Stamp",
61
- "TimeTemporal" => "Time",
62
- "DateTemporal" => "Date",
63
- "DateAndTimeTemporal" => "Date Time",
64
- "TrueOrFalseLogical" => "Boolean",
65
- "YesOrNoLogical" => "Boolean",
66
- "RowIdOther" => "Guid",
67
- "ObjectIdOther" => "Guid"
68
- }
69
- RESERVED_WORDS = %w{
70
- and but each each either false if maybe no none not one or some that true where
71
- }
72
-
73
- private
74
- def self.readfile(filename, *options)
75
- if File.basename(filename, '.orm') == "-"
76
- self.read(STDIN, "<standard input>", options)
77
- else
78
- File.open(filename) {|file|
79
- self.read(file, filename, *options)
80
- }
81
- end
82
- end
83
-
84
- def self.read(file, filename = "stdin", *options)
85
- ORM.new(file, filename, *options).read
86
- end
87
-
88
- def initialize(file, filename = "stdin", *options)
89
- @file = file
90
- @filename = filename
91
- @options = options
92
- end
93
-
94
- public
95
- def read #:nodoc:
96
- begin
97
- @document = Nokogiri::XML(@file)
98
- rescue => e
99
- puts "Failed to parse XML in #{@filename}: #{e.inspect}"
100
- end
101
-
102
- # Find the Vocabulary and do some setup:
103
- root = @document.root
104
- #p((root.methods-0.methods).sort.grep(/name/))
105
- if root.name == "ORM2" && root.namespace.prefix == "ormRoot"
106
- x_models = root.xpath('orm:ORMModel')
107
- throw "No vocabulary found" unless x_models.size == 1
108
- @x_model = x_models[0]
109
- elsif root.name == "ORMModel"
110
- p @document.children.map(&:name)
111
- @x_model = @document.children[0]
112
- else
113
- throw "NORMA model not found in #{@filename}"
114
- end
115
-
116
- read_vocabulary
117
- @vocabulary
118
- end
119
-
120
- private
121
-
122
- def read_vocabulary
123
- @constellation = ActiveFacts::API::Constellation.new(ActiveFacts::Metamodel)
124
- vocabulary_name = @x_model['Name']
125
- @vocabulary = @constellation.Vocabulary(vocabulary_name)
126
-
127
- # Find all elements having an "id" attribute and index them
128
- x_identified = @x_model.xpath(".//*[@id]")
129
- @x_by_id = x_identified.inject({}){|h, x|
130
- id = x['id']
131
- h[id] = x
132
- h
133
- }
134
-
135
- # Everything we build will be indexed here:
136
- @by_id = {}
137
-
138
- list_subtypes
139
- read_entity_types
140
- read_value_types
141
- read_fact_types
142
- read_nested_types
143
- read_subtypes
144
- read_roles
145
- complete_nested_types
146
- read_constraints
147
- read_instances if @options.include?("instances")
148
- read_diagrams if @options.include?("diagrams")
149
- end
150
-
151
- def id_of(x)
152
- x['id'][1..-1]
153
- end
154
-
155
- def read_entity_types
156
- # get and process all the entity types:
157
- entity_types = []
158
- x_entity_types = @x_model.xpath("orm:Objects/orm:EntityType")
159
- x_entity_types.each do |x|
160
- id = x['id']
161
- name = (x['Name'] || "").gsub(/\s+/,' ').gsub(/-/,'_').strip
162
- name = nil if name.size == 0
163
- entity_type =
164
- @by_id[id] =
165
- trace :orm, "Asserting new EntityType #{name.inspect}" do
166
- @vocabulary.valid_entity_type_name(name) ||
167
- @constellation.EntityType(@vocabulary, name, :concept => id_of(x))
168
- end
169
- entity_types << entity_type
170
- independent = x['IsIndependent']
171
- entity_type.is_independent = true if independent && independent == 'true'
172
- personal = x['IsPersonal']
173
- entity_type.pronoun = 'personal' if personal && personal == 'true'
174
- # x_pref = x.xpath("orm:PreferredIdentifier")[0]
175
- # if x_pref
176
- # pi_id = x_pref['ref']
177
- # @pref_id_for[pi_id] = x
178
- # end
179
- end
180
- end
181
-
182
- def read_value_types
183
- # Now the value types:
184
- value_types = []
185
- x_value_types = @x_model.xpath("orm:Objects/orm:ValueType")
186
- @value_type_id_read = {}
187
- x_value_types.each{|x|
188
- next if x['IsImplicitBooleanValue']
189
- value_types << read_value_type(x)
190
- }
191
- end
192
-
193
- def read_value_type x
194
- id = x['id']
195
- return if @value_type_id_read[id] # Don't read the same value type twice
196
- @value_type_id_read[id] = true
197
-
198
- name = (x['Name'] || "").gsub(/\s+/,' ').gsub(/-/,'_').strip
199
- name = nil if name.size == 0
200
-
201
- cdt = x.xpath('orm:ConceptualDataType')[0]
202
- scale = cdt['Scale']
203
- scale = scale != "" && scale.to_i
204
- length = cdt['Length']
205
- length = length != "" && length.to_i
206
- length = nil if length <= 0
207
- base_type = @x_by_id[cdt['ref']]
208
- type_name = "#{base_type.name}"
209
- type_name.sub!(/^orm:/,'')
210
-
211
- type_name.sub!(/DataType\Z/,'')
212
- if t = DataTypeMapping[type_name]
213
- existing = @constellation.ObjectType[[@vocabulary.identifying_role_values, t]]
214
- if !existing || existing.is_a?(ActiveFacts::Metamodel::ValueType)
215
- # There's no type conflict, so use the mapped name.
216
- type_name = t
217
- end
218
- end
219
- trace :orm, "Using #{type_name.inspect} as supertype for new #{name}"
220
- if !length and type_name =~ /\(([0-9]+)\)/
221
- length = $1.to_i
222
- end
223
- type_name = type_name.sub(/\(([0-9]*)\)/,'')
224
-
225
- subtype_roles = x.xpath("orm:PlayedRoles/orm:SubtypeMetaRole")
226
- value_super_type = nil
227
- if !subtype_roles.empty?
228
- subtype_role_id = subtype_roles[0]['ref']
229
- subtype_role = @x_by_id[subtype_role_id]
230
- subtyping_fact_roles = subtype_role.parent
231
- supertype_id = subtyping_fact_roles.xpath("orm:SupertypeMetaRole/orm:RolePlayer")[0]['ref']
232
- x_supertype = @x_by_id[supertype_id]
233
- read_value_type x_supertype unless @value_type_id_read[supertype_id]
234
- supertype = @by_id[supertype_id]
235
- supertype_name = x_supertype['Name']
236
- raise "Supertype of #{name} is post-defined but recursiving processing failed" unless supertype
237
- raise "Supertype #{supertype_name} of #{name} is not a value type" unless supertype.kind_of? ActiveFacts::Metamodel::ValueType
238
- trace :orm, "Asserting new ValueType #{supertype_name.inspect} for supertype" do
239
- value_super_type =
240
- @vocabulary.valid_value_type_name(supertype_name) ||
241
- @constellation.ValueType(@vocabulary, supertype_name, :concept => id_of(x_supertype))
242
- end
243
- else
244
- # REVISIT: Need to handle standard types better here:
245
- value_super_type =
246
- if type_name != name
247
- trace :orm, "Asserting new ValueType #{type_name.inspect} for supertype" do
248
- @vocabulary.valid_value_type_name(type_name) ||
249
- @constellation.ValueType(@vocabulary.identifying_role_values, type_name, :concept => :new)
250
- end
251
- else
252
- nil
253
- end
254
- end
255
-
256
- vt =
257
- trace :orm, "Asserting new ValueType #{name.inspect}" do
258
- @by_id[id] =
259
- @vocabulary.valid_value_type_name(name) ||
260
- @constellation.ValueType(@vocabulary.identifying_role_values, name, :concept => id_of(x))
261
- end
262
- vt.supertype = value_super_type
263
- vt.length = length if length
264
- vt.scale = scale if scale && scale != 0
265
- independent = x['IsIndependent']
266
- vt.is_independent = true if independent && independent == 'true'
267
- personal = x['IsPersonal']
268
- vt.pronoun = 'personal' if personal && personal == 'true'
269
-
270
- x_vr = x.xpath("orm:ValueRestriction/orm:ValueConstraint")
271
- x_vr.each{|vr|
272
- x_ranges = vr.xpath("orm:ValueRanges/orm:ValueRange")
273
- next if x_ranges.size == 0
274
- vt.value_constraint = @by_id[vr['id']] = @constellation.ValueConstraint(id_of(vr))
275
- x_ranges.each{|x_range|
276
- v_range = value_range(x_range)
277
- ar = @constellation.AllowedRange(vt.value_constraint, v_range)
278
- }
279
- }
280
- vt
281
- end
282
-
283
- def assert_value(val, string)
284
- if val
285
- @constellation.Value(val.to_s, string, nil)
286
- else
287
- nil
288
- end
289
- end
290
-
291
- def value_range(x_range)
292
- min = x_range['MinValue']
293
- max = x_range['MaxValue']
294
-
295
- strings = is_literal_string(min) || is_literal_string(max)
296
- # ValueRange takes a minimum and/or a maximum Bound, each takes value and whether inclusive
297
- @constellation.ValueRange(
298
- min.length > 0 ? @constellation.Bound(assert_value(min, strings), true) : nil,
299
- max.length > 0 ? @constellation.Bound(assert_value(max, strings), true) : nil)
300
- end
301
-
302
- def read_fact_types
303
- # Handle the fact types:
304
- facts = []
305
- @x_facts = @x_model.xpath("orm:Facts/orm:Fact")
306
- trace :orm, "Reading fact types" do
307
- @x_facts.each{|x|
308
- id = x['id']
309
- name = x['Name'] || x['_Name']
310
- name = "<unnamed>" if !name
311
- name = "" if !name || name.size == 0
312
- # Note that the new metamodel doesn't have a name for a facttype unless it's objectified
313
-
314
- trace :orm, "FactType #{name || id}"
315
- facts << @by_id[id] = fact_type = @constellation.FactType(id_of(x))
316
- }
317
- end
318
- end
319
-
320
- def list_subtypes
321
- @x_subtypes = @x_model.xpath("orm:Facts/orm:SubtypeFact")
322
- if @document.namespaces['xmlns:oialtocdb']
323
- oialtocdb = @document.xpath("ormRoot:ORM2/oialtocdb:MappingCustomization")
324
- @x_mappings = oialtocdb.xpath(".//oialtocdb:AssimilationMappings/oialtocdb:AssimilationMapping/oialtocdb:FactType")
325
- else
326
- @x_mappings = []
327
- end
328
- end
329
-
330
- def read_subtypes
331
- # Handle the subtype fact types:
332
- facts = []
333
-
334
- trace :orm, "Reading sub-types" do
335
- @x_subtypes.each{|x|
336
- id = x['id']
337
- name = (x['Name'] || x['_Name'] || '').gsub(/\s+/,' ').gsub(/-/,'_').strip
338
- name = nil if name.size == 0
339
- trace :orm, "FactType #{name || id}"
340
-
341
- x_subtype_role = x.xpath('orm:FactRoles/orm:SubtypeMetaRole')[0]
342
- subtype_role_id = x_subtype_role['id']
343
- subtype_id = x_subtype_role.xpath('orm:RolePlayer')[0]['ref']
344
- subtype = @by_id[subtype_id]
345
- # REVISIT: Provide a way in the metamodel of handling Partition, (and mapping choices that vary for each supertype?)
346
-
347
- x_supertype_role = x.xpath('orm:FactRoles/orm:SupertypeMetaRole')[0]
348
- supertype_role_id = x_supertype_role['id']
349
- supertype_id = x_supertype_role.xpath('orm:RolePlayer')[0]['ref']
350
- supertype = @by_id[supertype_id]
351
-
352
- throw "For Subtype fact #{name}, the supertype #{supertype_id} was not found" if !supertype
353
- throw "For Subtype fact #{name}, the subtype #{subtype_id} was not found" if !subtype
354
- trace :orm, "#{subtype.name} is a subtype of #{supertype.name}"
355
-
356
- # We already handled ValueType subtyping:
357
- next if subtype.kind_of? ActiveFacts::Metamodel::ValueType or
358
- supertype.kind_of? ActiveFacts::Metamodel::ValueType
359
-
360
- inheritance_fact = @constellation.TypeInheritance(subtype, supertype, :concept => id_of(x))
361
- if x["IsPrimary"] == "true" or # Old way
362
- x["PreferredIdentificationPath"] == "true" # Newer
363
- trace :orm, "#{supertype.name} is primary supertype of #{subtype.name}"
364
- inheritance_fact.provides_identification = true
365
- end
366
- mapping = @x_mappings.detect{ |m| m['ref'] == id }
367
- mapping_choice = mapping ? mapping.parent['AbsorptionChoice'] : 'Absorbed'
368
- inheritance_fact.assimilation = mapping_choice.downcase.sub(/partition/, 'partitioned') if mapping_choice != 'Absorbed'
369
- facts << @by_id[id] = inheritance_fact
370
-
371
- # Create the new Roles so we can find constraints on them:
372
- subtype_role = @by_id[subtype_role_id] = @constellation.Role(inheritance_fact, 0, :object_type => subtype, :concept => id_of(x_subtype_role))
373
- supertype_role = @by_id[supertype_role_id] = @constellation.Role(inheritance_fact, 1, :object_type => supertype, :concept => id_of(x_supertype_role))
374
-
375
- # Create readings, so constraints can be verbalised for example:
376
- rs = @constellation.RoleSequence(:new)
377
- @constellation.RoleRef(rs, 0, :role => subtype_role)
378
- @constellation.RoleRef(rs, 1, :role => supertype_role)
379
- @constellation.Reading(inheritance_fact, 0, :role_sequence => rs, :text => "{0} is a kind of {1}", :is_negative => false)
380
- @constellation.Reading(inheritance_fact, 1, :role_sequence => rs, :text => "{0} is a subtype of {1}", :is_negative => false)
381
-
382
- rs2 = @constellation.RoleSequence(:new)
383
- @constellation.RoleRef(rs2, 0, :role => supertype_role)
384
- @constellation.RoleRef(rs2, 1, :role => subtype_role)
385
- n = 'aeioh'.include?(subtype_role.object_type.name.downcase[0]) ? 1 : 0
386
- @constellation.Reading(inheritance_fact, 2+n, :role_sequence => rs2, :text => "{0} is a {1}", :is_negative => false)
387
- @constellation.Reading(inheritance_fact, 3-n, :role_sequence => rs2, :text => "{0} is an {1}", :is_negative => false)
388
- }
389
- end
390
- end
391
-
392
- def read_nested_types
393
- # Process NestedTypes, but ignore ones having a NestedPredicate with IsImplied="true"
394
- # We'll ignore the fact roles (and constraints) that implied objectifications have.
395
- # This happens for all ternaries and higher order facts
396
- @nested_types = []
397
- x_nested_types = @x_model.xpath("orm:Objects/orm:ObjectifiedType")
398
- trace :orm, "Reading objectified types" do
399
- x_nested_types.each{|x|
400
- id = x['id']
401
- name = (x['Name'] || "").gsub(/\s+/,' ').gsub(/-/,'_').strip
402
- name = nil if name.size == 0
403
-
404
- x_fact_type = x.xpath('orm:NestedPredicate')[0]
405
- is_implied = x_fact_type['IsImplied'] == "true"
406
-
407
- fact_id = x_fact_type['ref']
408
- fact_type = @by_id[fact_id]
409
- next unless fact_type # "Nested fact #{fact_id} not found; objectification of a derived fact type?"
410
-
411
- trace :orm, "NestedType #{name} is #{id}, nests #{fact_type.concept.guid}"
412
- @nested_types <<
413
- @by_id[id] =
414
- nested_type = @vocabulary.valid_entity_type_name(name) ||
415
- @constellation.EntityType(@vocabulary, name, :concept => id_of(x))
416
- independent = x['IsIndependent']
417
- nested_type.is_independent = true if independent && independent == 'true' && !is_implied
418
- nested_type.concept.implication_rule = 'objectification' if is_implied
419
- nested_type.fact_type = fact_type
420
- }
421
- end
422
- end
423
-
424
- def complete_nested_types
425
- @nested_types.each do |nested_type|
426
- # Create the phantom roles here. These will be used later when we create objectification steps,
427
- # but for now there's nothing we import from NORMA which requires objectification steps.
428
- # Consequently there's no need to index them against NORMA's phantom roles.
429
- nested_type.create_implicit_fact_types
430
- end
431
- end
432
-
433
- def read_roles
434
- trace :orm, "Reading roles and readings" do
435
- @x_facts.each{|x|
436
- id = x['id']
437
- fact_type = @by_id[id]
438
- fact_name = x['Name'] || x['_Name'] || ''
439
- #fact_name.gsub!(/\s/,'')
440
- fact_name = nil if fact_name == ''
441
-
442
- x_fact_roles = x.xpath('orm:FactRoles/*')
443
-
444
- # Deal with FactRoles (Roles):
445
- trace :orm, "Reading fact roles" do
446
- x_fact_roles.each{|x|
447
- name = (x['Name'] || "").gsub(/\s+/,' ').gsub(/-/,'_').strip
448
- name = nil if name.size == 0
449
-
450
- # _IsMandatory = x['_IsMandatory']
451
- # _Multiplicity = x['_Multiplicity]
452
- id = x['id']
453
- rp = x.xpath('orm:RolePlayer')[0]
454
- raise "Invalid ORM file; fact has missing player (RolePlayer id=#{id})" unless rp
455
- ref = rp['ref']
456
-
457
- # Find the object_type that plays the role:
458
- object_type = @by_id[ref]
459
-
460
- # Skip implicit roles added by NORMA to make unaries into binaries.
461
- # This would make constraints over the deleted roles impossible,
462
- # so as a SPECIAL CASE we index the unary role by the id of the
463
- # implicit role. That means care is needed when handling unary FTs.
464
- if (ox = @x_by_id[ref]) && ox['IsImplicitBooleanValue']
465
- x_other_role = x.parent.xpath('orm:Role').reject{|x_role|
466
- x_role == x
467
- }[0]
468
- other_role_id = x_other_role["id"]
469
- other_role = @by_id[other_role_id]
470
- trace :orm, "Indexing unary FT role #{other_role_id} by implicit boolean role #{id}"
471
- @by_id[id] = other_role
472
-
473
- # The role name of the ignored role is the one that applies:
474
- role_name = x['Name']
475
- other_role.role_name = role_name if role_name && role_name != ''
476
-
477
- @by_id.delete(ref) # and de-index it from our list
478
- next
479
- end
480
- if !object_type
481
- throw "RolePlayer for '#{name}' #{ref} in fact type #{x.parent.parent['_Name']} was not found"
482
- end
483
-
484
- trace :orm, "#{@vocabulary.name}, RoleName=#{x['Name'].inspect} played by object_type=#{object_type.name}"
485
- throw "Role is played by #{object_type.class} not ObjectType" if !(@constellation.vocabulary.object_type(:ObjectType) === object_type)
486
-
487
- trace :orm, "Creating role #{name} nr#{fact_type.all_role.size} of #{fact_type.concept.guid} played by #{object_type.name}"
488
-
489
- role = @by_id[id] = @constellation.Role(fact_type, fact_type.all_role.size, :object_type => object_type, :concept => id_of(x))
490
- role.role_name = name if name && name != object_type.name
491
- trace :orm, "Fact #{fact_name} (id #{fact_type.concept.guid}) role #{x['Name']} is played by #{object_type.name}, role is #{role.concept.guid}"
492
-
493
- x_vr = x.xpath("orm:ValueRestriction/orm:RoleValueConstraint")
494
- x_vr.each{|vr|
495
- x_ranges = vr.xpath("orm:ValueRanges/orm:ValueRange")
496
- next if x_ranges.size == 0
497
- role.role_value_constraint = @by_id[vr['id']] = @constellation.ValueConstraint(id_of(vr))
498
- x_ranges.each{|x_range|
499
- v_range = value_range(x_range)
500
- ar = @constellation.AllowedRange(role.role_value_constraint, v_range)
501
- }
502
- }
503
-
504
- trace :orm, "Adding Role #{role.role_name || role.object_type.name} to #{fact_type.describe}"
505
- #fact_type.add_role(role)
506
- trace :orm, "Role #{role} is #{id}"
507
- }
508
- end
509
-
510
- # Deal with Readings:
511
- trace :orm, "Reading fact readings" do
512
- x_reading_orders = x.xpath('orm:ReadingOrders/*')
513
- x_reading_orders.each{|x|
514
- x_role_sequence = x.xpath('orm:RoleSequence/*')
515
- x_readings = x.xpath('orm:Readings/orm:Reading/orm:Data')
516
-
517
- # Build an array of the Roles needed:
518
- role_array = x_role_sequence.map{|x| @by_id[x['ref']] }
519
-
520
- trace :orm, "Reading #{x_readings.map(&:text).inspect}"
521
- role_sequence = get_role_sequence(role_array)
522
-
523
- #role_sequence.all_role_ref.each_with_index{|rr, i|
524
- # # REVISIT: rr.leading_adjective = ...; Add adjectives here
525
- # }
526
-
527
- x_readings.each_with_index{|x, i|
528
- reading = @constellation.Reading(fact_type, fact_type.all_reading.size, :is_negative => false)
529
- reading.role_sequence = role_sequence
530
- # REVISIT: The downcase here only needs to be the initial letter of each word, but be safe:
531
- reading.text = extract_adjectives(x.text, role_sequence)
532
- }
533
- }
534
- end
535
- }
536
- end
537
- end
538
-
539
- def extract_adjectives(text, role_sequence)
540
- all_role_refs = role_sequence.all_role_ref.sort_by{|rr| rr.ordinal}
541
- (0...all_role_refs.size).each{|i|
542
- role_ref = all_role_refs[i]
543
- role = role_ref.role
544
-
545
- word = '\b[A-Za-z_][A-Za-z0-9_]+\b'
546
- leading_adjectives_re = "#{word}-+(?: +#{word})*"
547
- trailing_adjectives_re = "(?:#{word} +)*-+#{word}"
548
- role_with_adjectives_re =
549
- %r| ?(#{leading_adjectives_re})? *\{#{i}\} *(#{trailing_adjectives_re})? ?|
550
-
551
- # A hyphenated pre-bound reading looks like this:
552
- # <orm:Data>{0} has pre-- bound {1}</orm:Data>
553
-
554
- text.gsub!(role_with_adjectives_re) {
555
- # REVISIT: Don't want to strip all spaces here any more:
556
- #puts "text=#{text.inspect}, la=#{$1.inspect}, ta=#{$2.inspect}" if $1 || $2
557
- la = ($1||'').gsub(/\s+/,' ') # Strip duplicate spaces
558
- ta = ($2||'').gsub(/\s+/,' ')
559
- # When we have "aaa-bbb" we want "aaa bbb"
560
- # When we have "aaa- bbb" we want "aaa bbb"
561
- # When we have "aaa-- bbb" we want "aaa-bbb"
562
- la = la.sub(/(-)?- ?/,'\1').strip
563
- ta = ta.sub(/ ?(-)?-/,'\1').strip
564
- #puts "Setting leading adj #{la.inspect} from #{text.inspect} for #{role_ref.role.object_type.name}" if la != ""
565
- # REVISIT: Dunno what's up here, but removing the "if" test makes this chuck exceptions:
566
- role_ref.leading_adjective = la if la != ""
567
- role_ref.trailing_adjective = ta if ta != ""
568
-
569
- #puts "Reading '#{text}' has role #{i} adjectives '#{la}' '#{ta}'" if la != "" || ta != ""
570
-
571
- " {#{i}} "
572
- }
573
- }
574
- text.sub!(/\s\s*/, ' ') # Compress extra spaces
575
- text.strip!
576
- text.downcase! # Check for reserved words and object type names *after* downcasing
577
- elided = ''
578
- text.gsub!(/( |[a-z]+(-[a-z]+)+|-?\b[A-Za-z_][A-Za-z0-9_]*\b-?|\{\d\})|./) do |w|
579
- case w
580
- when /[A-Za-z]/
581
- if RESERVED_WORDS.include?(w)
582
- $stderr.puts "Masking reserved word '#{w}' in reading '#{text}'"
583
- next "_#{w}"
584
- elsif @constellation.ObjectType[[[@vocabulary.name], w]]
585
- $stderr.puts "Masking object type name '#{w}' in reading '#{text}'"
586
- next "_#{w}"
587
- elsif all_role_refs.detect{|rr| rr.role.role_name == w}
588
- $stderr.puts "Masking role name '#{w}' in reading '#{text}'"
589
- next "_#{w}"
590
- end
591
- next w
592
- when /\{\d\}/
593
- next w
594
- when / /
595
- next w
596
- else
597
- elided << w
598
- next ''
599
- end
600
- end
601
- $stderr.puts "Elided illegal characters '#{elided}' from reading #{text.inspect}" unless elided.empty?
602
- text
603
- end
604
-
605
- def get_role_sequence(role_array)
606
- # puts "Getting RoleSequence [#{role_array.map{|r| "#{r.object_type.name} (role #{r.concept.guid})" }*", "}]"
607
-
608
- # Look for an existing RoleSequence
609
- # REVISIT: This searches all role sequences. Perhaps we could narrow it down first instead?
610
- role_sequence = @constellation.RoleSequence.values.detect{|c|
611
- #puts "Checking RoleSequence [#{c.all_role_ref.map{|rr| rr.role.object_type.name}*", "}]"
612
- role_array == c.all_role_ref.sort_by{|rr| rr.ordinal}.map{|rr| rr.role }
613
- }
614
- # puts "Found matching RoleSequence!" if role_sequence
615
- return role_sequence if role_sequence
616
-
617
- # Make a new RoleSequence:
618
- role_sequence = @constellation.RoleSequence(:new) unless role_sequence
619
- role_array.each_with_index do |r, i|
620
- role_ref = @constellation.RoleRef(role_sequence, i)
621
- role_ref.role = r
622
- end
623
-
624
- role_sequence
625
- end
626
-
627
- def map_roles(x_roles, why = nil)
628
- role_array = x_roles.map do |x|
629
- id = x['ref']
630
- role = @by_id[id]
631
- if (why && !role)
632
- # We didn't make Implied objects, so some constraints are unconnectable
633
- x_role = @x_by_id[id]
634
- x_player = x_role.xpath('orm:RolePlayer')[0]
635
- x_object = @x_by_id[x_player['ref']]
636
- x_nests = nil
637
- if (x_object.name.to_s == 'ObjectifiedType')
638
- x_nests = x_object.xpath('orm:NestedPredicate')[0]
639
- implied = x_nests['IsImplied']
640
- # x_fact is the fact of which the role player is an objectification, not the fact this role belongs to
641
- x_fact = @x_by_id[x_nests['ref']]
642
- end
643
-
644
- # This might have been a role of an ImpliedFact, which makes it safe to ignore.
645
- next if 'ImpliedFact' == x_role.parent.parent.name
646
-
647
- # Talk about why this wasn't found - this shouldn't happen.
648
- if (!x_nests || !implied)
649
- #puts "="*60
650
- # We skip creating TypeInheritance implied fact types for ValueType inheritance
651
- return nil if x_role.name = 'orm:SubtypeMetaRole' or x_role.name = 'orm:SupertypeMetaRole'
652
- raise "Skipping #{why}, #{x_role.name} #{id} not found"
653
-
654
- if (x_nests)
655
- puts "Role is on #{implied ? "implied " : ""}objectification #{x_object}"
656
- puts "which objectifies #{x_fact}"
657
- end
658
- puts x_object.to_s
659
- end
660
- end
661
- role
662
- end
663
- return nil if role_array.include?(nil)
664
-
665
- get_role_sequence(role_array)
666
- end
667
-
668
- def read_constraints
669
- @constraints_by_rs = {}
670
-
671
- read_mandatory_constraints
672
- read_uniqueness_constraints
673
- read_exclusion_constraints
674
- read_subset_constraints
675
- read_ring_constraints
676
- read_equality_constraints
677
- read_frequency_constraints
678
- read_residual_mandatory_constraints
679
- end
680
-
681
- def read_mandatory_constraints
682
- x_mandatory_constraints = @x_model.xpath("orm:Constraints/orm:MandatoryConstraint")
683
- @mandatory_constraints_by_rs = {}
684
- @mandatory_constraint_rs_by_id = {}
685
- trace :orm, "Scanning mandatory constraints" do
686
- x_mandatory_constraints.each{|x|
687
- name = x["Name"] || ''
688
- name = nil if name.size == 0
689
-
690
- # As of Feb 2008, all NORMA ValueTypes have an implied mandatory constraint.
691
- next if x.xpath("orm:ImpliedByObjectType").size > 0
692
-
693
- x_roles = x.xpath("orm:RoleSequence/orm:Role")
694
- role_sequence = map_roles(x_roles, "mandatory constraint #{name}")
695
- next if !role_sequence
696
-
697
- trace :orm, "New MC #{x['Name']} over #{role_sequence.describe}"
698
- @mandatory_constraints_by_rs[role_sequence] = x
699
- @mandatory_constraint_rs_by_id[x['id']] = role_sequence
700
- }
701
- end
702
- end
703
-
704
- # Mandatory constraints that didn't get merged with an exclusion constraint or a uniqueness constraint are simple mandatories
705
- def read_residual_mandatory_constraints
706
- trace :orm, "Processing non-absorbed mandatory constraints" do
707
- @mandatory_constraints_by_rs.each { |role_sequence, x|
708
- id = x['id']
709
- # Create a simply-mandatory PresenceConstraint for each mandatory constraint
710
- name = x["Name"] || ''
711
- name = nil if name.size == 0
712
- #puts "Residual Mandatory #{name}: #{role_sequence.to_s}"
713
-
714
- if (players = role_sequence.all_role_ref.map{|rr| rr.role.object_type}).uniq.size > 1
715
- join_over, = *ActiveFacts::Metamodel.plays_over(role_sequence.all_role_ref.map{|rr| rr.role}, :proximate)
716
- raise "Mandatory join constraint #{name} has incompatible players #{players.map{|o| o.name}.inspect}" unless join_over
717
- if players.detect{|p| p != join_over}
718
- trace :query, "subtyping step simple mandatory constraint #{name} over #{join_over.name}"
719
- players.each_with_index do |player, i|
720
- next if player != join_over
721
- # REVISIT: We don't need to make a subtyping step here (from join_over to player)
722
- end
723
- end
724
- end
725
-
726
- pc = @constellation.PresenceConstraint(id_of(x))
727
- pc.vocabulary = @vocabulary
728
- pc.name = name
729
- pc.role_sequence = role_sequence
730
- pc.is_mandatory = true
731
- pc.min_frequency = 1
732
- pc.max_frequency = nil
733
- pc.is_preferred_identifier = false
734
-
735
- (@constraints_by_rs[role_sequence] ||= []) << pc
736
- @by_id[id] = pc
737
- }
738
- end
739
- end
740
-
741
- def read_uniqueness_constraints
742
- x_uniqueness_constraints = @x_model.xpath("orm:Constraints/orm:UniquenessConstraint")
743
- trace :orm, "Reading uniqueness constraints" do
744
- x_uniqueness_constraints.each{|x|
745
- name = x["Name"] || ''
746
- name = nil if name.size == 0
747
- uc_id = x["id"]
748
- x_pi = x.xpath("orm:PreferredIdentifierFor")[0]
749
- pi = x_pi ? @by_id[eref = x_pi['ref']] : nil
750
-
751
- # Skip uniqueness constraints on implied object_types
752
- next if x_pi && !pi
753
-
754
- # Get the RoleSequence:
755
- x_roles = x.xpath("orm:RoleSequence/orm:Role")
756
- next if x_roles.size == 0
757
- role_sequence = map_roles(x_roles, "uniqueness constraint #{name}")
758
- next if !role_sequence
759
- #puts "uc: #{role_sequence.all_role_ref.map{|rr|rr.role.fact_type.default_reading}*', '}"
760
-
761
- # Check for a query
762
- if (fact_types = role_sequence.all_role_ref.map{|rr| rr.role.fact_type}).uniq.size > 1
763
- join_over, = *ActiveFacts::Metamodel.plays_over(role_sequence.all_role_ref.map{|rr| rr.role}, :counterpart)
764
-
765
- players = role_sequence.all_role_ref.map{|rr| rr.role.object_type.name}.uniq
766
- unless join_over
767
- if x.xpath("orm:RoleSequence/orm:JoinRule").size > 0
768
- $stderr.puts "Ignoring Join #{name} because its query is not understood"
769
- next
770
- end
771
- raise "Uniqueness join constraint #{name} has incompatible players #{players.inspect}"
772
- end
773
- subtyping = players.size > 1 ? 'subtyping ' : ''
774
- # REVISIT: Create the Query, the Variable for join_over, and steps from each role_ref to join_over
775
- trace :query, "#{subtyping}join uniqueness constraint over #{join_over.name} in #{fact_types.map(&:default_reading)*', '}"
776
- end
777
-
778
- # There is an implicit uniqueness constraint when any object plays a unary. Skip it.
779
- if (x_roles.size == 1 &&
780
- (id = x_roles[0]['ref']) &&
781
- (x_role = @x_by_id[id]) &&
782
- (nodes = x_role.parent.elements).size == 2 &&
783
- (sibling = nodes[1]) &&
784
- (ib_id = sibling.elements[0]['ref']) &&
785
- (ib = @x_by_id[ib_id]) &&
786
- ib['IsImplicitBooleanValue'])
787
- unary_identifier = true
788
- end
789
-
790
- mc_id = nil
791
-
792
- if (mc = @mandatory_constraints_by_rs[role_sequence])
793
- # Remove absorbed mandatory constraints, leaving residual ones.
794
- trace :orm, "Absorbing MC #{mc['Name']} over #{role_sequence.describe}"
795
- @mandatory_constraints_by_rs.delete(role_sequence)
796
- mc_id = mc['id']
797
- @mandatory_constraint_rs_by_id.delete(mc['id'])
798
- elsif (fts = role_sequence.all_role_ref.map{|rr| rr.role.fact_type}.uniq).size == 1 and
799
- fts[0].entity_type
800
- # this uniqueness constraint is an internal UC on an objectified fact type,
801
- # so the covered roles are always mandatory (wrt the OFT)
802
- # That is, the phantom roles are mandatory, even if the visible roles are not.
803
- mc = true
804
- else
805
- trace :orm, "No MC to absorb over #{role_sequence.describe}"
806
- end
807
-
808
- # A TypeInheritance fact type has a uniqueness constraint on each role.
809
- # If this UC is on the supertype and identifies the subtype, it's preferred:
810
- is_supertype_constraint =
811
- (rr = role_sequence.all_role_ref.single) &&
812
- (role = rr.role) &&
813
- (fact_type = role.fact_type) &&
814
- fact_type.is_a?(ActiveFacts::Metamodel::TypeInheritance) &&
815
- role.object_type == fact_type.supertype &&
816
- fact_type.provides_identification
817
-
818
- pc = @constellation.PresenceConstraint(id_of(x))
819
- pc.vocabulary = @vocabulary
820
- pc.name = name
821
- pc.role_sequence = role_sequence
822
- pc.is_mandatory = true if mc
823
- pc.min_frequency = mc ? 1 : 0
824
- pc.max_frequency = 1
825
- pc.is_preferred_identifier = true if pi || unary_identifier || is_supertype_constraint
826
- trace :orm, "#{name} covers #{role_sequence.describe} has min=#{pc.min_frequency}, max=1, preferred=#{pc.is_preferred_identifier.inspect}"
827
-
828
- trace :orm, role_sequence.all_role_ref.to_a[0].role.fact_type.describe + " is subject to " + pc.describe if role_sequence.all_role_ref.all?{|r| r.role.fact_type.is_a? ActiveFacts::Metamodel::TypeInheritance }
829
-
830
- (@constraints_by_rs[role_sequence] ||= []) << pc
831
- @by_id[uc_id] = pc
832
- @by_id[mc_id] = pc if mc_id
833
- }
834
- end
835
- end
836
-
837
- def subtype_step(query, ti)
838
- subtype_node = query.all_variable.detect{|jn| jn.object_type == ti.subtype } ||
839
- @constellation.Variable(query, query.all_variable.size, :object_type => ti.subtype)
840
- supertype_node = query.all_variable.detect{|jn| jn.object_type == ti.supertype } ||
841
- @constellation.Variable(query, query.all_variable.size, :object_type => ti.supertype)
842
- step = @constellation.Step(:guid => :new, :fact_type => ti)
843
- rs = @constellation.RoleSequence(:new)
844
- @constellation.RoleRef(rs, 0, :role => ti.subtype_role)
845
- sub_play = @constellation.Play(:step => step, :variable => subtype_node, :role => ti.subtype_role)
846
- @constellation.RoleRef(rs, 1, :role => ti.supertype_role)
847
- sup_play = @constellation.Play(:step => step, :variable => supertype_node, :role => ti.supertype_role, :is_input => true)
848
- trace :query, "New subtyping step #{step.describe}"
849
- step
850
- end
851
-
852
- # Make as many steps as it takes to get from subtype to supertype
853
- def subtype_steps(query, subtype, supertype)
854
- primary_ti = nil
855
- other_ti = nil
856
- subtype.all_type_inheritance_as_subtype.each do |ti|
857
- next unless ti.supertype.supertypes_transitive.include? supertype
858
- if ti.provides_identification
859
- primary_ti ||= ti
860
- else
861
- other_ti ||= ti
862
- end
863
- end
864
- ti = primary_ti || other_ti
865
- # Make supertype steps first:
866
- (ti.supertype == supertype ? [] : subtype_steps(query, ti.supertype, supertype)) +
867
- [subtype_step(query, ti)]
868
- end
869
-
870
- # If there's a query, build it and return a new RoleSequence containing the projected roles:
871
- def query_over_role_sequence(role_sequence, join_over, joined_roles, end_points)
872
- # Skip if there's no query here (sequence join nor end-point subset join)
873
- role_refs = role_sequence.all_role_ref_in_order
874
- if !join_over and !role_refs.detect{|rr| rr.role.object_type != end_points[rr.ordinal]}
875
- # No sequence join nor end_point join here
876
- return role_sequence
877
- end
878
-
879
- # A RoleSequence for the actual query end-points
880
- replacement_rs = @constellation.RoleSequence(:new)
881
-
882
- query = @constellation.Query(:new)
883
- variable = nil
884
- query_role = nil
885
- role_refs.zip(joined_roles||[]).each_with_index do |(role_ref, joined_role), i|
886
-
887
- # Each role_ref is to an object joined via joined_role to variable (or which will be the variable)
888
-
889
- # Create a variable for the actual end-point (supertype of the constrained roles)
890
- end_point = end_points[i]
891
- unless end_point
892
- raise "In #{constraint_type} #{name}, there is a faulty or non-translated query"
893
- end
894
- trace :query, "Variable #{query.all_variable.size} is for #{end_point.name}"
895
- end_node = @constellation.Variable(query, query.all_variable.size, :object_type => end_point)
896
-
897
- # We're going to rewrite the constraint to constrain the supertype roles, but assume they're the same:
898
- role_node = end_node
899
- end_role = role_ref.role
900
-
901
- # Create subtyping steps at the end-point, if needed:
902
- projecting_play = nil
903
- constrained_play = nil
904
- if (subtype = role_ref.role.object_type) != end_point
905
- trace :query, "Making subtyping steps from #{subtype.name} to #{end_point.name}" do
906
- # There may be more than one supertyping level. Make the steps:
907
- subtyping_steps = subtype_steps(query, subtype, end_point)
908
- step = subtyping_steps[0]
909
- #constrained_play = subtyping_steps[-1].all_play.detect{|p| p.role.object_type == end_point}
910
- subtyping_steps.detect{|s| constrained_play = s.all_play.detect{|p| p.role.object_type == end_point}}
911
-
912
- # Replace the constrained role and node with the supertype ones:
913
- end_node = query.all_variable.detect{|jn| jn.object_type == end_point }
914
- #projecting_play = step.all_play.detect{|p| p.role.object_type == subtype}
915
- subtyping_steps.detect{|s| projecting_play = s.all_play.detect{|p| p.role.object_type == subtype}}
916
- end_role = step.fact_type.all_role.detect{|r| r.object_type == end_point }
917
- role_node = query.all_variable.detect{|jn| jn.object_type == role_ref.role.object_type }
918
- end
919
- end
920
-
921
- if end_role.object_type != end_node.object_type
922
- raise "Internal error in #{constraint_type} #{name}: making illegal reference to variable, end role mismatch"
923
- end
924
-
925
- if join_over
926
- if !variable # Create the Variable when processing the first role
927
- trace :query, "Variable #{query.all_variable.size} is over #{join_over.name}"
928
- variable = @constellation.Variable(query, query.all_variable.size, :object_type => join_over)
929
- end
930
- trace :query, "Making step from #{end_point.name} to #{join_over.name}" do
931
- rs = @constellation.RoleSequence(:new)
932
- # Detect the fact type over which we're stepping (may involve objectification)
933
- raise "Internal error in #{constraint_type} #{name}: making illegal reference to variable, object type mismatch" if role_ref.role.object_type != role_node.object_type
934
- step = @constellation.Step(:guid => :new, :fact_type => joined_role.fact_type)
935
- @constellation.RoleRef(rs, 0, :role => role_ref.role)
936
- role_play = @constellation.Play(:step => step, :variable => role_node, :role => role_ref.role, :is_input => true)
937
- # Make the projected RoleRef:
938
- rr = @constellation.RoleRef(replacement_rs, replacement_rs.all_role_ref.size, :role => role_ref.role, :play => role_play)
939
- raise "Internal error in #{constraint_type} #{name}: making illegal reference to variable, joined_role mismatch" if joined_role.object_type != variable.object_type
940
- @constellation.RoleRef(rs, 1, :role => joined_role)
941
- join_play = @constellation.Play(:step => step, :variable => variable, :role => joined_role)
942
- trace :query, "New step #{step.describe}"
943
- end
944
- else
945
- trace :query, "Need step for non-join_over role #{end_point.name} #{role_ref.describe} in #{role_ref.role.fact_type.default_reading}"
946
- if (roles = role_ref.role.fact_type.all_role.to_a).size > 1
947
- # Here we have an end join (step already created) but no sequence join
948
- if variable
949
- raise "Internal error in #{constraint_type} #{name}: making illegal step" if role_ref.role.object_type != role_node.object_type
950
- step = @constellation.Step(:guid => :new, :fact_type => role_ref.role.fact_type)
951
- join_play = @constellation.Play(:step => step, :variable => variable, :role => query_role, :is_input => true)
952
- role_play = @constellation.Play(:step => step, :variable => role_node, :role => role_ref.role)
953
- # Make the projected RoleRef:
954
- rr = @constellation.RoleRef(replacement_rs, replacement_rs.all_role_ref.size, :role => role_ref.role, :play => role_play)
955
- roles -= [query_role, role_ref.role]
956
- roles.each do |incidental_role|
957
- jn = @constellation.Variable(query, query.all_variable.size, :object_type => incidental_role.object_type)
958
- play = @constellation.Play(:step => step, :variable => jn, :role => incidental_role, :step => step)
959
- end
960
- else
961
-
962
- if role_sequence.all_role_ref.size > 1
963
- variable = role_node
964
- query_role = role_ref.role
965
-
966
- # Make the projected RoleRef:
967
- rr = @constellation.RoleRef(replacement_rs, replacement_rs.all_role_ref.size, :role => constrained_play.role, :play => constrained_play)
968
- else
969
- # We enter this fact type (requiring that a role be played) but don't exit it.
970
- # I think this can only happen where we have subtyping steps, above.
971
-
972
- # There's no query in this role sequence, so we'd drop off the bottom without doing the right things. Why?
973
- # Without this case, Supervision.orm omits "that runs Company" from the exclusion constraint, and I'm not sure why.
974
- # I think the "then" code causes it to drop out the bottom without making the step (which is otherwise made in every case, see CompanyDirectorEmployee for example)
975
- step = @constellation.Step(:guid => :new, :fact_type => role_ref.role.fact_type)
976
-
977
- # p constrained_play.role.object_type.name
978
- # p projecting_play.role.object_type.name
979
- # debugger
980
-
981
- role_play = @constellation.Play(:step => step, :variable => role_node, :role => role_ref.role, :is_input => true)
982
-
983
- # Make the projected RoleRef:
984
- rr = @constellation.RoleRef(replacement_rs, replacement_rs.all_role_ref.size, :role => constrained_play.role, :play => constrained_play)
985
-
986
- # role_ref.role.fact_type.all_role.each do |role|
987
- # next if role == role_play.role
988
- # next if role_sequence.all_role_ref.detect{|rr| rr.role == role}
989
- # jn = @constellation.Variable(query, query.all_variable.size, :object_type => role.object_type)
990
- # play = @constellation.Play(:step => step, :variable => jn, :role => role)
991
- # if role == role_ref.role
992
- # # Make the projected RoleRef:
993
- # rr = @constellation.RoleRef(replacement_rs, replacement_rs.all_role_ref.size, :role => role, :play => play)
994
- # end
995
- # end
996
-
997
- end
998
- end
999
- else
1000
- # Unary fact type, make a Step from and to the constrained_play
1001
- step = @constellation.Step(:guid => :new, :fact_type => role_ref.role.fact_type)
1002
- play = @constellation.Play(:step => step, :variable => constrained_play.variable, :role => role_ref.role, :is_input => true)
1003
- # Make the projected RoleRef:
1004
- rr = @constellation.RoleRef(replacement_rs, replacement_rs.all_role_ref.size, :role => role_ref.role, :play => play)
1005
- end
1006
- end
1007
- end
1008
- raise "hell" if replacement_rs.all_role_ref.size != role_sequence.all_role_ref.size
1009
-
1010
- # Thoroughly check that this is a valid query
1011
- query.validate
1012
- trace :query, "Query has projected nodes #{replacement_rs.describe}"
1013
- replacement_rs
1014
- end
1015
-
1016
- # Equality and subset join constraints involve two or more role sequences,
1017
- # and the respective roles from each sequence must be compatible,
1018
- # Compatibility might involve subtyping steps but not objectification steps
1019
- # to the respective end-point (constrained object type).
1020
- # Also, all roles in each sequence constitute a join over a single
1021
- # object type, which might involve subtyping or objectification steps.
1022
- #
1023
- def make_queries(constraint_type, name, role_sequences)
1024
- begin
1025
- # Get the object types constrained for each position in the role sequences.
1026
- # Supertyping steps may be needed to reach them.
1027
- end_points = [] # An array of the common supertype for matching role_refs across the sequences
1028
- end_step_needed = [] # An array of booleans indicating whether any role_sequence requires subtyping steps
1029
- role_sequences[0].all_role_ref.size.times do |i|
1030
- role_refs = role_sequences.map{|rs| rs.all_role_ref.detect{|rr| rr.ordinal == i}}
1031
- if (fact_types = role_refs.map{|rr| rr.role.fact_type}).uniq.size == 1
1032
- # $stderr.puts(role_sequences.map{|rs| rs.all_role_ref.map{|rr| rr.role.fact_type.describe(rr.role)}}.inspect)
1033
- raise "In #{constraint_type} #{name} role sequence #{i}, there is a faulty join involving just 1 fact type: '#{fact_types[0].default_reading}'"
1034
- end
1035
- if (players = role_refs.map{|rr| rr.role.object_type}).uniq.size == 1
1036
- # All roles in this set are played by the same object type
1037
- end_point = players[0]
1038
- end_step_needed[i] = false
1039
- else
1040
- # Can the players be joined using subtyping steps?
1041
- common_supertypes = players[1..-1].
1042
- inject(players[0].supertypes_transitive) do |remaining, player|
1043
- remaining & player.supertypes_transitive
1044
- end
1045
- end_point = common_supertypes[0]
1046
-
1047
- raise "constrained roles of #{constraint_type} #{name} are incompatible (#{players.map(&:name)*', '})" if common_supertypes.size == 0
1048
- end_step_needed[i] = true
1049
- end
1050
- end_points[i] = end_point
1051
- end
1052
-
1053
- # For each role_sequence, find the object type over which the join is implied (nil if no join)
1054
- sequence_join_over = []
1055
- if role_sequences[0].all_role_ref.size > 1 # There are queries within each sequence.
1056
- sequence_join_over = []
1057
- sequence_joined_roles = []
1058
- role_sequences.map do |rs|
1059
- join_over, joined_roles = *ActiveFacts::Metamodel.plays_over(rs.all_role_ref.map{|rr| rr.role})
1060
- sequence_join_over << join_over
1061
- sequence_joined_roles << joined_roles
1062
- end
1063
- end
1064
-
1065
- # If there are no queries, we can drop out here.
1066
- if sequence_join_over.compact.empty? && !end_step_needed.detect{|e| e}
1067
- return true
1068
- end
1069
-
1070
- trace :query, "#{constraint_type} join constraint #{name} over #{role_sequences.map{|rs|rs.describe}*', '}"
1071
-
1072
- query = nil
1073
- trace :query, "#{constraint_type} join constraint #{name} constrains #{
1074
- end_points.zip(end_step_needed).map{|(p,j)| (p ? p.name : 'NULL')+(j ? ' & subtypes':'')}*', '
1075
- }#{
1076
- if role_sequences[0].all_role_ref.size > 1
1077
- ", joined over #{
1078
- sequence_join_over.zip(sequence_joined_roles).map{|o, roles|
1079
- (o ? o.name : '(none)') +
1080
- (roles ? " to (#{roles.map{|role| role ? role.fact_type.default_reading : 'null'}*','})" : '')
1081
- }*', '}"
1082
- else
1083
- ''
1084
- end
1085
- }" do
1086
-
1087
- # There may be one query per role sequence:
1088
- role_sequences.zip(sequence_join_over||[], sequence_joined_roles||[]).map do |role_sequence, join_over, joined_roles|
1089
- position = role_sequences.index(role_sequence)
1090
- replacement_rs = query_over_role_sequence(role_sequence, join_over, joined_roles, end_points)
1091
- if role_sequence != replacement_rs
1092
- role_sequences[position] = replacement_rs
1093
- end
1094
- end
1095
-
1096
- return true
1097
- end
1098
- rescue => e
1099
- debugger if trace :debug
1100
- $stderr.puts "// #{e.to_s}: #{e.backtrace[0]}"
1101
- return false
1102
- end
1103
-
1104
- end
1105
-
1106
- def read_exclusion_constraints
1107
- x_exclusion_constraints = @x_model.xpath("orm:Constraints/orm:ExclusionConstraint")
1108
- trace :orm, "Reading #{x_exclusion_constraints.size} exclusion constraints" do
1109
- x_exclusion_constraints.each{|x|
1110
- id = x['id']
1111
- name = x["Name"] || ''
1112
- name = nil if name.size == 0
1113
- x_mandatory = (m = x.xpath("orm:ExclusiveOrMandatoryConstraint")[0]) &&
1114
- @x_by_id[mc_id = m['ref']]
1115
- role_sequences =
1116
- x.xpath("orm:RoleSequences/orm:RoleSequence").map{|x_rs|
1117
- x_role_refs = x_rs.xpath("orm:Role")
1118
- map_roles(
1119
- x_role_refs , # .map{|xr| @x_by_id[xr['ref']] },
1120
- "exclusion constraint #{name}"
1121
- )
1122
- }
1123
- if x_mandatory
1124
- # Remove absorbed mandatory constraints, leaving residual ones.
1125
- mc_rs = @mandatory_constraint_rs_by_id[mc_id]
1126
- @mandatory_constraint_rs_by_id.delete(mc_id)
1127
- @mandatory_constraints_by_rs.delete(mc_rs)
1128
- end
1129
-
1130
- if role_sequences.compact.size != role_sequences.size # Role sequence missing; includes a derived fact type role
1131
- trace :orm, "skipped exclusion constraint #{id}, missing role sequence"
1132
- next
1133
- end
1134
-
1135
- unless make_queries('exclusion', name+(x_mandatory ? '/'+x_mandatory['Name'] : ''), role_sequences)
1136
- trace :orm, "skipped exclusion constraint #{id}, can't make_queries"
1137
- next
1138
- end
1139
-
1140
- ec = @constellation.SetExclusionConstraint(id_of(x))
1141
- ec.vocabulary = @vocabulary
1142
- ec.name = name
1143
- # ec.enforcement =
1144
- role_sequences.each_with_index do |rs, i|
1145
- @constellation.SetComparisonRoles(ec, i, :role_sequence => rs)
1146
- end
1147
- ec.is_mandatory = true if x_mandatory
1148
- @by_id[id] = ec
1149
- @by_id[mc_id] = ec if mc_id
1150
- }
1151
- end
1152
- end
1153
-
1154
- def read_equality_constraints
1155
- x_equality_constraints = @x_model.xpath("orm:Constraints/orm:EqualityConstraint")
1156
- trace :orm, "Reading equality constraints" do
1157
- x_equality_constraints.each{|x|
1158
- id = x['id']
1159
- name = x["Name"] || ''
1160
- name = nil if name.size == 0
1161
- role_sequences =
1162
- x.xpath("orm:RoleSequences/orm:RoleSequence").map{|x_rs|
1163
- x_role_refs = x_rs.xpath("orm:Role")
1164
- map_roles(
1165
- x_role_refs , # .map{|xr| @x_by_id[xr['ref']] },
1166
- "equality constraint #{name}"
1167
- )
1168
- }
1169
-
1170
- # Role sequence missing; includes a derived fact type role
1171
- next if role_sequences.compact.size != role_sequences.size
1172
-
1173
- next unless make_queries('equality', name, role_sequences)
1174
-
1175
- ec = @constellation.SetEqualityConstraint(id_of(x))
1176
- ec.vocabulary = @vocabulary
1177
- ec.name = name
1178
- # ec.enforcement =
1179
- role_sequences.each_with_index do |rs, i|
1180
- @constellation.SetComparisonRoles(ec, i, :role_sequence => rs)
1181
- end
1182
- @by_id[id] = ec
1183
- }
1184
- end
1185
- end
1186
-
1187
- def read_subset_constraints
1188
- x_subset_constraints = @x_model.xpath("orm:Constraints/orm:SubsetConstraint")
1189
- trace :orm, "Reading subset constraints" do
1190
- x_subset_constraints.each{|x|
1191
- id = x['id']
1192
- name = x["Name"] || ''
1193
- name = nil if name.size == 0
1194
- role_sequences =
1195
- x.xpath("orm:RoleSequences/orm:RoleSequence").map{|x_rs|
1196
- x_role_refs = x_rs.xpath("orm:Role")
1197
- map_roles(
1198
- x_role_refs , # .map{|xr| @x_by_id[xr['ref']] },
1199
- "equality constraint #{name}"
1200
- )
1201
- }
1202
- next if role_sequences.compact.size != role_sequences.size # Role sequence missing; includes a derived fact type role
1203
- next unless make_queries('subset', name, role_sequences)
1204
-
1205
- ec = @constellation.SubsetConstraint(id_of(x))
1206
- ec.vocabulary = @vocabulary
1207
- ec.name = name
1208
- # ec.enforcement =
1209
- ec.subset_role_sequence = role_sequences[0]
1210
- ec.superset_role_sequence = role_sequences[1]
1211
- @by_id[id] = ec
1212
- }
1213
- end
1214
- end
1215
-
1216
- def read_ring_constraints
1217
- x_ring_constraints = @x_model.xpath("orm:Constraints/orm:RingConstraint")
1218
- trace :orm, "Reading ring constraints" do
1219
- x_ring_constraints.each{|x|
1220
- id = x['id']
1221
- name = x["Name"] || ''
1222
- name = nil if name.size == 0
1223
- ring_type = x["Type"]
1224
-
1225
- from, to = *x.xpath("orm:RoleSequence/orm:Role").
1226
- map do |xr|
1227
- @by_id[xr['ref']]
1228
- end
1229
- next unless from && to # Roles missing; covers a derived fact type
1230
- if from.object_type != to.object_type
1231
- join_over, = *ActiveFacts::Metamodel.plays_over([from, to], :counterpart)
1232
- raise "Ring constraint has incompatible players #{from.object_type.name}, #{to.object_type.name}" if !join_over
1233
- trace :query, "join ring constraint over #{join_over.name}"
1234
- end
1235
- rc = @constellation.RingConstraint(id_of(x))
1236
- rc.vocabulary = @vocabulary
1237
- rc.name = name
1238
- # rc.enforcement =
1239
- rc.role = from
1240
- rc.other_role = to
1241
- rc.ring_type = ring_type.gsub(/PurelyReflexive/,'Reflexive')
1242
- @by_id[id] = rc
1243
- }
1244
- end
1245
- end
1246
-
1247
- def read_frequency_constraints
1248
- x_frequency_constraints = @x_model.xpath("orm:Constraints/orm:FrequencyConstraint")
1249
- trace :orm, "Reading frequency constraints" do
1250
- x_frequency_constraints.each do |x_frequency_constraint|
1251
- id = x_frequency_constraint['id']
1252
- min_frequency = x_frequency_constraint["MinFrequency"].to_i
1253
- min_frequency = nil if min_frequency == 0
1254
- max_frequency = x_frequency_constraint["MaxFrequency"].to_i
1255
- max_frequency = nil if max_frequency == 0
1256
- x_roles = x_frequency_constraint.xpath("orm:RoleSequence/orm:Role")
1257
- role = @by_id[x_roles[0]["ref"]]
1258
- role_sequence = @constellation.RoleSequence(:new)
1259
- role_ref = @constellation.RoleRef(role_sequence, 0, :role => role)
1260
- next unless role # Role missing; belongs to a derived fact type
1261
- trace :orm, "FrequencyConstraint(min #{min_frequency.inspect} max #{max_frequency.inspect} over #{role.fact_type.describe(role)} #{id} role ref = #{x_roles[0]["ref"]}"
1262
- @by_id[id] = @constellation.PresenceConstraint(
1263
- id_of(x_frequency_constraint),
1264
- :vocabulary => @vocabulary,
1265
- :name => name = x_frequency_constraint["Name"] || '',
1266
- :role_sequence => role_sequence,
1267
- :is_mandatory => false,
1268
- :min_frequency => min_frequency,
1269
- :max_frequency => max_frequency,
1270
- :is_preferred_identifier => false
1271
- )
1272
- end
1273
- end
1274
- end
1275
-
1276
- def read_instances
1277
- trace :orm, "Reading sample data" do
1278
- population = @constellation.Population(@vocabulary, "sample", :concept => :new)
1279
-
1280
- # Value instances first, then entities then facts:
1281
-
1282
- x_values = @x_model.xpath("orm:Objects/orm:ValueType/orm:Instances/orm:ValueTypeInstance/orm:Value")
1283
- #pp x_values.map{|v| [ v.parent['id'], v.text ] }
1284
- trace :orm, "Reading sample values" do
1285
- x_values.each{|v|
1286
- id = v.parent['id']
1287
- # Get details of the ValueType:
1288
- xvt = v.parent.parent.parent
1289
- vt_id = xvt['id']
1290
- vtname = xvt['Name'] || ''
1291
- #vtname.gsub!(/\s/,'')
1292
- vtname = nil if vtname.size == 0
1293
- vt = @by_id[vt_id]
1294
- throw "ValueType #{vtname} not found" unless vt
1295
-
1296
- i = @constellation.Instance(id_of(v.parent), :population => population, :object_type => vt, :value => [v.text, is_literal_string(v.text), nil])
1297
- @by_id[id] = i
1298
- # show_xmlobj(v)
1299
- }
1300
- end
1301
-
1302
- # Use the "id" attribute of EntityTypeInstance
1303
- x_entities = @x_model.xpath("orm:Objects/orm:EntityType/orm:Instances/orm:EntityTypeInstance")
1304
- #pp x_entities
1305
- # x_entities.each{|v| show_xmlobj(v) }
1306
- last_et_id = nil
1307
- last_et = nil
1308
- et = nil
1309
- trace :orm, "Reading sample entities" do
1310
- x_entities.each{|v|
1311
- id = v['id']
1312
-
1313
- # Get details of the EntityType:
1314
- xet = v.parent.parent
1315
- et_id = xet['id']
1316
- if (et_id != last_et_id)
1317
- etname = xet['Name'] || ''
1318
- #etname.gsub!(/\s/,'')
1319
- etname = nil if etname.size == 0
1320
- last_et = et = @by_id[et_id]
1321
- last_et_id = et_id
1322
- throw "EntityType #{etname} not found" unless et
1323
- end
1324
-
1325
- instance = @constellation.Instance(id_of(v), :population => population, :object_type => et, :value => nil)
1326
- @by_id[id] = instance
1327
- trace :orm, "Made new EntityType #{etname}"
1328
- }
1329
- end
1330
-
1331
- # The EntityType instances have implicit facts for the PI facts.
1332
- # These are in the ORM file, but instead of using those,
1333
- # We create implicit PI facts after all the instances.
1334
- entity_count = 0
1335
- pi_fact_count = 0
1336
- trace :orm, "Creating identifying facts for entities" do
1337
- x_entities.each do |v|
1338
- id = v['id']
1339
- instance = @by_id[id]
1340
- et = @by_id[v.parent.parent['id']]
1341
- next unless (preferred_id = et.preferred_identifier)
1342
-
1343
- trace :orm, "Create identifying facts using #{preferred_id}"
1344
-
1345
- # Collate the referenced objects by role:
1346
- role_instances = v.elements[0].elements.inject({}){|h, v|
1347
- etri = @x_by_id[v['ref']]
1348
- x_role_id = etri.parent.parent['id']
1349
- role = @by_id[x_role_id]
1350
- object = @by_id[object_id = etri['ref']]
1351
- h[role] = object
1352
- h
1353
- }
1354
-
1355
- # Create an instance of each required fact type, for compound identification:
1356
- identifying_fact_types =
1357
- preferred_id.role_sequence.all_role_ref.map { |rr| rr.role.fact_type }.uniq
1358
- identifying_fact_types.
1359
- each do |ft|
1360
- trace :orm, "For FactType #{ft}" do
1361
- fact = @constellation.Fact(:new, :population => population, :fact_type => ft)
1362
- fact_roles = ft.all_role.map do |role|
1363
- if role.object_type == et
1364
- object = instance
1365
- else
1366
- object = role_instances[role]
1367
- trace :orm, "instance for role #{role} is #{object}"
1368
- end
1369
- @constellation.RoleValue(:instance => object, :population => population, :fact => fact, :role => role)
1370
- end
1371
- end
1372
- pi_fact_count += 1
1373
- end
1374
-
1375
- entity_count += 1
1376
- end
1377
- end
1378
- trace :orm, "Created #{pi_fact_count} facts to identify #{entity_count} entities"
1379
-
1380
- # Use the "ref" attribute of FactTypeRoleInstance:
1381
- x_fact_roles = @x_model.xpath("orm:Facts/orm:Fact/orm:Instances/orm:FactTypeInstance/orm:RoleInstances/orm:FactTypeRoleInstance")
1382
-
1383
- # REVISIT: This presumably duplicates the identifying fact instances for the above entities. Hmmm.
1384
- last_id = nil
1385
- fact = nil
1386
- fact_roles = []
1387
- trace :orm, "Reading sample facts" do
1388
- x_fact_roles.each do |v|
1389
- fact_type_id = v.parent.parent.parent.parent['id']
1390
- id = v.parent.parent['id']
1391
- fact_type = @by_id[fact_type_id]
1392
- throw "Fact type #{fact_type_id} not found" unless fact_type
1393
-
1394
- # Create initial and subsequent Fact objects:
1395
- fact = @constellation.Fact(:new, :population => population, :fact_type => fact_type) unless fact && last_id == id
1396
- last_id = id
1397
-
1398
- # REVISIT: This doesn't handle instances of objectified fact types (where a RoleValue.instance objectifies Fact)
1399
-
1400
- x_role_instance = @x_by_id[v['ref']]
1401
- x_role_id = x_role_instance.parent.parent['id']
1402
- role = @by_id[x_role_id]
1403
- throw "Role not found for instance #{x_role_id}" unless role
1404
- instance_id = x_role_instance['ref']
1405
- instance = @by_id[instance_id]
1406
- throw "Instance not found for FactRole #{instance_id}" unless instance
1407
- @constellation.RoleValue(:instance => instance, :population => population, :fact => fact, :role => role)
1408
- end
1409
- end
1410
-
1411
- end
1412
- end
1413
-
1414
- def read_diagrams
1415
- x_diagrams = @document.root.xpath("ormDiagram:ORMDiagram")
1416
- trace :orm, "Reading diagrams" do
1417
- x_diagrams.each do |x|
1418
- name = (x["Name"] || '').strip
1419
- diagram = @constellation.ORMDiagram(@vocabulary, name)
1420
- trace :diagram, "Starting to read diagram #{name}"
1421
- shapes = x.xpath("ormDiagram:Shapes/*")
1422
- trace :orm, "Reading shapes" do
1423
- shapes.map do |x_shape|
1424
- x_subject = x_shape.xpath("ormDiagram:Subject")[0]
1425
- subject = @by_id[x_subject["ref"]]
1426
- is_expanded = v = x_shape['IsExpanded'] and v == 'true'
1427
- bounds = x_shape['AbsoluteBounds']
1428
- case shape_type = x_shape.name
1429
- when 'FactTypeShape'
1430
- if subject
1431
- read_fact_type_shape diagram, x_shape, is_expanded, bounds, subject
1432
- # else REVISIT: probably a derived fact type
1433
- end
1434
- when 'ExternalConstraintShape', 'FrequencyConstraintShape'
1435
- # REVISIT: The offset might depend on the constraint type. This is right for subset and other round ones.
1436
- location = convert_location(bounds, Gravity::C)
1437
- shape = @constellation.ConstraintShape(
1438
- :guid => id_of(x_shape), :orm_diagram => diagram, :location => location, :is_expanded => is_expanded,
1439
- :constraint => subject
1440
- )
1441
- when 'RingConstraintShape'
1442
- # REVISIT: The offset might depend on the ring constraint type. This is right for basic round ones.
1443
- location = convert_location(bounds, Gravity::C)
1444
- shape = @constellation.RingConstraintShape(
1445
- :guid => id_of(x_shape), :orm_diagram => diagram, :location => location, :is_expanded => is_expanded,
1446
- :constraint => subject
1447
- )
1448
- shape.fact_type_shape = subject.role.fact_type.all_fact_type_shape.to_a[0]
1449
- when 'ModelNoteShape'
1450
- # REVISIT: Add model notes
1451
- when 'ObjectTypeShape'
1452
- location = convert_location(bounds, Gravity::C)
1453
- # $stderr.puts "#{subject.name}: bounds=#{bounds} -> location = (#{location.x}, #{location.y})"
1454
- shape = @constellation.ObjectTypeShape(
1455
- :guid => id_of(x_shape), :orm_diagram => diagram, :location => location, :is_expanded => is_expanded,
1456
- :object_type => subject,
1457
- :location => location
1458
- )
1459
- else
1460
- raise "Unknown shape #{x_shape.name}"
1461
- end
1462
- end
1463
- end
1464
- end
1465
- end
1466
- end
1467
-
1468
- def read_fact_type_shape diagram, x_shape, is_expanded, bounds, fact_type
1469
- display_role_names_setting = v = x_shape["DisplayRoleNames"] and
1470
- case v
1471
- when 'Off'; 'false'
1472
- when 'On'; 'true'
1473
- else nil
1474
- end
1475
- rotation_setting = v = x_shape['DisplayOrientation'] and
1476
- case v
1477
- when 'VerticalRotatedLeft'; 'left'
1478
- when 'VerticalRotatedRight'; 'right'
1479
- else nil
1480
- end
1481
-
1482
- # Location of a fact type is the centre of the row of role boxes
1483
- offs_x = 11
1484
- offs_y = -12
1485
- if fact_type.entity_type
1486
- offs_x -= 12
1487
- offs_y -= 9 if !fact_type.entity_type.concept.implication_rule # .implication_rule_name == 'objectification'
1488
- end
1489
-
1490
- location = convert_location(bounds, Gravity::S, offs_x, offs_y)
1491
-
1492
- # $stderr.puts "#{fact_type.describe}: bounds=#{bounds} -> location = (#{location.x}, #{location.y})"
1493
-
1494
- trace :orm, "REVISIT: Can't place rotated fact type correctly on diagram yet" if rotation_setting
1495
-
1496
- trace :orm, "fact type at #{location.x},#{location.y} has display_role_names_setting=#{display_role_names_setting.inspect}, rotation_setting=#{rotation_setting.inspect}"
1497
- shape = @constellation.FactTypeShape(
1498
- :guid => id_of(x_shape),
1499
- :orm_diagram => diagram,
1500
- :location => location,
1501
- :is_expanded => is_expanded,
1502
- :display_role_names_setting => display_role_names_setting,
1503
- :rotation_setting => rotation_setting,
1504
- :fact_type => fact_type
1505
- )
1506
- # Create RoleDisplay objects if necessary
1507
- x_role_display = x_shape.xpath("ormDiagram:RoleDisplayOrder/ormDiagram:Role")
1508
- # print "Fact type '#{fact_type.preferred_reading.expand}' (#{fact_type.all_role.map{|r|r.object_type.name}*' '})"
1509
- if x_role_display.size > 0
1510
- trace :orm, " has roleDisplay (#{x_role_display.map{|rd| @by_id[rd['ref']].object_type.name}*','})'"
1511
- x_role_display.each_with_index do |rd, ordinal|
1512
- role_display = @constellation.RoleDisplay(shape, ordinal, :role => @by_id[rd['ref']])
1513
- end
1514
- else
1515
- # Decide whether to create all RoleDisplay objects for this fact type, which is in role order
1516
- # Omitting this here might lead to incomplete RoleDisplay sequences,
1517
- # because each RoleNameShape or ValueConstraintShape creates just one.
1518
- trace :orm, " has no roleDisplay"
1519
- end
1520
-
1521
- relative_shapes = x_shape.xpath('ormDiagram:RelativeShapes/*')
1522
- relative_shapes.each do |xr_shape|
1523
- location = convert_location(xr_shape['AbsoluteBounds'])
1524
- case xr_shape.name
1525
- when 'ObjectifiedFactTypeNameShape'
1526
- @constellation.ObjectifiedFactTypeNameShape(:guid => id_of(xr_shape), :fact_type_shape => shape, :orm_diagram => diagram, :location => location, :is_expanded => false)
1527
- when 'ReadingShape'
1528
- begin
1529
- @constellation.ReadingShape(:guid => id_of(xr_shape), :fact_type_shape => shape, :orm_diagram => diagram, :location => location, :is_expanded => false, :reading => fact_type.preferred_reading)
1530
- rescue =>e
1531
- debugger
1532
- @constellation.ReadingShape(:guid => id_of(xr_shape), :fact_type_shape => shape, :orm_diagram => diagram, :location => location, :is_expanded => false, :reading => fact_type.preferred_reading)
1533
- end
1534
- when 'RoleNameShape'
1535
- role = @by_id[xr_shape.xpath("ormDiagram:Subject")[0]['ref']]
1536
- role_display = role_display_for_role(shape, x_role_display, role)
1537
- trace :orm, "Fact type '#{fact_type.preferred_reading.expand}' has #{xr_shape.name}"
1538
- @constellation.RoleNameShape(
1539
- :guid => id_of(xr_shape), :orm_diagram => diagram, :location => location, :is_expanded => false,
1540
- :role_display => role_display
1541
- )
1542
- when 'ValueConstraintShape'
1543
- vc_subject_id = xr_shape.xpath("ormDiagram:Subject")[0]['ref']
1544
- constraint = @by_id[vc_subject_id]
1545
- trace :orm, "Fact type '#{fact_type.preferred_reading.expand}' has #{xr_shape.name} for #{constraint.inspect}"
1546
-
1547
- role_display = role_display_for_role(shape, x_role_display, constraint.role)
1548
- trace :orm, "ValueConstraintShape is on #{role_display.ordinal}'th role (by #{x_role_display.size > 0 ? 'role_display' : 'fact roles'})"
1549
- @constellation.ValueConstraintShape(
1550
- :guid => id_of(xr_shape), :orm_diagram => diagram, :location => location, :is_expanded => false,
1551
- :constraint => constraint,
1552
- :object_type_shape => nil, # This constraint is relative to a Fact Type, so must be on a role
1553
- :role_display => role_display
1554
- )
1555
- else raise "Unknown relative shape #{xr_shape.name}"
1556
- end
1557
- end
1558
- end
1559
-
1560
- # Find or create the RoleDisplay for this role in this fact_type_shape, given (possibly empty) x_role_display nodes:
1561
- def role_display_for_role(fact_type_shape, x_role_display, role)
1562
- if x_role_display.size == 0
1563
- # There's no x_role_display, which means the roles are in displayed
1564
- # the same order as in the fact type. However, we need a RoleDisplay
1565
- # to attach a ReadingShape or ValueConstraintShape, so make them all.
1566
- fact_type_shape.fact_type.all_role.each{|r| @constellation.RoleDisplay(fact_type_shape, r.ordinal, :role => r) }
1567
- role_ordinal = fact_type_shape.fact_type.all_role_in_order.index(role)
1568
- else
1569
- role_ordinal = x_role_display.map{|rd| @by_id[rd['ref']]}.index(role)
1570
- end
1571
- role_display = @constellation.RoleDisplay(fact_type_shape, role_ordinal, :role => role)
1572
- end
1573
-
1574
- DIAGRAM_SCALE = 96*1.5
1575
- def convert_location(bounds, gravity = Gravity::C, xoffs = 0, yoffs = 0)
1576
- return nil unless bounds
1577
- # Bounds is top, left, width, height in inches
1578
- bf = bounds.split(/, /).map{|b|b.to_f}
1579
- sizefrax = [
1580
- [0, 0], [1, 0], [2, 0],
1581
- [0, 1], [1, 1], [2, 2],
1582
- [0, 2], [1, 2], [2, 2],
1583
- ]
1584
-
1585
- x = (DIAGRAM_SCALE * (bf[0]+bf[2]*sizefrax[gravity][0]/2)).round + xoffs
1586
- y = (DIAGRAM_SCALE * (bf[1]+bf[3]*sizefrax[gravity][1]/2)).round + yoffs
1587
- @constellation.Location(x, y)
1588
- end
1589
-
1590
- # Detect numeric data and denote it as a string:
1591
- def is_literal_string(value)
1592
- value =~ /[^ \d.]/
1593
- end
1594
-
1595
- def read_rest
1596
- puts "Reading Implied Facts (not yet)"
1597
- =begin
1598
- x_implied_facts = @x_model.xpath("orm:Facts/orm:ImpliedFact")
1599
- pp x_implied_facts
1600
- =end
1601
- puts "Reading Data Types (not yet)"
1602
- =begin
1603
- x_datatypes = @x_model.xpath("orm:DataTypes/*")
1604
- pp x_datatypes
1605
- =end
1606
- puts "Reading Reference Mode Kinds (not yet)"
1607
- =begin
1608
- x_refmodekinds = @x_model.xpath("orm:ReferenceModeKinds/*")
1609
- pp x_refmodekinds
1610
- =end
1611
- end
1612
-
1613
- def show_xmlobj(x, indent = "")
1614
- parentage = []
1615
- p = x
1616
- while (p)
1617
- parentage.unshift(p)
1618
- p = p.parent
1619
- end
1620
- #parentage = parentage.shift
1621
- puts "#{indent}#{x.name} object has heritage {"
1622
- parentage.each{|p|
1623
- next if REXML::Document === p
1624
- puts "#{indent}\t#{p.name}#{
1625
- }#{(n = p['Name']) ? " Name='#{n}'" : ""
1626
- }#{(id = p['id']) ? " #{id}" : ""
1627
- }#{(ref = p['ref']) ? " -> #{ref}" : ""
1628
- }#{/\S/ === ((text = p.text)) ? " "+text.inspect : ""
1629
- }"
1630
- show_xmlobj(@x_by_id[ref], "\t#{indent}") if ref
1631
- }
1632
- puts "#{indent}}"
1633
- end
1634
- end
1635
- end
1636
- end