activefacts-metamodel 1.7.0 → 1.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1358 @@
1
+ #
2
+ # ActiveFacts Vocabulary Metamodel.
3
+ # Extensions to the ActiveFacts Vocabulary classes (which are generated from the Metamodel)
4
+ #
5
+ # Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
6
+ #
7
+ module ActiveFacts
8
+ module Metamodel
9
+ class Vocabulary
10
+ def finalise
11
+ constellation.FactType.values.each do |fact_type|
12
+ if c = fact_type.check_and_add_spanning_uniqueness_constraint
13
+ trace :constraint, "Checking for existence of at least one uniqueness constraint over the roles of #{fact_type.default_reading.inspect}"
14
+ fact_type.check_and_add_spanning_uniqueness_constraint = nil
15
+ c.call
16
+ end
17
+ end
18
+ end
19
+
20
+ # This name does not yet exist (at least not as we expect it to).
21
+ # If it in fact does exist (but as the wrong type), complain.
22
+ # If it doesn't exist, but its name would cause existing fact type
23
+ # readings to be re-interpreted to a different meaning, complain.
24
+ # Otherwise return nil.
25
+ def check_valid_nonexistent_object_type_name name
26
+ if ot = valid_object_type_name(name)
27
+ raise "Cannot redefine #{ot.class.basename} #{name}"
28
+ end
29
+ end
30
+
31
+ def valid_object_type_name name
32
+ # Raise an exception if adding this name to the vocabulary would create anomalies
33
+ anomaly = constellation.Reading.detect do |r_key, reading|
34
+ expanded = reading.expand do |role_ref, *words|
35
+ words.map! do |w|
36
+ case
37
+ when w == nil
38
+ w
39
+ when w[0...name.size] == name
40
+ '_ok_'+w
41
+ when w[-name.size..-1] == name
42
+ w[-1]+'_ok_'
43
+ else
44
+ w
45
+ end
46
+ end
47
+
48
+ words
49
+ end
50
+ expanded =~ %r{\b#{name}\b}
51
+ end
52
+ raise "Adding new term '#{name}' would create anomalous re-interpretation of '#{anomaly.expand}'" if anomaly
53
+ @constellation.ObjectType[[identifying_role_values, name]]
54
+ end
55
+
56
+ # If this entity type exists, ok, otherwise check it's ok to add it
57
+ def valid_entity_type_name name
58
+ @constellation.EntityType[[identifying_role_values, name]] or
59
+ check_valid_nonexistent_object_type_name name
60
+ end
61
+
62
+ # If this entity type exists, ok, otherwise check it's ok to add it
63
+ def valid_value_type_name name
64
+ @constellation.ValueType[[identifying_role_values, name]] or
65
+ check_valid_nonexistent_object_type_name name
66
+ end
67
+ end
68
+
69
+ class Concept
70
+ def describe
71
+ case
72
+ when object_type; "#{object_type.class.basename} #{object_type.name.inspect}"
73
+ when fact_type; "FactType #{fact_type.default_reading.inspect}"
74
+ when role; "Role in #{role.fact_type.describe(role)}"
75
+ when constraint; constraint.describe
76
+ when instance; "Instance #{instance.verbalise}"
77
+ when fact; "Fact #{fact.verbalise}"
78
+ when query; query.describe
79
+ when context_note; "ContextNote#{context_note.verbalise}"
80
+ when unit; "Unit #{unit.describe}"
81
+ when population; "Population: #{population.name}"
82
+ else
83
+ raise "ROGUE CONCEPT OF NO TYPE"
84
+ end
85
+ end
86
+
87
+ def embodied_as
88
+ case
89
+ when object_type; object_type
90
+ when fact_type; fact_type
91
+ when role; role
92
+ when constraint; constraint
93
+ when instance; instance
94
+ when fact; fact
95
+ when query; query
96
+ when context_note; context_note
97
+ when unit; unit
98
+ when population; population
99
+ else
100
+ raise "ROGUE CONCEPT OF NO TYPE"
101
+ end
102
+ end
103
+
104
+ # Return an array of all Concepts that must be defined before this concept can be defined:
105
+ def precursors
106
+ case body = embodied_as
107
+ when ActiveFacts::Metamodel::ValueType
108
+ [ body.supertype, body.unit ] +
109
+ body.all_value_type_parameter.map{|f| f.facet_value_type } +
110
+ body.all_value_type_parameter_restriction.map{|vr| vr.value.unit}
111
+ when ActiveFacts::Metamodel::EntityType
112
+ # You can't define the preferred_identifier fact types until you define the entity type,
113
+ # but the objects which play the identifying roles must be defined:
114
+ body.preferred_identifier.role_sequence.all_role_ref.map {|rr| rr.role.object_type } +
115
+ # You can't define the objectified fact type until you define the entity type:
116
+ # [ body.fact_type ] # If it's an objectification
117
+ body.all_type_inheritance_as_subtype.map{|ti| ti.supertype} # If it's a subtype
118
+ when FactType
119
+ body.all_role.map(&:object_type)
120
+ when Role # We don't consider roles as they cannot be separately defined
121
+ []
122
+ when ActiveFacts::Metamodel::PresenceConstraint
123
+ body.role_sequence.all_role_ref.map do |rr|
124
+ rr.role.fact_type
125
+ end
126
+ when ActiveFacts::Metamodel::ValueConstraint
127
+ [ body.role ? body.role.fact_type : nil, body.value_type ] +
128
+ body.all_allowed_range.map do |ar|
129
+ [ ar.value_range.minimum_bound, ar.value_range.maximum_bound ].compact.map{|b| b.value.unit}
130
+ end
131
+ when ActiveFacts::Metamodel::SubsetConstraint
132
+ body.subset_role_sequence.all_role_ref.map{|rr| rr.role.fact_type } +
133
+ body.superset_role_sequence.all_role_ref.map{|rr| rr.role.fact_type }
134
+ when ActiveFacts::Metamodel::SetComparisonConstraint
135
+ body.all_set_comparison_roles.map{|scr| scr.role_sequence.all_role_ref.map{|rr| rr.role.fact_type } }
136
+ when ActiveFacts::Metamodel::RingConstraint
137
+ [ body.role.fact_type, body.other_role.fact_type ]
138
+ when Instance
139
+ [ body.population, body.object_type, body.value ? body.value.unit : nil ]
140
+ when Fact
141
+ [ body.population, body.fact_type ]
142
+ when Query
143
+ body.all_variable.map do |v|
144
+ [ v.object_type,
145
+ v.value ? v.value.unit : nil,
146
+ v.step ? v.step.fact_type : nil
147
+ ] +
148
+ v.all_play.map{|p| p.role.fact_type }
149
+ end
150
+ when ContextNote
151
+ []
152
+ when Unit
153
+ body.all_derivation_as_derived_unit.map{|d| d.base_unit }
154
+ when Population
155
+ []
156
+ else
157
+ raise "ROGUE CONCEPT OF NO TYPE"
158
+ end.flatten.compact.uniq.map{|c| c.concept }
159
+ end
160
+ end
161
+
162
+ class Topic
163
+ def precursors
164
+ # Precursors of a topic are the topics of all precursors of items in this topic
165
+ all_concept.map{|c| c.precursors }.flatten.uniq.map{|c| c.topic}.uniq-[self]
166
+ end
167
+ end
168
+
169
+ class Unit
170
+ def describe
171
+ 'Unit' +
172
+ name +
173
+ (plural_name ? '/'+plural_name : '') +
174
+ '=' +
175
+ coefficient.to_s+'*' +
176
+ all_derivation_as_derived_unit.map do |derivation|
177
+ derivation.base_unit.name +
178
+ (derivation.exponent != 1 ? derivation.exponent.to_s : '')
179
+ end.join('') +
180
+ (offset ? ' + '+offset.to_s : '')
181
+ end
182
+ end
183
+
184
+ class Coefficient
185
+ def to_s
186
+ numerator.to_s +
187
+ (denominator != 1 ? '/' + denominator.to_s : '')
188
+ end
189
+ end
190
+
191
+ class FactType
192
+ attr_accessor :check_and_add_spanning_uniqueness_constraint
193
+
194
+ def all_reading_by_ordinal
195
+ all_reading.sort_by{|reading| reading.ordinal}
196
+ end
197
+
198
+ def preferred_reading negated = false
199
+ pr = all_reading_by_ordinal.detect{|r| !r.is_negative == !negated }
200
+ raise "No reading for (#{all_role.map{|r| r.object_type.name}*", "})" unless pr || negated
201
+ pr
202
+ end
203
+
204
+ def describe(highlight = nil)
205
+ (entity_type ? entity_type.name : "")+
206
+ '('+all_role.map{|role| role.describe(highlight) }*", "+')'
207
+ end
208
+
209
+ def default_reading(frequency_constraints = [], define_role_names = nil)
210
+ preferred_reading.expand(frequency_constraints, define_role_names)
211
+ end
212
+
213
+ # Does any role of this fact type participate in a preferred identifier?
214
+ def is_existential
215
+ return false if all_role.size > 2
216
+ all_role.detect do |role|
217
+ role.all_role_ref.detect do |rr|
218
+ rr.role_sequence.all_presence_constraint.detect do |pc|
219
+ pc.is_preferred_identifier
220
+ end
221
+ end
222
+ end
223
+ end
224
+
225
+ def internal_presence_constraints
226
+ all_role.map do |r|
227
+ r.all_role_ref.map do |rr|
228
+ !rr.role_sequence.all_role_ref.detect{|rr1| rr1.role.fact_type != self } ?
229
+ rr.role_sequence.all_presence_constraint.to_a :
230
+ []
231
+ end
232
+ end.flatten.compact.uniq
233
+ end
234
+
235
+ def implicit_boolean_type vocabulary
236
+ @constellation.ImplicitBooleanValueType[[vocabulary.identifying_role_values, "_ImplicitBooleanValueType"]] or
237
+ @constellation.ImplicitBooleanValueType(vocabulary.identifying_role_values, "_ImplicitBooleanValueType", :concept => [:new, :implication_rule => 'unary'])
238
+ end
239
+
240
+ # This entity type has just objectified a fact type. Create the necessary ImplicitFactTypes with phantom roles
241
+ def create_implicit_fact_type_for_unary
242
+ role = all_role.single
243
+ return if role.link_fact_type # Already exists
244
+ # NORMA doesn't create an implicit fact type here, rather the fact type has an implicit extra role, so looks like a binary
245
+ # We only do it when the unary fact type is not objectified
246
+ link_fact_type = @constellation.LinkFactType(:new, :implying_role => role)
247
+ link_fact_type.concept.implication_rule = 'unary'
248
+ entity_type = @entity_type || implicit_boolean_type(role.object_type.vocabulary)
249
+ phantom_role = @constellation.Role(link_fact_type, 0, :object_type => entity_type, :concept => :new)
250
+ end
251
+
252
+ def reading_preferably_starting_with_role role, negated = false
253
+ all_reading_by_ordinal.detect do |reading|
254
+ reading.text =~ /\{\d\}/ and
255
+ reading.role_sequence.all_role_ref_in_order[$1.to_i].role == role and
256
+ reading.is_negative == !!negated
257
+ end || preferred_reading(negated)
258
+ end
259
+
260
+ def all_role_in_order
261
+ all_role.sort_by{|r| r.ordinal}
262
+ end
263
+
264
+ def compatible_readings types_array
265
+ all_reading.select do |reading|
266
+ ok = true
267
+ reading.role_sequence.all_role_ref_in_order.each_with_index do |rr, i|
268
+ ok = false unless types_array[i].include?(rr.role.object_type)
269
+ end
270
+ ok
271
+ end
272
+ end
273
+ end
274
+
275
+ class Role
276
+ def describe(highlight = nil)
277
+ object_type.name + (self == highlight ? "*" : "")
278
+ end
279
+
280
+ # Find any internal uniqueness constraint on this role only
281
+ def uniqueness_constraint
282
+ all_role_ref.detect{|rr|
283
+ rs = rr.role_sequence
284
+ rs.all_role_ref.size == 1 and
285
+ rs.all_presence_constraint.each{|pc|
286
+ return pc if pc.max_frequency == 1
287
+ }
288
+ }
289
+ nil
290
+ end
291
+
292
+ # Is there are internal uniqueness constraint on this role only?
293
+ def unique
294
+ uniqueness_constraint ? true : false
295
+ end
296
+
297
+ def is_mandatory
298
+ return fact_type.implying_role.is_mandatory if fact_type.is_a?(LinkFactType)
299
+ all_role_ref.detect{|rr|
300
+ rs = rr.role_sequence
301
+ rs.all_role_ref.size == 1 and
302
+ rs.all_presence_constraint.detect{|pc|
303
+ pc.min_frequency and pc.min_frequency >= 1 and pc.is_mandatory
304
+ }
305
+ } ? true : false
306
+ end
307
+
308
+ def preferred_reference
309
+ fact_type.preferred_reading.role_sequence.all_role_ref.detect{|rr| rr.role == self }
310
+ end
311
+
312
+ # Return true if this role is functional (has only one instance wrt its player)
313
+ # A role in an objectified fact type is deemed to refer to the implicit role of the objectification.
314
+ def is_functional
315
+ fact_type.entity_type or
316
+ fact_type.all_role.size != 2 or
317
+ is_unique
318
+ end
319
+
320
+ def is_unique
321
+ all_role_ref.detect do |rr|
322
+ rr.role_sequence.all_role_ref.size == 1 and
323
+ rr.role_sequence.all_presence_constraint.detect do |pc|
324
+ pc.max_frequency == 1 and !pc.enforcement # Alethic uniqueness constraint
325
+ end
326
+ end
327
+ end
328
+
329
+ def name
330
+ role_name || object_type.name
331
+ end
332
+
333
+ end
334
+
335
+ class RoleRef
336
+ def describe
337
+ role_name
338
+ end
339
+
340
+ def preferred_reference
341
+ role.preferred_reference
342
+ end
343
+
344
+ def role_name(separator = "-")
345
+ return 'UNKNOWN' unless role
346
+ name_array =
347
+ if role.fact_type.all_role.size == 1
348
+ if role.fact_type.is_a?(LinkFactType)
349
+ "#{role.object_type.name} phantom for #{role.fact_type.role.object_type.name}"
350
+ else
351
+ role.fact_type.preferred_reading.text.gsub(/\{[0-9]\}/,'').strip.split(/\s/)
352
+ end
353
+ else
354
+ role.role_name || [leading_adjective, role.object_type.name, trailing_adjective].compact.map{|w| w.split(/\s/)}.flatten
355
+ end
356
+ return separator ? Array(name_array)*separator : Array(name_array)
357
+ end
358
+
359
+ def cql_leading_adjective
360
+ if leading_adjective
361
+ # 'foo' => "foo-"
362
+ # 'foo bar' => "foo- bar "
363
+ # 'foo-bar' => "foo-- bar "
364
+ # 'foo-bar baz' => "foo-- bar baz "
365
+ # 'bat foo-bar baz' => "bat- foo-bar baz "
366
+ leading_adjective.strip.
367
+ sub(/[- ]|$/, '-\0 ').sub(/ /, ' ').sub(/[^-]$/, '\0 ').sub(/- $/,'-')
368
+ else
369
+ ''
370
+ end
371
+ end
372
+
373
+ def cql_trailing_adjective
374
+ if trailing_adjective
375
+ # 'foo' => "-foo"
376
+ # 'foo bar' => " foo -bar"
377
+ # 'foo-bar' => " foo --bar"
378
+ # 'foo-bar baz' => " foo-bar -baz"
379
+ # 'bat foo-bar baz' => " bat foo-bar -baz"
380
+ trailing_adjective.
381
+ strip.
382
+ sub(/(?<a>.*) (?<b>[^- ]+$)|(?<a>.*)(?<b>-[^- ]*)$|(?<a>)(?<b>.*)/) {
383
+ " #{$~[:a]} -#{$~[:b]}"
384
+ }.
385
+ sub(/^ *-/, '-') # A leading space is not needed if the hyphen is at the start
386
+ else
387
+ ''
388
+ end
389
+ end
390
+
391
+ def cql_name
392
+ if role.fact_type.all_role.size == 1
393
+ role_name
394
+ elsif role.role_name
395
+ role.role_name
396
+ else
397
+ # Where an adjective has multiple words, the hyphen is inserted outside the outermost space, leaving the space
398
+ cql_leading_adjective +
399
+ role.object_type.name+
400
+ cql_trailing_adjective
401
+ end
402
+ end
403
+ end
404
+
405
+ class RoleSequence
406
+ def describe(highlighted_role_ref = nil)
407
+ "("+
408
+ all_role_ref.sort_by{|rr| rr.ordinal}.map{|rr| rr.describe + (highlighted_role_ref == rr ? '*' : '') }*", "+
409
+ ")"
410
+ end
411
+
412
+ def all_role_ref_in_order
413
+ all_role_ref.sort_by{|rr| rr.ordinal}
414
+ end
415
+ end
416
+
417
+ class ObjectType
418
+ # Placeholder for the surrogate transform
419
+ attr_reader :injected_surrogate_role
420
+
421
+ def is_separate
422
+ is_independent or concept.all_concept_annotation.detect{|ca| ca.mapping_annotation == 'separate'}
423
+ end
424
+ end
425
+
426
+ class ValueType
427
+ def supertypes_transitive
428
+ [self] + (supertype ? supertype.supertypes_transitive : [])
429
+ end
430
+
431
+ def subtypes
432
+ all_value_type_as_supertype
433
+ end
434
+
435
+ def subtypes_transitive
436
+ [self] + subtypes.map{|st| st.subtypes_transitive}.flatten
437
+ end
438
+
439
+ def common_supertype(other)
440
+ return nil unless other.is_?(ActiveFacts::Metamodel::ValueType)
441
+ return self if other.supertypes_transitive.include?(self)
442
+ return other if supertypes_transitive.include(other)
443
+ nil
444
+ end
445
+ end
446
+
447
+ class EntityType
448
+ def identification_is_inherited
449
+ preferred_identifier and
450
+ preferred_identifier.role_sequence.all_role_ref.detect{|rr| rr.role.fact_type.is_a?(ActiveFacts::Metamodel::TypeInheritance) }
451
+ end
452
+
453
+ def assimilation
454
+ if rr = identification_is_inherited
455
+ rr.role.fact_type.assimilation
456
+ else
457
+ nil
458
+ end
459
+ end
460
+
461
+ def preferred_identifier
462
+ return @preferred_identifier if @preferred_identifier
463
+ if fact_type
464
+ # When compiling a fact instance, the delayed creation of a preferred identifier might be necessary
465
+ if c = fact_type.check_and_add_spanning_uniqueness_constraint
466
+ fact_type.check_and_add_spanning_uniqueness_constraint = nil
467
+ c.call
468
+ end
469
+
470
+ # For a nested fact type, the PI is a unique constraint over N or N-1 roles
471
+ fact_roles = Array(fact_type.all_role)
472
+ trace :pi, "Looking for PI on nested fact type #{name}" do
473
+ pi = catch :pi do
474
+ fact_roles[0,2].each{|r| # Try the first two roles of the fact type, that's enough
475
+ r.all_role_ref.map{|rr| # All role sequences that reference this role
476
+ role_sequence = rr.role_sequence
477
+
478
+ # The role sequence is only interesting if it cover only this fact's roles
479
+ # or roles of the objectification
480
+ next if role_sequence.all_role_ref.size < fact_roles.size-1 # Not enough roles
481
+ next if role_sequence.all_role_ref.size > fact_roles.size # Too many roles
482
+ next if role_sequence.all_role_ref.detect do |rsr|
483
+ if (of = rsr.role.fact_type) != fact_type
484
+ case of.all_role.size
485
+ when 1 # A unary FT must be played by the objectification of this fact type
486
+ next rsr.role.object_type != fact_type.entity_type
487
+ when 2 # A binary FT must have the objectification of this FT as the other player
488
+ other_role = (of.all_role-[rsr.role])[0]
489
+ next other_role.object_type != fact_type.entity_type
490
+ else
491
+ next true # A role in a ternary (or higher) cannot be usd in our identifier
492
+ end
493
+ end
494
+ rsr.role.fact_type != fact_type
495
+ end
496
+
497
+ # This role sequence is a candidate
498
+ pc = role_sequence.all_presence_constraint.detect{|c|
499
+ c.max_frequency == 1 && c.is_preferred_identifier
500
+ }
501
+ throw :pi, pc if pc
502
+ }
503
+ }
504
+ throw :pi, nil
505
+ end
506
+ trace :pi, "Got PI #{pi.name||pi.object_id} for nested #{name}" if pi
507
+ trace :pi, "Looking for PI on entity that nests this fact" unless pi
508
+ raise "Oops, pi for nested fact is #{pi.class}" unless !pi || pi.is_a?(ActiveFacts::Metamodel::PresenceConstraint)
509
+ return @preferred_identifier = pi if pi
510
+ end
511
+ end
512
+
513
+ trace :pi, "Looking for PI for ordinary entity #{name} with #{all_role.size} roles:" do
514
+ trace :pi, "Roles are in fact types #{all_role.map{|r| r.fact_type.describe(r)}*", "}"
515
+ pi = catch :pi do
516
+ all_supertypes = supertypes_transitive
517
+ trace :pi, "PI roles must be played by one of #{all_supertypes.map(&:name)*", "}" if all_supertypes.size > 1
518
+ all_role.each{|role|
519
+ next unless role.unique || fact_type
520
+ ftroles = Array(role.fact_type.all_role)
521
+
522
+ # Skip roles in ternary and higher fact types, they're objectified, and in unaries, they can't identify us.
523
+ next if ftroles.size != 2
524
+
525
+ trace :pi, "Considering role in #{role.fact_type.describe(role)}"
526
+
527
+ # Find the related role which must be included in any PI:
528
+ # Note this works with unary fact types:
529
+ pi_role = ftroles[ftroles[0] != role ? 0 : -1]
530
+
531
+ next if ftroles.size == 2 && pi_role.object_type == self
532
+ trace :pi, " Considering #{pi_role.object_type.name} as a PI role"
533
+
534
+ # If this is an identifying role, the PI is a PC whose role_sequence spans the role.
535
+ # Walk through all role_sequences that span this role, and test each:
536
+ pi_role.all_role_ref.each{|rr|
537
+ role_sequence = rr.role_sequence # A role sequence that includes a possible role
538
+
539
+ trace :pi, " Considering role sequence #{role_sequence.describe}"
540
+
541
+ # All roles in this role_sequence must be in fact types which
542
+ # (apart from that role) only have roles played by the original
543
+ # entity type or a supertype.
544
+ #trace :pi, " All supertypes #{all_supertypes.map{|st| "#{st.object_id}=>#{st.name}"}*", "}"
545
+ if role_sequence.all_role_ref.detect{|rsr|
546
+ fact_type = rsr.role.fact_type
547
+ trace :pi, " Role Sequence touches #{fact_type.describe(pi_role)}"
548
+
549
+ fact_type_roles = fact_type.all_role
550
+ trace :pi, " residual is #{fact_type_roles.map{|r| r.object_type.name}.inspect} minus #{rsr.role.object_type.name}"
551
+ residual_roles = fact_type_roles-[rsr.role]
552
+ residual_roles.detect{|rfr|
553
+ trace :pi, " Checking residual role #{rfr.object_type.object_id}=>#{rfr.object_type.name}"
554
+ # This next line looks right, but breaks things. Find out what and why:
555
+ # !rfr.unique or
556
+ !all_supertypes.include?(rfr.object_type)
557
+ }
558
+ }
559
+ trace :pi, " Discounting this role_sequence because it includes alien roles"
560
+ next
561
+ end
562
+
563
+ # Any presence constraint over this role sequence is a candidate
564
+ rr.role_sequence.all_presence_constraint.detect{|pc|
565
+ # Found it!
566
+ if pc.is_preferred_identifier
567
+ trace :pi, "found PI #{pc.name||pc.object_id}, is_preferred_identifier=#{pc.is_preferred_identifier.inspect} over #{pc.role_sequence.describe}"
568
+ throw :pi, pc
569
+ end
570
+ }
571
+ }
572
+ }
573
+ throw :pi, nil
574
+ end
575
+ raise "Oops, pi for entity is #{pi.class}" if pi && !pi.is_a?(ActiveFacts::Metamodel::PresenceConstraint)
576
+ trace :pi, "Got PI #{pi.name||pi.object_id} for #{name}" if pi
577
+
578
+ if !pi
579
+ if (supertype = identifying_supertype)
580
+ # This shouldn't happen now, as an identifying supertype is connected by a fact type
581
+ # that has a uniqueness constraint marked as the preferred identifier.
582
+ #trace :pi, "PI not found for #{name}, looking in supertype #{supertype.name}"
583
+ #pi = supertype.preferred_identifier
584
+ #return nil
585
+ elsif fact_type
586
+ possible_pi = nil
587
+ fact_type.all_role.each{|role|
588
+ role.all_role_ref.each{|role_ref|
589
+ # Discount role sequences that contain roles not in this fact type:
590
+ next if role_ref.role_sequence.all_role_ref.detect{|rr| rr.role.fact_type != fact_type }
591
+ role_ref.role_sequence.all_presence_constraint.each{|pc|
592
+ next unless pc.max_frequency == 1
593
+ possible_pi = pc
594
+ next unless pc.is_preferred_identifier
595
+ pi = pc
596
+ break
597
+ }
598
+ break if pi
599
+ }
600
+ break if pi
601
+ }
602
+ if !pi && possible_pi
603
+ trace :pi, "Using existing PC as PI for #{name}"
604
+ pi = possible_pi
605
+ end
606
+ else
607
+ byebug
608
+ trace :pi, "No PI found for #{name}"
609
+ end
610
+ end
611
+ raise "No PI found for #{name}" unless pi
612
+ @preferred_identifier = pi
613
+ end
614
+ end
615
+
616
+ # An array of all direct subtypes:
617
+ def subtypes
618
+ # REVISIT: There's no sorting here. Should there be?
619
+ all_type_inheritance_as_supertype.map{|ti| ti.subtype }
620
+ end
621
+
622
+ def subtypes_transitive
623
+ [self] + subtypes.map{|st| st.subtypes_transitive}.flatten.uniq
624
+ end
625
+
626
+ def all_supertype_inheritance
627
+ all_type_inheritance_as_subtype.sort_by{|ti|
628
+ [ti.provides_identification ? 0 : 1, ti.supertype.name]
629
+ }
630
+ end
631
+
632
+ # An array all direct supertypes
633
+ def supertypes
634
+ all_supertype_inheritance.map{|ti|
635
+ ti.supertype
636
+ }
637
+ end
638
+
639
+ # An array of self followed by all supertypes in order:
640
+ def supertypes_transitive
641
+ ([self] + all_type_inheritance_as_subtype.map{|ti|
642
+ ti.supertype.supertypes_transitive
643
+ }).flatten.uniq
644
+ end
645
+
646
+ # A subtype does not have a identifying_supertype if it defines its own identifier
647
+ def identifying_supertype
648
+ trace "Looking for identifying_supertype of #{name}"
649
+ all_type_inheritance_as_subtype.detect{|ti|
650
+ trace "considering supertype #{ti.supertype.name}"
651
+ next unless ti.provides_identification
652
+ trace "found identifying supertype of #{name}, it's #{ti.supertype.name}"
653
+ return ti.supertype
654
+ }
655
+ trace "Failed to find identifying supertype of #{name}"
656
+ return nil
657
+ end
658
+
659
+ def common_supertype(other)
660
+ return nil unless other.is_?(ActiveFacts::Metamodel::EntityType)
661
+ candidates = supertypes_transitive & other.supertypes_transitive
662
+ return candidates[0] if candidates.size <= 1
663
+ candidates[0] # REVISIT: This might not be the closest supertype
664
+ end
665
+
666
+ # This entity type has just objectified a fact type. Create the necessary ImplicitFactTypes with phantom roles
667
+ def create_implicit_fact_types
668
+ fact_type.all_role.map do |role|
669
+ next if role.link_fact_type # Already exists
670
+ link_fact_type = @constellation.LinkFactType(:new, :implying_role => role)
671
+ link_fact_type.concept.implication_rule = 'objectification'
672
+ phantom_role = @constellation.Role(link_fact_type, 0, :object_type => self, :concept => :new)
673
+ # We could create a copy of the visible external role here, but there's no need yet...
674
+ # Nor is there a need for a presence constraint, readings, etc.
675
+ link_fact_type
676
+ end
677
+ end
678
+ end
679
+
680
+ class Reading
681
+ # The frequency_constraints array here, if supplied, may provide for each role either:
682
+ # * a PresenceConstraint to be verbalised against the relevant role, or
683
+ # * a String, used as a definite or indefinite article on the relevant role, or
684
+ # * an array containing two strings (an article and a super-type name)
685
+ # The order in the array is the same as the reading's role-sequence.
686
+ # REVISIT: This should probably be changed to be the fact role sequence.
687
+ #
688
+ # define_role_names here is false (use defined names), true (define names) or nil (neither)
689
+ def expand(frequency_constraints = [], define_role_names = nil, literals = [], &subscript_block)
690
+ expanded = "#{text}"
691
+ role_refs = role_sequence.all_role_ref.sort_by{|role_ref| role_ref.ordinal}
692
+ (0...role_refs.size).each{|i|
693
+ role_ref = role_refs[i]
694
+ role = role_ref.role
695
+ l_adj = "#{role_ref.leading_adjective}".sub(/(\b-\b|.\b|.\Z)/, '\1-').sub(/\b--\b/,'-- ').sub(/- /,'- ')
696
+ l_adj = nil if l_adj == ""
697
+ # Double the space to compensate for space removed below
698
+ # REVISIT: hyphenated trailing adjectives are not correctly represented here
699
+ t_adj = "#{role_ref.trailing_adjective}".sub(/(\b.|\A.)/, '-\1').sub(/ -/,' -')
700
+ t_adj = nil if t_adj == ""
701
+
702
+ expanded.gsub!(/\{#{i}\}/) do
703
+ role_ref = role_refs[i]
704
+ if role_ref.role
705
+ player = role_ref.role.object_type
706
+ role_name = role.role_name
707
+ role_name = nil if role_name == ""
708
+ if role_name && define_role_names == false
709
+ l_adj = t_adj = nil # When using role names, don't add adjectives
710
+ end
711
+
712
+ freq_con = frequency_constraints[i]
713
+ freq_con = freq_con.frequency if freq_con && freq_con.is_a?(ActiveFacts::Metamodel::PresenceConstraint)
714
+ if freq_con.is_a?(Array)
715
+ freq_con, player_name = *freq_con
716
+ else
717
+ player_name = player.name
718
+ end
719
+ else
720
+ # We have an unknown role. The reading cannot be correctly expanded
721
+ player_name = "UNKNOWN"
722
+ role_name = nil
723
+ freq_con = nil
724
+ end
725
+
726
+ literal = literals[i]
727
+ words = [
728
+ freq_con ? freq_con : nil,
729
+ l_adj,
730
+ define_role_names == false && role_name ? role_name : player_name,
731
+ t_adj,
732
+ define_role_names && role_name && player_name != role_name ? "(as #{role_name})" : nil,
733
+ # Can't have both a literal and a value constraint, but we don't enforce that here:
734
+ literal ? literal : nil
735
+ ]
736
+ if (subscript_block)
737
+ words = subscript_block.call(role_ref, *words)
738
+ end
739
+ words.compact*" "
740
+ end
741
+ }
742
+ expanded.gsub!(/ ?- ?/, '-') # Remove single spaces around adjectives
743
+ #trace "Expanded '#{expanded}' using #{frequency_constraints.inspect}"
744
+ expanded
745
+ end
746
+
747
+ def words_and_role_refs
748
+ text.
749
+ scan(/(?: |\{[0-9]+\}|[^{} ]+)/). # split up the text into words
750
+ reject{|s| s==' '}. # Remove white space
751
+ map do |frag| # and go through the bits
752
+ if frag =~ /\{([0-9]+)\}/
753
+ role_sequence.all_role_ref.detect{|rr| rr.ordinal == $1.to_i}
754
+ else
755
+ frag
756
+ end
757
+ end
758
+ end
759
+
760
+ # Return the array of the numbers of the RoleRefs inserted into this reading from the role_sequence
761
+ def role_numbers
762
+ text.scan(/\{(\d)\}/).flatten.map{|m| Integer(m) }
763
+ end
764
+
765
+ def expand_with_final_presence_constraint &b
766
+ # Arrange the roles in order they occur in this reading:
767
+ role_refs = role_sequence.all_role_ref_in_order
768
+ role_numbers = text.scan(/\{(\d)\}/).flatten.map{|m| Integer(m) }
769
+ roles = role_numbers.map{|m| role_refs[m].role }
770
+ fact_constraints = fact_type.internal_presence_constraints
771
+
772
+ # Find the constraints that constrain frequency over each role we can verbalise:
773
+ frequency_constraints = []
774
+ roles.each do |role|
775
+ frequency_constraints <<
776
+ if (role == roles.last) # On the last role of the reading, emit any presence constraint
777
+ constraint = fact_constraints.
778
+ detect do |c| # Find a UC that spans all other Roles
779
+ c.is_a?(ActiveFacts::Metamodel::PresenceConstraint) &&
780
+ roles-c.role_sequence.all_role_ref.map(&:role) == [role]
781
+ end
782
+ constraint && constraint.frequency
783
+ else
784
+ nil
785
+ end
786
+ end
787
+
788
+ expand(frequency_constraints) { |*a| b && b.call(*a) }
789
+ end
790
+ end
791
+
792
+ class ValueConstraint
793
+ def describe
794
+ as_cql
795
+ end
796
+
797
+ def as_cql
798
+ "restricted to "+
799
+ ( if regular_expression
800
+ '/' + regular_expression + '/'
801
+ else
802
+ '{' + all_allowed_range_sorted.map{|ar| ar.to_s(false) }*', ' + '}'
803
+ end
804
+ )
805
+ end
806
+
807
+ def all_allowed_range_sorted
808
+ all_allowed_range.sort_by{|ar|
809
+ ((min = ar.value_range.minimum_bound) && min.value.literal) ||
810
+ ((max = ar.value_range.maximum_bound) && max.value.literal)
811
+ }
812
+ end
813
+
814
+ def to_s
815
+ if all_allowed_range.size > 1
816
+ "[" +
817
+ all_allowed_range_sorted.map { |ar| ar.to_s(true) }*", " +
818
+ "]"
819
+ else
820
+ all_allowed_range.single.to_s
821
+ end
822
+ end
823
+ end
824
+
825
+ class AllowedRange
826
+ def to_s(infinity = true)
827
+ min = value_range.minimum_bound
828
+ max = value_range.maximum_bound
829
+ # Open-ended string ranges will fail in Ruby
830
+
831
+ if min = value_range.minimum_bound
832
+ min = min.value
833
+ if min.is_literal_string
834
+ min_literal = min.literal.inspect.gsub(/\A"|"\Z/,"'") # Escape string characters
835
+ else
836
+ min_literal = min.literal
837
+ end
838
+ else
839
+ min_literal = infinity ? "-Infinity" : ""
840
+ end
841
+ if max = value_range.maximum_bound
842
+ max = max.value
843
+ if max.is_literal_string
844
+ max_literal = max.literal.inspect.gsub(/\A"|"\Z/,"'") # Escape string characters
845
+ else
846
+ max_literal = max.literal
847
+ end
848
+ else
849
+ max_literal = infinity ? "Infinity" : ""
850
+ end
851
+
852
+ min_literal +
853
+ (min_literal != (max&&max_literal) ? (".." + max_literal) : "")
854
+ end
855
+ end
856
+
857
+ class Value
858
+ def to_s
859
+ if is_literal_string
860
+ "'"+
861
+ literal.
862
+ inspect. # Use Ruby's inspect to generate necessary escapes
863
+ gsub(/\A"|"\Z/,''). # Remove surrounding quotes
864
+ gsub(/'/, "\\'") + # Escape any single quotes
865
+ "'"
866
+ else
867
+ literal
868
+ end +
869
+ (unit ? " " + unit.name : "")
870
+ end
871
+
872
+ def inspect
873
+ to_s
874
+ end
875
+ end
876
+
877
+ class PresenceConstraint
878
+ def frequency
879
+ min = min_frequency
880
+ max = max_frequency
881
+ [
882
+ ((min && min > 0 && min != max) ? "at least #{min == 1 ? "one" : min.to_s}" : nil),
883
+ ((max && min != max) ? "at most #{max == 1 ? "one" : max.to_s}" : nil),
884
+ ((max && min == max) ? "#{max == 1 ? "one" : "exactly "+max.to_s}" : nil)
885
+ ].compact * " and "
886
+ end
887
+
888
+ def describe
889
+ min = min_frequency
890
+ max = max_frequency
891
+ 'PresenceConstraint over '+role_sequence.describe + " occurs " + frequency + " time#{(min&&min>1)||(max&&max>1) ? 's' : ''}"
892
+ end
893
+ end
894
+
895
+ class SubsetConstraint
896
+ def describe
897
+ 'SubsetConstraint(' +
898
+ subset_role_sequence.describe
899
+ ' < ' +
900
+ superset_role_sequence.describe +
901
+ ')'
902
+ end
903
+ end
904
+
905
+ class SetComparisonConstraint
906
+ def describe
907
+ self.class.basename+'(' +
908
+ all_set_comparison_roles.map do |scr|
909
+ '['+
910
+ scr.role_sequence.all_role_ref.map{|rr|
911
+ rr.role.fact_type.describe(rr.role)
912
+ }*',' +
913
+ ']'
914
+ end*',' +
915
+ ')'
916
+ end
917
+ end
918
+
919
+ class RingConstraint
920
+ def describe
921
+ 'RingConstraint(' +
922
+ ring_type.to_s+': ' +
923
+ role.describe+', ' +
924
+ other_role.describe+' in ' +
925
+ role.fact_type.default_reading +
926
+ ')'
927
+ end
928
+ end
929
+
930
+ class TypeInheritance
931
+ def describe(role = nil)
932
+ "#{subtype.name} is a kind of #{supertype.name}"
933
+ end
934
+
935
+ def supertype_role
936
+ (roles = all_role.to_a)[0].object_type == supertype ? roles[0] : roles[1]
937
+ end
938
+
939
+ def subtype_role
940
+ (roles = all_role.to_a)[0].object_type == subtype ? roles[0] : roles[1]
941
+ end
942
+ end
943
+
944
+ class Step
945
+ def describe
946
+ "Step " +
947
+ "#{is_optional ? 'maybe ' : ''}" +
948
+ (is_unary_step ? '(unary) ' : "from #{input_play.describe} ") +
949
+ "#{is_disallowed ? 'not ' : ''}" +
950
+ "to #{output_plays.map(&:describe)*', '}" +
951
+ (objectification_variable ? ", objectified as #{objectification_variable.describe}" : '') +
952
+ " '#{fact_type.default_reading}'"
953
+ end
954
+
955
+ def input_play
956
+ all_play.detect{|p| p.is_input}
957
+ end
958
+
959
+ def output_plays
960
+ all_play.reject{|p| p.is_input}
961
+ end
962
+
963
+ def is_unary_step
964
+ # Preserve this in case we have to use a real variable for the phantom
965
+ all_play.size == 1
966
+ end
967
+
968
+ def is_objectification_step
969
+ !!objectification_variable
970
+ end
971
+
972
+ def external_fact_type
973
+ fact_type.is_a?(LinkFactType) ? fact_type.role.fact_type : fact_type
974
+ end
975
+ end
976
+
977
+ class Variable
978
+ def describe
979
+ object_type.name +
980
+ (subscript ? "(#{subscript})" : '') +
981
+ " Var#{ordinal}" +
982
+ (value ? ' = '+value.to_s : '')
983
+ end
984
+
985
+ def all_step
986
+ all_play.map(&:step).uniq
987
+ end
988
+ end
989
+
990
+ class Play
991
+ def describe
992
+ "#{role.object_type.name} Var#{variable.ordinal}" +
993
+ (role_ref ? " (projected)" : "")
994
+ end
995
+ end
996
+
997
+ class Query
998
+ def describe
999
+ steps_shown = {}
1000
+ 'Query(' +
1001
+ all_variable.sort_by{|var| var.ordinal}.map do |variable|
1002
+ variable.describe + ': ' +
1003
+ variable.all_step.map do |step|
1004
+ next if steps_shown[step]
1005
+ steps_shown[step] = true
1006
+ step.describe
1007
+ end.compact.join(',')
1008
+ end.join('; ') +
1009
+ ')'
1010
+ end
1011
+
1012
+ def show
1013
+ steps_shown = {}
1014
+ trace :query, "Displaying full contents of Query #{concept.guid}" do
1015
+ all_variable.sort_by{|var| var.ordinal}.each do |variable|
1016
+ trace :query, "#{variable.describe}" do
1017
+ variable.all_step.
1018
+ each do |step|
1019
+ next if steps_shown[step]
1020
+ steps_shown[step] = true
1021
+ trace :query, "#{step.describe}"
1022
+ end
1023
+ variable.all_play.each do |play|
1024
+ trace :query, "role of #{play.describe} in '#{play.role.fact_type.default_reading}'"
1025
+ end
1026
+ end
1027
+ end
1028
+ end
1029
+ end
1030
+
1031
+ def all_step
1032
+ all_variable.map{|var| var.all_step.to_a}.flatten.uniq
1033
+ end
1034
+
1035
+ # Check all parts of this query for validity
1036
+ def validate
1037
+ show
1038
+ return
1039
+
1040
+ # Check the variables:
1041
+ steps = []
1042
+ variables = all_variable.sort_by{|var| var.ordinal}
1043
+ variables.each_with_index do |variable, i|
1044
+ raise "Variable #{i} should have ordinal #{variable.ordinal}" unless variable.ordinal == i
1045
+ raise "Variable #{i} has missing object_type" unless variable.object_type
1046
+ variable.all_play do |play|
1047
+ raise "Variable for #{object_type.name} includes role played by #{play.object_type.name}" unless play.object_type == object_type
1048
+ end
1049
+ steps += variable.all_step
1050
+ end
1051
+ steps.uniq!
1052
+
1053
+ # Check the steps:
1054
+ steps.each do |step|
1055
+ raise "Step has missing fact type" unless step.fact_type
1056
+ raise "Step has missing input node" unless step.input_play
1057
+ raise "Step has missing output node" unless step.output_play
1058
+ if (role = input_play).role.fact_type != fact_type or
1059
+ (role = output_play).role.fact_type != fact_type
1060
+ raise "Step has role #{role.describe} which doesn't belong to the fact type '#{fact_type.default_reading}' it traverses"
1061
+ end
1062
+ end
1063
+
1064
+ # REVISIT: Do a connectivity check
1065
+ end
1066
+ end
1067
+
1068
+ class LinkFactType
1069
+ def default_reading
1070
+ # There are two cases, where role is in a unary fact type, and where the fact type is objectified
1071
+ # If a unary fact type is objectified, only the LinkFactType for the objectification is asserted
1072
+ if objectification = implying_role.fact_type.entity_type
1073
+ "#{objectification.name} involves #{implying_role.object_type.name}"
1074
+ else
1075
+ implying_role.fact_type.default_reading+" Boolean" # Must be a unary FT
1076
+ end
1077
+ end
1078
+
1079
+ def add_reading implicit_reading
1080
+ @readings ||= []
1081
+ @readings << implicit_reading
1082
+ end
1083
+
1084
+ def all_reading
1085
+ @readings ||=
1086
+ [ ImplicitReading.new(
1087
+ self,
1088
+ implying_role.fact_type.entity_type ? "{0} involves {1}" : implying_role.fact_type.default_reading+" Boolean"
1089
+ )
1090
+ ] +
1091
+ Array(implying_role.fact_type.entity_type ? ImplicitReading.new(self, "{1} is involved in {0}") : nil)
1092
+ end
1093
+
1094
+ def reading_preferably_starting_with_role role, negated = false
1095
+ all_reading[role == implying_role ? 1 : 0]
1096
+ end
1097
+
1098
+ # This is only used for debugging, from RoleRef#describe
1099
+ class ImplicitReading
1100
+ attr_accessor :fact_type, :text
1101
+ attr_reader :is_negative # Never true
1102
+
1103
+ def initialize(fact_type, text)
1104
+ @fact_type = fact_type
1105
+ @text = text
1106
+ end
1107
+
1108
+ class ImplicitReadingRoleSequence
1109
+ class ImplicitReadingRoleRef
1110
+ attr_reader :role
1111
+ attr_reader :role_sequence
1112
+ def initialize(role, role_sequence)
1113
+ @role = role
1114
+ @role_sequence = role_sequence
1115
+ end
1116
+ def variable; nil; end
1117
+ def play; nil; end
1118
+ def leading_adjective; nil; end
1119
+ def trailing_adjective; nil; end
1120
+ def describe
1121
+ @role.object_type.name
1122
+ end
1123
+ end
1124
+
1125
+ def initialize roles
1126
+ @role_refs = roles.map{|role| ImplicitReadingRoleRef.new(role, self) }
1127
+ end
1128
+
1129
+ def all_role_ref
1130
+ @role_refs
1131
+ end
1132
+ def all_role_ref_in_order
1133
+ @role_refs
1134
+ end
1135
+ def describe
1136
+ '('+@role_refs.map(&:describe)*', '+')'
1137
+ end
1138
+ def all_reading
1139
+ []
1140
+ end
1141
+ end
1142
+
1143
+ def role_sequence
1144
+ ImplicitReadingRoleSequence.new([@fact_type.implying_role, @fact_type.all_role.single])
1145
+ end
1146
+
1147
+ def ordinal; 0; end
1148
+
1149
+ def expand
1150
+ text.gsub(/\{([01])\}/) do
1151
+ if $1 == '0'
1152
+ fact_type.all_role[0].object_type.name
1153
+ else
1154
+ fact_type.implying_role.object_type.name
1155
+ end
1156
+ end
1157
+ end
1158
+ end
1159
+ end
1160
+
1161
+ # Some queries must be over the proximate roles, some over the counterpart roles.
1162
+ # Return the common superclass of the appropriate roles, and the actual roles
1163
+ def self.plays_over roles, options = :both # Or :proximate, :counterpart
1164
+ # If we can stay inside this objectified FT, there's no query:
1165
+ roles = Array(roles) # To be safe, in case we get a role collection proxy
1166
+ return nil if roles.size == 1 or
1167
+ options != :counterpart && roles.map{|role| role.fact_type}.uniq.size == 1
1168
+ proximate_sups, counterpart_sups, obj_sups, counterpart_roles, objectification_roles =
1169
+ *roles.inject(nil) do |d_c_o, role|
1170
+ object_type = role.object_type
1171
+ fact_type = role.fact_type
1172
+
1173
+ proximate_role_supertypes = object_type.supertypes_transitive
1174
+
1175
+ # A role in an objectified fact type may indicate either the objectification or the counterpart player.
1176
+ # This could be ambiguous. Figure out both and prefer the counterpart over the objectification.
1177
+ counterpart_role_supertypes =
1178
+ if fact_type.all_role.size > 2
1179
+ possible_roles = fact_type.all_role.select{|r| d_c_o && d_c_o[1].include?(r.object_type) }
1180
+ if possible_roles.size == 1 # Only one candidate matches the types of the possible variables
1181
+ counterpart_role = possible_roles[0]
1182
+ d_c_o[1] # No change
1183
+ else
1184
+ # puts "#{constraint_type} #{name}: Awkward, try counterpart-role query on a >2ary '#{fact_type.default_reading}'"
1185
+ # Try all roles; hopefully we don't have two roles with a matching candidate here:
1186
+ # Find which role is compatible with the existing supertypes, if any
1187
+ if d_c_o
1188
+ st = nil
1189
+ counterpart_role =
1190
+ fact_type.all_role.detect{|r| ((st = r.object_type.supertypes_transitive) & d_c_o[1]).size > 0}
1191
+ st
1192
+ else
1193
+ counterpart_role = nil # This can't work, we don't have any basis for a decision (must be objectification)
1194
+ []
1195
+ end
1196
+ #fact_type.all_role.map{|r| r.object_type.supertypes_transitive}.flatten.uniq
1197
+ end
1198
+ else
1199
+ # Get the supertypes of the counterpart role (care with unaries):
1200
+ ftr = role.fact_type.all_role.to_a
1201
+ (counterpart_role = ftr[0] == role ? ftr[-1] : ftr[0]).object_type.supertypes_transitive
1202
+ end
1203
+
1204
+ if fact_type.entity_type
1205
+ objectification_role_supertypes =
1206
+ fact_type.entity_type.supertypes_transitive+object_type.supertypes_transitive
1207
+ objectification_role = role.link_fact_type.all_role.single # Find the phantom role here
1208
+ else
1209
+ objectification_role_supertypes = counterpart_role_supertypes
1210
+ objectification_role = counterpart_role
1211
+ end
1212
+
1213
+ if !d_c_o
1214
+ d_c_o = [proximate_role_supertypes, counterpart_role_supertypes, objectification_role_supertypes, [counterpart_role], [objectification_role]]
1215
+ #puts "role player supertypes starts #{d_c_o.map{|dco| dco.map(&:name).inspect}*' or '}"
1216
+ else
1217
+ #puts "continues #{[proximate_role_supertypes, counterpart_role_supertypes, objectification_role_supertypes]map{|dco| dco.map(&:name).inspect}*' or '}"
1218
+ d_c_o[0] &= proximate_role_supertypes
1219
+ d_c_o[1] &= counterpart_role_supertypes
1220
+ d_c_o[2] &= objectification_role_supertypes
1221
+ d_c_o[3] << (counterpart_role || objectification_role)
1222
+ d_c_o[4] << (objectification_role || counterpart_role)
1223
+ end
1224
+ d_c_o
1225
+ end # inject
1226
+
1227
+ # Discount a subtype step over an object type that's not a player here,
1228
+ # if we can use an objectification step to an object type that is:
1229
+ if counterpart_sups.size > 0 && obj_sups.size > 0 && counterpart_sups[0] != obj_sups[0]
1230
+ trace :query, "ambiguous query, could be over #{counterpart_sups[0].name} or #{obj_sups[0].name}"
1231
+ if !roles.detect{|r| r.object_type == counterpart_sups[0]} and roles.detect{|r| r.object_type == obj_sups[0]}
1232
+ trace :query, "discounting #{counterpart_sups[0].name} in favour of direct objectification"
1233
+ counterpart_sups = []
1234
+ end
1235
+ end
1236
+
1237
+ # Choose the first entry in the first non-empty supertypes list:
1238
+ if options != :counterpart && proximate_sups[0]
1239
+ [ proximate_sups[0], roles ]
1240
+ elsif !counterpart_sups.empty?
1241
+ [ counterpart_sups[0], counterpart_roles ]
1242
+ else
1243
+ [ obj_sups[0], objectification_roles ]
1244
+ end
1245
+ end
1246
+
1247
+ class Fact
1248
+ def verbalise(context = nil)
1249
+ reading = fact_type.preferred_reading
1250
+ reading_roles = reading.role_sequence.all_role_ref.sort_by{|rr| rr.ordinal}.map{|rr| rr.role }
1251
+ role_values_in_reading_order = all_role_value.sort_by{|rv| reading_roles.index(rv.role) }
1252
+ instance_verbalisations = role_values_in_reading_order.map do |rv|
1253
+ if rv.instance.value
1254
+ v = rv.instance.verbalise
1255
+ else
1256
+ if (c = rv.instance.object_type).is_a?(EntityType)
1257
+ if !c.preferred_identifier.role_sequence.all_role_ref.detect{|rr| rr.role.fact_type == fact_type}
1258
+ v = rv.instance.verbalise
1259
+ end
1260
+ end
1261
+ end
1262
+ next nil unless v
1263
+ v.to_s.sub(/(#{rv.instance.object_type.name}|\S*)\s/,'')
1264
+ end
1265
+ reading.expand([], false, instance_verbalisations)
1266
+ end
1267
+ end
1268
+
1269
+ class Instance
1270
+ def verbalise(context = nil)
1271
+ return "#{object_type.name} #{value}" if object_type.is_a?(ValueType)
1272
+
1273
+ return "#{object_type.name} (in which #{fact.verbalise(context)})" if object_type.fact_type
1274
+
1275
+ # It's an entity that's not an objectified fact type
1276
+
1277
+ # If it has a simple identifier, there's no need to fully verbalise the identifying facts.
1278
+ # This recursive block returns either the identifying value or nil
1279
+ simple_identifier = proc do |instance|
1280
+ if instance.object_type.is_a?(ActiveFacts::Metamodel::ValueType)
1281
+ instance
1282
+ else
1283
+ pi = instance.object_type.preferred_identifier
1284
+ identifying_role_refs = pi.role_sequence.all_role_ref_in_order
1285
+ if identifying_role_refs.size != 1
1286
+ nil
1287
+ else
1288
+ role = identifying_role_refs[0].role
1289
+ my_role = (role.fact_type.all_role.to_a-[role])[0]
1290
+ identifying_fact = my_role.all_role_value.detect{|rv| rv.instance == self}.fact
1291
+ irv = identifying_fact.all_role_value.detect{|rv| rv.role == role}
1292
+ identifying_instance = irv.instance
1293
+ simple_identifier.call(identifying_instance)
1294
+ end
1295
+ end
1296
+ end
1297
+
1298
+ if (id = simple_identifier.call(self))
1299
+ "#{object_type.name} #{id.value}"
1300
+ else
1301
+ pi = object_type.preferred_identifier
1302
+ identifying_role_refs = pi.role_sequence.all_role_ref_in_order
1303
+ "#{object_type.name}" +
1304
+ " is identified by " + # REVISIT: Where the single fact type is TypeInheritance, we can shrink this
1305
+ identifying_role_refs.map do |rr|
1306
+ rr = rr.preferred_reference
1307
+ [ (l = rr.leading_adjective) ? l+"-" : nil,
1308
+ rr.role.role_name || rr.role.object_type.name,
1309
+ (t = rr.trailing_adjective) ? l+"-" : nil
1310
+ ].compact*""
1311
+ end * " and " +
1312
+ " where " +
1313
+ identifying_role_refs.map do |rr| # Go through the identifying roles and emit the facts that define them
1314
+ instance_role = object_type.all_role.detect{|r| r.fact_type == rr.role.fact_type}
1315
+ identifying_fact = all_role_value.detect{|rv| rv.fact.fact_type == rr.role.fact_type}.fact
1316
+ #counterpart_role = (rr.role.fact_type.all_role.to_a-[instance_role])[0]
1317
+ #identifying_instance = counterpart_role.all_role_value.detect{|rv| rv.fact == identifying_fact}.instance
1318
+ identifying_fact.verbalise(context)
1319
+ end*", "
1320
+ end
1321
+
1322
+ end
1323
+ end
1324
+
1325
+ class ContextNote
1326
+ def verbalise(context=nil)
1327
+ as_cql
1328
+ end
1329
+
1330
+ def as_cql
1331
+ ' (' +
1332
+ ( if all_context_according_to
1333
+ 'according to '
1334
+ all_context_according_to.map do |act|
1335
+ act.agent.agent_name+', '
1336
+ end.join('')
1337
+ end
1338
+ ) +
1339
+ context_note_kind.gsub(/_/, ' ') +
1340
+ ' ' +
1341
+ discussion +
1342
+ ( if agreement
1343
+ ', as agreed ' +
1344
+ (agreement.date ? ' on '+agreement.date.iso8601.inspect+' ' : '') +
1345
+ 'by '
1346
+ agreement.all_context_agreed_by.map do |acab|
1347
+ acab.agent.agent_name+', '
1348
+ end.join('')
1349
+ else
1350
+ ''
1351
+ end
1352
+ ) +
1353
+ ')'
1354
+ end
1355
+ end
1356
+
1357
+ end
1358
+ end