Class: Syskit::Models::SpecializationManager
- Extended by:
- MetaRuby::Attributes
- Defined in:
- lib/syskit/models/specialization_manager.rb
Overview
Management of the specializations of a particular composition model
Defined Under Namespace
Classes: SpecializationBlockContext
Instance Attribute Summary collapse
-
#composition_model ⇒ Model<Composition>
readonly
The composition model.
Instance Method Summary collapse
-
#add_specialization_constraint(explicit = nil) {|spec0, spec1| ... } ⇒ void
Registers a block that will be able to tell the system that two specializations are not compatible (i.e. should never be applied at the same time).
-
#all_default_specialization ⇒ Array<Object>
The union, along the class hierarchy, of all the values stored in default_specialization.
-
#compatible_specializations?(spec1, spec2) ⇒ Boolean
Returns true if the two given specializations are compatible, as given by the registered specialization constraints.
- #create_specialized_model(composite_spec, applied_specializations) ⇒ Object
-
#default_specialization(child, child_model) ⇒ Object
Declares a preferred specialization in case two specializations match that are not related to each other.
- #default_specializations ⇒ Object
-
#deregister(specialization) ⇒ void
Deregisters the given specialization on this manager.
- #each_default_specialization(key = nil, uniq = true) ⇒ Object
-
#each_specialization {|CompositionSpecialization| ... } ⇒ Object
Enumerates all specializations defined on #composition_model.
-
#empty? ⇒ Boolean
Returns true if no specializations are registered on this manager.
-
#find_common_specialization_subset(candidates) ⇒ Object
Given a set of specialization sets, returns subset common to all of the contained sets.
-
#find_default_specialization(key) ⇒ Object?
Looks for objects registered in default_specialization under the given key, and returns the first one in the ancestor chain (i.e. the one tha thas been registered in the most specialized class).
-
#find_matching_specializations(selection) ⇒ [CompositionSpecialization,Array<CompositionSpecialization>]
Find the sets of specializations that match
selection
. -
#has_default_specialization?(key) ⇒ Boolean
Returns true if an object is registered in default_specialization anywhere in the class hierarchy.
-
#initialize(composition_model) ⇒ SpecializationManager
constructor
A new instance of SpecializationManager.
- #instanciate_all_possible_specializations ⇒ Object
-
#instanciated_specializations ⇒ Hash{Hash{String=>Model} => CompositionSpecialization}
Set of specialized composition models already instantiated with #specialized_model.
-
#matching_specialized_model(selection, options = Hash.new) ⇒ Model<Composition>
Looks for a single composition model that matches the given selection.
-
#normalize_specialization_mappings(mappings) ⇒ {String=>Model<Component>,Model<DataService>}
Transforms specialization specifications given to #specialize into a name => model_set mapping.
-
#partition_specializations(specialization_set) ⇒ [(CompositionSpecialization,Array<CompositionSpecialization>)]
Partitions a set of specializations into the smallest number of partitions, where all the specializations in a subset of the partition can be applied together.
-
#register(specialization) ⇒ void
Registers the given specialization on this manager.
-
#specializations ⇒ {{String => Array<Model<DataService>,Model<Component>>}=>CompositionSpecialization}
The set of specializations defined on #composition_model.
-
#specialize(options = Hash.new, &block) ⇒ Object
Specifies a modification that should be applied on #composition_model when select children fullfill some specific models.
-
#specialized_model(composite_spec, applied_specializations = [composite_spec]) ⇒ Object
Returns the composition model that is a specialization of #composition_model, applying the set of specializations in 'applied_specializations'
composite_spec
is a Specialization object in which all the required specializations have been merged andapplied_specializations
the list of the specializations, separate. -
#validate_specialization_mappings(new_spec) ⇒ void
Verifies that the child selection in
new_spec
is valid.
Constructor Details
#initialize(composition_model) ⇒ SpecializationManager
Returns a new instance of SpecializationManager
14 15 16 |
# File 'lib/syskit/models/specialization_manager.rb', line 14 def initialize(composition_model) @composition_model = composition_model end |
Instance Attribute Details
#composition_model ⇒ Model<Composition> (readonly)
The composition model
10 11 12 |
# File 'lib/syskit/models/specialization_manager.rb', line 10 def composition_model @composition_model end |
Instance Method Details
#add_specialization_constraint(explicit = nil) {|spec0, spec1| ... } ⇒ void
This method returns an undefined value.
Registers a block that will be able to tell the system that two specializations are not compatible (i.e. should never be applied at the same time).
252 253 254 |
# File 'lib/syskit/models/specialization_manager.rb', line 252 def add_specialization_constraint(explicit = nil, &as_block) specialization_constraints << (explicit || as_block) end |
#all_default_specialization ⇒ Array<Object>
The union, along the class hierarchy, of all the values stored in default_specialization
12 |
# File 'lib/syskit/models/specialization_manager.rb', line 12 inherited_attribute(:default_specialization, :default_specializations, :map => true) { Hash.new } |
#compatible_specializations?(spec1, spec2) ⇒ Boolean
Returns true if the two given specializations are compatible, as given by the registered specialization constraints
This method also checks that the values returned by the constraints are symmetric
268 269 270 271 272 273 274 275 276 277 278 279 280 281 |
# File 'lib/syskit/models/specialization_manager.rb', line 268 def compatible_specializations?(spec1, spec2) specialization_constraints.all? do |validator| # This is potentially expensive, but the specialization # compatibilities are done at modelling time, so that's not # an issue given the added robustness -- designing properly # symmetric constraint blocks can be a bit tricky result = validator[spec1, spec2] sym_result = validator[spec2, spec1] if result != sym_result raise NonSymmetricSpecializationConstraint.new(validator, [spec1, spec2]), "#{validator} returned #{!!result} on (#{spec1},#{spec2}) and #{!!sym_result} on (#{spec2},#{spec1}). Specialization constraints must be symmetric" end result end end |
#create_specialized_model(composite_spec, applied_specializations) ⇒ Object
441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 |
# File 'lib/syskit/models/specialization_manager.rb', line 441 def create_specialized_model(composite_spec, applied_specializations) # There's no composition with that spec. Create a new one child_composition = composition_model.new_specialized_submodel child_composition.private_model child_composition.root_model = composition_model.root_model child_composition.specialized_children.merge!(composite_spec.specialized_children) child_composition.applied_specializations = applied_specializations.to_set composite_spec.compatibilities.each do |single_spec| child_composition.specializations.register(single_spec) end composite_spec.specialized_children.each do |child_name, child_models| child_composition.overload child_name, child_models end applied_specializations.each do |applied_spec| applied_spec.specialization_blocks.each do |block| reference_model = if applied_spec == composite_spec child_composition else applied_spec.composition_model end context = SpecializationBlockContext.new(child_composition, reference_model) context.apply_block(block) end end child_composition end |
#default_specialization(child, child_model) ⇒ Object
Declares a preferred specialization in case two specializations match that are not related to each other.
In the following case:
composition 'ManualDriving' do
specialize 'Control', SimpleController, :not => FourWheelController do
end
specialize 'Control', FourWheelController, :not => SimpleController do
end
end
If a Control model is selected that fullfills both SimpleController and FourWheelController, then there is an ambiguity as both specializations apply and one cannot be preferred w.r.t. the other.
By using
default_specialization 'Control', SimpleController
the first one will be preferred by default. The second one can then be selected at instanciation time with
add 'ManualDriving',
'Control' => controller_model.as(FourWheelController)
308 309 310 311 312 313 314 315 316 317 |
# File 'lib/syskit/models/specialization_manager.rb', line 308 def default_specialization(child, child_model) raise NotImplementedError child = if child.respond_to?(:to_str) child.to_str else child.name.gsub(/.*::/, '') end default_specializations[child] = child_model end |
#default_specializations ⇒ Object
12 |
# File 'lib/syskit/models/specialization_manager.rb', line 12 inherited_attribute(:default_specialization, :default_specializations, :map => true) { Hash.new } |
#deregister(specialization) ⇒ void
This method returns an undefined value.
Deregisters the given specialization on this manager
37 38 39 40 41 42 43 |
# File 'lib/syskit/models/specialization_manager.rb', line 37 def deregister(specialization) if specializations[specialization.specialized_children] == specialization instanciated_specializations.delete(specialization.specialized_children) specializations.delete(specialization.specialized_children) composition_model.deregister_submodels([specialization.composition_model].to_set) end end |
#each_default_specialization(key, uniq = true) {|element| ... } ⇒ Object #each_default_specialization(nil, uniq = true) {|key, element| ... } ⇒ Object
12 |
# File 'lib/syskit/models/specialization_manager.rb', line 12 inherited_attribute(:default_specialization, :default_specializations, :map => true) { Hash.new } |
#each_specialization {|CompositionSpecialization| ... } ⇒ Object
Enumerates all specializations defined on #composition_model
55 56 57 58 59 60 |
# File 'lib/syskit/models/specialization_manager.rb', line 55 def each_specialization return enum_for(:each_specialization) if !block_given? specializations.each_value do |spec| yield(spec) end end |
#empty? ⇒ Boolean
Returns true if no specializations are registered on this manager
48 49 50 |
# File 'lib/syskit/models/specialization_manager.rb', line 48 def empty? specializations.empty? end |
#find_common_specialization_subset(candidates) ⇒ Object
Given a set of specialization sets, returns subset common to all of the contained sets
657 658 659 660 661 662 663 664 665 666 667 |
# File 'lib/syskit/models/specialization_manager.rb', line 657 def find_common_specialization_subset(candidates) result = candidates[0][1].to_set candidates[1..-1].each do |merged, subset| result &= subset.to_set end merged = result.inject(CompositionSpecialization.new) do |merged, spec| merged.merge(spec) end [merged, result] end |
#find_default_specialization(key) ⇒ Object?
Looks for objects registered in default_specialization under the given key, and returns the first one in the ancestor chain (i.e. the one tha thas been registered in the most specialized class)
12 |
# File 'lib/syskit/models/specialization_manager.rb', line 12 inherited_attribute(:default_specialization, :default_specializations, :map => true) { Hash.new } |
#find_matching_specializations(selection) ⇒ [CompositionSpecialization,Array<CompositionSpecialization>]
Find the sets of specializations that match selection
Further disambiguation would, for instance, have to pick one of these sets and call
specialized_model(*candidates[selected_element])
to get the corresponding composition model
560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 |
# File 'lib/syskit/models/specialization_manager.rb', line 560 def find_matching_specializations(selection) if specializations.empty? || selection.empty? return [[CompositionSpecialization.new, []]] end Models.debug do Models.debug "looking for specialization of #{composition_model.short_name} on" selection.each do |k, v| Models.debug " #{k} => #{v}" end break end matching_specializations = each_specialization.find_all do |spec_model| spec_model.weak_match?(selection) end Models.debug do Models.debug " #{matching_specializations.size} matching specializations found" matching_specializations.each do |m| Models.debug " #{m.specialized_children}" end break end if matching_specializations.empty? return [[CompositionSpecialization.new, []]] end return partition_specializations(matching_specializations) end |
#has_default_specialization?(key) ⇒ Boolean
Returns true if an object is registered in default_specialization anywhere in the class hierarchy
12 |
# File 'lib/syskit/models/specialization_manager.rb', line 12 inherited_attribute(:default_specialization, :default_specializations, :map => true) { Hash.new } |
#instanciate_all_possible_specializations ⇒ Object
319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 |
# File 'lib/syskit/models/specialization_manager.rb', line 319 def instanciate_all_possible_specializations all = partition_specializations(specializations.values) done_subsets = Set.new result = [] all.each do |merged, set| (1..set.size).each do |subset_size| set.to_a.combination(subset_size) do |subset| subset = subset.to_set if !done_subsets.include?(subset) merged = Specialization.new subset.each { |spec| merged.merge(spec) } result << specialized_model(merged, subset) done_subsets << subset end end end end result end |
#instanciated_specializations ⇒ Hash{Hash{String=>Model} => CompositionSpecialization}
Returns set of specialized composition models already instantiated with #specialized_model. The key is the specialization selectors and the value the composite specialization, in which CompositionSpecialization#composition_model returns the composition model
347 348 349 350 351 352 353 |
# File 'lib/syskit/models/specialization_manager.rb', line 347 def instanciated_specializations root = composition_model.root_model if root == composition_model return (@instanciated_specializations ||= Hash.new) else return root.specializations.instanciated_specializations end end |
#matching_specialized_model(selection, options = Hash.new) ⇒ Model<Composition>
Looks for a single composition model that matches the given selection
603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 |
# File 'lib/syskit/models/specialization_manager.rb', line 603 def matching_specialized_model(selection, = Hash.new) = Kernel. , :strict => true, :specialization_hints => Set.new component_selection = selection.map_value do |_, selected| selected.selected.model.to_component_model end candidates = find_matching_specializations(component_selection) if candidates.size > 1 filtered_candidates = candidates.find_all do |spec, _| [:specialization_hints].any? do |hint| spec.weak_match?(hint) end end if !filtered_candidates.empty? candidates = filtered_candidates end end if candidates.size > 1 filtered_candidates = candidates.find_all do |spec, _| spec.weak_match?(selection) end if !filtered_candidates.empty? candidates = filtered_candidates end end if candidates.empty? return composition_model elsif candidates.size > 1 if [:strict] selection = selection.map_value do |_, sel| sel.selected end raise AmbiguousSpecialization.new(composition_model, selection, candidates) else candidates = [find_common_specialization_subset(candidates)] end end specialized_model = specialized_model(*candidates.first) Models.debug do if specialized_model != composition_model Models.debug "using specialization #{specialized_model.short_name} of #{composition_model.short_name}" end break end return specialized_model end |
#normalize_specialization_mappings(mappings) ⇒ {String=>Model<Component>,Model<DataService>}
Transforms specialization specifications given to #specialize into a name => model_set mapping
171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 |
# File 'lib/syskit/models/specialization_manager.rb', line 171 def normalize_specialization_mappings(mappings) # Normalize the user-provided specialization mapping new_spec = Hash.new mappings.each do |child, child_model| if Models.is_model?(child) children = composition_model.each_child. find_all { |name, child_definition| child_definition.fullfills?(child) }. map { |name, _| name } if children.empty? raise ArgumentError, "invalid specialization #{child.short_name} => #{child_model.short_name}: no child of #{composition_model.short_name} fullfills #{child.short_name}" elsif children.size > 1 children = children.map do |child_name| child_models = composition_model.find_child(child_name). each_required_model.map(&:short_name).sort.join(",") "#{child_name}: #{child_models}" end raise ArgumentError, "invalid specialization #{child.short_name} => #{child_model.short_name}: more than one child of #{composition_model.short_name} fullfills #{child.short_name} (#{children.sort.join("; ")}). You probably want to select one specifically by name" end child = children.first elsif !child.respond_to?(:to_str) raise ArgumentError, "invalid child selector #{child}" end child_model = Array(child_model) child_model.each do |m| if !Models.is_model?(m) || m.kind_of?(Models::BoundDataService) raise ArgumentError, "invalid specialization selector #{child} => #{child_model}: #{m} is not a component model or a data service model" end end new_spec[child.to_str] = child_model.to_set end return new_spec end |
#partition_specializations(specialization_set) ⇒ [(CompositionSpecialization,Array<CompositionSpecialization>)]
Partitions a set of specializations into the smallest number of partitions, where all the specializations in a subset of the partition can be applied together
484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 |
# File 'lib/syskit/models/specialization_manager.rb', line 484 def partition_specializations(specialization_set) if specialization_set.empty? return [] end # What we have to do here is: # # for each S0 in specialization_set # for each S1 in compatibilities(S0) # there exists C in result which contain S0 and S1 # end # end # # We gather all the capabilities and iteratively remove the ones # for which this property is fullfilled result = [] specialization_set = specialization_set.to_set compatibilities = Hash.new specialization_set.each do |s0| compatibilities[s0] = (s0.compatibilities.to_set & specialization_set) << s0 end compatibilities.each do |s0, remaining| # Iterate over the existing elements result.each do |merged, all| if all.include?(s0) remaining.subtract(all) elsif merged.compatible_with?(s0) # not there yet and compatible, add it merged.merge(s0) all << s0 remaining.subtract(all) end end # Now, add new elements for what is left merged, all = nil while !remaining.empty? if !merged merged = CompositionSpecialization.new merged.merge(s0) all = [s0].to_set end remaining.delete(s1 = remaining.first) next if s0 == s1 # possible if the iteration on 'result' above did not find anything if merged.compatible_with?(s1) merged.merge(s1) all << s1 else result << [merged, all] merged = nil end end if merged result << [merged, all] end end result end |
#register(specialization) ⇒ void
This method returns an undefined value.
Registers the given specialization on this manager
27 28 29 30 |
# File 'lib/syskit/models/specialization_manager.rb', line 27 def register(specialization) specialization.root_name = composition_model.root_model.name specializations[specialization.specialized_children] = specialization end |
#specializations ⇒ {{String => Array<Model<DataService>,Model<Component>>}=>CompositionSpecialization}
The set of specializations defined on #composition_model
21 |
# File 'lib/syskit/models/specialization_manager.rb', line 21 attribute(:specializations) { Hash.new } |
#specialize(options = Hash.new, &block) ⇒ Object
Specifies a modification that should be applied on #composition_model when select children fullfill some specific models.
88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 |
# File 'lib/syskit/models/specialization_manager.rb', line 88 def specialize( = Hash.new, &block) Models.debug do Models.debug "trying to specialize #{composition_model.short_name}" Models.log_nest 2 Models.debug "with" .map do |name, models| Models.debug " #{name} => #{models}" end Models.debug "" break end , mappings = Kernel. , :not => [] if ![:not].respond_to?(:to_ary) [:not] = [[:not]] end mappings = normalize_specialization_mappings(mappings) # validate the mappings validate_specialization_mappings(mappings) # register it if specialization = specializations[mappings] new_specialization = specialization.dup else new_specialization = CompositionSpecialization.new end # validate the block new_specialization.add(mappings, block) specialized_composition_model = create_specialized_model(new_specialization, [new_specialization]) # ... and update compatibilities # # NOTE: this code does NOT updates compatibilities based on # whether two specialization selection models are compatible as, # by definition, the system should never try to instantiate one # of these (since the models that would trigger this # instantiation cannot be represented) each_specialization do |spec| next if spec == new_specialization || spec == new_specialization if compatible_specializations?(spec, new_specialization) spec.compatibilities << new_specialization new_specialization.compatibilities << spec else spec.compatibilities.delete(new_specialization) new_specialization.compatibilities.delete(spec) end end # and register the result if specialization deregister(specialization) end new_specialization.composition_model = specialized_composition_model register(new_specialization) # Finally, we create new_specialization ensure Models.debug do Models.log_nest -2 break end end |
#specialized_model(composite_spec, applied_specializations = [composite_spec]) ⇒ Object
Returns the composition model that is a specialization of
#composition_model, applying the set of specializations in
'applied_specializations' composite_spec
is a
Specialization object in which all the required specializations have been
merged and applied_specializations
the list of the
specializations, separate.
361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 |
# File 'lib/syskit/models/specialization_manager.rb', line 361 def specialized_model(composite_spec, applied_specializations = [composite_spec]) Models.debug do Models.debug "instanciating specializations: #{applied_specializations.map(&:to_s).sort.join(", ")}" Models.log_nest(2) break end if composite_spec.specialized_children.empty? return composition_model elsif current_model = instanciated_specializations[composite_spec.specialized_children] return current_model.composition_model end child_composition = create_specialized_model(composite_spec, applied_specializations) composite_spec.composition_model = child_composition instanciated_specializations[composite_spec.specialized_children] = composite_spec child_composition ensure Models.debug do Models.log_nest -2 break end end |
#validate_specialization_mappings(new_spec) ⇒ void
This method returns an undefined value.
Verifies that the child selection in new_spec
is valid
218 219 220 221 222 223 224 225 226 227 228 229 230 |
# File 'lib/syskit/models/specialization_manager.rb', line 218 def validate_specialization_mappings(new_spec) new_spec.each do |child_name, child_models| child_m = composition_model.find_child(child_name) if !child_m raise ArgumentError, "there is no child called #{child_name} in #{composition_model.short_name}" end merged = Placeholder.for(child_models).merge(child_m.model) if merged == child_m.model raise ArgumentError, "#{child_models.map(&:short_name).sort.join(",")} does not specify a specialization of #{child_m.model}" end end end |