activefacts-metamodel 1.7.0 → 1.7.1

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